通过 Goyacc 构建 Elasticsearch Querystring 解析器 - 领域特定语言语法分析实践

本文详细介绍了如何使用Goyacc构建一个Elasticsearch Querystring的解析器。通过Golang实现词法分析器和语法分析器,利用Goyacc生成的LALR解析器处理Querystring的语法结构,构建抽象语法树(AST)。文章涵盖了Goyacc的使用方法,包括语法定义文件的组成部分,如目标语言代码、Union声明、Token声明、Type声明以及语法规则定义。同时,还讨论了词法分析器的实现,以及在实际应用中处理查询条件优化、类型推断等问题。

动手点关注 干货不迷路 👆

背景

领域特定语言(DSL),如 SQL、Elasticsearch Querystring 等,往往是为专门的目的设计的。在特定的任务中,DSL 通过在表达能力上做的妥协换取在某一领域内的高效。

在飞书套件日志系统的私有化研发过程中,为了符合研发同学查询日志的习惯,尝试使用 Elasticquery Querystring(下简称为 Querystring)作为过滤器的查询条件语句,由此需要可用的 Golang Querystring 解析器。由于目前开源界无法找到完善的实现,尝试使用 Goyacc 自行构建。

Yacc 是一个被普遍采用的编译器代码生成器,生成出的代码主要用于语法分析阶段,常常与作为词法分析器的 lex 匹配使用,使用 LALR 算法,将源代码构建为抽象语法树(AST)。Goyacc 是 yacc 的 Golang 版本。

本文尝试实现的 Querystring 解析器本质上是词法分析器和语法分析器的组合。语法分析器由 Goyacc 生成;为了方便实现符合 Querystring 习惯的反转义,词法分析器是自行实现的。Yacc 和它在各个语言上的实现,中文互联网上较少有具体的介绍。本文会尽量详细的描述 Goyacc 实践中可能需要注意的点。所述的 Querystring 解析器代码可在 https://github.com/bytedance/go-querystring-parser 此处查看。

框架

一套完整的应用 Goyacc 的 DSL 解析器包含以下部分:

  • 语法分析器(parser)

  • 词法分析器(lexer)

    • 较为简单的做法,是采用Scanner来进行构建;当存在特殊需求时,一般自行构建。

  • AST 节点的定义

  • 包装实体与工具函数

大致流程为,语法分析器调用词法分析器将源代码拆解为基本「Token(记号)」,根据语法规程将若干个「Token」组合成「Expr(表达式)」(Token 本身也被作为 Expr 处理)。「Expr」的结构构建在「AST」中,得到结果。

语法分析器

语法分析(syntactic analysis,或 parsing)是根据某种给定的形式文法对由记号序列构成的输入文本进行分析并确定其语法结构的一种过程。

语法分析器的作用是进行语法检查、并构建由输入的记号组成的数据结构(一般是语法分析树、抽象语法树等层次化的数据结构)。语法分析器通常使用一个独立的词法分析器从输入字符流中分离出一个个的「记号」,并将记号流作为其输入。实际开发中,语法分析器可以手工编写,也可以使用工具(半)自动生成。

在本例中,即为工具自动生成。使用巴科斯范式描述对应的语法定义,并使用 goyacc 生成 golang 代码,提供一个 LALR 语法分析器,并定义了供 lexer 返回的 token 定义。

安装 Goyacc

在安装了 golang 的环境中,执行:

1go get -u golang.org/x/tools/cmd/goyacc

如安装后无法正常运行,请检查 $GOPATH/bin 是否加入到了 $PATH 中。

Goyacc 语法定义文件

一个 yacc 语法定义文件,一般由以下若干部分构成。

头部目标语言代码

可参考「附录一:L1-L3」,使用 %{ }% 将需要的目标语言原生代码段落包围起来。目标语言往往涉及到语言应用中的一些头部代码。在例子里,golang 所需的包名称定义等需要通过这一部分添加进来。其他的例子如常量的定义、结构体的定义等。

Union(集合)声明

可参考「附录一:L5-L9」,以 %union{} 格式定义,只可定义一次。「Union」这一概念包含了下述类型声明中的各个类型,及这些类型对应的目标语言类型(即 Golang 中的类型)。与 c 语言中 union 的概念类似,在目标代码中(生成为 yySymType),union 将被生成为一个结构体(struct),根据「类型声明」,将匹配出的值放到结构体的对应字段中,作为存放/传递值的媒介而存在。在例子中,s即为string,类型声明中凡是标记为 s 类型的「表达式」,都会被保存到s字段中。

Token(记号)声明

可参考「附录一:L11-L14」,以 %token 为行首的记号声明列表。对于 lexer 会直接分析出的 token,需要通过「Token 声明」来列出。Token 声明本身不需要关注顺序和组合,只需要单独列出需要由 lexer 输出的 token 类型即可。

Type(类型)声明

可参考「附录一:L11-L14」,以 %type <%TYPE%> 为行首的记号声明列表,%TYPE% 为本行所列举的「Expr」将会保存到的 union 中的字段/类型。

声明与语法定义规则间的分隔符

各个声明与语法规则定义间,以 %% 分隔(L23)。

语法规则定义

可参考「附录一:L25-L202」,语法规则使用巴科斯范式(Backus Normal Form,缩写为 BNF)定义。在 Goyacc(及它仿造的 yacc)中,以摘自「附录一:L53-L64」定义的一个 expr 为例,大致由以下部分构成:

1searchLogicPart:
 2searchLogicSimplePart {
 3    $$ = $1
 4}
 5|
 6searchLogicSimplePart tAND searchLogicPart {
 7  &n
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值