Пример использования antlr 4

В этой заметке я рассмотрю использование antlr версии 4. Разбирать будем определения макросов для языка С/С++ и сделаем упрощенный препроцессинг — замену в теле макросов параметров на их значения.

Упрощения касаются обработки пробелов — пробелы не пропускаются инструкцией skip. В первую очередь из-за того, что пробелы могут быть в теле/описании макроса: «(X) * (X)». Тело макроса macroBody задается весьма нестрогим правилом, но при этом не хочется, чтоб при парсинге были выкинуты пробелы, если уж автор макроса захотел их там поставить. Разумеется, эту логику можно изменить.

Для IDE Eclipse есть плагин, который берет на себя обработку antlr описаний. Если вы хотите обойтись без него, то генерировать исходники нужно командами вида:

alias antlr4='java -Xmx500M -cp "/home/dk/workspace/antlr-4.4-complete.jar:$CLASSPATH" org.antlr.v4.Tool'
 
 antlr4  -o /home/dk/workspace/Drinker/target/generated-sources/antlr4/grammar \
-package grammar -encoding UTF-8 \
-listener -visitor /home/dk/workspace/Drinker/src/ru/outofrange/grammar/Define.g4

Готовые описания различных конструкций языка и фрагментов можно взять из описания языка С : C.g4

Моя грамматика для макросов:

grammar Define;
 /*
antlr4  -o /home/dk/workspace/Drinker/target/generated-sources/antlr4/grammar \
-package grammar -encoding UTF-8 \
-listener -visitor /home/dk/workspace/Drinker/src/ru/outofrange/grammar/Define.g4  
  */
 
// Parser Rules
 
defineSentence : 
DEFINE Whitespace+ macroName Whitespace* (argList)? Whitespace* ( macroBody Whitespace*)? EOF
 ;
 
macroBody : .+ ;
 
DEFINE : '#define';
 
argList : LPAREN argListMember  ( Whitespace* COMMA Whitespace* argListMember )* Whitespace* RPAREN ;
 
argListMember : Identifier;
 
macroName : Identifier;
 
macroValue : Identifier | STRING;
 
STRING
    :  '"' ( ESC_SEQ | ~('\\'|'"') )* '"'
    ;
 
fragment
ESC_SEQ
    :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
    |   UNICODE_ESC
    |   OCTAL_ESC
    ;
 
fragment
OCTAL_ESC
    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7')
    ;
 
fragment
UNICODE_ESC
    :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
    ;
 
fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;
 
Identifier
    : IdentifierNondigit ( IdentifierNondigit  | Digit )* ;
 
fragment
IdentifierNondigit
    : Nondigit  | UniversalCharacterName;
 
fragment
UniversalCharacterName :
    '\\u' HexQuad
    | '\\U' HexQuad HexQuad ;
 
fragment
HexQuad :
     HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit ;
 
fragment
HexadecimalDigit : [0-9a-fA-F] ;
 
fragment
Nondigit : [a-zA-Z_];
 
SEMICOLON: ';';
MISC : '?' | ':' | '<' | '>' | '\\' ;
PERCENT: '%' ;
LPAREN : '(' ;
RPAREN : ')' ;
LCURL : '{' ;
RCURL : '}' ;
LSQUARE : '[' ;
RSQUARE : ']' ;
DOT: '.';
COMMA: ',';
OPERATION: '*' | '/' | '+' | '-' | '=' | '|' | '&' | '~' | '!';
 
fragment Digit: [0-9];
 
Whitespace : ( '\t' | ' ' | '\r' | '\n'| '\u000C' )+ /*-> skip */;

Входным правилом будет:

defineSentence : 
DEFINE Whitespace+ macroName Whitespace* (argList)? Whitespace* ( macroBody Whitespace*)? EOF
 ;

(Whitespace+ — один пробел или более, Whitespace* — ноль и более пробелов)

В этом правиле ты ищем в начале ”#define”, за которым обязательно есть имя макроса и необязательно есть список параметров. Далее идет необязательное тело/значение макроса. Необязательно оно из-за того, что в некоторых случаях достаточно просто сказать препроцессору, что определенная переменная задана:

#define DEBUG

Правило для списка аргументов указывает, что должен быть один аргумент или их перечисление.

К примеру, для макроса «#define square(X) (X) * (X)» именем будет «square», списком аргуметов — X, телом макроса «(X) * (X)».

Одним из преимуществ antlr 4 является разделение грамматики от действий на языке программирования. Результатом обработки грамматики является набор файлов — listener, visitor – от которых можно унаследоваться и переопределить логику.

