|
|
google 的go语言里呆了goyacc工具,使用方法基本类似于贝尔实验室的yacc工具. 所以熟悉了解yacc工具的人应该比较容易掌握。不过网上几乎没有找到详细的step by step。 虽然yacc方面的资料比较多, 但是具体细节上goyacc还是有些不同的。所幸在源代码里有一个sample。具体位置是~/go/src/cmd/goyacc/units.y. 算是比较详细。于是照猫画虎,经过代码裁剪,我自己写了个简单的表达式计算器goexpr。
一个.y文件中, 由^%{ 和 ^%}包括的部分, 是嵌入代码。 %%中间的部分则是要解析的LALR(1)文法,其定义是非常直接的。
于yacc不同的是, goyacc没有配套的lex/flex程序, 需要手工写一个方法 Lex()来得到所有的token. 还要写一个方法Error作为出错时的回调函数。除此之外都是顺水顺风的比较简单的东西。
使用方法
goyacc goexpr.y && 6g y.go && 6l -o goexpr y.6
如果是x86架构,则把6换成8即可
代码如下
%{package mainimport ("fmt""os""io/ioutil""flag""bufio")var fi *bufio.Readervar peekrune intvar data []bytevar linep int = 0var finalval float = 0%}%union{vvar string;numval float;}%token NUMBER%token OP%%expr: expr1| expr '+' expr1{$$.numval = $1.numval + $3.numvalfinalval = $$.numval}| expr '-' expr1{$$.numval = $1.numval - $3.numvalfinalval = $$.numval}expr1: NUMBER| expr1 '*' NUMBER{$$.numval = $1.numval * $3.numvalfinalval = $$.numval}| expr1 '/' NUMBER{$$.numval = $1.numval / $3.numvalfinalval = $$.numval}%%func getrune() int {if linep >= len(data) {return 0}c := data[linep]return int(c)}func next() {linep++}func getnumber(c int) int {var n int = 0for ;c>='0' && c <= '9'; {n += (c - '0')next()c = getrune()}yylval.numval = float(n)return NUMBER}func readblank() {var c intfor c = getrune(); c == ' '; {next()c = getrune()}}func Lex() int {var c intreadblank()c = getrune()if c >= '0' && c <= '9' {return getnumber(c)}switch c {case '+', '-', '*', '/':yylval.vvar = string(c)next()return c}return c}func Error(s string, v ...) {fmt.Printf("ERROR:%s\n", s)}func main() {if flag.NArg() == 0 {fmt.Printf("Usage goexpr <expr file>\n")os.Exit(1)}file := flag.Arg(0)f, err := os.Open(file, os.O_RDONLY, 0)if err != nil {fmt.Printf("Error opening %v: %v", file, err)os.Exit(2)}data, err = ioutil.ReadAll(f)if err != nil {fmt.Printf("Error reading file %v, %v\n", file, err)os.Exit(3)}Parse() // Parse the datafmt.Printf("result = %g\n", finalval)}
这段代码中, 如果 $3.numval 写成和上面一行, 这样$$.numval = $1.numval / $3.numval 就会报错。 <div class="quote_title">引用 |
|