“除草帖。翻到了几年前做的一个语言解析的小项目,感觉笔记躺在自己笔记本里挺浪费,放在博客上或许还能体现点价值”。
先附上参考资料:
博客:ANTLR(一)_qq_33669252的博客-CSDN博客
相关教程:https://www.crifan.com/files/doc/docbook/antlr_tutorial/release/html/antlr_tutorial.html
很好的讲解(从简介到环境准备等):antlr v4 使用指南连载1——简介 - eventer - 博客园
ANTLR简介
ANTLR是一个强大语言解析工具,可用于处理结构化文本,二进制文件,是一个更强大的正则表达式工具。
两个重要部分——词法laxer和语法parser。
词法就是:
-
标识符,即各类编程语言中所说的以下划线、字母开头的字符串
-
字面量,英文叫Literal,其实就是可以当作值的东西,放在操作符两边。如数字、单引号字符串、双引号字符串、各个进制写法等
-
字符,单字符(!、~、=、>等)、双字符(>=、<=)等
-
关键字,如Java中的class、package、import、public等
语法就很容易理解了,比如变量定义、类定义,这些都是语法。 分别放在两个文件里,一个叫xxxLexer.g4,一个叫xxxParser.g4。当然名字是否包含Lexer、Parser不是强制的,看各人喜好。不过两个文件内容的第一行可以看出来是lexer还是parser。此外antlr也提供一个combine模式,即把lexer、parser写在同一个文件里。
比如:
grammar Hello; //Definea grammar called Hello
r :'hello' ID; //match key word hello followed by an identifier
ID : [a-z]+; //match lower-case identifiers
WS : [\t\r\n ]+->skip; //skip spaces,tabs,newlines,\r(Windows)
第一行是语法文件名Hello,保存之后文件要按这个名字取,即Hello.g4
第二行以小写字母开头,是一个语法规则。hello后面跟着一个ID标识符。ID标识符的定义在第三行定义
第三行以大写字母开头,是一个词法规则。ID由a-z这26个英文小写字母的一个或多个组成
第四行以大写字母开头,是一个词法规则。WS由制表符、换行符的一个或多个组成。->skip是action,表示当处理这个词法规则时采取的处理方法。skip表示跳过,不处理制表符、换行符,直接处理下一个词法规则。
Python antlr4 安装: pip install antlr4-python2-runtime
antlr preview及插件安装:ANTLR4的IntelliJ插件安装及示例Hello.g4_Jeremy_ku的博客-CSDN博客_antlr g4
g4文件概览:
grammar Name;
options {...}
import ...;
tokens {...}
@actionName {...}
<<rule1>> ... <<rule2>>
这是一个完整的antlr语法规则文件,简要声明:
-
grammar Name 这是词法跟语法都在同一个文件声明的写法,称之为combined。若要分开,可以使用lexer grammar Name和parser grammar Name。
-
options 可以是如下四个选项。
superClass:用于生成xxxLexer.java、xxxParser.java的父类
language:目标语句,如java
tokenVocab:toekn词库
TokenLabelType:默认的是antlr的Token类型,这里可以使用自定义的token类,如MyToken。需要配合TokenFactory使用
-
import 可以导入各个独立的lexer、parser文件,只能用于combined写法。
-
actionName 可以是如下内容
@header:定义类文件头。比如嵌入java的package、import声明
@member:定义类文件内容。比如类成员、方法(如果要指定在lexer或者parser中,可以使用 @lexer::membere、@parser::member。)
-
rule 语法规则。
截取部分对myparser.g4的语句解释
grammar myparser;
options {
language=Python2;
}
WS: [ \t\r\n]+ -> skip;
ADD: '+'; // 加法
SUB: '-'; // 减法
MUL: '*' |'\\ast' | '·'; // 乘法
DIV: '/'; // 除法
DOT: '\u2022' | '\u22c5'; // 点乘
EXP: '**'; // 指数运算
FACTORIAL: '!'; // 阶乘
TRI: '\u25b3' | '\\triangle'; // △
ANG: '\u2220' | '\\angle'; // ∠
SIBIAN: '\u25b1'; // ▱
DU: '\u00B0' | '^\\circ' | '^{\\circ }'; // °
UNION: '\u222a'; // ∪
INTER: '\u2229'; // ∩
BELON: '\u2208'; // ∈
NOTBELON: '\u2209' | '\\not ∈'; // ∉
CHILD: '\u2286' | '\u228a'; // ⊆ or ⊊
PARENT: '\u2287'; // ⊇
PARENT2: '\\supsetneqq';
INVERT: L_BRACE? ('C' |'\u2201' | '\\complement ' | '\\complement') UNDERSCORE L_BRACE UPPER R_BRACE R_BRACE?; // C_{A}
LEFT: '\\left';
RIGHT: '\\right.' | '\\right';
BEGIN: '\\begin';
EQU: '{equation}';
END: '\\end';
CASE: '{cases}';
//CC: '{c}';
EQSEP: '\\\\';
EQBRACE: '\\{';
R_EQBRACE: '\\}';
ARRAY: '{array}';
ALIGN: '{align}';
CR: '\\cr';
AND: '&';
L_PAREN: '(';
R_PAREN: ')';
L_BRACE: '{';
R_BRACE: '}';
L_BRACKET: '[';
R_BRACKET: ']';
L_ESCAPE: '\u201c'; // “
R_ESCAPE: '\u201d'; // ”
BAR: '|';
FUNC_FLAG: [fgh];
FUNC_INT: '\\int';
FUNC_SUM: '\\sum';
FUNC_PROD: '\\prod';
FUNC_LOG: '\\'? 'log';
FUNC_LOG10: '\\'? 'lg';
FUNC_LN: '\\'? 'ln';
FUNC_SIN: '\\'? 'sin';
FUNC_COS: '\\'? 'cos' | '\\ cos';
FUNC_TAN: '\\'? 'tan';
FUNC_CSC: '\\'? 'csc';
FUNC_SEC: '\\'? 'sec';
FUNC_COT: '\\'? 'cot';
FUNC_ARCSIN: '\\'? 'arcsin';
FUNC_ARCCOS: '\\'? 'arccos';
FUNC_ARCTAN: '\\'? 'arctan';
FUNC_ARCCSC: '\\'? 'arccsc';
FUNC_ARCSEC: '\\'?' arcsec';
FUNC_ARCCOT: '\\'? 'arccot';
FUNC_SGN: '\\'? 'sgn';
FUNC_SQRT: '\\sqrt';
FUNC_ROOT: '\\root';
CMD_TIMES: '\\times';
CMD_CDOT: '\\cdot';
CMD_DIV: '\\div';
CMD_FRAC: '\\frac' | '\\dfrac';
CMD_VEC: '\\overrightarrow' | '\\overline' | '\\widehat' | '\\vec';
condition: L_PAREN (inequality|equality|belong) R_PAREN
| L_PAREN (inequality|equality|belong) DELIMITER (inequality|equality|belong) R_PAREN
| L_PAREN (CHINESE+ | atom | inequality | equality | belong)+ R_PAREN
| L_PAREN text R_PAREN;
func_cond: poly DELIMITER? AND? condition| poly DELIMITER (belong | inequality | equality) | poly L_PAREN (belong | inequality) R_PAREN;
cc:L_BRACE LETTER+ R_BRACE;
equations: BEGIN EQU BEGIN CASE equality (EQSEP equality)+ END CASE END EQU
| BEGIN CASE (equality | inequality) DELIMITER* (AND? EQSEP (equality | inequality) DELIMITER*)+ END CASE
| L_BRACE? BEGIN ALIGN (AND? (inequality | equality)) (DELIMITER* CR? AND? (inequality | equality))+ DELIMITER* END ALIGN R_BRACE?
| LEFT? EQBRACE BEGIN ARRAY cc (poly | equality | func_cond | inequality) DELIMITER* (EQSEP (poly | equality | func_cond | inequality) DELIMITER*)+ (END ARRAY RIGHT?)?
| L_BRACE? BEGIN ALIGN AND? func_cond DELIMITER* (CR AND? func_cond)+ DELIMITER* END ALIGN R_BRACE?
;
zhixian: atom COLON equality;
yuan: YUAN atom COLON equality;
yuanname: YUAN atom;
hypo: (atom COLON)? (ANY|EXIST) (belong|inequality) (DELIMITER (inequality|equality))?;
not_hypo: NEG atom;
boolop: atom (WEDGE|VEE) atom;
//inequalities: LEFT EQBRACE BEGIN ARRAY CC inequality (EQSEP inequality)+ END ARRAY RIGHT;
function: func_name EQUAL (poly | equations) (DELIMITER belong)?
| atom EQUAL equations;
projection: FUNC_FLAG COLON UPPER PORJ UPPER;
//functions: func_name EQUAL LEFT EQBRACE BEGIN ARRAY CC equation (EQSEP equation)+ END ARRAY RIGHT;
question: L_PAREN R_PAREN (CM|DU)?;
triangle: 'Rt'? TRI upper upper upper;
sibian: SIBIAN upper upper upper upper;
compare: poly L_PAREN R_PAREN poly;
mingtiname: 'MT' NUMBER;
mingti: 'MT' NUMBER EQUAL L_ESCAPE exprs R_ESCAPE;
mingti2: ('p:' | 'q:') expr;
expr: (projection | mingti | mingti2 | mingtiname | compare | geo_expr | zhixian_expr | yuan | yuanname | hypo | not_hypo| boolop | zhixian | vectoreq | equations | sibian | triangle | belong | point | partial | function | geo_atom | interval | poly | inequality | equality | question | sett) (question| DELIMITER? condition)? | condition;
exprs: text? (expr text)* expr?;
具体解释词法规则:
包括unicode编码(Unicode编码转换 - 站长工具)及LaTeX(LaTeX 公式编辑器)
fragment DIGIT: [0-9]; // 0,1,2,3,4,5,6,7,8,9
词法片段,构成词法的元素,不是一个词法规则。在词法规则中可引用一个或多个词法片段。(antlr v4 使用指南连载5——如何编写词法定义 - eventer - 博客园)
在词法规则中那些不会被语法规则直接调用的词法规则可以用一个fragment关键字来标识,fragment标识的规则只能为其它词法规则提供基础。
Antlr 中 fragment词法规则_jazwoo的博客-CSDN博客_antlr4 fragment
每一个规则后面都要需要有一个标签 ,以 # 开头,后面接标签内容。在 antlr 为每一个规则生成事件的时候会用到。
语法规则:text ( ; . 、: 中文字符 一-鿿?“”…《》①②③④【】 多项式)一个或多个;func_normal 基本函数
有些根据项目要求的具体修改举例
乘除法时第二项因子自动加了括号
因为def visitT2(self, ctx):
if ctx.DOT()!=None:
return self.visit(ctx.poly(0))+'*'+self.visit(ctx.poly(1))
else:
return self.visit(ctx.poly(0))+self.visit(ctx.getChild(1))+ '(' + self.visit(ctx.poly(1)) + ')'
>>>把括号去掉
def visitT2(self, ctx):
if ctx.DOT()!=None:
return self.visit(ctx.poly(0))+'*'+self.visit(ctx.poly(1))
else:
return self.visit(ctx.poly(0))+self.visit(ctx.getChild(1))+ self.visit(ctx.poly(1))
除法符号的修改
def visitT2(self, ctx):
if ctx.DOT()!=None:
return self.visit(ctx.poly(0))+'*'+self.visit(ctx.poly(1))
elif ctx.getChild(1).getText() == '\div':
return self.visit(ctx.poly(0))+'÷'+self.visit(ctx.poly(1))
else:
return self.visit(ctx.poly(0))+self.visit(ctx.getChild(1))+ self.visit(ctx.poly(1))
改exp2
def visitExp2(self, ctx):
if ctx.getChild(0).getText() == 'm':
return self.visit(ctx.getChild(0)) + "^{" + self.visit(ctx.getChild(3)) + "}"
else:
under = self.visit(ctx.getChild(0))
upper = self.visit(ctx.poly())
return '(' + under + ')' + '**' + '(' + upper + ')'
输出带中文单位的字段而不输出纯粹的中文单位
def visitUnit(self, ctx):
return ctx.getText()
def visitUnit(self, ctx:myparserParser.UnitContext):
if len(ctx.children) > 1:
self.parse_result.append({'type': 'unit', 'text': ctx.getText(), 'sympy': self.visitor.visit(ctx)})
else:
self.parse_result.append({'type': 'unit', 'text': ctx.getText()})
代码逻辑