`
cywhoyi
  • 浏览: 419108 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

基于Antlr4编写DSL

    博客分类:
  • JAVA
阅读更多

Tiny最近新的模块DSL和UITemplate,都或多或少涉及到Antlr,特别在使用DSL的时候特别有感触,能否设计的像SQL那样的领域驱动型语言呢?我的对于DSL的理解:

1.是用来解决复杂的业务模型而产生的,假设以计算机为例,采用代码的方式,可能就需要一大堆的判断,用来跟踪是否有有括号、是否有左括号没右括号、是否加减乘除优先制度等很多条件,那如果是业务模型像支付流程,比起计算机更加复杂,如果一大堆if、else等代码复杂不算,仅仅是代码样子都要吐了,其二,假设采用XML这种可读性极强的方式,个人感觉问题不大,唯一不好的层次太多,以及需要一大堆节点,最后如果采用antlr4类似g4格式文件,好处一目了然

grammar Mu;

parse
 : block EOF
 ;

block
 : stat*
 ;

stat
 : assignment
 | if_stat
 | while_stat
 | log
 | OTHER {System.err.println("unknown char: " + $OTHER.text);}
 ;

assignment
 : ID ASSIGN expr SCOL
 ;

if_stat
 : IF condition_block (ELSE IF condition_block)* (ELSE stat_block)?
 ;

condition_block
 : expr stat_block
 ;

stat_block
 : OBRACE block CBRACE
 | stat
 ;

while_stat
 : WHILE expr stat_block
 ;

log
 : LOG expr SCOL
 ;

expr
 : expr POW<assoc=right> expr           #powExpr
 | MINUS expr                           #unaryMinusExpr
 | NOT expr                             #notExpr
 | expr op=(MULT | DIV | MOD) expr      #multiplicationExpr
 | expr op=(PLUS | MINUS) expr          #additiveExpr
 | expr op=(LTEQ | GTEQ | LT | GT) expr #relationalExpr
 | expr op=(EQ | NEQ) expr              #equalityExpr
 | expr AND expr                        #andExpr
 | expr OR expr                         #orExpr
 | atom                                 #atomExpr
 ;

atom
 : OPAR expr CPAR #parExpr
 | (INT | FLOAT)  #numberAtom
 | (TRUE | FALSE) #booleanAtom
 | ID             #idAtom
 | STRING         #stringAtom
 | NIL            #nilAtom
 ;

OR : '||';
AND : '&&';
EQ : '==';
NEQ : '!=';
GT : '>';
LT : '<';
GTEQ : '>=';
LTEQ : '<=';
PLUS : '+';
MINUS : '-';
MULT : '*';
DIV : '/';
MOD : '%';
POW : '^';
NOT : '!';

SCOL : ';';
ASSIGN : '=';
OPAR : '(';
CPAR : ')';
OBRACE : '{';
CBRACE : '}';

TRUE : 'true';
FALSE : 'false';
NIL : 'nil';
IF : 'if';
ELSE : 'else';
WHILE : 'while';
LOG : 'log';

ID
 : [a-zA-Z_] [a-zA-Z_0-9]*
 ;

INT
 : [0-9]+
 ;

FLOAT
 : [0-9]+ '.' [0-9]* 
 | '.' [0-9]+
 ;

STRING
 : '"' (~["\r\n] | '""')* '"'
 ;

COMMENT
 : '#' ~[\r\n]* -> skip
 ;

SPACE
 : [ \t\r\n] -> skip
 ;

OTHER
 : . 
 ;

 

2.Antlr的方式能够进一步规避一些语法的错误,假设你的表达式是2+(1+2,语法分析器就会parse解析时候,得到missing')',显著得提升我们解决问题

 

3.Antlr4采用visit观察者模式,在每一个context都有accept(Context ctx)的方式,通过visitor接口,对于抽象语法树AST进行解析的工作

package mu;

import org.antlr.v4.runtime.misc.NotNull;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class EvalVisitor extends MuBaseVisitor<Value> {

    // used to compare floating point numbers
    public static final double SMALL_VALUE = 0.00000000001;

    // store variables (there's only one global scope!)
    private Map<String, Value> memory = new HashMap<String, Value>();

    // assignment/id overrides
    @Override
    public Value visitAssignment(MuParser.AssignmentContext ctx) {
        String id = ctx.ID().getText();
        Value value = this.visit(ctx.expr());
        return memory.put(id, value);
    }

    @Override
    public Value visitIdAtom(MuParser.IdAtomContext ctx) {
        String id = ctx.getText();
        Value value = memory.get(id);
        if(value == null) {
            throw new RuntimeException("no such variable: " + id);
        }
        return value;
    }

    // atom overrides
    @Override
    public Value visitStringAtom(MuParser.StringAtomContext ctx) {
        String str = ctx.getText();
        // strip quotes
        str = str.substring(1, str.length() - 1).replace("\"\"", "\"");
        return new Value(str);
    }

    @Override
    public Value visitNumberAtom(MuParser.NumberAtomContext ctx) {
        return new Value(Double.valueOf(ctx.getText()));
    }

    @Override
    public Value visitBooleanAtom(MuParser.BooleanAtomContext ctx) {
        return new Value(Boolean.valueOf(ctx.getText()));
    }

    @Override
    public Value visitNilAtom(MuParser.NilAtomContext ctx) {
        return new Value(null);
    }

    // expr overrides
    @Override
    public Value visitParExpr(MuParser.ParExprContext ctx) {
        return this.visit(ctx.expr());
    }

    @Override
    public Value visitPowExpr(MuParser.PowExprContext ctx) {
        Value left = this.visit(ctx.expr(0));
        Value right = this.visit(ctx.expr(1));
        return new Value(Math.pow(left.asDouble(), right.asDouble()));
    }

    @Override
    public Value visitUnaryMinusExpr(MuParser.UnaryMinusExprContext ctx) {
        Value value = this.visit(ctx.expr());
        return new Value(-value.asDouble());
    }

    @Override
    public Value visitNotExpr(MuParser.NotExprContext ctx) {
        Value value = this.visit(ctx.expr());
        return new Value(!value.asBoolean());
    }

    @Override
    public Value visitMultiplicationExpr(@NotNull MuParser.MultiplicationExprContext ctx) {

        Value left = this.visit(ctx.expr(0));
        Value right = this.visit(ctx.expr(1));

        switch (ctx.op.getType()) {
            case MuParser.MULT:
                return new Value(left.asDouble() * right.asDouble());
            case MuParser.DIV:
                return new Value(left.asDouble() / right.asDouble());
            case MuParser.MOD:
                return new Value(left.asDouble() % right.asDouble());
            default:
                throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]);
        }
    }

    @Override
    public Value visitAdditiveExpr(@NotNull MuParser.AdditiveExprContext ctx) {

        Value left = this.visit(ctx.expr(0));
        Value right = this.visit(ctx.expr(1));

        switch (ctx.op.getType()) {
            case MuParser.PLUS:
                return left.isDouble() && right.isDouble() ?
                        new Value(left.asDouble() + right.asDouble()) :
                        new Value(left.asString() + right.asString());
            case MuParser.MINUS:
                return new Value(left.asDouble() - right.asDouble());
            default:
                throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]);
        }
    }

    @Override
    public Value visitRelationalExpr(@NotNull MuParser.RelationalExprContext ctx) {

        Value left = this.visit(ctx.expr(0));
        Value right = this.visit(ctx.expr(1));

        switch (ctx.op.getType()) {
            case MuParser.LT:
                return new Value(left.asDouble() < right.asDouble());
            case MuParser.LTEQ:
                return new Value(left.asDouble() <= right.asDouble());
            case MuParser.GT:
                return new Value(left.asDouble() > right.asDouble());
            case MuParser.GTEQ:
                return new Value(left.asDouble() >= right.asDouble());
            default:
                throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]);
        }
    }

    @Override
    public Value visitEqualityExpr(@NotNull MuParser.EqualityExprContext ctx) {

        Value left = this.visit(ctx.expr(0));
        Value right = this.visit(ctx.expr(1));

        switch (ctx.op.getType()) {
            case MuParser.EQ:
                return left.isDouble() && right.isDouble() ?
                        new Value(Math.abs(left.asDouble() - right.asDouble()) < SMALL_VALUE) :
                        new Value(left.equals(right));
            case MuParser.NEQ:
                return left.isDouble() && right.isDouble() ?
                        new Value(Math.abs(left.asDouble() - right.asDouble()) >= SMALL_VALUE) :
                        new Value(!left.equals(right));
            default:
                throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]);
        }
    }

    @Override
    public Value visitAndExpr(MuParser.AndExprContext ctx) {
        Value left = this.visit(ctx.expr(0));
        Value right = this.visit(ctx.expr(1));
        return new Value(left.asBoolean() && right.asBoolean());
    }

    @Override
    public Value visitOrExpr(MuParser.OrExprContext ctx) {
        Value left = this.visit(ctx.expr(0));
        Value right = this.visit(ctx.expr(1));
        return new Value(left.asBoolean() || right.asBoolean());
    }

    // log override
    @Override
    public Value visitLog(MuParser.LogContext ctx) {
        Value value = this.visit(ctx.expr());
        System.out.println(value);
        return value;
    }

    // if override
    @Override
    public Value visitIf_stat(MuParser.If_statContext ctx) {

        List<MuParser.Condition_blockContext> conditions =  ctx.condition_block();

        boolean evaluatedBlock = false;

        for(MuParser.Condition_blockContext condition : conditions) {

            Value evaluated = this.visit(condition.expr());

            if(evaluated.asBoolean()) {
                evaluatedBlock = true;
                // evaluate this block whose expr==true
                this.visit(condition.stat_block());
                break;
            }
        }

        if(!evaluatedBlock && ctx.stat_block() != null) {
            // evaluate the else-stat_block (if present == not null)
            this.visit(ctx.stat_block());
        }

        return Value.VOID;
    }

    // while override
    @Override
    public Value visitWhile_stat(MuParser.While_statContext ctx) {

        Value value = this.visit(ctx.expr());

        while(value.asBoolean()) {

            // evaluate the code block
            this.visit(ctx.stat_block());

            // evaluate the expression
            value = this.visit(ctx.expr());
        }

        return Value.VOID;
    }
}

 用户关注点已经有从编写流程文件,到visitor对于AST树的控制,到使用我们的DSL

 MuLexer lexer = new MuLexer(new ANTLRFileStream(args[0]));
        MuParser parser = new MuParser(new CommonTokenStream(lexer));
        ParseTree tree = parser.parse();
        EvalVisitor visitor = new EvalVisitor();
        visitor.visit(tree);

 

 

3
2
分享到:
评论

相关推荐

    The Definitive ANTLR 4 Reference.pdf

    ANTLR 4生成的解析器基于LL(*)解析算法,具有强大的错误恢复和错误报告能力,使其在处理语法错误时具有更高的灵活性。 这本书《The Definitive ANTLR 4 Reference》是ANTLR 4的主要作者Terence Parr的著作,被誉为...

    antlr4-4.5

    1. **语法定义**:ANTLR4支持基于EBNF(扩展巴科斯范式)的语法定义,允许用户以简洁明了的方式描述语言的结构。语法文件通常以`.g4`为扩展名,其中包含了语法规则和词汇规则。 2. **词法分析**:ANTLR4首先将输入...

    antlr开发eclipse插件

    虽然Xtext不是直接用于ANTLR,但它可以与ANTLR结合使用,帮助开发者构建基于ANTLR的语言工具链。 在配置ANTLR开发环境时,首先需要确保你的Eclipse已经安装了ANTLR 4 IDE插件。这通常可以通过Eclipse的内置软件更新...

    smt-antlr4-java-parser:该项目在构建时将生成 ANTLR4 Java 解析类

    在这个名为“smt-antlr4-java-parser”的项目中,重点是使用ANTLR4来生成Java解析类。ANTLR4是一个更新的版本,相比ANTLR3,它提供了许多改进,如更快的词法分析速度、更简洁的语法定义以及增强的错误处理能力。这个...

    antlr.jar-2.7.6

    在部署和管理基于Hibernate的Java应用时,ANTLR可能会被用来解析Hibernate的配置文件,这些配置文件定义了数据模型和数据库之间的映射关系。Hibernate是一个流行的ORM(对象关系映射)框架,它允许Java开发者使用...

    用XText工具开发的自定义DSL语言

    XText生成的解析器基于ANTLR(ANother Tool for Language Recognition)。ANTLR是一个广泛使用的解析器生成器,它可以生成Java、C#、Python等多种语言的解析器和词法分析器。XText利用ANTLR生成的词法分析器将源代码...

    Compiler:使用Java和ANTLR4的编译器

    标题 "Compiler: 使用Java和ANTLR4的编译器" 指向了构建一个特定类型的编译器,这个编译器是用Java编程语言开发的,并且利用ANTLR4作为解析工具。ANTLR4是一个强大的解析器生成器,广泛用于构建语言、工具和框架,...

    dsl-maker:Kotlin多平台库,可帮助使用ANTLR或简单的内置解析器创建DSL

    DSL-maker是一个Kotlin编写的多平台库,它的主要目标是为开发者提供便利,使他们能够轻松地创建自己的DSL,无论是基于ANTLR解析器还是使用库内提供的简单内置解析器。 Kotlin是一种现代、静态类型的编程语言,由...

    glagol-dsl:利用领域驱动设计的领域特定语言

    Glagol DSL是基于这一理念构建的一种领域特定语言(Domain-Specific Language),它专注于提供一个强大的工具,帮助开发者更直观、高效地表达业务逻辑。 领域驱动设计强调将复杂的业务逻辑转化为易于理解和维护的...

    Manning.DSLs.in.Action

    - 工具支持:介绍可以用来辅助外部DSL开发的工具,如ANTLR等。 #### 三、结论 《Manning.DSLs.in.Action》不仅为读者提供了理论知识,还通过丰富的实践案例帮助读者深入理解DSL的设计与实现。无论是对于想要学习...

    gradle user guide

    它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,相比于XML,这种方式更为简洁、灵活。Gradle用户指南是为使用者提供的官方文档,旨在指导用户如何使用Gradle这一强大工具进行项目构建和管理。 《Gradle...

    文法分析工具jacc

    尽管现代有许多高级的编译器构造工具,如ANTLR、ANTLR4等,但`jacc`因其简洁性和专一性,仍然在教学和小型项目中占有一定地位。 总之,`jacc`是一个强大的工具,用于生成Java语法解析器,它使得开发者能够轻松地...

    ply[python lex yacc]-3.2

    Ply基于经典的词法分析工具Lex和语法分析工具Yacc,并将其移植到Python环境中。 1. **Ply的由来与原理** Ply全称为Python Lex Yacc,它是对经典Unix工具Flex(用于词法分析)和Bison(用于语法分析)的Python实现...

    代码生成器

    代码生成器基于模板引擎,通过读取用户设定的模板和参数,按照预定义的规则生成相应的代码。模板通常包含占位符,这些占位符在生成过程中会被具体的值替换。例如,类名、变量名、方法名等。此外,代码生成器还可以...

    词法语法分析器(JAVA)

    词法分析器通常基于正则表达式来识别不同的符号,它会忽略掉程序中的注释和空白字符。例如,在Java中,"int" 是一个关键字,"myVariable" 是一个标识符,"5" 是一个整型常量。 2. **语法分析器(Parser 或 Syntax ...

    J2EE博客精华

    - **Tiny SQL DSL**:介绍Tiny SQL DSL的设计理念、语法特点以及如何通过它编写简洁高效的SQL查询语句。 - **Tiny DBF数据文件处理**:介绍Tiny DBF库的功能,如何使用该库读取、写入DBF文件,适用于需要处理大量...

    LL(1)文法分析

    - **解析器生成器**:如ANTLR等工具可以自动生成LL(1)解析器,大大简化了解析器的编写工作。 在压缩包"Test_ll1"中,可能包含了一些示例代码或测试用例,用于演示如何实现或使用LL(1)文法分析。通过对这些文件的...

Global site tag (gtag.js) - Google Analytics