Первым делом надо переопределить DefineBaseVisitor. Мы хотим обойти дерево и извлечь нужные значения:

package ru.outofrange.define;
 
import java.util.ArrayList;
import java.util.List;
 
import org.antlr.v4.runtime.misc.NotNull;
import grammar.DefineBaseVisitor;
import grammar.DefineParser;
 
public class DefineLoader extends DefineBaseVisitor<Void> {
 
	private List<String> args = new ArrayList<String>(); 
	private String name = null;
	private String body = null;
 
	@Override
	public Void visitMacroName( DefineParser.MacroNameContext ctx) {
		this.name = ctx.getText();
		return super.visitMacroName(ctx);
	}
 
	@Override 
	public Void visitMacroBody(@NotNull DefineParser.MacroBodyContext ctx) {
		this.body = ctx.getText();
		return super.visitMacroBody(ctx); 
	}
 
	@Override 
	public Void visitArgListMember(@NotNull DefineParser.ArgListMemberContext ctx) 
	{ 
		args.add(ctx.getText());
		return visitChildren(ctx); 
	}
 
	public String getName(){
		return this.name;
	}
 
	public String getBody(){
		return this.body;
	}
 
	public List<String> getArgList(){
		return this.args;
	}
 
 
	// imitates preprocessing
	public String substitute(List<String> params){
 
		if (params == null || args == null || params.size() != args.size() ){
			return "";
		}
 
		String alteredBody = body;
 
		for (int index = 0; index < args.size(); index ++){
			alteredBody = alteredBody.replaceAll(args.get(index), params.get(index));
		}
 
		return alteredBody;
	}
 
	// creating string representation of result
	public String toString(){
		StringBuffer sb = new StringBuffer(); 
		sb.append(" Name: " + getName() + ";\n");
 
		if (args.size() > 0) {
			sb.append(" Args: ");
			for (int index = 0; index < args.size(); index ++){
				sb.append(args.get(index));
				if (index != args.size() - 1){
					sb.append(", ");
				}
			}
			sb.append(";\n");
		}
		sb.append(" Body: " + getBody());
		return sb.toString();
	}
}

В данном классе переопределены методы, срабатывающие в случае детектирования имени макроса, списка параметров и тела/значения макроса и необходимые геттеры. Для простоты метод препроцессинга substitute() задан в этом же классе.

Остается создать небольшой тестовый класс:

package ru.outofrange.define;
 
import java.util.ArrayList;
import java.util.List;
 
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
import grammar.DefineLexer;
import grammar.DefineParser;
import grammar.DefineParser.DefineSentenceContext;
 
public class LoaderTest {
 
	public static void main(String[] args) {
 
		String s = "#define MACRO(NUM, STR) ({            printf(\"%d\", NUM);            printf(\" is\");" +
		"           printf(\" %s number\", STR);})";
 
 
		// will use this list for substitute
		List<String> params = new ArrayList<String>();
		params.add("78");
		params.add("\"even\"");
 
		DefineLoader dl = getLoader(s);
 
		System.out.println(dl);
		System.out.println("==========");
		System.out.println("After preprocessing:");
		System.out.println(dl.substitute(params));
		System.out.println("==========");
 
		dl = getLoader("#define square(X) (X) * (X) ");
 
		System.out.println(dl);
		System.out.println("==========");
 
		params = new ArrayList<String>();
		params.add("55");
 
		System.out.println("After preprocessing:");
		System.out.println(dl.substitute(params));
		System.out.println("==========");
	}
 
	private static DefineLoader getLoader(String str){
	    ANTLRInputStream input = new ANTLRInputStream(str);
	    DefineLexer lexer = new DefineLexer(input);
	    CommonTokenStream tokens = new CommonTokenStream(lexer);
 
	    DefineParser parser = new DefineParser(tokens);
 
 
	    ParseTree tree = parser.defineSentence();
	    DefineLoader loader = new DefineLoader();
	    loader.visit(tree);
	    return loader;
	}
}

В выводе программы получаем:

 Name: MACRO;
 Args: NUM, STR;
 Body: ({            printf("%d", NUM);            printf(" is");           printf(" %s number", STR);})
==========
After preprocessing:
({            printf("%d", 78);            printf(" is");           printf(" %s number", "even");})
==========
 Name: square;
 Args: X;
 Body: (X) * (X) 
==========
After preprocessing:
(55) * (55) 
==========
[sc:social_networks ]
You can leave a response, or trackback from your own site.

Leave a Reply