用 gocc 构建抽象语法树AST从 BNF 到 Go 数据结构的完整教程【免费下载链接】goccParser / Scanner Generator项目地址: https://gitcode.com/gh_mirrors/go/goccgocc 是一个用 Go 编写的编译器工具包Parser / Scanner Generator它能从 BNF 文法自动生成词法分析器和 LR(1) 解析器并通过内嵌的语义动作把输入源码直接转换为抽象语法树AST——也就是你自定义的 Go 数据结构。本教程将带你从零开始通过一个可运行的真实示例完整演示如何用 gocc 构建抽象语法树让你在 10 分钟内跑通文法 → 生成 → 解析的整条链路。为什么用 gocc 构建抽象语法树很多新手写解析器时都会从手写递归下降解析器开始但一旦文法变复杂代码就会迅速失控。gocc 的价值在于你只需要写文法解析器代码全部自动生成。用 gocc 构建 AST 的核心优势自动生成词法分析器与解析器无需手写状态机天然支持 UTF-8 输入识别 LR(1) 语言并自动处理 shift/reduce、reduce/reduce 冲突语义动作与文法合为一体直接在 BNF 里写 ... 表达式调用你的 AST 构造函数生成代码零依赖可直接嵌入你的项目使用gocc 构建 AST 的完整工作流程整个过程只有三步编写.bnf文法文件定义词法单元、语法规则和内嵌的语义动作编写 AST 包定义节点类型Go 数据结构与构造函数运行 gocc 生成代码自动产出 lexer、parser、token 等包然后解析输入得到 AST接下来我们以项目自带的 example/astx 示例为蓝本一步步实操。第一步编写 BNF 文法文件ast.bnf先看文法文件 example/astx/ast.bnf它分为词法部分和语法部分/* Lexical elements */ _letter : a-z | A-Z ; _digit : 0-9 ; _idchar : _letter | _digit | _ ; id : (_letter | _) {_idchar} ; !whitespace : | \t | \n | \r ; /* Syntax elements */ import ( github.com/goccmack/gocc/example/astx/ast github.com/goccmack/gocc/example/astx/token ) StmtList : Stmt ast.NewStmtList($0) | StmtList Stmt ast.AppendStmt($0, $1) ; Stmt : id ast.NewStmt($T0) ;关键点解析id定义了一个标识符词法单元由一个字母或下划线开头后跟任意_idchar!whitespace表示空白符号被忽略不出现在语法中语法规则里每个产生式后面的 ast.NewStmtList($0) 就是action 表达式语义动作当该产生式被归约时调用你提供的构造函数来构建 AST 节点第二步编写 AST 包定义 Go 数据结构AST 包需要你自己编写见 example/astx/ast/ast.gopackage ast import ( github.com/goccmack/gocc/example/astx/token ) type ( StmtList []Stmt Stmt string ) func NewStmtList(stmt interface{}) (StmtList, error) { return StmtList{stmt.(Stmt)}, nil } func AppendStmt(stmtList, stmt interface{}) (StmtList, error) { return append(stmtList.(StmtList), stmt.(Stmt)), nil } // stmtList is passed using $T0. func NewStmt(stmtList *token.Token) (Stmt, error) { return Stmt(stmtList.Lit), nil }这里StmtList就是最终得到的抽象语法树根节点Stmt是叶子节点。所有构造函数的参数类型都是interface{}即 gocc 约定的Attrib类型返回值是(节点类型, error)。第三步运行 gocc 生成解析器代码确保 gocc 已安装后在文法文件所在目录执行一条命令cd example/astx gocc ast.bnf命令执行后gocc 会在当前目录自动生成lexer/、parser/、token/、util/、errors/等包。你不需要修改这些代码直接调用即可。也可以参考 example/astx/Makefile里面只有一行gocc ast.bnf以后重新生成文法就靠它。第四步解析输入文本拿到 Go 数据结构现在用生成的代码解析一段文本参考 example/astx/ast_test.gofunc test(src []byte) (astree ast.StmtList, err error) { s : lexer.NewLexer(src) p : parser.NewParser() a, err : p.Parse(s) if err nil { astree a.(ast.StmtList) } return }调用test([]byte(a b c d e f))gocc 会先让词法分析器切出一个个id记号再让解析器按文法归约每一次归约都会触发对应的 action 表达式最终返回一棵完整的StmtList树。整个过程你只写了文法解析器逻辑、状态表、错误处理全部由 gocc 自动生成。理解 action 表达式$0、$1 与 $T0这是用 gocc 构建 AST 时最需要掌握的知识点$0、$1、$2... 代表产生式右侧第 0、1、2 个符号归约后的Attrib值$T0、$T1... 是简写等价于$0.(*token.Token)专门用于已知是词法单元的情况例如Stmt : id ast.NewStmt($T0) 中$T0直接取出id对应的*token.TokenNewStmt取其中的Lit文本生成Stmt节点。更多例子可以参考 example/bools/example.bnf它展示了如何用 action 表达式构建布尔表达式、比较表达式和子串表达式的完整语法树。进阶玩法错误恢复与用户自定义上下文当你用 gocc 构建更复杂的抽象语法树时还有两个实用特性语法错误恢复在文法中加入error产生式即可实现错误恢复让解析器在遇到错误后继续解析后续内容参考 example/errorrecovery/er.bnf用户自定义上下文可以为词法分析器和解析器分别传入自定义上下文对象如源文件信息、回调函数参考 example/usercontext/ast/main.go常见问题Qgocc 生成代码的依赖怎么办生成的代码除 Go 标准库外几乎无额外依赖导入时指向你本地的lexer、parser、token包即可。Q可以只生成解析器而不生成词法分析器吗可以。gocc 也支持单独生成 DFA 或由你提供自定义扫描器参考 example/nolexer 示例。Q如何获取 gocc 源码和示例通过git clone https://gitcode.com/gh_mirrors/go/gocc即可克隆完整仓库所有示例和文档包括 doc/gocc_user_guide.pdf 用户指南都在其中。总结用 gocc 构建抽象语法树的核心思路就是一句话文法即规范动作即代码。你只需在 BNF 中声明语法规则和语义动作gocc 自动完成词法分析器、解析器与状态表的生成最终把输入文本变成结构清晰的 Go 数据结构。从本文的 astx 示例出发很快你就能为自己的语言或配置文件格式搭建出一套完整的解析框架。【免费下载链接】goccParser / Scanner Generator项目地址: https://gitcode.com/gh_mirrors/go/gocc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考