查看“Parser-rules.md”的源代码
←
Parser-rules.md
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
{{MARKDOWN}} # Parser Rules(解析器中的规则) Parsers由一组在parser/combined grammar 中的Parser Rules组成的。 Java应用程序通过调用由ANTLR生成的与所需启动rule相关联的rule function来启动Parser。 最基本的rule只是一个rule名称,后跟以分号终止的单个alternative: ``` /** Javadoc注释可以位于rule之前 */ retstat : 'return' expr ';' ; ``` Rules也可以有由|分割 ``` operator: stat: retstat | 'break' ';' | 'continue' ';' ; ``` Alternatives要么是rule元素列表,要么是空的。 例如,这里有一个带有空替代项的rule,使整个规则成为可选规则: ``` superClass : 'extends' ID | // 为空表示其他alternative是可选的 ; ``` ## Alternative Labels (候选标记) 正如我们在第7.4节中看到的那样,针对Precise Event Methods的Labeling Rule Alternatives,我们可以通过使用#符号来labeling一个rule的outermost alternatives来获得更精确的parse-tree listener事件。 Rule中的所有alternatives都必须labeled,或者一个都不labeled。 以下是两条带有labeled alternatives的Rules。 ``` grammar T; stat: 'return' e ';' # Return | 'break' ';' # Break ; e : e '*' e # Mult | e '+' e # Add | INT # Int ; ``` Alternative labels不必在行的末尾,并且在#符号之后也不必有空格。 ANTLR为每个Label生成Rule上下文类定义。例如,以下是ANTLR生成的listener: ```java public interface AListener extends ParseTreeListener { void enterReturn(AParser.ReturnContext ctx); void exitReturn(AParser.ReturnContext ctx); void enterBreak(AParser.BreakContext ctx); void exitBreak(AParser.BreakContext ctx); void enterMult(AParser.MultContext ctx); void exitMult(AParser.MultContext ctx); void enterAdd(AParser.AddContext ctx); void exitAdd(AParser.AddContext ctx); void enterInt(AParser.IntContext ctx); void exitInt(AParser.IntContext ctx); } ``` 有与每个labeled alternative相关联的enter和exit方法。 这些方法的参数特定于alternatives。 您可以在多个alternatives上重复使用相同的Label,以指示parse tree walker应该为这些alternatives触发相同的事件。 例如,以下是上述grammar A中Rule e的变体: ``` e : e '*' e # BinaryOp | e '+' e # BinaryOp | INT # Int ; ``` ANTLR将为e生成以下listener方法: ```java void enterBinaryOp(AParser.BinaryOpContext ctx); void exitBinaryOp(AParser.BinaryOpContext ctx); void enterInt(AParser.IntContext ctx); void exitInt(AParser.IntContext ctx); ``` 如果alternative名称与rule名称冲突,ANTLR会给出错误。 下面是rule e的另一个重写,其中两个alternative labels与rule名称冲突: ``` e : e '*' e # e | e '+' e # Stat | INT # Int ; ``` 从rule名称和Label生成的上下文对象大写,因此label Stat与rule stat冲突:(译者注:这里Stat是更前面的代码示例里面的) ```bash $ antlr4 A.g4 error(124): A.g4:5:23: rule alt label e conflicts with rule e error(124): A.g4:6:23: rule alt label Stat conflicts with rule stat warning(125): A.g4:2:13: implicit definition of token INT in parser ``` ## Rule Context Objects(rule上下文对象) ANTLR会生成用于访问与每个rule reference相关联的rule context objects (parse tree nodes) 的方法。 对于具有单个rule reference的rule,ANTLR生成没有参数的方法。 考虑下面的rule。 ``` inc : e '++' ; ``` ANTLR生成此上下文类: ```java public static class IncContext extends ParserRuleContext { public EContext e() { ... } // 返回与e关联的上下文对象 ... } ``` 当对rule有多个reference时,ANTLR还提供访问上下文对象的支持: ``` field : e '.' e ; ``` ANTLR生成一个方法,该方法具有访问第i个元素的索引,以及获取该规则所有引用的上下文的方法: ```java public static class FieldContext extends ParserRuleContext { public EContext e(int i) { ... } // get ith e context public List<EContext> e() { ... } // return ALL e contexts ... } ``` 如果我们有另一个rule s,references到field,则嵌入式action可以访问e rule匹配项列表,该列表可由field使用: ``` s : field { List<EContext> x = $field.ctx.e(); ... } ; ``` listener或visitor也可以做同样的事情。 指向FieldContext的f对象后,f.e()将返回List<EContext>。 ## Rule Element Labels(Rule元素标记) 您可以使用=符号来label rule元素(译者注:label做动词,标记),以向rule上下文对象添加字段: ``` stat: 'return' value=e ';' # Return | 'break' ';' # Break ; ``` 这里,value是对rule e返回值的label,该返回值已在其他地方定义。 Labels成为适当的解析树节点类中的字段。 在这种情况下,由于使用ReturnContext替代Label,Label Value将成为ReturnContext中的一个字段: ```java public static class ReturnContext extends StatContext { public EContext value; ... } ``` 跟踪大量令牌通常很方便,可以使用+=“list label”操作符来完成。 例如,以下规则创建与简单数组结构匹配的令牌对象列表: ``` array : '{' el+=INT (',' el+=INT)* '}' ; ``` ANTLR在适当的Rule上下文类中生成列表字段: ``` public static class ArrayContext extends ParserRuleContext { public List<Token> el = new ArrayList<Token>(); ... } ``` 这些Label列表也适用于rule references: ``` elist : exprs+=e (',' exprs+=e)* ; ``` ANTLR生成一个保存上下文对象列表的字段: ``` public static class ElistContext extends ParserRuleContext { public List<EContext> exprs = new ArrayList<EContext>(); ... } ``` ## Rule元素 Rule元素指定parser在给定时刻应该做什么,就像编程语言中的语句一样。 元素可以是rule、token、string literal,如表达式、ID和‘return’。 以下是rule元素的完整列表 (稍后我们将详细介绍actions和predicates): <table> <tr> <th>Syntax</th><th>Description</th> </tr> <tr> <td>T</td><td> 在当前输入位置匹配TokenT。Token始终以大写字母开头。</td> </tr> <tr> <td>’literal’</td><td> 在当前输入位置匹配string literal。string literal只是带有固定字符串的token</td> </tr> <tr> <td>r</td><td> 在当前输入位置匹配Rule r,这相当于像调用函数一样调用Rule。Parser rule名称始终以小写字母开头。</td> </tr> <tr> <td>r [«args»]</td><td> 在当前输入位置匹配Rule r,传递参数列表就像函数调用一样。 方括号内的参数采用目标语言的语法,通常是一个逗号分隔的表达式列表</td> </tr> <tr> <td>{«action»}</td><td> 在备选元素之前或之后的执行一个action。 该action符合目标语言的语法。 除了替换$x和$x.y等属性和token引用之外,ANTLR会将操作代码逐字复制到生成的类中。</td> </tr> <tr> <td>{«p»}?</td><td> 运行语义谓词 «p»。 如果«p»在运行时的计算结果为false,则不要继续分析谓词。 预测期间遇到的谓词,当ANTLR区分备选方案时,启用或禁用谓词周围的备选方案。</td> </tr> <tr> <td>.</td><td> 匹配除文件结尾以外的任何单个Token。“点” 运算符称为通配符。</td> </tr> </table> 如果要匹配除特定Token或Token集之外的所有内容,请使用`~` “非”运算符。 此操作符在解析器中很少使用,但可用。 '~ INT' 匹配除 'INT' Token以外的任何Token。' ~ ',''匹配除逗号以外的任何Token。 `~(INT | ID)`匹配除INT或ID之外的任何Token。 Token、string literal和semantic predicate rule元素可以接受选项。 请参阅规则元素选项。 ## Subrules(子规则) Rule可以包含称为Subrules的alternative块(在扩展BNF Notation:EBNF中允许)。 subrule类似于缺少名称并用括号括起来的规则。 Subrules可以在括号内有一个或多个alternatives。 Subrules不能像rules一样使用局部变量定义属性和返回值。 有四种子规则(x、y和z表示语法片段): <table> <tr> <th>Syntax</th><th>Description</th> </tr> <tr> <td><img src=images/xyz.png></td><td>(x|y|z). 精确匹配子规则中的任何选项一次。例子: <br> <tt> returnType : (type | 'void') ; </tt> </td> </tr> <tr> <td><img src=images/xyz_opt.png></td><td>(x|y|z)? 子规则中不匹配任何项或任何alternative。示例: <br> <tt> classDeclaration : 'class' ID (typeParameters)? ('extends' type)? ('implements' typeList)? classBody ; </tt> <tr> <td><img src=images/xyz_star.png></td><td>(x|y|z)* 在子零次或更多次内匹配alternative。示例: <br> <tt> annotationName : ID ('.' ID)* ; </tt> </tr> <tr> <td><img src=images/xyz_plus.png></td><td>(x|y|z)+ 将subrule中的alternative匹配一次或多次。例子: <br> <tt> annotations : (annotation)+ ; </tt> </td> </tr> </table> 您可以将 `?`, `*`, 和 `+` subrule运算符加上非贪心匹配运算符后缀,和正则表达式一样也是一个问号: `??`, `*?`, 和 `+?`。 参见第15.6节,Wildcard Operator and Nongreedy Subrules(通配符运算符和非通配符子规则)。 作为速记,您可以省略由具有单个Rule元素引用的单个alternative组成的subrole的括号。 例如`annotation+` 与 `(annotation)+` 相同,`ID+` 与 `(ID)+` 相同。 Labels也可以使用简写法。`ids+=INT+`产生`INT`token对象列表。 ## 捕获异常 当规则中出现语法错误时,ANTLR会捕获异常,报告错误,尝试恢复(可能通过使用更多Token),然后从rule返回。 每个rule都包装在 “try/catch/finalally” 语句中: ``` void r() throws RecognitionException { try { rule-body } catch (RecognitionException re) { _errHandler.reportError(this, re); _errHandler.recover(this, re); } finally { exitRule(); } } ``` 在第9.5节“改变ANTLR的错误处理策略”中,我们看到了如何使用Strategy对象来改变ANTLR的错误处理。 但是,替换strategy会更改所有rule的strategy。 要更改单个rule的异常处理,请在rule定义后指定异常: ``` r : ... ; catch[RecognitionException e] { throw e; } ``` 该示例演示了如何避免默认错误报告和恢复。 r重新抛出异常,当需要对更高级别的rule报告更有意义的错误时,这很有用。 指定任何exception子句会阻止ANTLR生成子句来处理`RecognitionException`。 您也可以指定其他异常: ``` r : ... ; catch[FailedPredicateException fpe] { ... } catch[RecognitionException e] { ... } ``` 大括号内的代码片段和异常“参数”操作必须用目标语言编写; 以上是Java的写法。 当你即使发生异常,也需要执行操作,可以把它到 'finaly' 子句中: ``` r : ... ; // catch blocks go first finally { System.out.println("exit rule r"); } ``` finally子句在规则触发`exitRule`之前执行,然后返回。 如果您想要在rule匹配完alternatives之后,但在它执行清理工作之前执行某个操作,请使用`after`操作。 以下是例外的完整列表: <table> <tr> <th>Exception name</th><th>Description</th> </tr> <tr> <td>RecognitionException</td><td> ANTLR生成的识别器引发的所有异常的超类。 它是RuntimeException的子类,以避免检查异常的麻烦。 此异常记录了识别器 (lexer或parser) 在输入中的位置,它在ATN (表示语法的内部图形数据结构) 中的位置,规则调用堆栈以及发生了什么样的问题。</td> </tr> <tr> <td>NoViableAltException</td><td> 指示parser无法通过查看剩余的输入来决定采用两条或多条路径中的哪一条。 此异常跟踪违规输入的起始Token,并且还知道错误发生时parser在各种路径中的位置。</td> </tr> <tr> <td>LexerNoViableAltException</td><td> 相当于NoViableAltException,但仅适用于lexers。</td> </tr> <tr> <td>InputMismatchException</td><td> 当前输入Token与parser预期的不匹配。</td> </tr> <tr> <td>FailedPredicateException</td><td> 在预测(predicting)期间计算为False的语义谓词将使周围的alternative不可用。 当一条规则正在预测(predicting)采取哪种替代方案时,就会发生Prediction。 如果所有可行的路径都消失了,parser将抛出NoViableAltException。 在匹配Token和调用Rule的正常解析过程中,当语义谓词在预测之外评估为false时,解析器会抛出此谓词。</td> </tr> </table> ## Rule Attribute Definitions(规则属性定义) 有许多action相关的语法元素和rule相关需要注意。 Rules可以有参数、返回值和局部变量,就像编程语言中的函数一样。 (Rule可以在Rule元素中嵌入action,正如我们将在第15.4节 “操作和属性” 中看到的那样。) ANTLR收集您定义的所有变量,并将它们存储在Rule上下文对象中。 这些变量通常称为属性。 下面是显示所有可能的属性定义位置的通用语法: ``` rulename[args] returns [retvals] locals [localvars] : ... ; ``` 这些[…]中定义的属性可以像任何其他变量一样使用。 以下是复制参数以返回值的示例rule: ``` // 返回参数加上INT Token的整数值 add[int x] returns [int result] : '+=' INT {$result = $x + $INT.int;} ; ``` 参数,局部变量和返回值`[...]`通常是使用目标语言,但有一些限制。 `[...]`字符串是一个逗号分隔的声明列表,可以使用前缀或后缀类型表示法,也可以不使用类型表示法。 元素可以有`[int x = 32, float y]`这样的初始值设定项,但是不要太疯狂,因为我们正在用[ScopeParser](https://github.com/antlr/antlr4/blob/master/tool/src/org/antlr/v4/parse/ScopeParser.java).手动解析这个泛型文本 * Java,CSharp,C++ 使用 `int x` 表示法,但C++必须对数组引用 `int[] x` 使用稍微更改的表示法,以适合 *type* *id* 语法。 * Go和Swift在变量名后给出类型,但Swift需要在两者之间加一个`:`。 Go `i int`, Swift `i:int`. Go目标必须使用`int i`或`i:int`。 * Python和JavaScript不指定静态类型,因此操作只是标识符列表,例如 “[i,j]”。 从技术上讲,任何目标都可以使用这两种符号。 有关示例,请参见[TestScopeParsing](https://github.com/antlr/antlr4/blob/master/tool-testsuite/test/org/antlr/v4/test/tool/TestScopeParsing.java). 与grammar级别一样,您可以指定rule级别的命名操作。 对于rules,有效名称是`init`和`after`。 顾名思义,parsers在尝试匹配关联rule之前立即执行init操作,在匹配rule之后立即执行after操作。 ANTLR中的after操作不会作为生成的rule函数的最终代码块的一部分执行。 使用ANTLR finally操作将代码放入生成的规则函数finally 代码块中。 这些操作位于任何参数、返回值或本地属性定义操作之后。 第10.2节 “访问Token和rule属性” 中的 `row` rule前导码很好地说明了语法: actions/CSV.g4 ``` /** Derived from rule "row : field (',' field)* '\r'? '\n' ;" */ row[String[] columns] returns [Map<String,String> values] locals [int col=0] @init { $values = new HashMap<String,String>(); } @after { if ($values!=null && $values.size()>0) { System.out.println("values = "+$values); } } : ... ; ``` Rule row包含参数columns,返回值,并定义局部变量col。 方括号中的“actions”被直接复制到生成的代码中: ```java public class CSVParser extends Parser { ... public static class RowContext extends ParserRuleContext { public String [] columns; public Map<String,String> values; public int col=0; ... } ... } ``` 生成的rule函数还将rule参数指定为函数参数,并且它们会快速复制到本地RowContext对象中: ```java public class CSVParser extends Parser { ... public final RowContext row(String [] columns) throws RecognitionException { RowContext _localctx = new RowContext(_ctx, 4, columns); enterRule(_localctx, RULE_row); ... } ... } ``` ANTLR在action中跟踪嵌套的“[…]”,以便正确解析`String[]` column。 它还跟踪尖括号,以便泛型类型参数中的逗号不表示另一个属性的开始。 `Map<String,String>`值是一个属性定义。 每个动作中可以有多个属性,即使是返回值也是如此。 使用逗号分隔同一action中的属性: ``` a[Map<String,String> x, int y] : ... ; ``` ANTLR解释该action为定义两个参数x和y: ```java public final AContext a(Map<String,String> x, int y) throws RecognitionException { AContext _localctx = new AContext(_ctx, 0, x, y); enterRule(_localctx, RULE_a); ... } ``` ## Start Rules and EOF(起始和结尾Rule) start rule是parser最先使用的rule;它是语言应用程序调用的rule函数。 例如,解析为Java代码的语言应用程序可能是一个`JavaParser`类的对象名为`parser`,它可以调用`parser.compilationUnit()`方法 grammar中的任何rule都可以作为起始rule。 起始Rule不一定消耗所有输入。 它们仅消耗所需的输入,以匹配Rule的alternative。 例如,考虑下面的Rule,根据输入,匹配一个、两个或三个Token。 ``` s : ID | ID '+' | ID '+' INT ; ``` 在`a+3`时,rule`s`匹配第三个选项。 在`a+b`时,它与第二种选择匹配,并忽略最终的 `b` token。 在`a b`时,它匹配第一个alternative,忽略`b`标记。 在后两种情况下,parser不会使用完整的输入,因为rule`s‘没有显式地说明文件结尾必须在匹配rule的alternative之后才会出现。 这个默认功能对于构建像ide这样的东西非常有用。 假设IDE想在大java文件中间解析某个方法。 调用rule`method Declaration`应该尝试只匹配一个方法,而忽略接下来发生的任何事情。 另一方面,描述整个输入文件的规则应引用特殊的预定义令牌 `EOF`。 如果他们没有,你可能会挠头想一想,为什么不管你给出什么,起始rule都不会报告任何输入的错误。 以下是读取配置文件的语法的一部分: ``` config : element*; // 即使输入无效,也可以 “匹配”。 ``` 无效输入将导致`config`立即返回,而不匹配任何输入,也不报告错误。 以下是适当的specification: ``` file : element* EOF; // 不要过早的停止。必须匹配所有输入 ```
返回至“
Parser-rules.md
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息