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  }