第一行的 token 定义语法中的数据类型,由于单个字符本身没有歧义,在 Lex 和 Yacc 无需特别定义单字符 token,如 + 和 -,因此在这里我们只需要数字 NUMBER 。在第一个 %% 之后,定义了计算器的语法,含义非常直白,可读性强 。
然后再定义构词规则:
?复制代码
// lex.l%{#include "y.tab.h"extern int yylval;%} %%[0-9]+{ yylval = atoi(yytext); return NUMBER; }[ t] ;n return 0;. return yytext[0];%%
在两个 %% 中间的就是构词规则:
- 符合正则表达式 [0-9]+ 就是数字类型的词语,其对应的值为 atoi(yytext)
- 符合正则表达式 [ t] 的不处理,即忽略空格和制表符
- 符合正则表达式 n 的返回 0,即用换行符标识文本结束位置
- 符合正则表达式 . 的返回文本本身,即所有非数字的字符直接返回,这里实际上指的就是 + 和 - 。
?复制代码
# MacOS$ lex lex.l$ yacc -d parser.y$ gcc y.tab.c lex.yy.c -ly -ll -o calculator$ ./calculator> 128 + 128> = 256
对比分析从代码健壮性角度上看,lex 生成的词法分析器已经经受时间的检验,开发者大可相信其代码的健壮性;从可读性角度看,构词规则和语法规则定义简短,通俗易懂;从可扩展性角度看,任何可以通过上下文无关文法 (context-free grammar) 表达的语法都能支持;从效率角度看,yylex 与 yyparse 可以流式地处理文本,yyparse 从 yylex 获取词语,即时地根据语法规则组合成 IR,这种做法使得编译前端的工作只需要 1 次遍历便可完成 。但 lex 和 yacc 为了支持更复杂的场景,其生成的代码也会更复杂,这也是效率与通用性权衡的表现 。Nex & Goyacc报警平台使用 Go 语言编码,直接使用 lex 和 yacc 需要引入 cgo,这也使得二者的使用门槛变高 。好在 Go 官方提供了 goyacc,方便我们在 parser.y 中引入用 Go 语言编写的定制化逻辑;斯坦福的一位博士 Ben Lynn 开源了它的 nex 项目,作为用 Go 语言原生开发的词法分析器生成器,能与 goyacc 兼容,形成类似 lex 和 yacc 一般的搭档 。接下来我们将利用 nex 和 goyacc 来实现匹配器编译器 。
与计算器的例子类似,我们先看语法规则中定义的数据类型:
?复制代码
%union{str stringexpr *MatchExprpexpr *PrimitiveExpr} %token LABEL VALUE%token REG_EQ AND OR %type <expr> expr%type <pexpr> pexpr %type <str> LABEL VALUE%type <str> REG_EQ AND OR
其中,语法中的数据类型包括:- LABEL:原子表达式的 LHS
- VALUE:原子表达式的 RHS
- REG_EQ、AND、OR 分别为正则匹配,且和或
?复制代码
%left OR%left AND%left '(' ')'
left 表示先从运算符的 LHS 开始计算,三者的优先级关系是 OR < AND < '(' == ')',非常直观 。最后进入我们的语法规则:?复制代码
// 匹配器表达式可以是空字符串,也可以是一个合法的表达式matcher:{ setResult(yylex, &Matcher{}) }| expr{ setResult(yylex, $1) } // 表达式可能以下之一://复合表达式:expr AND expr//复合表达式:expr OR expr//原始表达式:pexpr//括号表达式:(expr)expr: expr AND expr{ $$ = &Matcher{IsCompound: true, Operator:$2, Operands:[]*Matcher{$1,$3}} }| expr OR expr{ $$ = &Matcher{IsCompound: true, Operator:$2, Operands:[]*Matcher{$1,$3}} }| pexpr{ $$ = &Matcher{IsCompound: false, PrimitiveMatcher:$1} }| '(' expr ')'{ $$ = $2 }// 原始表达式要么是 LABEL = VALUE, 要么是 LABEL =~ VALUEpexpr: LABEL '=' VALUE{ $$ = &PrimitiveMatcher{Label:$1, Text:$3, IsRegex: false} }| LABEL REG_EQ VALUE{ $$ = &PrimitiveMatcher{Label:$1, Text:$3, IsRegex: true} }
每条语法规则的含义已经标明在注释中,在每条语法规则之后,是 Go 语言编码的简单逻辑,告诉解析器在不同情况下如何拼装 IR 。搞定语法后,我们就可以定义构词规则:?复制代码
/[aA][nN][dD]/{ lval.str = "AND"; return AND }/[oO][rR]/{ lval.str = "OR"; return OR }/=~/{ return REG_EQ }/=/{ return int(yylex.Text()[0]) }/(/{ return int(yylex.Text()[0]) }/)/{ return int(yylex.Text()[0]) }/[A-Za-z][A-Za-z0-9_]*/{ lval.str = yylex.Text(); return LABEL }/".*"/{ lval.str = yylex.Text()[1:len(yylex.Text())-1]; return VALUE }/[ trn]+/{ /* white spaces ignored */ }//package c
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 82年拉菲到底有多少瓶?
- 适合收藏 如何将Rasa聊天机器人框架部署到linux,简明教程
- 白手起家需要什么素质
- 饮食不规律的危害
- 大枣到底是酸性还是碱性呢
- 三伏天到底吃什么好
- 淘宝的货源都是在那个渠道进的货 淘宝商家货源从哪来的
- 如果你不懂得怎么购买地漏,就该好好学习了,不要等到踩坑就晚了
- 人到中年,千万不要低估人性
- 一到春天就长湿疹?请收好这份预防清单