【背景】
之前折腾:
【记录】将antlr v2的C/C++的preprocess,即cpp.g,转换为antlr v3
期间,后来终于看懂原先的旧的cppLexer.g中,antlr v2版本的lexer,是如何实现,多参数的#define中,宏的替换的逻辑。
现总结如下:
【分析过程】
对于相关部分的代码:
protected static Map defines = new Hashtable(); // holds the defines
protected Map defineArgs = new Hashtable(); // holds the args for a macro call
public void uponEOF() throws TokenStreamException, CharStreamException {
try {
selector.pop(); // return to old lexer/stream
selector.retry();
} catch (NoSuchElementException e) {
// return a real EOF if nothing in stack
}
}
......
DIRECTIVE {
List args = new ArrayList();
boolean condition = true;
} : '#'
......
| "define" WS defineMacro:RAW_IDENTIFIER
{
args.add(""); // first element will hold the macro text
}
(
( '(' // get arguments if you find them (no spaces before left paren)
(WS)? defineArg0:RAW_IDENTIFIER (WS)? {args.add(defineArg0.getText());}
( COMMA (WS)? defineArg1:RAW_IDENTIFIER (WS)? {args.add(defineArg1.getText());} )*
')'
| ' '|'\t'|'\f'
)
( options{greedy=true;}: ' '|'\t'|'\f' )*
// store the text verbatim - tokenize when called
defineText:MACRO_TEXT {args.set(0,defineText.getText());}
)? '\n' {newline();}
{ if (ifState==1) {
defines.put( defineMacro.getText(), args );
$setType(Token.SKIP);
}}
......
IDENTIFIER options {testLiterals=true;} {
List define = new ArrayList();
List args = new ArrayList();
} :
identifier:RAW_IDENTIFIER
{
// see if this is a macro argument
define = (List)defineArgs.get(identifier.getText());
if (_createToken && define==null) {
// see if this is a macro call
define = (List)defines.get(identifier.getText());
}
}
( { (define!=null) && (define.size()>1) }? (WS|COMMENT)?
// take in arguments if macro call requires them
'('
callArg0:EXPR {args.add(callArg0.getText());}
( COMMA callArg1:EXPR {args.add(callArg1.getText());} )*
{ args.size()==define.size()-1 }? // better have right amount
')'
| { !((define!=null) && (define.size()>1)) }?
)
{ if (define!=null) {
String defineText = (String)define.get(0);
if (!_createToken) {
// just substitute text if called from EXPR - no token created
$setText(defineText);
} else {
// create a new lexer to handle the macro text
cppLexer sublexer = new cppLexer(new DataInputStream(new StringBufferInputStream(defineText)));
for (int i=0;i<args.size();++i) {
// treat macro arguments similar to local defines
List arg = new ArrayList();
arg.add((String)args.get(i));
sublexer.defineArgs.put( (String)define.get(1+i), arg );
}
selector.push(sublexer);
// retry in new lexer
selector.retry();
}
}};以如下要处理的代码为例:
#define ADD(A,B,C) A+B+C; ADD(1,2,3); |
其内部执行过程是:
1.代码:
| "define" WS defineMacro:RAW_IDENTIFIER |
匹配到了ADD,此时:
defineMacro="ADD"
2.代码:
| args.add(""); // first element will hold the macro text |
的作用是:
将args这个List的第1个元素,即index为0的位置,暂时留空(留作后用,放define的内容)
此时:
args这个List的index为0的位置是空字符串
3.代码:
(WS)? defineArg0:RAW_IDENTIFIER (WS)? {args.add(defineArg0.getText());} |
去匹配到了
(A,B,C) |
部分,此时是:
defineArg0="A"
defineArg1="B"
defineArg1="C"
List变量args为:
| "" |
| "A" |
| "B" |
| "C" |
4.代码:
// store the text verbatim – tokenize when called |
作用是,匹配到了:
A+B+C; |
此时:
defineText="A+B+C;"
List变量args为:
| "A+B+C;" |
| "A" |
| "B" |
| "C" |
5.代码:
{ if (ifState==1) { $setType(Token.SKIP); }} |
作用是,(此处忽略ifState),将define的内容,和之前已经获得的参数,都保存起来。
此时是:
Map类型的defines,相当于字典类型的变量,内容是:
{ "A+B+C;", "A", "B", "C" ] } |
"ADD" | "A+B+C;" |
"A" | |
"B" | |
"C" |
6.代码:
IDENTIFIER options {testLiterals=true;} { List args = new ArrayList(); } : |
作用是,新建局部变量,define和args。
此时是:
局部变量define和args
7.代码:
identifier:RAW_IDENTIFIER // see if this is a macro argument define = (List)defineArgs.get(identifier.getText()); if (_createToken && define==null) { // see if this is a macro call define = (List)defines.get(identifier.getText()); } } |
作用是,匹配到了:
ADD(1,2,3); |
中的:
ADD |
将ID存为identifier,
将ID这个字符串,拿出来,然后从之前全局的Map类型的defineArgs去尝试取值,
很明显:
- 如果之前没有定义此ID,那么此处define得到的值就是空null了;
- 如果之前定义了此ID:此时就是已经定义了ADD,所以可以获得对应的值,即对应的那个List类型的args
此时:
defines
==从Map类型的defineArgs所得到的字典变量中,通过"ADD"所获得对应的那个List类型的args
==
| "A+B+C;" |
| "A" |
| "B" |
| "C" |
8.代码:
( { (define!=null) && (define.size()>1) }? (WS|COMMENT)? ‘(‘ callArg0:EXPR {args.add(callArg0.getText());} ( COMMA callArg1:EXPR {args.add(callArg1.getText());} )* { args.size()==define.size()-1 }? // better have right amount ‘)’ | { !((define!=null) && (define.size()>1)) }? ) |
作用是:
去匹配到了:
| ADD(1,2,3); |
中的
| (1,2,3) |
通过判断上述得到的define是否为空,即
define!=null,且define.size()大于1,即对应的List类型的args中超过1个值,即是带参数的define
(否则,如果是不带参数的define,则上述的List的args,就只是只包含单个元素的列表了,其值就只是:
| "A+B+C;" |
只是define的内容了。)
然后,把对应此处,调用define的地方,以此分析得到调用时所传入的参数,分别赋值给callArg0以及后续的(可能多个的)callArg1,然后都add到args的list中了。
并且,还通过:
args.size()==define.size()-1 |
去判断,最好是参数个数一致。
即此处是:
(1)(define!=null) && (define.size()>1)
此处就是:
define的确不为空:是包含了4个元素的List
define.size() == 4,的确大于1
(2)
callArg0=1
callArg1=2,callArg1=3
(3)args这个List中的值是:
| 1 |
| 2 |
| 3 |
(4)args.size()==define.size()-1
对应的是:
3 == 4-1
即,此表达式为True
9.代码:
| String defineText = (String)define.get(0); |
作用是,获得对应的,之前define的index为0的内容,即
| "A+B+C;" |
| "A" |
| "B" |
| "C" |
的index为0的内容,即:
| "A+B+C;" |
即,之前define时,define的内容。
此时:
defineText ="A+B+C;"
10.代码:
// create a new lexer to handle the macro text for (int i=0;i<args.size();++i) { // treat macro arguments similar to local defines List arg = new ArrayList(); arg.add((String)args.get(i)); sublexer.defineArgs.put( (String)define.get(1+i), arg ); } selector.push(sublexer); // retry in new lexer selector.retry(); |
作用是:
新建一个lexer
然后,针对此处的args,即:
| 1 |
| 2 |
| 3 |
去,针对此List的每个值,
先得到其值,比如1,然后再加到arg这个单独新建的List中,此时:
arg是个List,内容为:
| 1 |
然后,通过:
sublexer.defineArgs.put( (String)define.get(1+i), arg );
中的
(String)define.get(1+i),
得到对应的,define中的参数的ID,此处即:
| "A+B+C;" |
| "A" |
| "B" |
| "C" |
中的index为1+1=2,即:
"A"
然后再放到Map类型的defineArgs中,就成了:
{ 1 ] } |
然后后面的for循环中,也是如此逻辑,对应的结果为:
{ 2 ] } |
和
{ 3 ] } |
如此,就很清晰其用意了:
将,最开始的对于宏的调用:
| ADD(1,2,3); |
通过此时已有的映射关系(defineArgs):
{ "A+B+C;", "A", "B", "C" ] } |
变成对应的,化解后的,以define中参数为键,以调用处的实际参数为值的键对:
{ 1 ] } { 2 ] } { 3 ] } |
另外,其中的代码:
// create a new lexer to handle the macro text …… selector.push(sublexer); // retry in new lexer selector.retry(); |
就是之前所分析的,新建一个lexer,并且将相应的内容,让新建的lexer处理。
处理之后,再通过之前的:
public void uponEOF() throws TokenStreamException, CharStreamException { selector.pop(); // return to old lexer/stream selector.retry(); } catch (NoSuchElementException e) { // return a real EOF if nothing in stack } } |
的逻辑,pop出来,回到当前的lexer,继续后续的处理。
【总结】
如此地,化解了一层的键值的匹配关系,即把直接的宏定义ADD化解掉了。
然后生成了,以ADD的参数为键,对应调用处的参数为值的键对关系,保存起来,
然后再去重新调用一个lexer,如此的循环处理,
就可以依次地,把上述的A换成1,B换成2,C换成3了。
至此,算是真正了解了,其整个的处理过程和逻辑;