goyacc初体験


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
x 86アーキテクチャの場合は6を8に変更すればよい
コードは次のとおりです.

%{
	package main
	import (
		"fmt"
		"os"
		"io/ioutil"
		"flag"
		"bufio"
		)
	var fi *bufio.Reader
	var peekrune int
	var data []byte
	var linep int = 0
	var finalval float = 0
%}

%union
{
	vvar   string;
	numval float;
}

%token NUMBER
%token OP

%%
expr: 
         expr1
| expr '+' expr1
{
	$$.numval = $1.numval + $3.numval
	finalval = $$.numval
}
| expr '-' expr1
{
	$$.numval = $1.numval - $3.numval
	finalval = $$.numval
}

expr1:
         NUMBER
|        expr1 '*' NUMBER
{
	$$.numval = $1.numval * $3.numval
	finalval = $$.numval
}
|       expr1 '/' NUMBER
{
	$$.numval = $1.numval / 
		$3.numval
	finalval = $$.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 = 0
	for ;c>='0' && c <= '9'; {
		n += (c - '0')
		next()
		c = getrune()
	}
	yylval.numval = float(n)
	return NUMBER
	
}

func readblank() {
	var c int
	for c = getrune(); c == ' '; {
		next()
		c = getrune()
	}
}

func Lex() int {
	var c int
	readblank()
	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
", s) } func main() { if flag.NArg() == 0 { fmt.Printf("Usage goexpr <expr file>
") 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
", file, err) os.Exit(3) } Parse() // Parse the data fmt.Printf("result = %g
", finalval) }

このコードでは、$3の場合.numvalは上の行と書きます.
$$.numval = $1.numval / $3.numval
はエラーを報告します.
参照
y.go:61: syntax error: unexpected $
,ソースコードから,「/」は単行注釈の始まりとされているようで,バグと疑われ,後で報告する.