Antlr 3

Переходим на antlr 3, т.к. он более удобен для реализации первого компилятора.

Итак, crash-course по antlr 3.

Грамматика

Два основных отличая от antlr 4: нельзя использовать левую рекурсию, игнорирование пробельных символов записывается иначе.

calc.g

grammar calc;

calc
	: lines EOF
	;

lines
	: line NL lines?
	;

line
	: expr
	;

expr
	: term ((PLUS | MINUS) term)*
	;

term
	: factor ((MULT | DIV) factor)*
	;

factor
	: (PLUS | MINUS) factor
	| atom
	;

atom
	: INT
	| LPAR expr RPAR
	;

INT
	: '0'..'9'+
	;

PLUS
	: '+'
	;

MINUS
	: '-'
	;

MULT
	: '*'
	;

DIV
	: '/'
	;

LPAR
    : '('
    ;

RPAR
    : ')'
    ;

NL
	: '\r'|'\n'
	;

WHITESPACE
	: ('\t'|' '|'\u000C')+ { $channel = HIDDEN; }
	;

Вычисления

Calc2.g

grammar calc2;

@header {
import java.lang.Integer;
import java.lang.Math;
}

calc
	: lines EOF
	;

lines
	: (line NL)+
	;

line
	: expr {System.out.println($expr.value);}
	;

expr returns [int value]
	: t1=term {$value = $t1.value;} (PLUS t2=term {$value += $t2.value;} | MINUS t2=term {$value -= $t2.value;})*
	;

term returns [int value]
	: f1=factor {$value = $f1.value;} (MULT f2=factor {$value *= $f2.value;} | DIV f2=factor {$value /= $f2.value;})*
	;

factor returns [int value]
	: PLUS f1=factor {$value = $f1.value;}
	| MINUS f2=factor {$value = -$f2.value;}
	| power {$value = $power.value;}
	;

power returns [int value]
	: atom {$value = $atom.value;} (POW f=factor {$value = (int)Math.pow((double)$value, (double)$f.value);})?
	;

atom returns [int value]
	: INT {$value = Integer.parseInt($INT.text);}
	| PI {$value = 3;}
	| E {$value = 2;}
	| LPAR expr RPAR {$value = $expr.value;}
	;

PI
	: 'PI'
	;

E
	: 'E'
	;

INT
	: '0'..'9'+
	;

PLUS
	: '+'
	;

MINUS
	: '-'
	;

MULT
	: '*'
	;

DIV
	: '/'
	;

POW
	: '**'
	;

LPAR
    : '('
    ;

RPAR
    : ')'
    ;

NL
	: '\r'|'\n'
	;

WHITESPACE
	: ('\t'|' '|'\u000C')+ {$channel = HIDDEN;}
	;

AST

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

calc5.g

grammar calc5;

options {
    output = AST;
}

tokens {
    EXPR;
}

calc
    : lines EOF!
    ;

lines
    : (line NL!)+
    ;

line
    : expr
    ;

expr
    : t1=term (PLUS^ t2=term | MINUS^ t2=term)*
    ;

term
    : f1=factor (MULT^ f2=factor | DIV^ f2=factor)*
    ;

factor
    : PLUS^ f1=factor
    | MINUS^ f2=factor
    | power
    ;

power
    : atom (POW^ f=factor)?
    ;

atom
    : DOUBLE
    | PI
    | E
    | LPAR! expr RPAR!
    ;

PI
    : 'PI'
    ;

E
    : 'E'
    ;

DOUBLE
    : INT
    | '.' INT
    | INT '.'
    | INT '.' INT
    ;

fragment INT
    : '0'..'9'+
    ;

PLUS
    : '+'
    ;

MINUS
    : '-'
    ;

MULT
    : '*'
    ;

DIV
    : '/'
    ;

POW
    : '**'
    ;

LPAR
    : '('
    ;

RPAR
    : ')'
    ;

NL
    : '\r'|'\n'
    ;

WHITESPACE
    : ('\t'|' '|'\u000C')+ {$channel = HIDDEN;}
    ;

calc5Tree.g

tree grammar calc5Tree;

options {
    tokenVocab=calc5;
    ASTLabelType=CommonTree;
}

@header {
import java.lang.Double;
import java.lang.Math;
}

@members {
boolean unary;
}

calc
    : (expr)+
    ;

expr
    : ^(PLUS e1=expr {unary = true;} (e2=expr {unary = false;})?)  {System.out.println(unary ? "uplus" : "add");}
    | ^(MINUS e1=expr {unary = true;} (e2=expr {unary = false;})?) {System.out.println(unary ? "uminus" : "sub");}
    | ^(MULT e1=expr e2=expr)   {System.out.println("mul");}
    | ^(DIV e1=expr e2=expr)    {System.out.println("div");}
    | ^(POW e1=expr e2=expr)    {System.out.println("pow");}
    | DOUBLE                    {System.out.println("push " + $DOUBLE.text);}
    | PI                        {System.out.println("push " + Math.PI);}
    | E                         {System.out.println("push " + Math.E);}
    ;

Calc.java

import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;

public class Calc {
    public static void main(String[] args) throws Exception {
        // create a CharStream that reads from standard input
        ANTLRInputStream input = new ANTLRInputStream(System.in);
        // create a lexer that feeds off of input CharStream
        calc5Lexer lexer = new calc5Lexer(input);
        // create a buffer of tokens pulled from the lexer
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        // create a parser that feeds off the tokens buffer
        calc5Parser parser = new calc5Parser(tokens);
        // begin parsing at rule r
        calc5Parser.calc_return r = parser.calc();

        CommonTree t = (CommonTree)r.getTree();
        System.out.println(t.toStringTree());

        // Walk resulting tree; create treenode stream first
        CommonTreeNodeStream nodes = new CommonTreeNodeStream(t);
        // AST nodes have payloads that point into token stream
        nodes.setTokenStream(tokens);
        // Create a tree Walker attached to the nodes stream
        calc5Tree walker = new calc5Tree(nodes);
        // Invoke the start symbol, rule program
        walker.calc();
        //
    }
}

Домашнее задание

Создать с использованием AST + tree grammar транслятор микроязыка с переменными, арифметическими выражениями и вводом-выводом в scheme.