diff options
Diffstat (limited to 'src/urubu/cmd/vartan/compile.go')
-rw-r--r-- | src/urubu/cmd/vartan/compile.go | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/src/urubu/cmd/vartan/compile.go b/src/urubu/cmd/vartan/compile.go new file mode 100644 index 0000000..79fa9ef --- /dev/null +++ b/src/urubu/cmd/vartan/compile.go @@ -0,0 +1,190 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + + verr "urubu/error" + "urubu/grammar" + spec "urubu/spec/grammar" + "urubu/spec/grammar/parser" +) + + + +func runCompile(args []string) (retErr error) { + var tmpDirPath string + defer func() { + if tmpDirPath == "" { + return + } + os.RemoveAll(tmpDirPath) + }() + + var grmPath string + if len(args) > 0 { + grmPath = args[0] + } + defer func() { + if retErr != nil { + specErrs, ok := retErr.(verr.SpecErrors) + if ok { + for _, err := range specErrs { + if len(args) > 0 { + err.FilePath = grmPath + err.SourceName = grmPath + } else { + err.FilePath = grmPath + err.SourceName = "stdin" + } + } + } + } + }() + + if grmPath == "" { + var err error + tmpDirPath, err = os.MkdirTemp("", "vartan-compile-*") + if err != nil { + return err + } + + src, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } + + grmPath = filepath.Join(tmpDirPath, "stdin.vartan") + err = os.WriteFile(grmPath, src, 0600) + if err != nil { + return err + } + } + + gram, report, err := readGrammar(grmPath) + if err != nil { + return err + } + + err = writeCompiledGrammarAndReport(gram, report, "") + if err != nil { + 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 +} + +func readGrammar(path string) (*spec.CompiledGrammar, *spec.Report, error) { + f, err := os.Open(path) + if err != nil { + return nil, nil, fmt.Errorf("Cannot open the grammar file %s: %w", path, err) + } + defer f.Close() + + ast, err := parser.Parse(f) + if err != nil { + return nil, nil, err + } + + b := grammar.GrammarBuilder{ + AST: ast, + } + return b.Build(grammar.EnableReporting()) +} + +// 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 + } + + { + 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 err + } + fmt.Fprintf(cgramW, "%v\n", string(b)) + } + + { + 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 +} |