【背景】
之前折腾:
【记录】将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了。
至此,算是真正了解了,其整个的处理过程和逻辑;