凹语言版本 yacc 简介 - 以表达式解析为例

d77b3a5759e23c7c720eb24c91d51b9b.jpeg

yacc 是用于生成语法解析器的程序,是编译器爱好者的工具。凹语言的 yacc 从 goyacc 移植而来,目前可以初步支持输出 凹语言 版本解析器代码。本文以以表达式解析为例展示下用法。

完整的例子可以参考(这里使用的是expr前缀):https://gitee.com/wa-lang/wa/blob/master/_examples/expr/expr.y。

如果对 yacc 不太了解可以先参考以下图书:

32d1e888fb2dea0ab3e3ed8ba66bcfa2.jpeg

1. yacc 文件结构简介

yacc 文件一般以 *.y 格式命名,其格式如下:

// *.y 文件本身的注释

%{
// 生成解析器代码的头部,一般是 import 等语句
%}


// yacc 语法对应的词法类似、语法树节点等

%%

// BNF 语法定义

%%

// 生成解析器代码的尾部

简单来说,y 文件由两个 %% 行分隔为三个部分(类似文章的凤头、猪肚、豹尾):

  • 凤头:对应生成的解析器的头部,其中%{ ... %} 包含的部分为原样输出,其他部分是 yacc 语法定义的词法类型和语法树节点等

  • 猪肚:是 yacc 文件等核心,通过 BNF 语法定义了语法结构,这里主要是针对 LALR(1) 语法

  • 豹尾:如果是独立的程序,可以在这个部分引入词法解析器和 main 函数;如果是 package 则是可以省略的

2. 定义expr.y文件 - 凤头部分

创建表达式语法文件如下:

// 版权 @2023 凹语言 作者。保留所有权利。

%{
// 这是 凹语言 yacc 的例子, 用于对表达式进行解析, 为了简化词法部分暂时通过手工录入.
%}

%union {
	num :int
}

%type  <num> expr expr1 expr2 expr3
%token '+' '-' '*' '/' '(' ')'
%token <num> NUM

%%

其中%union定义了词法和语法解析器对接的结构体类型。%type语句定义了expr expr1 expr2 expr3几种语法节点,都是对应<num>类型值,而数字的值需要填充到%union定义的num属性部分。%token语句定义的运算符和NUM类型的数字。

3. 定义expr.y文件 - 猪肚部分

猪肚部分对应表达式的语法结构:

%%

top:
	expr { println($1) }

expr:
	expr1
	| '+' expr { $$ = $2 }
	| '-' expr { $$ = -$2 }

expr1:
	expr2
	| expr1 '+' expr2 { $$ = $1 + $3 }
	| expr1 '-' expr2 { $$ = $1 - $3 }

expr2:
	expr3
	| expr2 '*' expr3 { $$ = $1 * $3 }
	| expr2 '/' expr3 { $$ = $1 / $3 }

expr3:
	NUM
	| '(' expr ')' { $$ = $2 }

%%

当遇到expr语法规则是直接输出结果,expr1表示加减法、expr2表示乘除法、expr3表示数字或小括弧。在每个最终后面的{}中包含的是动作代码,它们根据不同的语法规则选择不同的计算方式得到结果,结果赋值给$$(也就是对应%type <num> expr expr1 expr2 expr3语句中的<num>部分类型,也对应%union定义的num成员)。

4. 定义expr.y文件 - 豹尾部分 - 01

有了凤头和猪肚部分,yacc就可以生成必要的解析器代码了。默认后生成以下格式的解析器函数yyParse

func yyParse(yylex: *yyLexer) => int {
	return yyNewParser().Parse(yylex)
}

yyLexer词法解析器则是用户需要自行实现的(词法解析实现相对简单),主要包含以下2个方法:

type yyLexer struct {}

func yyLexer.Lex(yylval *yySymType) => int {
	// 返回 Token 类型, 并且将对应的值填充到 yylval 相应的属性中
}

func yyLexer.Error(s string) {
	// 遇到错误
}

yyLexer.Lex 返回 Token 类型,并且将对应的值填充到 yylval 相应的属性中,遇到文件结尾时返回0表示文件结束。方法参数对应的yySymType类型由yacc工具生成,对应如下的代码:

type yySymType struct {
	yys :int
	num :int
}

其中num对应对应%union定义的属性,也就是数字的值。

5. 定义expr.y文件 - 豹尾部分 - 02

为了简化演示代码,我们先手工构造词法序列,然后通过yyLexer.Lex 返回。

// Lex 结束标志
const eof = 0

