暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

goyacc 新手入门,有趣。。。

TiDB之路 2022-02-28
8259

前言

大家好,今天我们来了解一下goyacc
。随着Parse
的深入,这里需要理解一下这方面的知识。这点知识掌握后,以后可以自建SQL语法。

goyacc

在了解goyacc之前,我们需要先了解一下yacc

yacc(Yet Another Compiler Compiler),是一个经典的生成语法分析器的工具。yacc生成的编译器主要是用C语言写成的语法解析器(Parser),需要与词法解析器Lex一起使用,再把两部分产生出来的C程序一并编译。

这是百度百科对yacc
的解释,那么goyacc
就是golang语言的yacc
版本。简单来说goyacc
实现了go语言版本的词法分析、语法解析

我们来初步使用一下,直接安装。

go install golang.org/x/tools/cmd/goyacc@latest

安装完成后,就可以到go环境的bin目录下看到goyacc可执行文件。

ls -lrt
-rwxr-xr-x  1 buddy  staff  2952834 Feb 26 16:30 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")

这里几个参数含义如下。

参数含义
-l禁用line指令
-o解析器输出,默认为"y.go"
-p在生成的代码中使用的名称前缀(默认"yy")
-v创建解析表(默认"y.output")

现在还很难理解它的流程,我们继续看github上官方提供的例子。

goyacc/testdata/expr 目录里面有一个非常简单的 yacc 程序。把这个文件下载下来。

./goyacc -p "expr" expr.y

ls -lrt

-rwxr-xr-x  1 buddy  staff  2952834 Feb 26 16:30 goyacc
-rw-r--r--  1 buddy  staff     2902 Feb 26 22:44 expr.y
-rw-r--r--  1 buddy  staff    12145 Feb 26 22:44 y.go
-rw-r--r--  1 buddy  staff     2634 Feb 26 22:44 y.output


执行-p参数,生成了2个文件,一个是解析器输出语法解析器y.go文件,另外一个是解析表y.output文件。

接下来执行go run y.go,一个简易的计算器程序就实现了。

go run y.go
1+2
3
3+3
6

什么是.y文件

如上面例子所示,可以说.y文件是非常重要的,只有编写好了.y文件,我们才可以通过goyacc程序把它转化成语法解析器。而这个.y文件就是我们前面科普的语法规则文件,它主要由三部分组成。

第一个称为定义部分,可以在其中进行各种定义。

第二个部分是语法的规则,是比较关键的部分。

第三个部分是用户定义部分,一般是空白,不用太关注。

我们来看看示例的expr.y文件。

第一部分如下

%union {
 num *big.Rat
}
%type <num> expr expr1 expr2 expr3

%token '+' '-' '*' '/' '(' ')'

%token <num> NUM

这个%union,它其实是定义了一个go的struct,xxxSymType

从我们生成的y.go中,你会看到一个exprSymType
的结构体,它来自于expr.y的第18行,就是我们上面union定义的部分。

这个struct主要有两个作用,第一,定义表达式和TOKEN的类型。表达式其实就是单个或者多个TOKEN 的组合。第二就是存放变量。

%type,定义非终结符。这里定义为num类型的,给出了4个规则,expr expr1 expr2 expr3。

top:
 expr
 {
  if $1.IsInt() {
   fmt.Println($1.Num().String())
  } else {
   fmt.Println($1.String())
  }
 }

expr:
 expr1
'+' expr
 {
  $$ = $2
 }
'-' expr
 {
  $$ = $2.Neg($2)
 }

expr1:
 expr2
| expr1 '+' expr2
 {
  $$ = $1.Add($1, $3)
 }
| expr1 '-' expr2
 {
  $$ = $1.Sub($1, $3)
 }

expr2:
 expr3
| expr2 '*' expr3
 {
  $$ = $1.Mul($1, $3)
 }
| expr2 '/' expr3
 {
  $$ = $1.Quo($1, $3)
 }

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

这里的规则是一种递归下降的方式,递归下降是一种自顶向下从起始的非终结符开始,不断地对非终结符进行分解,直到匹配到输入的终结符。

%token,定义终结符,表示不能再拆的字符。

其实还有像%left %right %nonassoc等定义,它们也是终结符,定义形式与%token类似,但是它们不同的地方在于,它们具有某种优先级和结合性,%left表示左结合,%right表示右结合,%nonassoc表示不可结合。而优先级关系则是以他们定义出现的顺序决定的,先定义的优先级低,最后定义的优先级最高,同时定义的优先级相同。

将lexer 和 goyacc 联合起来

介绍完goyacc
之后,我们需要和lexer
进行联合起来使用,如下图所示:

goyacc
生成的解析器 yyParse
要求词法分析器提供下面的接口。

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

或者是

type yyLexerEx interface {
 yyLexer
 // Hook for recording a reduction.
 Reduced(rule, state int, lval *yySymType) (stop bool// Client should copy *lval.
}

Lex 是词法分析函数,解析过程会多次回调此方法,每次调用都会返一个token值,并将token值放到放在lval中。Error 等价于 yacc 中的 yyerror。

我们回到上篇文章中研究 TiDB 中的 Parse模块 (一)写的解析函数上进行调试。

stmtNodes, _, err := p.Parse(sql, """")

在执行Parse
函数的时候会调用ParseSQL
函数,ParseSQL
是真正实现将字符串转换成ast.StmtNode
的函数。

// ParseSQL parses a query string to raw ast.StmtNode.
func (parser *Parser) ParseSQL(sql string, params ...ParseParam) (stmt []ast.StmtNode, warns []error, err error) {
 resetParams(parser)
 for _, p := range params {
  if err := p.ApplyOn(parser); err != nil {
   return nilnil, err
  }
 }
 sql = parser.lexer.tryDecodeToUTF8String(sql)
 parser.src = sql
 parser.result = parser.result[:0]

 var l yyLexer
 parser.lexer.reset(sql)
 l = &parser.lexer
 yyParse(l, parser)

 warns, errs := l.Errors()
 if len(warns) > 0 {
  warns = append([]error(nil), warns...)
 } else {
  warns = nil
 }
 if len(errs) != 0 {
  return nil, warns, errors.Trace(errs[0])
 }
 for _, stmt := range parser.result {
  ast.SetFlag(stmt)
 }
 return parser.result, warns, nil
}

在这个ParseSQL
函数的中间的部分,它定义了一个yyLexer。然后执行yyParse(l, parse)
。在这里就会进入我们的goyacc执行程序parse.go

后记

这一篇科普了一下goyacc
的一点用法,它需要和lexer
结合起来使用,这些知识学起来不容易,坚持一下。

reference

https://wizardforcel.gitbooks.io/diy-c-compiler/content/5.html
https://1thx.com/golang/189.html
https://cloud.tencent.com/developer/article/1744609
https://pkg.go.dev/github.com/cznic/goyacc
https://blog.csdn.net/tencent_teg/article/details/112914108


文章转载自TiDB之路,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论