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, os.Stdout) 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 // /.json and /-report.json files, respectively. // -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 .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 /-report.json. func writeCompiledGrammarAndReport( cgram *spec.CompiledGrammar, report *spec.Report, w io.Writer, ) error { out := map[string]interface{}{ "grammar": cgram, "report": report, } b, err := json.Marshal(out) if err != nil { return err } fmt.Fprintf(w, "%v\n", string(b)) return nil }