3

Go实现自定义语言的基础 - goyacc简易入门

 3 years ago
source link: https://studygolang.com/articles/34701
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go实现自定义语言的基础 - goyacc简易入门

mob604756f0bbf4 · 大约7小时之前 · 11 次点击 · 预计阅读时间 4 分钟 · 大约8小时之前 开始浏览    

0x01 自言自语
一直就对解析文档,比较感兴趣,一直没深入研究,只停留在仅知道 Lex & yacc 和 antlr 的名词阶段,最近看了go-zero的api解析器,觉得甚好,是时候花时间学习一下了。

简单看了go-zero发现是自己实现了词法分析、语法解析,这不符合我的一贯偷懒作风,所以并未其源码开始学习。既然用golang那么他自带的goyacc就是我学习的不二之选。当然你可能会听说Lex&yacc 已经很古老了,antlr更先进一点。但是既然goyacc能成为golang官方工具,那么肯定还是值得你学习的。

goyacc的文档非常的少,少到什么程度?少到你未来一定能搜到这篇。甚至连github上的使用例子也不多,大致就分两类:计算器、sql解析器,其中计算器目测是国外某大学的课程。

所以研究goyacc我花了好几个通宵、掉了少许头发。不经让这篇文章有了一个营销文案:花了一夜时间,搞懂了外国的一堂编译原理课。

个人对技术文章的理解是,文章可以有自己的观点、啰嗦、甚至幽默,但尽量不要放在学术部分,毕竟技术是严禁的。所以下面描述,我可尽能做一个无情的打字机,尽可能的按照文档风描述。

0x02 goyacc简易入门
安装 goyacc
golang 1.8 版本之前 yacc 直接再带与go tool 无需自行安装。
鉴于使用的频率太少,遂在 golang 1.8 版本后 移除默认安装,即之后版本需手动安装(仍然为官方包)。

一键安装:

go get -u github.com/golang/tools/tree/master/cmd/goyacc

之后就可以通过执行goyacc 命令来测试安装情况了,如看到如下信息大致就安装成功了

$ goyacc -h
Usage of goyacc:
  -l    disable line directives
  -o string
        parser output (default "y.go")
  -p string
        name prefix to use in generated code (default "yy")
  -v string
        create parsing tables (default "y.output")

goyacc 工具带有一些可选参数

Go实现自定义语言的基础 - goyacc简易入门
goyacc 编译 .y 文件
goyacc 工具的参数很简单,其作用主要是用来翻译BNF(一种语法标识方法)语法描述文件(通常以.y 为后缀的文件)

让我们先看一个.y 文件的例子:


file: parser.y

%{
package parser

import (
    "fmt"
)
%}

%union {
   var string
   num int
}

%token <num> NUM
%token <var> ADD SUB '+' '-'

%type <val> expr

%start expr

%%

expr: NUM {
  $$ =$1
}
| expr '+' NUM {
  $$ = $1 + $3
}
| expr '-' NUM {
  $$ = $1 + $3
}
;

复制上面代码命名为 parser.y 然后用我们之前安装好的goyacc执行:


$ goyacc parser.y

查看一下文件变化:

$ ls
parser.y y.go     y.output

我们发现生成了两个新文件:y.go y.output

至此 你已经实践了goyacc 的作用,即:通过文法文件(parser.y)生成语法解析器(y.go) 、解析过程表 (y.output)

请观察一下 y.go 文件的内容 (迫于篇幅,此处不贴y.go内容了,请按照上面进行试验即可得到)

下面我们详细说明一下 文法(.y文件)的格式:

.y 构成格式
如同 c版本的yacc一样, goyacc 的.y文件大致分成几个部分:

1,由{%与%} 包括的 嵌入代码
2,用%%分割的三段: 文法定义%%文法规则%%嵌入代码


{%
嵌入代码
%}
文法定义
%%
文法规则
%%

Go实现自定义语言的基础 - goyacc简易入门
下面详细描述一下 文法定义 与 文法规则:

文法定义
通常使用 %union %type %token %left %right 等构成

Go实现自定义语言的基础 - goyacc简易入门
如:
定义一个val类型 映射到 golang 的string类型,
定义一个stu 映射到 golang 中定义的一个 Student 结构体类型


%union {
    val string
    stu Student
}

%type

定义一个 val 类型的 expr 非终结符 (nonterminal)

%type <val> expr

%token 定义终结符

定义一个 val 类型的 Val 终结符 (terminal)

%token <val>  Val

(这听起来不好懂,可以想象成有个Val类型的变量接收器)

也可以定义个没有类型值的终结符,如

%token OP

%start 定义一个开始终结符

即设置一个开始的终结符,即从这里开始解析语法


%start expr

%left %right 主要用来设置匹配结合方式 这里先不展开了

文法规则
非终结符: 规则描述 {
动作描述
};

“:” 左边的是非终结符 右侧的规则 通常由 非终结符 终结符 构成

非终结符 使用%type定义的符号 可以理解为一串特定排序的 token
终结符 可以理解成 一个token
规则描述 可以是 非终结符、终结符, 可用 | 分割多个规则, {}内是具体动作,{}内符合golang语法,并且 $$ 代表规约后的值 $1 $2 代表类型变量值
动作描述 即用“{”与“}”包裹的部分 符合golang语法且拥有特殊宏替换的语法块

一条规则最后要用“;”结束


expr1: NUM '+' NUM {
 $$ = $1 + $3
};

这个例子定义了 一个 两数求和的规则 (a+b)
expr 构成终结符这个非 终结符的条件又三个终结符(token)构成 NUM '+' NUM ,方法是求两数之和 返回给expr

expr2: expr1 '+' NUM {
 $$ = $1 + $3
};

这个例子了 三个数求和,并使用了一个非终结符作为规则的一部分

当需要定一个多个数求和时就需要通过递归定义了

expr3: expr3 '+' NUM {
   $$ = $1 + $3
} | NUM '+' NUM {
  $$ = $1 + $3
};

这个例子通过递归定义了 支持多个数相加的规则 其中 使用“|”分割了两个规则描述

其实语法解析 就是根据token去拼出多个解析规则,然后通过递归下降等算法去解析。
yacc就是是通过.y 描述文件 生成递归下降程序(y.go程序)实现语法解析的一个过程。

实现lexer接口
goyacc 没有对应的 lex 生成工具
通常需要手工写两个方法来实现 yyLexer接口


type yyLexer interface {
    Lex(lval *yySymType) int
    Error(s string)
}

Lex() 词法分析函数 解析过程会多次回调此方法,每次调用应返一个token值, lval 是当前栈状态值
Error() 错误回调方法 在语法解析错误时被回调

一般可以通过 text/scanner 进行基础解析,再换算成自己的token,省去部分烦劳工作

0x03 附录A-名词解释

Go实现自定义语言的基础 - goyacc简易入门
0x04 附录B-常见错误

  • default action causes potential type clash: parser.go.y:40

  • token illegal on LHS of grammar rule

  • rules never reduced

  • rule $NAME: $NAME never reduced

  • conflicts: 1 shift/reduce

0x04 附录B-参考文献


有疑问加站长微信联系(非本文作者)

280

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK