diff options
Diffstat (limited to 'src/urubu/cmd/vartan/show.go')
-rw-r--r-- | src/urubu/cmd/vartan/show.go | 295 |
1 files changed, 295 insertions, 0 deletions
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 +} |