type yyToken struct {
	Kind  :int
	Value :int
}

type yyLexer struct {
	tokens :[]yyToken
	pos    :int 
}

func yyLexer.Lex(yylval *yySymType) => int {
	if this.pos >= len(this.tokens) {
		return eof
	}
	tok := this.tokens[this.pos]
	this.pos++

	yylval.num = tok.Value
	return tok.Kind
}

func yyLexer.Error(s string) {
	println("ERROR:", s)
}

首先定义yyToken,对应token的类型和值信息。然后yyLexer定义全部的token列表和当前的pos信息。yyLexer.Lex方法每次从this.tokens列表对应的this.pos位置返回一个token,如果是结束则返回eof

然后就可以构造main函数启动了:

func main {
	print("1+2*3 = ")
	yyParse(&yyLexer{
		tokens: []exprToken{
			{Kind: NUM, Value: 1},
			{Kind: '+'},
			{Kind: NUM, Value: 2},
			{Kind: '*'},
			{Kind: NUM, Value: 3},
		},
	})
}

6. 生成解析器代码

在生成解析器代码前再准备一个copyright.txt文件,比如“保留所有权利”或者“自由使用”之类的。然后通过以下命令生成解析器代码:

$ wa yacc -l -p=yy -c="copyright.txt" -o="y.wa" expr.y

其中-l表示生成的代码禁止映射到*.y文件行列号(用生成代码的位置),-p=yy表示生成的解析器函数和类型等用yy前缀(这也是默认值),-c="copyright.txt"为生成代码指定版权信息,-o="y.wa"指定输出文件,最后的expr.y对熟人的yacc规则文件。

生成代码成功之后可以执行:

$ wa y.wa
1+2*3 = 7

7. 下一步

目前的凹语言版 yacc 工具还是Go语言实现的,只是输出的解析器是凹语言代码。我们希望下一步可以将 yacc 工具本身移植到凹语言实现,最终可以通过 wasm 模块执行。

07bbf12225dbd671ad1c5234b9b59a63.png

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作者: 胡彦 本框架是一个lex/yacc完整的示例,用于学习lex/yacc程序基本的搭建方法,在linux/cygwin下敲入make就可以编译和执行。 本例子虽小却演示了lex/yacc程序最常见和重要的特征: * lex/yacc文件格式、程序结构。 * 如何在lex/yacc中使用C++和STL库,用extern "C"声明那些lex/yacc生成的、要链接的C函数,如yylex(), yywrap(), yyerror()。 * 重定义YYSTYPE/yylval为复杂类型。 * 用%token方式声明yacc记号。 * 用%type方式声明非终结符的类型。 * lex里正则表达式的定义、识别方式。 * lex里用yylval向yacc返回属性值。 * 在yacc嵌入的C代码动作里,对记号属性($1, $2等)、和非终结符属性($$)的正确引用方法。 * 对yyin/yyout重赋值,以改变yacc默认的输入/输出目标。 * 如何开始解析(yyparse函数),结束或继续解析(yywrap函数)。 本例子功能是,对当前目录下的file.txt文件,解析出其中的标识符、数字、其它符号,显示在屏幕上。linux调试环境是Ubuntu 10.04。 总之,大部分框架已经搭好了,你只要稍加扩展就可以成为一个计算器之类的程序,用于《编译原理》的课程设计。 文件列表: lex.l: lex程序文件。 yacc.y: yacc程序文件。 main.hpp: 共同使用的头文件。 Makefile: makefile文件。 file.txt: 给程序解析的文本文件。 使用方法: 1-把lex_yacc_example.rar解压到linux/cygwin下。 2-命令行进入lex_yacc_example目录。 3-敲入make,这时会自动执行以下操作: (1) 自动调用flex编译.l文件,生成lex.yy.c文件。 (2) 自动调用bison编译.y文件,生成yacc.tab.c和yacc.tab.h文件。 (3) 自动调用g++编译、链接出可执行文件main。 (4) 自动执行main,得到如下结果:。 bison -d yacc.y g++ -c lex.yy.c g++ -c yacc.tab.c g++ lex.yy.o yacc.tab.o -o main id: abc id: defghi int: 123 int: 45678 op: ! op: @ op: # op: $ AllId: abc defghi 参考资料:《Lex和Yacc从入门到精通(6)-解析C-C++包含文件》, http://blog.csdn.net/pandaxcl/article/details/1321552 其它文章和代码请留意我的blog: http://blog.csdn.net/huyansoft 2013-4-27

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值