文章目录

  • 算数表达式语言
    • 匹配算数表达式的语言
      • 语法
      • 语法导入
      • 处理错误输入
  • 使用访问者模式构建一个计算器
  • 利用监听器构建一个翻译器
  • 如何将动作直接嵌入语法文件
    • 在语法中嵌入任意动作
    • 使用语义判定改变语法分析过程
  • 词法分析特性
    • 孤岛语法
    • 重写输入流
    • 将词法符号送入不同通道

算数表达式语言

匹配算数表达式的语言

我们的表达式语言组成的程序就是一系列语句,每个语句都由换行符终止,一个语句可以是一个表达式、一个赋值语句或者是一个空行。

语法

//Expr.g4
grammar Expr;/** The start rule; begin parsing here. */
prog:   stat+ ; stat:   expr NEWLINE                |   ID '=' expr NEWLINE        |   NEWLINE                   ;expr:   expr ('*'|'/') expr   |   expr ('+'|'-') expr   |   INT                    |   ID                    |   '(' expr ')'         ;ID  :   [a-zA-Z]+ ;      // match identifiers <label id="code.tour.expr.3"/>
INT :   [0-9]+ ;         // match integers
NEWLINE:'\r'? '\n' ;     // return newlines to parser (is end-statement signal)
WS  :   [ \t]+ -> skip ; // toss out whitespace
  • 语法分析器的规则以小写字母开头
  • 词法分析器的规则以大写字母开头
  • 使用|来分割同一个语言规则的若干备选分支,使用圆括号吧一些符号组成子规则,('*' | '/')匹配一个乘法符号或除法符号

antlr4的最重要功能之一就是它在大部分情况下能够处理左递归规则,详细参考左递归

使用上面的Expr.g4文件做一个测试
antlr4 Expr.g4
javac Expr*.java
grun Expr prog -gui t.expr
运行结果

相应源码也可以在https://pragprog.com/titles/tpantlr2/the-definitive-antlr-4-reference/上下载

