ANTLR3完全参考指南读书笔记[05]
2024-10-19 17:27:45
前言
仅生成给出true/false的识别器是没有多大用处的,自然的就有在识别过程中遇到某一结构时执行一段代码、存储该结构中信息的想法。
ANTLR提供了在文法中嵌入属性和动作超级混合“文法”,可以生成内部表示AST或模板;当然如果直接输出部分结构识别结果的话动作也可以应付。
内容
基本与原文第6章一致
属性和动作
1 文法动作
2 Token属性
3 预定义规则属性
4 属性作用域
5 在动作中引用属性
属性和动作
用几个实例予以说明,动作中语言均是Java。
[ex1]动作可以在规则还没结束的位置
decl : type ID {System.out.println($ID.text);}';'; type : 'int' | 'float';
[ex2]动作放置在规则结束位置
decl : type ID ';' {System.out.println("var " + $ID.text+":"+$type.text+";");}; type : 'int' | 'float';
[ex3]属性标签,区分同名规则
decl : type ID ';' {System.out.println("var " + $ID.text+":"+$type.text+";");} | t=ID id=ID ';' {System.out.println("var " + $id.text+":"+$t.text+";");};
[ex4]规则中出现* +
decl : type ids+=ID (',' ids+=ID)* ';'; //ids是ID token的List
[ex5]规则参数和返回值
decl : type declarator[$type.text] ';' ;//使用规则参数 declarator[String typeText] : '*' ID {"var " + $ID.text+":^"+$typeText+";")} | ID {System.out.println("var " + $ID.text+":"+$typeText+";")}; field : d=decl ';' {System.out.println("type=)+$d.type+", vars="+$d.vars);};//使用规则返回值 decl returns[String type, List vars] : t=type ids+=ID (',' ids+=ID)* {$type=$t.text;$vars=ids;}
[ex6]规则间通信:共享变量
@members{String methodName;}//文法内全局作用域变量 method : type ID {methodName=$ID.text;} body ; body : '{' statement+ '}' ; statement : decl {...methodName...} ';'//引用变量 | ... ; method scope {String name;}//规则作用域变量 : type ID {$method::name=$ID.text;} body ; body : '{' statement+ '}' ; statement : decl {...$method::name...} ';'//引用变量 | ... ;
1 文法动作
动作是用目标语言编写的嵌入在文法中的代码片段。
命名全局动作
名称 | 说明 |
header | 生成的代码中类定义之前的代码,常是包定义和包导入语句 |
memebers | 定义实例变量和方法 |
rulecatch | 动作中语法错误的默认catch语句 |
synpredgate | 句法谓词开关 |
全局动作的作用域(scope)是lexer,parser或treeparser。
文法规则中嵌入的命名动作
名称 | 说明 |
init | 放置解析规则的代码执行前的代码 |
after | 放置解析规则的代码执行后的代码 |
catch | 放置解析规则的代码出现异常的处理代码 |
finally | 放置解析规则的代码出现异常的最终处理代码 |
sample:
parser grammar T; @header{package p;} @members{ int i; public TParser(TokenStream input, int foo){ this(input); i = foo; } } a[int x] returns [int y] @init {int z=0;} @after {System.out.println("after matching rule, before finally");} : {<<action1>>} A {<<action2>>} ; catch[RecognitionException re] { System.err.println("error"); } finally {<<do-this-no-matter-what-happened>>}
2 Token属性
Token作为lexer提交给parser的最小单元,在动作中可以引用的Token属性有
属性 | 类型 | 说明 |
text | String | token对应的文本,调用Token#getText() |
type | int | token的类型(正整数),调用Token#getType() |
line | int | token所在行(从1计数),调用Token#getLine() |
pos | int | 该行中token首字符的位置(从0计数),调用Token#getCharPositionInLine() |
index | int | token流中该token的索引(从0计数),调用Toekn#getTokenIndex() |
channel | int | token的channel号码,两个值Token.DEFAULT_CHANNEL, Token.HIDDEN_CHANNEL |
tree | Object | 构建AST时,该属性指向依据token创建的树节点 |
引用方式$label.attribute,label是token的标签;$label引用token本身。
例外:在lexer规则中,有些label不是token,而是字符。
sample:
lexer grammar T; R : a='c' b='hin' c=. {$a, $b.text, $c};// a,c都不是token,b是token
3 预定义规则属性
预定义属性通常是只读的,唯一的例外是生成AST时在after动作中可以设置tree和st属性。
用$attribute或$enclosingRuleName.attribute引用。
预定义的parser规则属性
属性 | 类型 | 说明 |
text | String | 匹配规则开始直至$text表示式求值时的文本,包含了hidden channel中token的文本 |
start | Token | 非hidden channel中匹配该规则的第一个token |
stop | Token | 非hidden channel中匹配该规则的最后一个token |
tree | Object | 规则计算出的AST,通常是重写规则的结果。引用当前规则时,仅在after动作中可用 |
st | StringTemplate | 规则计算出的模板,通常是重写规则的结果。引用当前规则时,仅在after动作中可用 |
预定的lexer规则属性
属性 | 类型 | 说明 |
text | String | 从匹配最外层规则的第一个token开始到当前位置的文本 |
type | int | 包围规则的token类型 |
line | int | 该规则的第一个字符所在行号(从1计数) |
pos | int | 该规则的第一个字符在所在行中的位置(从0计数) |
channel | int | 该规则所在channel |
预定义的tree grammar规则属性
属性 | 类型 | 说明 |
text | String | 该规则匹配的第一个节点开始推导出的文本 |
start | Object | 第一个匹配该规则的树节点 |
st | StringTemplate | 规则计算出的模板,通常是重写规则的结果。引用当前规则时,仅在after动作中可用 |
4 属性作用域
什么是动态作用域(dynamic scoping)
sample:
void foo(){int x=0; bar();} void bar(){int y=x;}
调用链中的方法可以访问之前定义的局部变量
属性扮演规则间通信中间记录的角色,ANTLR提供了两种属性作用域:全局作用域和规则内作用域。
规则内作用域
sample:
见代码[1]规则内作用域
全局作用域
sample:
见代码[2]全局作用域
5 在动作中引用属性
ANTLR会对动作中带$和%前缀的表达式做特殊处理:填入相应值/代码。
%引用的是模板表达式,留在其他笔记中说明。
名称 | 说明 |
$tokenRef | token本身引用 ID {$ID}(ELSE stat)?{if($ELSE!=null)...} |
$tokenRef.attr | token属性引用 id=ID {$id.text} INT {$INT.line} |
$listLabel | 由+=操作符标识的标签,表示一个List ids+=ID (',' ids+=ID)* {$ids} |
$ruleRef | 规则有动态作用域且无歧义的情况下,parser/tree grammar中规则才可这样引用。该表达式是Stack $block.size() |
$ruleRef.attr | 规则属性 e=expr {$e.value, $expre.tree} |
$lexerRuleRef | lexer规则引用,是个Token (DIGIT {$DIGIT, $DIGIT.text})+ |
$attr | 规则返回值、参数或预定义属性 r[int x] returns[Token t]:{$t=$start; $x}; |
$enclosingRule.attr | 规则返回值、参数或预定义属性的全限定名 r[int x] returns[Token t]:{$r.t=$r.start; $r.x}; |
$globalScopeName | 全局动态作用域引用 $symbols.size() |
$x::y | 动态作用域x中属性y引用 $CScope::symbols |
$x[-1]::y | 动态作用域x前一个作用域中属性y引用 $block[-1]::symbols |
$x[-i]::y | 动态作用域x前i个作用域中属性y引用 $block[-i]::symbols |
$x[i]::y | 动态作用域Stack中从栈底第i个作用域中属性y引用(从0计数) $block[2]::symbols |
$x[0]::y | 动态作用域Stack中栈底作用域中属性y引用(从0计数) $block[0]::symbols |
代码
[1]规则内作用域
说明:变量已定义,在嵌套代码块中跟踪变量定义所在层次,输出未定义变量及其层次
文法定义
grammar T; @members{ int level = 0; boolean isDefined(String variable){ boolean result = false; //注:不要将中文注释放到文法定义文件中,至少在我的环境中是这样 //这里索引i从level-1的原因是:代码块(block)层次从1计数,而block对应的Stack从0计数 for(int i=level-1; i>=0; i--){ if($block[i]::symbols.contains(variable)){ System.out.println(variable + " found in nesting level " + (i+1)); result = true; } } return result; } } prog : block; block scope{ List symbols; } @init{ $block::symbols = new ArrayList(); level++; } @after{ System.out.println("symbols level " + level + " = " + $block::symbols); level--; } : '{'decl* stat+'}' ; decl : 'int' ID {$block::symbols.add($ID.text);}';' ; stat : ID '=' INT ';' { System.err.println("undefined variable level " + level + ": "+ $ID.text); } } | block ; ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*; INT : '0'..'9'+; WS : ( ' '| '\t'| '\r'| '\n') {$channel=HIDDEN;};
测试输入
{ int i; int j; i = 0; { int i; int x; x = 5; } x = 3; }
[2]全局作用域sample
说明:变量已定义,在方法和代码块中跟踪变量定义所在层次名称,输出未定义变量及其层次名称
文法定义
grammar T; scope CScope{ String name; List symbols; } @members{ boolean isDefined(String variable){ boolean result = false; for(int i=$CScope.size()-1; i>=0; i--){ if($CScope[i]::symbols.contains(variable)){ System.out.println(">" + variable + " found in " +$CScope[i]::name); result = true; } } return result; } } prog scope CScope; @init { $CScope::symbols = new ArrayList(); $CScope::name = "global"; } @after { System.out.println("global symbols = " + $CScope::symbols); } : decl* func*; func scope CScope; @init { $CScope::symbols = new ArrayList(); } @after { System.out.println("function " + $CScope::name + "()'s symbols = " +$CScope::symbols); } : 'void' ID{$CScope::name=$ID.text;} '(' ')' '{' decl* stat+ '}' ; block scope CScope; @init { $CScope::symbols = new ArrayList(); $CScope::name = "level " + $CScope.size(); } @after{ System.out.println("code block level " + $CScope.size() + " symbols = " +$CScope::symbols); } : '{'decl* stat+'}' ; //注:不要将中文注释放到文法定义文件中,至少在我的环境中是这样 //查看生成的代码,decl()中调用了Stack#peek(), //即,会使用动态作用域Stack中当前的作用域,最终还是使用block(引用该decl)的作用域 decl : 'int' ID {$CScope::symbols.add($ID.text);}';' ; stat : ID '=' INT ';' { if(!isDefined($ID.text)){ System.err.println("undefined variable in " + $CScope::name + ": "+ $ID.text); } } | block ; ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*; INT : '0'..'9'+; WS : ( ' '| '\t'| '\r'| '\n') {$channel=HIDDEN;};
测试输入
int i; void f() { int i; { int i; i = 2; } i = 1; } void g(){ i = 0; x = 3; }
最新文章
- 灾难 bzoj 2815
- Java for LeetCode 206 Reverse Linked List
- HTML入门的简单学习
- .htaccess设置静态资源缓存(即浏览器缓存)
- Apache httpd和JBoss构建高可用集群环境
- DB2_SQL_常用知识点&;实践
- sql中临时表的创建和使用【本文转自多人博客】
- mac下安装redis
- Spark的TorrentBroadcast:概念和原理
- Js中JSON.stringify()与JSON.parse()与eval()详解及使用案例
- STL 源代码剖析 算法 stl_algo.h -- partition
- 夏令营讲课内容整理 Day 7.
- Windows下python2.7安装64位mysqlclient
- excel表格公式无效、不生效的解决方案及常见问题、常用函数
- 使用freemarker生成xml模板
- Java高并发 -- 线程池
- (Alpha)Let&#39;s-版本发布说明
- mongodb系列~ mongodb慢语句(3)
- windows7 安装虚拟机,xsheel连接不上的问题,记录一下
- asp.net 将数据导成Excel文件