第二章:上下文无关文法

本文详细介绍了ANTLR4工具的安装与使用,通过一个简单的表达式解析例子展示了ANTLR4的威力。ANTLR4根据上下文无关文法生成解析器,支持词法和语法的定义,允许开发者专注于业务逻辑而非语法解析实现。文章深入浅出地探讨了上下文无关文法的概念,包括定义、产生式、描述方式,并提供了文法规则的BNF表示。此外,还讲解了ANTLR4文法文件的结构和编写技巧。
摘要由CSDN通过智能技术生成

牛刀小试

antlr4 的安装

上一章节我们简单介绍了一下 antlr4,这一章节,开始讨论 antlr4 的使用和文法。首先简单介绍一下 antlr4 工具的安装和使用参数,非常简单。

  1. 安装 java 1.7 及以上版本,配置 java 环境变量

  2. 下载 antlr4 工具 https://www.antlr.org/download/antlr-4.9.2-complete.jar,可以将 antlr4 工具添加到 CLASSPATH 环境变量中,比如添加到 .bash_profile 或者 .bashrc 文件中

    export CLASSPATH=".:/usr/local/lib/antlr-4.9-complete.jar:$CLASSPATH"
    
  3. 创建 antlr4 工具的别名

    alias antlr4='java -Xmx500M -cp "/usr/local/lib/antlr-4.9-complete.jar:$CLASSPATH" org.antlr.v4.Tool'
    
    alias grun='java -Xmx500M -cp "/usr/local/lib/antlr-4.9-complete.jar:$CLASSPATH" org.antlr.v4.gui.TestRig'
    

温馨提示:antlr4 本身就是 java 来实现的,面向 java 语言的示例非常多,笔者写作的过程中主要使用的是 c++ 语言,后续的所有示例也几乎都是 c++ 语言来实现的。

上述安装完成之后,输入 antlr4,在 terminal 上会出现如下信息

ANTLR Parser Generator  Version 4.7.1
 -o ___              specify output directory where all output is generated
 -lib ___            specify location of grammars, tokens files
 -atn                generate rule augmented transition network diagrams
 -encoding ___       specify grammar file encoding; e.g., euc-jp
 -message-format ___ specify output style for messages in antlr, gnu, vs2005
 -long-messages      show exception details when available for errors and warnings
 -listener           generate parse tree listener (default)
 -no-listener        don't generate parse tree listener
 -visitor            generate parse tree visitor
 -no-visitor         don't generate parse tree visitor (default)
 -package ___        specify a package/namespace for the generated code
 -depend             generate file dependencies
 -D<option>=value    set/override a grammar-level option
 -Werror             treat warnings as errors
 -XdbgST             launch StringTemplate visualizer on generated code
 -XdbgSTWait         wait for STViz to close before continuing
 -Xforce-atn         use the ATN simulator for all predictions
 -Xlog               dump lots of logging info to antlr-timestamp.log
 -Xexact-output-dir  all output goes into -o dir regardless of paths/package

列出几个常用的比较重要的选项进行说明

  • -o 选项指定语法分析器生成的目录
  • -lib 目录制定语法,tokens 文件的位置
  • -encoding 制定语法文件的编码
  • -long-messages 现实详细的错误或者告警信息,通常可以配合与 -Werror 一起使用
  • -listener 生成监听器接口,默认选项
  • -no-listener 不生成监听器接口
  • -visitor 生成访问器接口
  • -no-visitor 不生成访问器接口,默认选项
  • -package 指定生成代码的命名空间,比如 c++ 代码,这就是指定了语法生成器的命名空间 namespace

一个简单识别表达式的例子

我们使用一个简单的例子来展示一下 antlr4 的使用方法和使用效果,语法 2-1 如下所示

grammar Math;

compileUnit
    : expr EOF
    ;

expr
    : '(' expr ')'                          # parenExpr
    | op=(ADD | SUB) expr                   # unaryExpr
    | left=expr op=(MUL | DIV) right=expr   # mulDivExpr
    | left=expr op=(ADD | SUB) right=expr   # addSubExpr
    | func=ID '(' expr (',' expr)? ')'      # funcExpr
    | NUM                                   # numExpr
    ;

ADD : '+';
SUB : '-';
MUL : '*';
DIV : '/';

