github.com/rsc/go@v0.0.0-20150416155037-e040fd465409/src/cmd/internal/gc/yaccerrors.go (about) 1 // Copyright 2010 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build ignore 6 7 // This program implements the core idea from 8 // 9 // Clinton L. Jeffery, Generating LR syntax error messages from examples, 10 // ACM TOPLAS 25(5) (September 2003). http://doi.acm.org/10.1145/937563.937566 11 // 12 // It reads Bison's summary of a grammar followed by a file 13 // like go.errors, replacing lines beginning with % by the 14 // yystate and yychar that will be active when an error happens 15 // while parsing that line. 16 // 17 // Unlike the system described in the paper, the lines in go.errors 18 // give grammar symbol name lists, not actual program fragments. 19 // This is a little less programmer-friendly but doesn't require being 20 // able to run the text through lex.c. 21 22 package main 23 24 import ( 25 "bufio" 26 "fmt" 27 "io" 28 "log" 29 "os" 30 "strconv" 31 "strings" 32 ) 33 34 func xatoi(s string) int { 35 n, err := strconv.Atoi(s) 36 if err != nil { 37 log.Fatal(err) 38 } 39 return n 40 } 41 42 func trimParen(s string) string { 43 s = strings.TrimPrefix(s, "(") 44 s = strings.TrimSuffix(s, ")") 45 return s 46 } 47 48 type action struct { 49 token string 50 n int 51 } 52 53 var shift = map[int][]action{} 54 var reduce = map[int][]action{} 55 56 type rule struct { 57 lhs string 58 size int 59 } 60 61 var rules = map[int]rule{} 62 63 func readYaccOutput() { 64 r, err := os.Open("y.output") 65 if err != nil { 66 log.Fatal(err) 67 } 68 defer r.Close() 69 70 var state int 71 72 scanner := bufio.NewScanner(r) 73 for scanner.Scan() { 74 f := strings.Fields(scanner.Text()) 75 nf := len(f) 76 77 if nf >= 4 && f[1] == "terminals," && f[3] == "nonterminals" { 78 // We're done. 79 break 80 } 81 82 if nf >= 2 && f[0] == "state" { 83 state = xatoi(f[1]) 84 continue 85 } 86 if nf >= 3 && (f[1] == "shift" || f[1] == "goto") { 87 shift[state] = append(shift[state], action{f[0], xatoi(f[2])}) 88 continue 89 } 90 if nf >= 3 && f[1] == "reduce" { 91 reduce[state] = append(reduce[state], action{f[0], xatoi(f[2])}) 92 continue 93 } 94 if nf >= 3 && strings.HasSuffix(f[0], ":") && strings.HasPrefix(f[nf-1], "(") && strings.HasSuffix(f[nf-1], ")") { 95 n := xatoi(trimParen(f[nf-1])) 96 97 size := nf - 2 98 if size == 1 && f[1] == "." { 99 size = 0 100 } 101 102 rules[n] = rule{strings.TrimSuffix(f[0], ":"), size} 103 continue 104 } 105 } 106 } 107 108 func runMachine(w io.Writer, s string) { 109 f := strings.Fields(s) 110 111 // Run it through the LR machine and print the induced "yystate, yychar," 112 // at the point where the error happens. 113 114 var stack []int 115 state := 0 116 i := 1 117 tok := "" 118 119 Loop: 120 if tok == "" && i < len(f) { 121 tok = f[i] 122 i++ 123 } 124 125 for _, a := range shift[state] { 126 if a.token == tok { 127 if false { 128 fmt.Println("SHIFT ", tok, " ", state, " -> ", a) 129 } 130 stack = append(stack, state) 131 state = a.n 132 tok = "" 133 goto Loop 134 } 135 } 136 137 for _, a := range reduce[state] { 138 if a.token == tok || a.token == "." { 139 stack = append(stack, state) 140 rule, ok := rules[a.n] 141 if !ok { 142 log.Fatal("missing rule") 143 } 144 stack = stack[:len(stack)-rule.size] 145 state = stack[len(stack)-1] 146 stack = stack[:len(stack)-1] 147 if tok != "" { 148 i-- 149 } 150 tok = rule.lhs 151 if false { 152 fmt.Println("REDUCE ", stack, " ", state, " ", tok, " rule ", rule) 153 } 154 goto Loop 155 } 156 } 157 158 // No shift or reduce applied - found the error. 159 fmt.Fprintf(w, "\t{%d, %s,\n", state, tok) 160 } 161 162 func processGoErrors() { 163 r, err := os.Open("go.errors") 164 if err != nil { 165 log.Fatal(err) 166 } 167 defer r.Close() 168 169 w, err := os.Create("yymsg.go") 170 if err != nil { 171 log.Fatal(err) 172 } 173 defer w.Close() 174 175 fmt.Fprintf(w, "// DO NOT EDIT - generated with go generate\n\n") 176 177 scanner := bufio.NewScanner(r) 178 for scanner.Scan() { 179 s := scanner.Text() 180 181 // Treat % as first field on line as introducing a pattern (token sequence). 182 if strings.HasPrefix(strings.TrimSpace(s), "%") { 183 runMachine(w, s) 184 continue 185 } 186 187 fmt.Fprintln(w, s) 188 } 189 } 190 191 func main() { 192 readYaccOutput() 193 processGoErrors() 194 }