diff options
Diffstat (limited to 'src/urubu/cmd')
-rw-r--r-- | src/urubu/cmd/ucdgen/main.go | 98 | ||||
-rw-r--r-- | src/urubu/cmd/vartan-go/generate.go | 98 | ||||
-rw-r--r-- | src/urubu/cmd/vartan-go/main.go | 14 | ||||
-rw-r--r-- | src/urubu/cmd/vartan/compile.go | 190 | ||||
-rw-r--r-- | src/urubu/cmd/vartan/main.go | 14 | ||||
-rw-r--r-- | src/urubu/cmd/vartan/parse.go | 110 | ||||
-rw-r--r-- | src/urubu/cmd/vartan/root.go | 22 | ||||
-rw-r--r-- | src/urubu/cmd/vartan/show.go | 295 | ||||
-rw-r--r-- | src/urubu/cmd/vartan/test.go | 50 |
9 files changed, 891 insertions, 0 deletions
diff --git a/src/urubu/cmd/ucdgen/main.go b/src/urubu/cmd/ucdgen/main.go new file mode 100644 index 0000000..d5599a6 --- /dev/null +++ b/src/urubu/cmd/ucdgen/main.go @@ -0,0 +1,98 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "strings" + "text/template" + + "urubu/ucd" +) + +func main() { + err := gen() + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +func gen() error { + var propValAliases *ucd.PropertyValueAliases + { + resp, err := http.Get("https://www.unicode.org/Public/13.0.0/ucd/PropertyValueAliases.txt") + if err != nil { + return err + } + defer resp.Body.Close() + propValAliases, err = ucd.ParsePropertyValueAliases(resp.Body) + if err != nil { + return err + } + } + var unicodeData *ucd.UnicodeData + { + resp, err := http.Get("https://www.unicode.org/Public/13.0.0/ucd/UnicodeData.txt") + if err != nil { + return err + } + defer resp.Body.Close() + unicodeData, err = ucd.ParseUnicodeData(resp.Body, propValAliases) + if err != nil { + return err + } + } + var scripts *ucd.Scripts + { + resp, err := http.Get("https://www.unicode.org/Public/13.0.0/ucd/Scripts.txt") + if err != nil { + return err + } + defer resp.Body.Close() + scripts, err = ucd.ParseScripts(resp.Body, propValAliases) + if err != nil { + return err + } + } + var propList *ucd.PropList + { + resp, err := http.Get("https://www.unicode.org/Public/13.0.0/ucd/PropList.txt") + if err != nil { + return err + } + defer resp.Body.Close() + propList, err = ucd.ParsePropList(resp.Body) + if err != nil { + return err + } + } + tmpl, err := template.ParseFiles("../ucd/codepoint.go.tmpl") + if err != nil { + return err + } + var b strings.Builder + err = tmpl.Execute(&b, struct { + GeneratorName string + UnicodeData *ucd.UnicodeData + Scripts *ucd.Scripts + PropList *ucd.PropList + PropertyValueAliases *ucd.PropertyValueAliases + }{ + GeneratorName: "generator/main.go", + UnicodeData: unicodeData, + Scripts: scripts, + PropList: propList, + PropertyValueAliases: propValAliases, + }) + if err != nil { + return err + } + f, err := os.OpenFile("../ucd/codepoint.go", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + fmt.Fprint(f, b.String()) + return nil +} diff --git a/src/urubu/cmd/vartan-go/generate.go b/src/urubu/cmd/vartan-go/generate.go new file mode 100644 index 0000000..8f2da84 --- /dev/null +++ b/src/urubu/cmd/vartan-go/generate.go @@ -0,0 +1,98 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "urubu/driver/lexer" + "urubu/driver/parser" + spec "urubu/spec/grammar" +) + +func runGenerate(args []string) error { + cgram, err := readCompiledGrammar(args[0]) + if err != nil { + return fmt.Errorf("Cannot read a compiled grammar: %w", err) + } + + { + b, err := lexer.GenLexer(cgram.Lexical, "main") + if err != nil { + return fmt.Errorf("Failed to generate a lexer: %w", err) + } + + filePath := fmt.Sprintf("%v_lexer.go", cgram.Name) + + f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return fmt.Errorf("Failed to create an output file: %v", err) + } + defer f.Close() + + _, err = f.Write(b) + if err != nil { + return fmt.Errorf("Failed to write lexer source code: %v", err) + } + } + + { + b, err := parser.GenParser(cgram, "main") + if err != nil { + return fmt.Errorf("Failed to generate a parser: %w", err) + } + + filePath := fmt.Sprintf("%v_parser.go", cgram.Name) + + f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return fmt.Errorf("Failed to create an output file: %v", err) + } + defer f.Close() + + _, err = f.Write(b) + if err != nil { + return fmt.Errorf("Failed to write parser source code: %v", err) + } + } + + { + b, err := parser.GenSemanticAction("main") + if err != nil { + return fmt.Errorf("Failed to generate a semantic action set: %w", err) + } + + filePath := fmt.Sprintf("%v_semantic_action.go", cgram.Name) + + f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return fmt.Errorf("Failed to create an output file: %v", err) + } + defer f.Close() + + _, err = f.Write(b) + if err != nil { + return fmt.Errorf("Failed to write semantic action source code: %v", err) + } + } + + return nil +} + +func readCompiledGrammar(path string) (*spec.CompiledGrammar, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + data, err := io.ReadAll(f) + if err != nil { + return nil, err + } + cgram := &spec.CompiledGrammar{} + err = json.Unmarshal(data, cgram) + if err != nil { + return nil, err + } + return cgram, nil +} diff --git a/src/urubu/cmd/vartan-go/main.go b/src/urubu/cmd/vartan-go/main.go new file mode 100644 index 0000000..315e7b3 --- /dev/null +++ b/src/urubu/cmd/vartan-go/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + err := runGenerate(os.Args[1:]) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} 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 +} diff --git a/src/urubu/cmd/vartan/main.go b/src/urubu/cmd/vartan/main.go new file mode 100644 index 0000000..98f98e1 --- /dev/null +++ b/src/urubu/cmd/vartan/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + err := Execute() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/src/urubu/cmd/vartan/parse.go b/src/urubu/cmd/vartan/parse.go new file mode 100644 index 0000000..9c5fd9c --- /dev/null +++ b/src/urubu/cmd/vartan/parse.go @@ -0,0 +1,110 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + "strings" + + driver "urubu/driver/parser" + spec "urubu/spec/grammar" +) + + + +func runParse(args []string) error { + cg, err := readCompiledGrammar(args[0]) + if err != nil { + return fmt.Errorf("Cannot read a compiled grammar: %w", err) + } + + src := os.Stdin + gram := driver.NewGrammar(cg) + + tb := driver.NewDefaultSyntaxTreeBuilder() + treeAct := driver.NewCSTActionSet(gram, tb) + + opts := []driver.ParserOption{} + opts = append(opts, driver.SemanticAction(treeAct)) + + toks, err := driver.NewTokenStream(cg, src) + if err != nil { + return err + } + + p, err := driver.NewParser(toks, gram, opts...) + if err != nil { + return err + } + + err = p.Parse() + if err != nil { + return err + } + + // A parser can construct a parse tree even if syntax errors occur. + // When therer is a parse tree, print it. + if tree := tb.Tree(); tree != nil { + b, err := json.Marshal(tree) + if err != nil { + return err + } + fmt.Fprintln(os.Stdout, string(b)) + } + + if len(p.SyntaxErrors()) > 0 { + var b strings.Builder + synErrs := p.SyntaxErrors() + writeSyntaxErrorMessage(&b, cg, synErrs[0]) + for _, synErr := range synErrs[1:] { + fmt.Fprintf(&b, "\n") + writeSyntaxErrorMessage(&b, cg, synErr) + } + if b.Len() > 0 { + return fmt.Errorf(b.String()) + } + } + + return nil +} + +func readCompiledGrammar(path string) (*spec.CompiledGrammar, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + data, err := io.ReadAll(f) + if err != nil { + return nil, err + } + cg := &spec.CompiledGrammar{} + err = json.Unmarshal(data, cg) + if err != nil { + return nil, err + } + return cg, nil +} + +func writeSyntaxErrorMessage(b *strings.Builder, cgram *spec.CompiledGrammar, synErr *driver.SyntaxError) { + fmt.Fprintf(b, "%v:%v: %v: ", synErr.Row+1, synErr.Col+1, synErr.Message) + + tok := synErr.Token + switch { + case tok.EOF(): + fmt.Fprintf(b, "<eof>") + case tok.Invalid(): + fmt.Fprintf(b, "'%v' (<invalid>)", string(tok.Lexeme())) + default: + if kind := cgram.Syntactic.Terminals[tok.TerminalID()]; kind != "" { + fmt.Fprintf(b, "'%v' (%v)", string(tok.Lexeme()), kind) + } else { + fmt.Fprintf(b, "'%v'", string(tok.Lexeme())) + } + } + + fmt.Fprintf(b, ": expected: %v", synErr.ExpectedTerminals[0]) + for _, t := range synErr.ExpectedTerminals[1:] { + fmt.Fprintf(b, ", %v", t) + } +} diff --git a/src/urubu/cmd/vartan/root.go b/src/urubu/cmd/vartan/root.go new file mode 100644 index 0000000..3dda70c --- /dev/null +++ b/src/urubu/cmd/vartan/root.go @@ -0,0 +1,22 @@ +package main + +import ( + "os" +) + +func Execute() error { + cmd := os.Args[1] + args := os.Args[2:] + + if cmd == "compile" { + return runCompile(args) + } else if cmd == "parse" { + return runParse(args) + } else if cmd == "show" { + return runShow(args) + } else if cmd == "test" { + return runTest(args) + } + + return nil // FIXME +} diff --git a/src/urubu/cmd/vartan/show.go b/src/urubu/cmd/vartan/show.go new file mode 100644 index 0000000..cab63a0 --- /dev/null +++ b/src/urubu/cmd/vartan/show.go @@ -0,0 +1,295 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + "strings" + "text/template" + + "urubu/grammar" + spec "urubu/spec/grammar" +) + + + +func runShow(args []string) error { + report, err := readReport(args[0]) + if err != nil { + return err + } + + err = writeReport(os.Stdout, report) + if err != nil { + return err + } + + return nil +} + +func readReport(path string) (*spec.Report, error) { + f, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("Cannot open the report %s: %w", path, err) + } + defer f.Close() + + d, err := io.ReadAll(f) + if err != nil { + return nil, err + } + + report := &spec.Report{} + err = json.Unmarshal(d, report) + if err != nil { + return nil, err + } + + return report, nil +} + +const reportTemplate = `# Conflicts + +{{ printConflictSummary . }} + +# Terminals + +{{ range slice .Terminals 1 -}} +{{ printTerminal . }} +{{ end }} +# Productions + +{{ range slice .Productions 1 -}} +{{ printProduction . }} +{{ end }} +# States +{{ range .States }} +## State {{ .Number }} + +{{ range .Kernel -}} +{{ printItem . }} +{{ end }} +{{ range .Shift -}} +{{ printShift . }} +{{ end -}} +{{ range .Reduce -}} +{{ printReduce . }} +{{ end -}} +{{ range .GoTo -}} +{{ printGoTo . }} +{{ end }} +{{ range .SRConflict -}} +{{ printSRConflict . }} +{{ end -}} +{{ range .RRConflict -}} +{{ printRRConflict . }} +{{ end -}} +{{ end }}` + +func writeReport(w io.Writer, report *spec.Report) error { + termName := func(sym int) string { + return report.Terminals[sym].Name + } + + nonTermName := func(sym int) string { + return report.NonTerminals[sym].Name + } + + termAssoc := func(sym int) string { + switch report.Terminals[sym].Associativity { + case "l": + return "left" + case "r": + return "right" + default: + return "no" + } + } + + prodAssoc := func(prod int) string { + switch report.Productions[prod].Associativity { + case "l": + return "left" + case "r": + return "right" + default: + return "no" + } + } + + fns := template.FuncMap{ + "printConflictSummary": func(report *spec.Report) string { + var implicitlyResolvedCount int + var explicitlyResolvedCount int + for _, s := range report.States { + for _, c := range s.SRConflict { + if c.ResolvedBy == grammar.ResolvedByShift.Int() { + implicitlyResolvedCount++ + } else { + explicitlyResolvedCount++ + } + } + for _, c := range s.RRConflict { + if c.ResolvedBy == grammar.ResolvedByProdOrder.Int() { + implicitlyResolvedCount++ + } else { + explicitlyResolvedCount++ + } + } + } + + var b strings.Builder + if implicitlyResolvedCount == 1 { + fmt.Fprintf(&b, "%v conflict occurred and resolved implicitly.\n", implicitlyResolvedCount) + } else if implicitlyResolvedCount > 1 { + fmt.Fprintf(&b, "%v conflicts occurred and resolved implicitly.\n", implicitlyResolvedCount) + } + if explicitlyResolvedCount == 1 { + fmt.Fprintf(&b, "%v conflict occurred and resolved explicitly.\n", explicitlyResolvedCount) + } else if explicitlyResolvedCount > 1 { + fmt.Fprintf(&b, "%v conflicts occurred and resolved explicitly.\n", explicitlyResolvedCount) + } + if implicitlyResolvedCount == 0 && explicitlyResolvedCount == 0 { + fmt.Fprintf(&b, "No conflict") + } + return b.String() + }, + "printTerminal": func(term spec.Terminal) string { + var prec string + if term.Precedence != 0 { + prec = fmt.Sprintf("%2v", term.Precedence) + } else { + prec = " -" + } + + var assoc string + if term.Associativity != "" { + assoc = term.Associativity + } else { + assoc = "-" + } + + return fmt.Sprintf("%4v %v %v %v", term.Number, prec, assoc, term.Name) + }, + "printProduction": func(prod spec.Production) string { + var prec string + if prod.Precedence != 0 { + prec = fmt.Sprintf("%2v", prod.Precedence) + } else { + prec = " -" + } + + var assoc string + if prod.Associativity != "" { + assoc = prod.Associativity + } else { + assoc = "-" + } + + var b strings.Builder + fmt.Fprintf(&b, "%v →", nonTermName(prod.LHS)) + if len(prod.RHS) > 0 { + for _, e := range prod.RHS { + if e > 0 { + fmt.Fprintf(&b, " %v", termName(e)) + } else { + fmt.Fprintf(&b, " %v", nonTermName(e*-1)) + } + } + } else { + fmt.Fprintf(&b, " ε") + } + + return fmt.Sprintf("%4v %v %v %v", prod.Number, prec, assoc, b.String()) + }, + "printItem": func(item spec.Item) string { + prod := report.Productions[item.Production] + + var b strings.Builder + fmt.Fprintf(&b, "%v →", nonTermName(prod.LHS)) + for i, e := range prod.RHS { + if i == item.Dot { + fmt.Fprintf(&b, " ・") + } + if e > 0 { + fmt.Fprintf(&b, " %v", termName(e)) + } else { + fmt.Fprintf(&b, " %v", nonTermName(e*-1)) + } + } + if item.Dot >= len(prod.RHS) { + fmt.Fprintf(&b, " ・") + } + + return fmt.Sprintf("%4v %v", prod.Number, b.String()) + }, + "printShift": func(tran spec.Transition) string { + return fmt.Sprintf("shift %4v on %v", tran.State, termName(tran.Symbol)) + }, + "printReduce": func(reduce spec.Reduce) string { + var b strings.Builder + { + fmt.Fprintf(&b, "%v", termName(reduce.LookAhead[0])) + for _, a := range reduce.LookAhead[1:] { + fmt.Fprintf(&b, ", %v", termName(a)) + } + } + return fmt.Sprintf("reduce %4v on %v", reduce.Production, b.String()) + }, + "printGoTo": func(tran spec.Transition) string { + return fmt.Sprintf("goto %4v on %v", tran.State, nonTermName(tran.Symbol)) + }, + "printSRConflict": func(sr spec.SRConflict) string { + var adopted string + switch { + case sr.AdoptedState != nil: + adopted = fmt.Sprintf("shift %v", *sr.AdoptedState) + case sr.AdoptedProduction != nil: + adopted = fmt.Sprintf("reduce %v", *sr.AdoptedProduction) + } + var resolvedBy string + switch sr.ResolvedBy { + case grammar.ResolvedByPrec.Int(): + if sr.AdoptedState != nil { + resolvedBy = fmt.Sprintf("symbol %v has higher precedence than production %v", termName(sr.Symbol), sr.Production) + } else { + resolvedBy = fmt.Sprintf("production %v has higher precedence than symbol %v", sr.Production, termName(sr.Symbol)) + } + case grammar.ResolvedByAssoc.Int(): + if sr.AdoptedState != nil { + resolvedBy = fmt.Sprintf("symbol %v and production %v has the same precedence, and symbol %v has %v associativity", termName(sr.Symbol), sr.Production, termName(sr.Symbol), termAssoc(sr.Symbol)) + } else { + resolvedBy = fmt.Sprintf("production %v and symbol %v has the same precedence, and production %v has %v associativity", sr.Production, termName(sr.Symbol), sr.Production, prodAssoc(sr.Production)) + } + case grammar.ResolvedByShift.Int(): + resolvedBy = fmt.Sprintf("symbol %v and production %v don't define a precedence comparison (default rule)", sr.Symbol, sr.Production) + default: + resolvedBy = "?" // This is a bug. + } + return fmt.Sprintf("shift/reduce conflict (shift %v, reduce %v) on %v: %v adopted because %v", sr.State, sr.Production, termName(sr.Symbol), adopted, resolvedBy) + }, + "printRRConflict": func(rr spec.RRConflict) string { + var resolvedBy string + switch rr.ResolvedBy { + case grammar.ResolvedByProdOrder.Int(): + resolvedBy = fmt.Sprintf("production %v and %v don't define a precedence comparison (default rule)", rr.Production1, rr.Production2) + default: + resolvedBy = "?" // This is a bug. + } + return fmt.Sprintf("reduce/reduce conflict (%v, %v) on %v: reduce %v adopted because %v", rr.Production1, rr.Production2, termName(rr.Symbol), rr.AdoptedProduction, resolvedBy) + }, + } + + tmpl, err := template.New("").Funcs(fns).Parse(reportTemplate) + if err != nil { + return err + } + + err = tmpl.Execute(w, report) + if err != nil { + return err + } + + return nil +} diff --git a/src/urubu/cmd/vartan/test.go b/src/urubu/cmd/vartan/test.go new file mode 100644 index 0000000..eb24c0c --- /dev/null +++ b/src/urubu/cmd/vartan/test.go @@ -0,0 +1,50 @@ +package main + +import ( + "errors" + "fmt" + "os" + + "urubu/tester" +) + + + +func runTest(args []string) error { + gram, _, err := readGrammar(args[0]) + if err != nil { + return fmt.Errorf("Cannot read a grammar: %w", err) + } + + var cs []*tester.TestCaseWithMetadata + { + cs = tester.ListTestCases(args[1]) + errOccurred := false + for _, c := range cs { + if c.Error != nil { + fmt.Fprintf(os.Stderr, "Failed to read a test case or a directory: %v\n%v\n", c.FilePath, c.Error) + errOccurred = true + } + } + if errOccurred { + return errors.New("Cannot run test") + } + } + + t := &tester.Tester{ + Grammar: gram, + Cases: cs, + } + rs := t.Run() + testFailed := false + for _, r := range rs { + fmt.Fprintln(os.Stdout, r) + if r.Error != nil { + testFailed = true + } + } + if testFailed { + return errors.New("Test failed") + } + return nil +} |