强悍的ANTLR Lexer
ANTLR的Lexer不是基于DFA的,所以更加灵活
比如,我要解析一段这样的代码,class的大括号{}需要解析,method的大括号{}内的内容不需要解析,只要作为一个整体就可以
class clazz{
test1(){
abc;
}
test2(){
{
efg;
}
}
}
code.g:
header{
import java.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
}
class JParser extends Parser;
options {
k = 2; // two token lookahead
buildAST = true;
}
tokens {
BLOCK;
}
startParse
: classDefinition
;
classDefinition
: "class" IDENT LCURLY (methodDefinition)* RCURLY
;
methodDefinition
: IDENT LPAREN RPAREN CODEBLOCK
;
class JLexer extends Lexer;
options {
k=2;
}
{
private static final Log log = LogFactory.getLog(JLexer.class);
int deep = 0;
}
LPAREN : '(' ;
RPAREN : ')' ;
SEMI : ';' ;
LCURLY : '{' {deep++;}
;
RCURLY : '}' {deep--;}
;
//in class, a code block don't need parse
CODEBLOCK : {deep > 0}?
'{' (~'}')* '}'
{
log.info($getText);
}
;
// whitespace
WS : ( ' '
| '/t'
| '/r' '/n' { newline(); }
| '/n' { newline(); }
)
{$setType(Token.SKIP);} //ignore this token
;
// an indentify, such as kent1_name
IDENT
options {testLiterals=true;}
: ('a'..'z'|'A'..'Z'|'_'|'$') ('a'..'z'|'A'..'Z'|'_'|'0'..'9'|'$')*
;
看看能打印出什么?
11:18:14,843 INFO JLexer[mCODEBLOCK]:956 - {
abc;
}
11:18:14,843 INFO JLexer[mCODEBLOCK]:956 - {
{
efg;
}
哈,方法内的内容全部打印出来了!注意k必须>1
同样的功能使用JavaCC要麻烦一些.ANTLR的确强大!
Lexer只是ANTLR的一小部分功能,还有其他更吸引人的地方,比如正则表达式方式书写产生式(不需要写成递归的),AST语法,比如#^!等等,还没搞清楚,等全部搞清了再写下一篇吧
强悍的ANTLR Lexer-续
上文中只能支持单层的{},如果{}嵌套则会出现问题,得出结果类似: {{}
比如下文中的例子:
class clazz{
test1(){
abc;
}
test2(){
{
efg;
{
hij;
{
klm;
}
opq;
}
rst;
}
}
}
多重嵌套如何处理呢?
对于ANTLR很简单,因为它支持递归的正则表达式,只需要把code.g修改如下就可以:
CODEBLOCK : {deep > 0}?
'{' {deep++;}
( CODEBLOCK | ~'}')*
'}' {deep--;}
{
if(deep == 1){
log.info("START" + $getText);
}
}
;
看看我们能打印出什么结果?
14:12:30,953 INFO JLexer[mCODEBLOCK]:961 - START{
abc;
}
14:12:30,953 INFO JLexer[mCODEBLOCK]:961 - START{
{
efg;
{
hij;
{
klm;
}
opq;
}
rst;
}
}
大功告成!
强悍的ANTLR LEXER-续2
接上文,我需要增加新的功能:除了把java method完全识别,还要识别java field.
就是说:类似以下语句:
abc = 123;
bde = 34{56{};ere{};sfa};
我必须把abc作为一个词, = 123作为一个词
bde作为一个词, = 34{56{};ere{};sfa};作为一个词
为什么要这样定义词法呢?因为对于java 类变量,一般有以下定义:
int i= 0; //第一种:直接赋值
int j= B.getIntValue(); //第二种:从其他类获得,包括 = (new B()).getValue();
B b = new B(); //第三种:new 一个
E e = (E) J.getValue(); //第四种,造型
D d = new D(){//第五种:使用内部类创建
public Object get(int i){
return null;
}
};
总结以上五种方法,有如下特征:
1.以=开始,以;结束
2.除了定义一个内部类会有{}和;嵌套,其他都不包含{}和;
code.g增加如下定义:
EXPR
: '=' (~(';'|'{'))* EXPRBLOCK
{
log.info("expr: " + $getText);
}
;
protected
EXPRBLOCK
:
(
{deep > 0}?
'{' {deep++;}
( EXPRBLOCK | ~'}')*
'}' {deep--;}
{
if(deep == 1){
//log.info("START" + $getText);
}
}
';'
)
|';'
;
看上去非常复杂,测试结果:
14:05:05,828 INFO JLexer[mEXPR]:266 - expr: = 123;
14:05:05,828 INFO JLexer[mEXPR]:266 - expr: = 34{56{};ere{};sfa};
成功了!
不知道这是否最佳写法,但是看上去似乎也解决了问题!
强悍的ANTLR LEXER-续3
接上文,在测试过程中,上文的正则表达式出现了问题.
解决方法就是:采用非贪婪设置
在ANTLR中,可以使用greedy设置是否贪婪.
贪婪本来是词法分析中的一个原则.比如,有两个表达式,分别是a和ab,现在有个串:abcd,按照贪婪原则,词法分析器应该匹配ab而不是 a.在ANTLR中可以关闭这个选项:greedy=false.同时,为了不报错误,还必须设置: generateAmbigWarnings= false;
按照非贪婪的写法,现在简单了:
必须说明的是,为了简单化,多个变量的定义用,分隔的,如:
a=1,b=1;这里识别为a和=1,b=1;
因为不能用,表明两个变量,而需要更加精确的分析表达式.
比如: a=new A(1,2), b=2;
这样的表达式必须分析 A(1,2)这样的表达式,很麻烦
EXPRBLOCK
: '='
(
options {
generateAmbigWarnings=false;
greedy = false;
}
: . | CODEBLOCK
)*
';'
{
log.info("expr: " + $getText);
}
;
//in class, a code block don't need parse
CODEBLOCK
: {deep > 0}?
'{' {deep++;}
( CODEBLOCK | ~'}')*
'}' {deep--;}
{
if(deep == 1){
log.info("START" + $getText);
}
}
;
ANTLR的Lexer不是基于DFA的,所以更加灵活
比如,我要解析一段这样的代码,class的大括号{}需要解析,method的大括号{}内的内容不需要解析,只要作为一个整体就可以
class clazz{
test1(){
abc;
}
test2(){
{
efg;
}
}
}
code.g:
header{
import java.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
}
class JParser extends Parser;
options {
k = 2; // two token lookahead
buildAST = true;
}
tokens {
BLOCK;
}
startParse
: classDefinition
;
classDefinition
: "class" IDENT LCURLY (methodDefinition)* RCURLY
;
methodDefinition
: IDENT LPAREN RPAREN CODEBLOCK
;
class JLexer extends Lexer;
options {
k=2;
}
{
private static final Log log = LogFactory.getLog(JLexer.class);
int deep = 0;
}
LPAREN : '(' ;
RPAREN : ')' ;
SEMI : ';' ;
LCURLY : '{' {deep++;}
;
RCURLY : '}' {deep--;}
;
//in class, a code block don't need parse
CODEBLOCK : {deep > 0}?
'{' (~'}')* '}'
{
log.info($getText);
}
;
// whitespace
WS : ( ' '
| '/t'
| '/r' '/n' { newline(); }
| '/n' { newline(); }
)
{$setType(Token.SKIP);} //ignore this token
;
// an indentify, such as kent1_name
IDENT
options {testLiterals=true;}
: ('a'..'z'|'A'..'Z'|'_'|'$') ('a'..'z'|'A'..'Z'|'_'|'0'..'9'|'$')*
;
看看能打印出什么?
11:18:14,843 INFO JLexer[mCODEBLOCK]:956 - {
abc;
}
11:18:14,843 INFO JLexer[mCODEBLOCK]:956 - {
{
efg;
}
哈,方法内的内容全部打印出来了!注意k必须>1
同样的功能使用JavaCC要麻烦一些.ANTLR的确强大!
Lexer只是ANTLR的一小部分功能,还有其他更吸引人的地方,比如正则表达式方式书写产生式(不需要写成递归的),AST语法,比如#^!等等,还没搞清楚,等全部搞清了再写下一篇吧
强悍的ANTLR Lexer-续
上文中只能支持单层的{},如果{}嵌套则会出现问题,得出结果类似: {{}
比如下文中的例子:
class clazz{
test1(){
abc;
}
test2(){
{
efg;
{
hij;
{
klm;
}
opq;
}
rst;
}
}
}
多重嵌套如何处理呢?
对于ANTLR很简单,因为它支持递归的正则表达式,只需要把code.g修改如下就可以:
CODEBLOCK : {deep > 0}?
'{' {deep++;}
( CODEBLOCK | ~'}')*
'}' {deep--;}
{
if(deep == 1){
log.info("START" + $getText);
}
}
;
看看我们能打印出什么结果?
14:12:30,953 INFO JLexer[mCODEBLOCK]:961 - START{
abc;
}
14:12:30,953 INFO JLexer[mCODEBLOCK]:961 - START{
{
efg;
{
hij;
{
klm;
}
opq;
}
rst;
}
}
大功告成!
强悍的ANTLR LEXER-续2
接上文,我需要增加新的功能:除了把java method完全识别,还要识别java field.
就是说:类似以下语句:
abc = 123;
bde = 34{56{};ere{};sfa};
我必须把abc作为一个词, = 123作为一个词
bde作为一个词, = 34{56{};ere{};sfa};作为一个词
为什么要这样定义词法呢?因为对于java 类变量,一般有以下定义:
int i= 0; //第一种:直接赋值
int j= B.getIntValue(); //第二种:从其他类获得,包括 = (new B()).getValue();
B b = new B(); //第三种:new 一个
E e = (E) J.getValue(); //第四种,造型
D d = new D(){//第五种:使用内部类创建
public Object get(int i){
return null;
}
};
总结以上五种方法,有如下特征:
1.以=开始,以;结束
2.除了定义一个内部类会有{}和;嵌套,其他都不包含{}和;
code.g增加如下定义:
EXPR
: '=' (~(';'|'{'))* EXPRBLOCK
{
log.info("expr: " + $getText);
}
;
protected
EXPRBLOCK
:
(
{deep > 0}?
'{' {deep++;}
( EXPRBLOCK | ~'}')*
'}' {deep--;}
{
if(deep == 1){
//log.info("START" + $getText);
}
}
';'
)
|';'
;
看上去非常复杂,测试结果:
14:05:05,828 INFO JLexer[mEXPR]:266 - expr: = 123;
14:05:05,828 INFO JLexer[mEXPR]:266 - expr: = 34{56{};ere{};sfa};
成功了!
不知道这是否最佳写法,但是看上去似乎也解决了问题!
强悍的ANTLR LEXER-续3
接上文,在测试过程中,上文的正则表达式出现了问题.
解决方法就是:采用非贪婪设置
在ANTLR中,可以使用greedy设置是否贪婪.
贪婪本来是词法分析中的一个原则.比如,有两个表达式,分别是a和ab,现在有个串:abcd,按照贪婪原则,词法分析器应该匹配ab而不是 a.在ANTLR中可以关闭这个选项:greedy=false.同时,为了不报错误,还必须设置: generateAmbigWarnings= false;
按照非贪婪的写法,现在简单了:
必须说明的是,为了简单化,多个变量的定义用,分隔的,如:
a=1,b=1;这里识别为a和=1,b=1;
因为不能用,表明两个变量,而需要更加精确的分析表达式.
比如: a=new A(1,2), b=2;
这样的表达式必须分析 A(1,2)这样的表达式,很麻烦
EXPRBLOCK
: '='
(
options {
generateAmbigWarnings=false;
greedy = false;
}
: . | CODEBLOCK
)*
';'
{
log.info("expr: " + $getText);
}
;
//in class, a code block don't need parse
CODEBLOCK
: {deep > 0}?
'{' {deep++;}
( CODEBLOCK | ~'}')*
'}' {deep--;}
{
if(deep == 1){
log.info("START" + $getText);
}
}
;