使用golang+antlr4构建一个自己的语言解析器(二)

打印 上一主题 下一主题

主题 1025|帖子 1025|积分 3075

Antlr4文件解析流程


该图展示了一个语言应用程序中的基本流动过程

  • 输入一个字符流,首先经过词法分析,获取各个Token
  • 然后经过语法分析,组成语法分析树
Antlr4语法书写规范

语法关键字和使用

符号作用?表达式可选*表达式出现0此或多次+表达式至少一次EOF语法结尾expr expr1 expr2序列模式,由多个表达式或Token组成一个语法规则expr|expr1|expr2选择模式,指定一个语法规则可以选择多个匹配模式expr|expr1|expr*嵌套模式,自身引用自身处理优先级、左递归和结合性

Antlr4默认使用自上而下,默认左递归的方式识别语法, 使用下面一个例子说明左递归的方式
expr:expr '*' expr
|expr '+' expr
|INT
;
输入1+2*3;识别的树为

这是因为首先定义了乘法语法规则,然后定义了加法语法规则。
更改左递归的方式

expr: expr '^' expr
|INT
;
指定第一个expr接受expr的结果作为结合一个语法规则,输入12^3,识别的树为

Antlr4的基本命令我们就了解到这,有兴趣研究的小伙伴,可以查看《Antlr4权威指南》这本书。
第一个脚本

避坑指南

使用go mode模式引用antlr4默认是1.X版本,这个是低版本antlr,无法识别最新的antlr语法,使用
go get -u github.com/antlr/antlr4/runtime/Go/antlr/v4
获取antlr4最新版本的golang包
语法文件

我这里使用的IDE是goland,这个大家根据自己的爱好自选就行。
新建文件夹:parser
在parser文件夹下新建文件:Calc.g4
输入一下内容:
  1. grammar Calc;
  2. //Token
  3. MUL: '*';
  4. DIV: '/';
  5. ADD:+;
  6. SUB-';
  7. NUMBER' | [1-9] ('_'? [0-9])*);
  8. WS_NLSEMI:[ \r\n\t]+ -> skip;
  9. //Rule
  10. expression:
  11. expression op = (MUL | DIV) expression #MulDiv
  12. | expression op =  (ADD | SUB) expression #AddSub
  13. | NUMBER
  14. start:expression EOF
复制代码

  • g4文件是Antlr4的文件类型
  • Token代表定制的语法中的关键字,Token都是全部大写
  • Rule是语法规则,由驼峰样式书写,首字母小写,后续每个单词首字母大写
  • EOF代表结尾
脚本命令

我们需要使用Antlr4命令生成Go语言的语法树和Listen方式的go文件
$ java -jar 'C:\Program Files\Java\antlr\antlr-4.12.0-complete.jar' -Dlanguage=Go -no-visitor -package parser *.g4
上述命令就是指定使用antlr4将文件目录下所有的.g4文件生成目标语言为Go的语言文件。
执行CMD命令:
cd .\parser\
执行上述命令,我们会在parser文件夹中看到生成了很多文件:
Calc.interp
Calc.tokens
calc_base_listener.go  //监听模式基类的文件
calc_lexer.go  //文法文件
calc_listener.go //监听模式的文件(定义多少个语法或者自定义类型就会有多少对Enter、Exit方法)
calc_parser.go //识别语法的文件
验证语法
  1. func main(){
  2.   is := antlr.NewInputStream("1+1*2")`
  3.   lexer := parser.NewCalcLexer(is)
  4.   // Read all tokens
  5.   for {
  6.         t := lexer.NextToken()
  7.         if t.GetTokenType() == antlr.TokenEOF {
  8.                 break
  9.         }
  10.         fmt.Printf("%s (%q)\n",lexer.SymbolicNames[t.GetTokenType()], t.GetText())
  11.       }
  12. }
复制代码
输入内容:
  1. NUMBER ("1")
  2. ADD ("+")
  3. NUMBER ("1")
  4. MUL ("*")
  5. NUMBER ("2")
复制代码
证明我们的每个Token都被识别
建立一个四则运算法则
  1. type calcListener struct {
  2.         *parser.BaseCalcListener
  3.         stack []int
  4. }
  5. func (l *calcListener) push(i int) {
  6.         l.stack = append(l.stack, i)
  7. }
  8. func (l *calcListener) pop() int {
  9.         if len(l.stack) < 1 {
  10.                 panic("stack is empty unable to pop")
  11.         }
  12.         // Get the last value from the stack.
  13.         result := l.stack[len(l.stack)-1]
  14.         // Remove the last element from the stack.
  15.         l.stack = l.stack[:len(l.stack)-1]
  16.         return result
  17. }
  18. func (l *calcListener) ExitMulDiv(c *parser.MulDivContext) {
  19.         right, left := l.pop(), l.pop()
  20.         switch c.GetOp().GetTokenType() {
  21.         case parser.CalcParserMUL:
  22.                 l.push(left * right)
  23.         case parser.CalcParserDIV:
  24.                 l.push(left / right)
  25.         default:
  26.                 panic(fmt.Sprintf("unexpected op: %s", c.GetOp().GetText()))
  27.         }
  28. }
  29. func (l *calcListener) ExitAddSub(c *parser.AddSubContext) {
  30.         right, left := l.pop(), l.pop()
  31.         switch c.GetOp().GetTokenType() {
  32.         case parser.CalcParserADD:
  33.                 l.push(left + right)
  34.         case parser.CalcParserSUB:
  35.                 l.push(left - right)
  36.         default:
  37.                 panic(fmt.Sprintf("unexpected op: %s", c.GetOp().GetText()))
  38.         }
  39. }
  40. func (l *calcListener) ExitNumber(c *parser.NumberContext) {
  41.         i, err := strconv.Atoi(c.GetText())
  42.         if err != nil {
  43.                 panic(err.Error())
  44.         }
  45.         l.push(i)
  46. }
  47. func calc(input string) int {
  48.         // Setup the input
  49.         is := antlr.NewInputStream(input)
  50.         // Create the Lexer
  51.         lexer := parser.NewCalcLexer(is)
  52.         stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
  53.         // Create the Parser
  54.         p := parser.NewCalcParser(stream)
  55.         // Finally parse the expression (by walking the tree)
  56.         var listener calcListener
  57.         antlr.ParseTreeWalkerDefault.Walk(&listener, p.Start())
  58.         return listener.pop()
  59. }
  60. func main(){
  61.         fmt.Println(calc(1+1*2))
  62. }
复制代码
至此,我们已经使用antlr4+golang开始自己第一个语法文件使用,接下来就是如何实现我们自定的语法了!!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

铁佛

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表