diff options
-rw-r--r-- | cmd/vartan/compile.go | 108 | ||||
-rw-r--r-- | driver/conflict_test.go | 2 | ||||
-rw-r--r-- | driver/lac_test.go | 2 | ||||
-rw-r--r-- | driver/parser_test.go | 2 | ||||
-rw-r--r-- | driver/semantic_action_test.go | 2 | ||||
-rw-r--r-- | driver/syntax_error_test.go | 2 | ||||
-rw-r--r-- | grammar/grammar.go | 70 |
7 files changed, 112 insertions, 76 deletions
diff --git a/cmd/vartan/compile.go b/cmd/vartan/compile.go index 7e594a8..b8b97b1 100644 --- a/cmd/vartan/compile.go +++ b/cmd/vartan/compile.go @@ -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" @@ -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 @@ -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 +} diff --git a/driver/conflict_test.go b/driver/conflict_test.go index 1a1199b..93a8111 100644 --- a/driver/conflict_test.go +++ b/driver/conflict_test.go @@ -499,7 +499,7 @@ assign: '='; t.Fatal(err) } - cg, err := grammar.Compile(g) + cg, _, err := grammar.Compile(g) if err != nil { t.Fatal(err) } diff --git a/driver/lac_test.go b/driver/lac_test.go index e612b13..d850e9b 100644 --- a/driver/lac_test.go +++ b/driver/lac_test.go @@ -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) } diff --git a/driver/parser_test.go b/driver/parser_test.go index 65958bc..bec6a51 100644 --- a/driver/parser_test.go +++ b/driver/parser_test.go @@ -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) } diff --git a/driver/semantic_action_test.go b/driver/semantic_action_test.go index d0e769e..d480a0a 100644 --- a/driver/semantic_action_test.go +++ b/driver/semantic_action_test.go @@ -194,7 +194,7 @@ char t.Fatal(err) } - gram, err := grammar.Compile(g) + gram, _, err := grammar.Compile(g) if err != nil { t.Fatal(err) } diff --git a/driver/syntax_error_test.go b/driver/syntax_error_test.go index c49d804..a004a44 100644 --- a/driver/syntax_error_test.go +++ b/driver/syntax_error_test.go @@ -126,7 +126,7 @@ c t.Fatal(err) } - gram, err := grammar.Compile(g) + gram, _, err := grammar.Compile(g) if err != nil { t.Fatal(err) } diff --git a/grammar/grammar.go b/grammar/grammar.go index 368ede6..2e0c8f4 100644 --- a/grammar/grammar.go +++ b/grammar/grammar.go @@ -1,10 +1,8 @@ package grammar import ( - "encoding/json" "fmt" "io" - "os" "strings" mlcompiler "github.com/nihei9/maleeni/compiler" @@ -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) @@ -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)) @@ -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 @@ -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()) @@ -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{ @@ -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)) @@ -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) { |