//ExprJoyRide.java
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import java.io.FileInputStream;
import java.io.InputStream;
public class ExprJoyRide {public static void main(String[] args) throws Exception {String inputFile = null; if ( args.length>0 ) inputFile = args[0];InputStream is = System.in;if ( inputFile!=null ) is = new FileInputStream(inputFile);ANTLRInputStream input = new ANTLRInputStream(is); ExprLexer lexer = new ExprLexer(input); CommonTokenStream tokens = new CommonTokenStream(lexer); ExprParser parser = new ExprParser(tokens); ParseTree tree = parser.prog(); // 从parse规则开始进行语法分析System.out.println(tree.toStringTree(parser)); //以文本形式打印树}
}

javac ExprJoyRide.java Expr*.java
java ExprJoyRide t.expr
运行结果

语法导入

//CommonLexerRules.g4
lexer grammar CommonLexerRules; // note "lexer grammar"ID  :   [a-zA-Z]+ ;      // match identifiers
INT :   [0-9]+ ;         // match integers
NEWLINE:'\r'? '\n' ;     // return newlines to parser (end-statement signal)
WS  :   [ \t]+ -> skip ; // toss out whitespace
//LabeledExpr.g4
grammar LabeledExpr; // rename to distinguish from Expr.g4prog:   stat+ ;stat:   expr NEWLINE                # printExpr|   ID '=' expr NEWLINE         # assign|   NEWLINE                     # blank;expr:   expr op=('*'|'/') expr      # MulDiv|   expr op=('+'|'-') expr      # AddSub|   INT                         # int|   ID                          # id|   '(' expr ')'                # parens;MUL :   '*' ; // assigns token name to '*' used above in grammar
DIV :   '/' ;
ADD :   '+' ;
SUB :   '-' ;
ID  :   [a-zA-Z]+ ;      // match identifiers
INT :   [0-9]+ ;         // match integers
NEWLINE:'\r'? '\n' ;     // return newlines to parser (is end-statement signal)
WS  :   [ \t]+ -> skip ; // toss out whitespace

能不能将CommonLexerRules.g4中的词法规则重构进LabeledExpr.g4呢?
答案是可以的

grammar LabeledExpr; // rename to distinguish from Expr.g4
import CommonLexerRules;//添加的代码prog:   stat+ ;stat:   expr NEWLINE                # printExpr|   ID '=' expr NEWLINE         # assign|   NEWLINE                     # blank;expr:   expr op=('*'|'/') expr      # MulDiv|   expr op=('+'|'-') expr      # AddSub|   INT                         # int|   ID                          # id|   '(' expr ')'                # parens;

测试

处理错误输入


可以看到,语法分析器报错并从错误中恢复


语法分析器会在错误节点标红

使用访问者模式构建一个计算器

//LabeledExpr.g4
grammar LabeledExpr; // rename to distinguish from Expr.g4prog:   stat+ ;stat:   expr NEWLINE                # printExpr|   ID '=' expr NEWLINE         # assign|   NEWLINE                     # blank;expr:   expr op=('*'|'/') expr      # MulDiv|   expr op=('+'|'-') expr      # AddSub|   INT                         # int|   ID                          # id|   '(' expr ')'                # parens;MUL :   '*' ; // assigns token name to '*' used above in grammar
DIV :   '/' ;
ADD :   '+' ;
SUB :   '-' ;
ID  :   [a-zA-Z]+ ;      // match identifiers
INT :   [0-9]+ ;         // match integers
NEWLINE:'\r'? '\n' ;     // return newlines to parser (is end-statement signal)
WS  :   [ \t]+ -> skip ; // toss out whitespace
//Calc.java
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;import java.io.FileInputStream;
import java.io.InputStream;public class Calc {public static void main(String[] args) throws Exception {String inputFile = null;if ( args.length>0 ) inputFile = args[0];InputStream is = System.in;if ( inputFile!=null ) is = new FileInputStream(inputFile);ANTLRInputStream input = new ANTLRInputStream(is);LabeledExprLexer lexer = new LabeledExprLexer(input);CommonTokenStream tokens = new CommonTokenStream(lexer);LabeledExprParser parser = new LabeledExprParser(tokens);ParseTree tree = parser.prog(); // parseEvalVisitor eval = new EvalVisitor();eval.visit(tree);}
}

命令行执行
antlr4 -no-listener -visitor LabeledExpr.g4

import java.util.HashMap;
import java.util.Map;public class EvalVisitor extends LabeledExprBaseVisitor<Integer> {/** "memory" for our calculator; variable/value pairs go here */Map<String, Integer> memory = new HashMap<String, Integer>();/** ID '=' expr NEWLINE */@Overridepublic Integer visitAssign(LabeledExprParser.AssignContext ctx) {String id = ctx.ID().getText();  // id is left-hand side of '='int value = visit(ctx.expr());   // compute value of expression on rightmemory.put(id, value);           // store it in our memoryreturn value;}/** expr NEWLINE */@Overridepublic Integer visitPrintExpr(LabeledExprParser.PrintExprContext ctx) {Integer value = visit(ctx.expr()); // evaluate the expr childSystem.out.println(value);         // print the resultreturn 0;                          // return dummy value}/** INT */@Overridepublic Integer visitInt(LabeledExprParser.IntContext ctx) {return Integer.valueOf(ctx.INT().getText());}/** ID */@Overridepublic Integer visitId(LabeledExprParser.IdContext ctx) {String id = ctx.ID().getText();if ( memory.containsKey(id) ) return memory.get(id);return 0;}/** expr op=('*'|'/') expr */@Overridepublic Integer visitMulDiv(LabeledExprParser.MulDivContext ctx) {int left = visit(ctx.expr(0));  // get value of left subexpressionint right = visit(ctx.expr(1)); // get value of right subexpressionif ( ctx.op.getType() == LabeledExprParser.MUL ) return left * right;return left / right; // must be DIV}/** expr op=('+'|'-') expr */@Overridepublic Integer visitAddSub(LabeledExprParser.AddSubContext ctx) {int left = visit(ctx.expr(0));  // get value of left subexpressionint right = visit(ctx.expr(1)); // get value of right subexpressionif ( ctx.op.getType() == LabeledExprParser.ADD ) return left + right;return left - right; // must be SUB}/** '(' expr ')' */@Overridepublic Integer visitParens(LabeledExprParser.ParensContext ctx) {return visit(ctx.expr()); // return child expr's value}
}

使用t.expr作为输入
javac Calc.java LabeledExpr*.java
type t.expr

无需再语法文件中插入Java代码编写的动作,语法文件独立于程序,具有编程语言中立性。

利用监听器构建一个翻译器

读取目标:

//Demo.java
import java.util.List;
import java.util.Map;
public class Demo {void f(int x, String y) { }int[ ] g(/*no args*/) { return null; }List<Map<String, Integer>>[] h() { return null; }
}

使用其中的方法签名生成一个接口,保留其中的全部空白字符和注释
想要得到:

interface IDemo {void f(int x,String y);int[ ] g(/*no args*/);List<Map<String, Integer>>[] h();
}
//ExtractInterfaceTool.java
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.*;import java.io.FileInputStream;
import java.io.InputStream;public class ExtractInterfaceTool {public static void main(String[] args) throws Exception {String inputFile = null;if ( args.length>0 ) inputFile = args[0];InputStream is = System.in;if ( inputFile!=null ) {is = new FileInputStream(inputFile);}ANTLRInputStream input = new ANTLRInputStream(is);JavaLexer lexer = new JavaLexer(input);CommonTokenStream tokens = new CommonTokenStream(lexer);JavaParser parser = new JavaParser(tokens);ParseTree tree = parser.compilationUnit(); // parseParseTreeWalker walker = new ParseTreeWalker(); // create standard walkerExtractInterfaceListener extractor = new ExtractInterfaceListener(parser);walker.walk(extractor, tree); // initiate walk of tree with listener}
}

操作
antlr4 Java.g4
java org.antlr.v4.Tool Java.g4
javac Java*.java Extract*.java
java ExtractInterfaceTool Demo.java

结果:

成功

如何将动作直接嵌入语法文件

在语法中嵌入任意动作

//t.rows文件
parrt   Terence Parr    101
tombu   Tom Burns   020
bke Kevin Edgar 008
//Rows.g4
grammar Rows;@parser::members { // add members to generated RowsParserint col;public RowsParser(TokenStream input, int col) { // custom constructorthis(input);this.col = col;}
}file: (row NL)+ ;row
locals [int i=0]: (   STUFF{$i++;if ( $i == col ) System.out.println($STUFF.text);})+;TAB  :  '\t' -> skip ;   // match but don't pass to the parser
NL   :  '\r'? '\n' ;     // match and pass to the parser
STUFF:  ~[\t\r\n]+ ;     // match any chars except tab, newline
//Col.java
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;import java.io.FileInputStream;
import java.io.InputStream;public class Col {public static void main(String[] args) throws Exception {ANTLRInputStream input = new ANTLRInputStream(System.in);RowsLexer lexer = new RowsLexer(input);CommonTokenStream tokens = new CommonTokenStream(lexer);int col = Integer.valueOf(args[0]);RowsParser parser = new RowsParser(tokens, col); // pass column number!parser.setBuildParseTree(false); // don't waste time bulding a treeparser.file(); // parse}
}

操作与结果

使用语义判定改变语法分析过程

//t.data
2 9 10 3 1 2 3
  • 2 ➡ 匹配9,10
  • 3 ➡ 匹配1,2,3
//Data.g4
grammar Data;file : group+ ;group: INT sequence[$INT.int] ;sequence[int n]
locals [int i = 1;]: ( {$i<=$n}? INT {$i++;} )* // match n integers;INT :   [0-9]+ ;             // match integers
WS  :   [ \t\n\r]+ -> skip ; // toss out all whitespace

操作与结果

词法分析特性

孤岛语法

处理相同文件中的不同格式,将模板语言表达式之外的文本按照不同的方式进行处理
例子:

//XMLLexer.g4
lexer grammar XMLLexer;// Default "mode": Everything OUTSIDE of a tag
OPEN        :   '<'                 -> pushMode(INSIDE) ;
COMMENT     :   '<!--' .*? '-->'    -> skip ;
EntityRef   :   '&' [a-z]+ ';' ;
TEXT        :   ~('<'|'&')+ ;           // match any 16 bit char minus < and &// ----------------- Everything INSIDE of a tag ---------------------
mode INSIDE;CLOSE       :   '>'                 -> popMode ; // back to default mode
SLASH_CLOSE :   '/>'                -> popMode ;
EQUALS      :   '=' ;
STRING      :   '"' .*? '"' ;
SlashName   :   '/' Name ;
Name        :   ALPHA (ALPHA|DIGIT)* ;
S           :   [ \t\r\n]           -> skip ;fragment
ALPHA       :   [a-zA-Z] ;fragment
DIGIT       :   [0-9] ;
<tools><tool name="ANTLR">A parser generator</tool>
</tools>

构建与测试

重写输入流

antlr4 Java.g4
javac InsertSerialID*.java Java*.java
java InsertSerialID Demo.java

将词法符号送入不同通道

对于大多数语法,语法分析器是可以忽略空白字符与注释,但这样同样意味着程序中将完全无法访问空白字符和注释,那么如何保留空白字符和注释呢,方法是将这些词法符号送入另外一个通道,语法分析器只处理一个通道,我们就将希望保留的符号送入其他通道,这样就可以实现对空白字符和注释的保留。
Java语法

COMMENT:   '/*' .*? '*/'    -> channel(HIDDEN) // match anything between /* and */;
WS  :   [ \r\t\u000C\n]+ -> channel(HIDDEN);

ANTLR学习(三)antlr的功能相关推荐

  1. 【技术综述】图像与CNN发家简史,集齐深度学习三巨头

    文章首发于微信公众号<有三AI> [技术综述]图像与CNN发家简史,集齐深度学习三巨头 没有一个经典的发现会是突然之间横空出世,它总是需要一些积淀. 提起卷积神经网络,我们总会从LeNet ...

  2. spring security 学习三-rememberMe

    spring security 学习三-rememberMe 功能:登录时的"记住我"功能 原理: rememberMeAuthenticationFilter在security过 ...

  3. PyTorch框架学习三——张量操作

    PyTorch框架学习三--张量操作 一.拼接 1.torch.cat() 2.torch.stack() 二.切分 1.torch.chunk() 2.torch.split() 三.索引 1.to ...

  4. 大数据基础学习三:Ubuntu下安装VMware Tools超详细步骤及需要注意的问题(以ubuntu-18.04.3、Mware Workstation 15.1.0 Pro为例)

    大数据基础学习三:Ubuntu下安装VMware Tools超详细步骤及需要注意的问题 (以ubuntu-18.04.3.Mware Workstation 15.1.0 Pro for Window ...

  5. Docker学习三:Docker 数据管理

    前言 本次学习来自于datawhale组队学习: 教程地址为: https://github.com/datawhalechina/team-learning-program/tree/master/ ...

  6. 深度学习三(PyTorch物体检测实战)

    深度学习三(PyTorch物体检测实战) 文章目录 深度学习三(PyTorch物体检测实战) 1.网络骨架:Backbone 1.1.神经网络基本组成 1.1.1.卷积层 1.1.2.激活函数层 1. ...

  7. 深度学习三巨头共获 2018 年图灵奖(经典重温)!

    整理 | 琥珀 出品 | AI科技大本营(ID:rgznai100) 2019 年 3 月 27 日,ACM 宣布,深度学习三位大牛 Yoshua Bengio.Yann LeCun.Geoffrey ...

  8. 深度学习三十年创新路

    深度学习三十年创新路 编者注:深度学习火了,从任何意义上,大家谈论它的热衷程度,都超乎想象.但是,似乎很少有人提出不同的声音,说深度学习的火热,有可能是过度的繁荣,乃至不理性的盲从.而这次,有不同的想 ...

  9. python学习三-基础语法

    python学习三-基础语法(2019-12-24日晚) 1.源码文件 Python源码文件名通常采用小写的方式,常见的扩展名有: py:基本的源码扩展名. pyw:是另一种源码扩展名,跟py唯一的区 ...

  10. 深度学习三巨头之Yann LeCun(杨立昆)简介

    在人工智能研究领域,Yann LeCun.Geoffrey Hinton 和 Yoshua Bengio一直被公认为深度学习三巨头. Yann LeCun,自称中文名"杨立昆",计 ...

最新文章

  1. 路易斯安那州立大学计算机科学,西北路易斯安那州立大学
  2. 【MAVEN】如何在Eclipse中创建MAVEN项目
  3. 第七十一期:管理 | 技术Leader:选OKR还是KPI?
  4. loadrunner学习理论之一
  5. WebLogic清理缓存
  6. Linux调试工具strace和gdb常用命令小结-转
  7. 文本分类实战--从TFIDF到深度学习CNN系列效果对比(附代码)
  8. oracle 获取多个序列值,一次性获取多个oracle序列值问题
  9. ai背景合成_视频素材不好找!图片也能生成视频啦,AI剪辑助力原创短视频创作...
  10. 配有傲腾内存的电脑如何安装系统
  11. My Fifty-First Page - 组合总和 - By Nicolas
  12. POJ 3295: Tautology
  13. usb转并口支持linux,USB转并口;USB转真并口(支持仿真调试加密狗等);USB TO LPT
  14. Linux:ftrace: 为什么有些函数没有在available_filter_functions
  15. 新代系统反向间隙参数_如何调试新代系统SYNTEC参数
  16. XB文件开发详解(上报证监会文件)_入门系列
  17. Matlab画图彩色变黑白
  18. 一碗牛肉面的成本是多少钱?
  19. 免费不限速跨平台文件传输神器—文件疯巢
  20. 如何下载国家标准分幅影像地图

热门文章

  1. 群、环、域基础与例子
  2. GotW#63 狂乱的代码
  3. 【去后厂村开游戏厅吧】基于pp-tinypose的体感贪吃蛇游戏
  4. c盘内存不足怎么清理(如何清理电脑c盘空间)
  5. 铁路信号基础知识——信号部分
  6. SDN的两种方式分析
  7. Android开发入门到实战精通 完整全套开发教程送给你
  8. 解决PCL报错: Assertion `point_representation_->isValid (point) “Invalid (NaN, Inf) point coordinates
  9. Mac宝藏软件推荐(笔者也在用)(二)
  10. 前后端微信小程序开发