blob: 9dc54d9c8c73a8f10baf4923e46d81b8eebe21b6 [file] [log] [blame]
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
// This program implements the core idea from
//
// Clinton L. Jeffery, Generating LR syntax error messages from examples,
// ACM TOPLAS 25(5) (September 2003). http://doi.acm.org/10.1145/937563.937566
//
// It reads Bison's summary of a grammar followed by a file
// like go.errors, replacing lines beginning with % by the
// yystate and yychar that will be active when an error happens
// while parsing that line.
//
// Unlike the system described in the paper, the lines in go.errors
// give grammar symbol name lists, not actual program fragments.
// This is a little less programmer-friendly but doesn't require being
// able to run the text through lex.c.
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"strconv"
"strings"
)
func xatoi(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
log.Fatal(err)
}
return n
}
func trimParen(s string) string {
s = strings.TrimPrefix(s, "(")
s = strings.TrimSuffix(s, ")")
return s
}
type action struct {
token string
n int
}
var shift = map[int][]action{}
var reduce = map[int][]action{}
type rule struct {
lhs string
size int
}
var rules = map[int]rule{}
func readYaccOutput() {
r, err := os.Open("y.output")
if err != nil {
log.Fatal(err)
}
defer r.Close()
var state int
scanner := bufio.NewScanner(r)
for scanner.Scan() {
f := strings.Fields(scanner.Text())
nf := len(f)
if nf >= 4 && f[1] == "terminals," && f[3] == "nonterminals" {
// We're done.
break
}
if nf >= 2 && f[0] == "state" {
state = xatoi(f[1])
continue
}
if nf >= 3 && (f[1] == "shift" || f[1] == "goto") {
shift[state] = append(shift[state], action{f[0], xatoi(f[2])})
continue
}
if nf >= 3 && f[1] == "reduce" {
reduce[state] = append(reduce[state], action{f[0], xatoi(f[2])})
continue
}
if nf >= 3 && strings.HasSuffix(f[0], ":") && strings.HasPrefix(f[nf-1], "(") && strings.HasSuffix(f[nf-1], ")") {
n := xatoi(trimParen(f[nf-1]))
size := nf - 2
if size == 1 && f[1] == "." {
size = 0
}
rules[n] = rule{strings.TrimSuffix(f[0], ":"), size}
continue
}
}
}
func runMachine(w io.Writer, s string) {
f := strings.Fields(s)
// Run it through the LR machine and print the induced "yystate, yychar,"
// at the point where the error happens.
var stack []int
state := 0
i := 1
tok := ""
Loop:
if tok == "" && i < len(f) {
tok = f[i]
i++
}
for _, a := range shift[state] {
if a.token == tok {
if false {
fmt.Println("SHIFT ", tok, " ", state, " -> ", a)
}
stack = append(stack, state)
state = a.n
tok = ""
goto Loop
}
}
for _, a := range reduce[state] {
if a.token == tok || a.token == "." {
stack = append(stack, state)
rule, ok := rules[a.n]
if !ok {
log.Fatal("missing rule")
}
stack = stack[:len(stack)-rule.size]
state = stack[len(stack)-1]
stack = stack[:len(stack)-1]
if tok != "" {
i--
}
tok = rule.lhs
if false {
fmt.Println("REDUCE ", stack, " ", state, " ", tok, " rule ", rule)
}
goto Loop
}
}
// No shift or reduce applied - found the error.
fmt.Fprintf(w, "\t{%d, %s,\n", state, tok)
}
func processGoErrors() {
r, err := os.Open("go.errors")
if err != nil {
log.Fatal(err)
}
defer r.Close()
w, err := os.Create("yymsg.go")
if err != nil {
log.Fatal(err)
}
defer w.Close()
fmt.Fprintf(w, "// DO NOT EDIT - generated with go generate\n\n")
scanner := bufio.NewScanner(r)
for scanner.Scan() {
s := scanner.Text()
// Treat % as first field on line as introducing a pattern (token sequence).
if strings.HasPrefix(strings.TrimSpace(s), "%") {
runMachine(w, s)
continue
}
fmt.Fprintln(w, s)
}
}
func main() {
readYaccOutput()
processGoErrors()
}