
本文详解 Go 内置 go tool yacc 的基本用法,指出常见语法错误(如非法规则格式),并提供一个结构正确、可编译运行的回显解析器完整实现,涵盖词法分析器(Lex)、语法规则和主程序集成。
本文详解 go 内置 `go tool yacc` 的基本用法,指出常见语法错误(如非法规则格式),并提供一个结构正确、可编译运行的回显解析器完整实现,涵盖词法分析器(lex)、语法规则和主程序集成。
Go 标准工具链中的 go tool yacc 是对经典 Berkeley Yacc 的兼容实现,用于生成 LALR(1) 解析器。但其语法严格遵循传统 Yacc 规范,不允许在 %% 分隔的 rules 区域中直接书写正则式或自由语句(如 .|\n ECHO;),这正是原问题报错 bad syntax on first rule 的根本原因。
Yacc 文件必须严格分为三部分(以 %% 分隔):
- Declarations(声明区):包含 Go 包声明、导入、%union、%token、%type 等;
- Rules(语法规则区):每条规则形如 NonTerminal : Symbol1 Symbol2 ... { Action };,: 和 ; 不可省略;
- Programs(程序区):Go 代码,如 main()、词法分析器 Lex()、错误处理 Error() 等。
下面是一个功能完整、可直接编译运行的“字符回显解析器”示例(保存为 echo.y):
%{
package main
import (
"bufio"
"fmt"
"os"
"unicode"
)
// 定义词法分析器状态结构体
type InputLex struct {
s string // 输入字符串(含换行符)
pos int // 当前解析位置
}
// 为 yacc 生成的符号类型提供别名(必需)
type InputSymType struct {
val string
}
%}
// 声明终结符(token)
%token <val> CHARACTER
%%
// 语法规则:支持空输入,或递归匹配多字符 + 换行
in : /* empty */
| in input '\n'
{ fmt.Printf("Read character: %s\n", $2) }
;
input : CHARACTER
| input CHARACTER
{ $$ = $1 + $2 }
;
%%
// 词法分析器:逐字符扫描并返回对应 token
func (l *InputLex) Lex(lval *InputSymType) int {
if l.pos >= len(l.s) {
return 0 // EOF
}
c := rune(l.s[l.pos])
l.pos++
// 跳过空白(实际应用中可按需调整)
if unicode.IsSpace(c) {
return -1 // 忽略,继续 Lex
}
// 仅接受小写字母和数字作为有效输入字符
if unicode.IsLower(c) || unicode.IsDigit(c) {
lval.val = string(c)
return CHARACTER
}
// 其他字符直接作为 error token 返回(触发语法错误)
return int(c)
}
// 错误处理函数
func (l *InputLex) Error(s string) {
fmt.Fprintf(os.Stderr, "syntax error: %s\n", s)
}
// 主函数:读取标准输入并启动解析
func main() {
fi := bufio.NewReader(os.Stdin)
line, ok := readline(fi)
if !ok {
fmt.Fprintln(os.Stderr, "read error")
return
}
lexer := &InputLex{s: line}
InputParse(lexer)
}
// 辅助函数:读取一行(模仿 calc.y 实现)
func readline(fi *bufio.Reader) (string, bool) {
line, isPrefix, err := fi.ReadLine()
if err != nil {
return "", false
}
for isPrefix {
var l []byte
l, isPrefix, err = fi.ReadLine()
if err != nil {
return "", false
}
line = append(line, l...)
}
return string(line), true
}</val>
✅ 编译与运行命令:
go tool yacc -o echo.go -p Input echo.y go run echo.go
输入 hello 后回车,将逐字符输出:
Read character: h Read character: e Read character: l Read character: l Read character: o
⚠️ 关键注意事项:
- go tool yacc 不支持正则式直接匹配(如 .|\n),所有词法逻辑必须在 Lex() 方法中手动实现;
- %union 在 Go yacc 中虽非强制,但若需传递值(如字符串),应配合 type SymType 显式定义(如本例 InputSymType);
- 生成的解析器函数名默认为 {prefix}Parse(由 -p Input 决定),调用时务必匹配;
- Lex() 返回 0 表示 EOF,负数表示忽略该字符,正整数为 token 类型(需与 %token 一致);
- 初学者建议从 calc.y 等官方示例入手,先理解“表达式求值”这类典型文法,再拓展至自定义场景。
通过此示例,你已掌握 Go yacc 的最小可行结构:合法规则定义 + 可控词法器 + 清晰生命周期管理。后续可基于此骨架,扩展更复杂的语法(如关键字识别、嵌套结构解析),或结合 go generate 实现自动化构建流程。