ID  : [a-zA-Z]+ ;
NUM : [0-9]+ ('.' [0-9]+)? ([eE] [+-]? [0-9]+)? ;
WS  : [ \t\r\n] -> channel(HIDDEN) ;
(语法 2-1)

这是一段识别简单表达式——加减乘除表达式——的语法。expr 代表表达式,而表达式本身可以是括号括起来的表达式,也可以是加减乘除表达式,还可以是函数调用,数字同样也是表达式,这里就应用到了递归的思想。下面我们来看一下解析的效果。

我们通过 antlr 工具,生成表达式的语法分析器

java -Xmx500M -cp antlr-4.9.2-complete.jar org.antlr.v4.Tool -Dlanguage=Cpp -listener -visitor -o generated/ Math.g4

笔者选择的目标语言是 c++ 语言,所以 antlr4 生成语法分析器是针对 cpp 的。执行上述操作后,在当前目录的 generated 文件夹下会生成如下文件

MathBaseListener.cpp  MathBaseVisitor.cpp  Math.interp    MathLexer.h       MathLexer.tokens  MathListener.h  MathParser.h  MathVisitor.cpp
MathBaseListener.h    MathBaseVisitor.h    MathLexer.cpp  MathLexer.interp  MathListener.cpp  MathParser.cpp  Math.tokens   MathVisitor.h

生成了监听器接口,访问器接口,因为没有使用 -package 选项,所以没有指定命名空间。下面我们使用一个简单的 main 方法来调用这些接口,代码 2-1 如下所示

int main(int argc, char *argv[]) {
  ANTLRInputStream input(argv[1]);
  MathLexer lexer(&input);
  CommonTokenStream tokens(&lexer);

  // show all the tokens
  tokens.fill();
  for (auto token : tokens.getTokens()) {
    std::cout << token->toString() << std::endl;
  }

  MathParser parser(&tokens);
  auto compile_unit_ctx = parser.compileUnit();
  std::cout << "text: " << tokens.getText() << std::endl;
  std::cout << ((tree::ParseTree*)(compile_unit_ctx))->toStringTree(&parser) << std::endl;

  return 0;
}
(代码 2-1)

main 方法中,将命令行参数的第一个参数作为表达式输入,创建 antlr 输入流对象,通过输入流构造 MathLexer 词法对象,进行词法解析,词法将输入流解析成 token 流,将 token 流传递给语法分析器 MathParser 进行语法解析,语法解析结果就是一棵 Parse tree 的树型结构。语法生成器提供了监听器和访问器两种方式对树形结构进行访问。上面的代码,在语法解析器成功解析的情况下,将 token 标记和 Parse tree 都打印出来了,我们尝试对如下的表达式进行解析

10-3/(8-1*4)

结果输出如下

[@0,0:1='10',<9>,1:0]
[@1,2:2='-',<5>,1:2]
[@2,3:3='3',<9>,1:3]
[@3,4:4='/',<7>,1:4]
[@4,5:5='(',<1>,1:5]
[@5,6:6='8',<9>,1:6]
[@6,7:7='-',<5>,1:7]
[@7,8:8='1',<9>,1:8]
[@8,9:9='*',<6>,1:9]
[@9,10:10='4',<9>,1:10]
[@10,11:11=')',<2>,1:11]
[@11,12:11='<EOF>',<-1>,1:12]
text: 10-3/(8-1*4)
(compileUnit (expr (expr 10) - (expr (expr 3) / (expr ( (expr (expr 8) - (expr (expr 1) * (expr 4))) )))) <EOF>)

@0 表示第0个位置(从0开始), 0:1 表明在第0-1个字符之间,内容是 ‘10’,token 的 id 是 9, 1:0 表示的是,位于输入字符串第一行,第0个位置处。那么这个 token 的 id 是哪来的呢,这个可以从上面生成的文件 MathLexer.tokens 文件中查看

T__0=1
T__1=2
T__2=3
ADD=4
SUB=5
MUL=6
DIV=7
ID=8
NUM=9
WS=10
'('=1
')'=2
','=3
'+'=4
'-'=5
'*'=6
'/'=7

id 是 9 说明这个 token 是数字 NUM,而

(compileUnit (expr (expr 10) - (expr (expr 3) / (expr ( (expr (expr 8) - (expr (expr 1) * (expr 4))) )))) <EOF>)

就是语法解析的完整的树形结构,把这棵树整理一下,如图 2-1 所示

图 2-1

(图 2-1) <
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫步旅人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值