aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/vartan/compile.go108
-rw-r--r--driver/conflict_test.go2
-rw-r--r--driver/lac_test.go2
-rw-r--r--driver/parser_test.go2
-rw-r--r--driver/semantic_action_test.go2
-rw-r--r--driver/syntax_error_test.go2
-rw-r--r--grammar/grammar.go70
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) {