Skip to content

Commit

Permalink
Allows a directory to be specified as the --output option for the var…
Browse files Browse the repository at this point in the history
…tan-compile command
  • Loading branch information
nihei9 committed May 22, 2022
1 parent b5ad1d3 commit def1459
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 76 deletions.
108 changes: 89 additions & 19 deletions cmd/vartan/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"

verr "github.com/nihei9/vartan/error"
"github.com/nihei9/vartan/grammar"
Expand Down Expand Up @@ -84,20 +84,31 @@ func runCompile(cmd *cobra.Command, args []string) (retErr error) {
return err
}

var reportFileName string
{
_, grmFileName := filepath.Split(grmPath)
reportFileName = fmt.Sprintf("%v-report.json", strings.TrimSuffix(grmFileName, ".vartan"))
}

cgram, err := grammar.Compile(gram, grammar.EnableReporting(reportFileName))
cgram, report, err := grammar.Compile(gram, grammar.EnableReporting())
if err != nil {
return err
}

err = writeCompiledGrammar(cgram, *compileFlags.output)
err = writeCompiledGrammarAndReport(cgram, report, *compileFlags.output)
if err != nil {
return fmt.Errorf("Cannot write a compiled grammar: %w", err)
return fmt.Errorf("Cannot write an output files: %w", err)
}

var implicitlyResolvedCount int
for _, s := range report.States {
for _, c := range s.SRConflict {
if c.ResolvedBy == grammar.ResolvedByShift.Int() {
implicitlyResolvedCount++
}
}
for _, c := range s.RRConflict {
if c.ResolvedBy == grammar.ResolvedByProdOrder.Int() {
implicitlyResolvedCount++
}
}
}
if implicitlyResolvedCount > 0 {
fmt.Fprintf(os.Stdout, "%v conflicts\n", implicitlyResolvedCount)
}

return nil
Expand All @@ -121,20 +132,79 @@ func readGrammar(path string) (grm *grammar.Grammar, retErr error) {
return b.Build()
}

func writeCompiledGrammar(cgram *spec.CompiledGrammar, path string) error {
out, err := json.Marshal(cgram)
// writeCompiledGrammarAndReport writes a compiled grammar and a report to a files located at a specified path.
// This function selects one of the following output methods depending on how the path is specified.
//
// 1. When the path is a directory path, this function writes the compiled grammar and the report to
// <path>/<grammar-name>.json and <path>/<grammar-name>-report.json files, respectively.
// <grammar-name>-report.json as the output files.
// 2. When the path is a file path or a non-exitent path, this function asumes that the path represents a file
// path for the compiled grammar. Then it also writes the report in the same directory as the compiled grammar.
// The report file is named <grammar-name>.json.
// 3. When the path is an empty string, this function writes the compiled grammar to the stdout and writes
// the report to a file named <current-directory>/<grammar-name>-report.json.
func writeCompiledGrammarAndReport(cgram *spec.CompiledGrammar, report *spec.Report, path string) error {
cgramPath, reportPath, err := makeOutputFilePaths(cgram.Name, path)
if err != nil {
return err
}
w := os.Stdout
if path != "" {
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)

{
var cgramW io.Writer
if cgramPath != "" {
cgramFile, err := os.OpenFile(cgramPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer cgramFile.Close()
cgramW = cgramFile
} else {
cgramW = os.Stdout
}

b, err := json.Marshal(cgram)
if err != nil {
return fmt.Errorf("Cannot open the output file %s: %w", path, err)
return err
}
defer f.Close()
w = f
fmt.Fprintf(cgramW, "%v\n", string(b))
}
fmt.Fprintf(w, "%v\n", string(out))

{
reportFile, err := os.OpenFile(reportPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer reportFile.Close()

b, err := json.Marshal(report)
if err != nil {
return err
}
fmt.Fprintf(reportFile, "%v\n", string(b))
}

return nil
}

func makeOutputFilePaths(gramName string, path string) (string, string, error) {
reportFileName := gramName + "-report.json"

if path == "" {
wd, err := os.Getwd()
if err != nil {
return "", "", err
}
return "", filepath.Join(wd, reportFileName), nil
}

fi, err := os.Stat(path)
if err != nil && !os.IsNotExist(err) {
return "", "", err
}
if os.IsNotExist(err) || !fi.IsDir() {
dir, _ := filepath.Split(path)
return path, filepath.Join(dir, reportFileName), nil
}

return filepath.Join(path, gramName+".json"), filepath.Join(path, reportFileName), nil
}
2 changes: 1 addition & 1 deletion driver/conflict_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ assign: '=';
t.Fatal(err)
}

cg, err := grammar.Compile(g)
cg, _, err := grammar.Compile(g)
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion driver/lac_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ d: 'd';
t.Fatal(err)
}

gram, err := grammar.Compile(g)
gram, _, err := grammar.Compile(g)
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion driver/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ bar: 'bar';
t.Fatal(err)
}

cg, err := grammar.Compile(g)
cg, _, err := grammar.Compile(g)
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion driver/semantic_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ char
t.Fatal(err)
}

