antlr 라이브러리는 .g4
파일을 통해 파싱 규칙을 정의한다.
미리 정의한 .g4
파일을 통해 gradle에서 자바코드로 문법클래스를 자동으로 생성한다.
이렇게 문법파일을 자바 클래스로 변환하여 자바 코드 레벨에서 String형태의 자료형에 문법을 적용하여 파싱을 진행할 수 있다.
먼저, gradle에서 antlr플러그인 + runtime을 추가한다.
plugins {
id 'antlr'
}
dependencies {
antlr "org.antlr:antlr4:4.13.1" // g4 파일을 java 코드로 변환
implementation "org.antlr:antlr4-runtime:4.13.1" // 실행할 때 사용
}
antlr {
arguments += ['-visitor', '-long-messages'] // Visitor & Listener 자동 생성
}
.g4
파일 위치는 src/main/antlr/Arithmetic.g4
로 두면 된다.grammar Arithmetic;
// Parser rules
expr : addExpr ;
addExpr : mulExpr ('+' mulExpr)* ;
mulExpr : atom ('*' atom)* ;
atom : INT | '(' expr ')' ;
// Lexer rules
INT : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;
ANTLR문법은 크게 3가지 파트로 나눌 수 잇다.
Grammar 선언
grammar {규칙이름};
으로 작성한다.{규칙이름}.g4
가 되어야 한다.Arithmetic.g4
이 될 것이다.Parser 규칙
expr
라는 의미 단위를 곱셈, 덧셈, 괄호처리를 작은 단위로 분해할 수 있도록 정의하였다. expr
→ addExpr
→ mulExpr
→ atom
으로 진행되는 흐름을 확인할 수 있다.[0-9]+
로 정의되어 하나 이상의 숫자를 의미한다.skip
을 붙여 무시하도록 설정하였다.build.gradle
를 작성하는 것과 비슷하다고 생각했다../gradlew generateGrammarSource
.g4
파일을 자바 코드로 변환한다.Lexer
, Parser
, BaseVisitor
, Visitor
, Listener
클래스가 build/generated-src/antlr/main/
경로에 생성된다.public class Main {
public static void main(String[] args) {
String input = "2+3*4";
// 1. 문자열을 CharStream으로 변환
CodePointCharStream stream = CharStreams.fromString(input);
// 2. Lexer 생성
ArithmeticLexer lexer = new ArithmeticLexer(stream);
// 3. TokenStream 생성
CommonTokenStream tokens = new CommonTokenStream(lexer);
// 4. Parser 생성
ArithmeticParser parser = new ArithmeticParser(tokens);
// 5. 진입점(expr) 파싱 → ParseTree 얻기
ParseTree tree = parser.expr();
System.out.println(tree.toStringTree(parser));
}
}
parser.expr();
를 통해 ParseTree
라는 것을 생성한다.ParseTree
는 각 노드로 이루어지며, 각 파서를 담당하는 노드 클래스는 ParserTree.{파서이름Context}
로 구현되어있다.(regex (expr (element (atom (charClass [ (classAtom a) (classAtom b) (classAtom c) ])))))
BaseVisitor.class
로 문제를 해결할 수 있다.public class ArithmeticBaseVisitor<T> extends AbstractParseTreeVisitor<T>
implements ArithmeticVisitor<T> {
@Override public T visitMulExpr(ArithmeticParser.MulExprContext ctx) {
return visitChildren(ctx);
}
@Override public T visitAddExpr(ArithmeticParser.AddExprContext ctx) {
return visitChildren(ctx);
}
...
}
visitChildren(ctx);
를 반환한다.MulExpr
라는 파서를 .g4
에서 선언했기에 자동생성되었다.public class ArithmeticVisitorImpl extends ArithmeticBaseVisitor<Integer> {
@Override
public Integer visitIntExpr(ArithmeticParser.IntExprContext ctx) {
return Integer.valueOf(ctx.INT().getText());
}
@Override
public Integer visitAddExpr(ArithmeticParser.AddExprContext ctx) {
int left = visit(ctx.expr(0));
int right = visit(ctx.expr(1));
return left + right;
}
@Override
public Integer visitMulExpr(ArithmeticParser.MulExprContext ctx) {
int left = visit(ctx.expr(0));
int right = visit(ctx.expr(1));
return left * right;
}
@Override
public Integer visitParenExpr(ArithmeticParser.ParenExprContext ctx) {
return visit(ctx.expr());
}
}
visitAddExpr
메서드에서, AddxprContext
라는 노드의 첫 번째 expr
에 방문하면, 다시 visitIntExpr
메서드가 invoke되어, 해당 expr노드의 값을 리턴한다.expr
의 값을 더해 덧셈이 구현된다.BaseVisitor
는, 사용자가 트리를 탐색하는 시점(visit(ctx.expr())
)을 직접 제어한다.Listener
클래스는 파서가 ParseTree
를 생성할 때, 자동으로 콜백메서드를 호출한다.public class RegexBaseListener implements RegexListener {
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void enterRegex(RegexParser.RegexContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void exitRegex(RegexParser.RegexContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void enterExpr(RegexParser.ExprContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void exitExpr(RegexParser.ExprContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void enterElement(RegexParser.ElementContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void exitElement(RegexParser.ElementContext ctx) { }