aboutsummaryrefslogtreecommitdiff
path: root/src/urubu/cmd/vartan/compile.go
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2024-12-10 12:29:03 -0300
committerEuAndreh <eu@euandre.org>2024-12-10 12:29:03 -0300
commit8359c047aaebe274a2d811d61922b571ca7d10df (patch)
tree070e0ed93d27a842776ada805eeb4270e7e3c806 /src/urubu/cmd/vartan/compile.go
parentStart building test files (diff)
downloadcotia-8359c047aaebe274a2d811d61922b571ca7d10df.tar.gz
cotia-8359c047aaebe274a2d811d61922b571ca7d10df.tar.xz
Namespace packages with "urubu/"
Diffstat (limited to 'src/urubu/cmd/vartan/compile.go')
-rw-r--r--src/urubu/cmd/vartan/compile.go190
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
+}