gram, err := grammar.Compile(g)
gram, _, err := grammar.Compile(g)
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion driver/syntax_error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ c
t.Fatal(err)
}

gram, err := grammar.Compile(g)
gram, _, err := grammar.Compile(g)
if err != nil {
t.Fatal(err)
}
Expand Down
70 changes: 18 additions & 52 deletions grammar/grammar.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package grammar

import (
"encoding/json"
"fmt"
"io"
"os"
"strings"

mlcompiler "github.com/nihei9/maleeni/compiler"
Expand Down Expand Up @@ -1287,18 +1285,18 @@ func (b *GrammarBuilder) genPrecAndAssoc(symTab *symbolTable, errSym symbol, pro
}

type compileConfig struct {
reportFileName string
isReportingEnabled bool
}

type CompileOption func(config *compileConfig)

func EnableReporting(fileName string) CompileOption {
func EnableReporting() CompileOption {
return func(config *compileConfig) {
config.reportFileName = fileName
config.isReportingEnabled = true
}
}

func Compile(gram *Grammar, opts ...CompileOption) (*spec.CompiledGrammar, error) {
func Compile(gram *Grammar, opts ...CompileOption) (*spec.CompiledGrammar, *spec.Report, error) {
config := &compileConfig{}
for _, opt := range opts {
opt(config)
Expand All @@ -1313,9 +1311,9 @@ func Compile(gram *Grammar, opts ...CompileOption) (*spec.CompiledGrammar, error
fmt.Fprintf(&b, "\n")
writeCompileError(&b, cerr)
}
return nil, fmt.Errorf(b.String())
return nil, nil, fmt.Errorf(b.String())
}
return nil, err
return nil, nil, err
}

kind2Term := make([]int, len(lexSpec.KindNames))
Expand All @@ -1330,7 +1328,7 @@ func Compile(gram *Grammar, opts ...CompileOption) (*spec.CompiledGrammar, error

sym, ok := gram.symbolTable.toSymbol(k.String())
if !ok {
return nil, fmt.Errorf("terminal symbol '%v' was not found in a symbol table", k)
return nil, nil, fmt.Errorf("terminal symbol '%v' was not found in a symbol table", k)
}
kind2Term[i] = sym.num().Int()
term2Kind[sym.num()] = i
Expand All @@ -1346,7 +1344,7 @@ func Compile(gram *Grammar, opts ...CompileOption) (*spec.CompiledGrammar, error

terms, err := gram.symbolTable.terminalTexts()
if err != nil {
return nil, err
return nil, nil, err
}

kindAliases := make([]string, gram.symbolTable.termNum.Int())
Expand All @@ -1356,24 +1354,25 @@ func Compile(gram *Grammar, opts ...CompileOption) (*spec.CompiledGrammar, error

nonTerms, err := gram.symbolTable.nonTerminalTexts()
if err != nil {
return nil, err
return nil, nil, err
}

firstSet, err := genFirstSet(gram.productionSet)
if err != nil {
return nil, err
return nil, nil, err
}

lr0, err := genLR0Automaton(gram.productionSet, gram.augmentedStartSymbol, gram.errorSymbol)
if err != nil {
return nil, err
return nil, nil, err
}

var tab *ParsingTable
var report *spec.Report
{
lalr1, err := genLALR1Automaton(lr0, gram.productionSet, firstSet)
if err != nil {
return nil, err
return nil, nil, err
}

b := &lrTableBuilder{
Expand All @@ -1387,48 +1386,15 @@ func Compile(gram *Grammar, opts ...CompileOption) (*spec.CompiledGrammar, error
}
tab, err = b.build()
if err != nil {
return nil, err
}

report, err := b.genReport(tab, gram)
if err != nil {
return nil, err
return nil, nil, err
}

if config.reportFileName != "" {
f, err := os.OpenFile(config.reportFileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if config.isReportingEnabled {
report, err = b.genReport(tab, gram)
if err != nil {
return nil, err
}
defer f.Close()

d, err := json.Marshal(report)
if err != nil {
return nil, err
}

_, err = f.Write(d)
if err != nil {
return nil, fmt.Errorf("failed to write a report: %w", err)
return nil, nil, err
}
}

var implicitlyResolvedCount int
for _, s := range report.States {
for _, c := range s.SRConflict {
if c.ResolvedBy == ResolvedByShift.Int() {
implicitlyResolvedCount++
}
}
for _, c := range s.RRConflict {
if c.ResolvedBy == ResolvedByProdOrder.Int() {
implicitlyResolvedCount++
}
}
}
if implicitlyResolvedCount > 0 {
fmt.Fprintf(os.Stderr, "%v conflicts\n", implicitlyResolvedCount)
}
}

action := make([]int, len(tab.actionTable))
Expand Down Expand Up @@ -1499,7 +1465,7 @@ func Compile(gram *Grammar, opts ...CompileOption) (*spec.CompiledGrammar, error
ASTAction: &spec.ASTAction{
Entries: astActEnties,
},
}, nil
}, report, nil
}

func writeCompileError(w io.Writer, cErr *mlcompiler.CompileError) {
Expand Down

0 comments on commit def1459

Please sign in to comment.