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);
相关推荐
ANTLR 4生成的解析器基于LL(*)解析算法,具有强大的错误恢复和错误报告能力,使其在处理语法错误时具有更高的灵活性。 这本书《The Definitive ANTLR 4 Reference》是ANTLR 4的主要作者Terence Parr的著作,被誉为...
1. **语法定义**:ANTLR4支持基于EBNF(扩展巴科斯范式)的语法定义,允许用户以简洁明了的方式描述语言的结构。语法文件通常以`.g4`为扩展名,其中包含了语法规则和词汇规则。 2. **词法分析**:ANTLR4首先将输入...
虽然Xtext不是直接用于ANTLR,但它可以与ANTLR结合使用,帮助开发者构建基于ANTLR的语言工具链。 在配置ANTLR开发环境时,首先需要确保你的Eclipse已经安装了ANTLR 4 IDE插件。这通常可以通过Eclipse的内置软件更新...
在这个名为“smt-antlr4-java-parser”的项目中,重点是使用ANTLR4来生成Java解析类。ANTLR4是一个更新的版本,相比ANTLR3,它提供了许多改进,如更快的词法分析速度、更简洁的语法定义以及增强的错误处理能力。这个...
在部署和管理基于Hibernate的Java应用时,ANTLR可能会被用来解析Hibernate的配置文件,这些配置文件定义了数据模型和数据库之间的映射关系。Hibernate是一个流行的ORM(对象关系映射)框架,它允许Java开发者使用...
XText生成的解析器基于ANTLR(ANother Tool for Language Recognition)。ANTLR是一个广泛使用的解析器生成器,它可以生成Java、C#、Python等多种语言的解析器和词法分析器。XText利用ANTLR生成的词法分析器将源代码...
标题 "Compiler: 使用Java和ANTLR4的编译器" 指向了构建一个特定类型的编译器,这个编译器是用Java编程语言开发的,并且利用ANTLR4作为解析工具。ANTLR4是一个强大的解析器生成器,广泛用于构建语言、工具和框架,...
DSL-maker是一个Kotlin编写的多平台库,它的主要目标是为开发者提供便利,使他们能够轻松地创建自己的DSL,无论是基于ANTLR解析器还是使用库内提供的简单内置解析器。 Kotlin是一种现代、静态类型的编程语言,由...
Glagol DSL是基于这一理念构建的一种领域特定语言(Domain-Specific Language),它专注于提供一个强大的工具,帮助开发者更直观、高效地表达业务逻辑。 领域驱动设计强调将复杂的业务逻辑转化为易于理解和维护的...
- 工具支持:介绍可以用来辅助外部DSL开发的工具,如ANTLR等。 #### 三、结论 《Manning.DSLs.in.Action》不仅为读者提供了理论知识,还通过丰富的实践案例帮助读者深入理解DSL的设计与实现。无论是对于想要学习...
它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,相比于XML,这种方式更为简洁、灵活。Gradle用户指南是为使用者提供的官方文档,旨在指导用户如何使用Gradle这一强大工具进行项目构建和管理。 《Gradle...
尽管现代有许多高级的编译器构造工具,如ANTLR、ANTLR4等,但`jacc`因其简洁性和专一性,仍然在教学和小型项目中占有一定地位。 总之,`jacc`是一个强大的工具,用于生成Java语法解析器,它使得开发者能够轻松地...
Ply基于经典的词法分析工具Lex和语法分析工具Yacc,并将其移植到Python环境中。 1. **Ply的由来与原理** Ply全称为Python Lex Yacc,它是对经典Unix工具Flex(用于词法分析)和Bison(用于语法分析)的Python实现...
代码生成器基于模板引擎,通过读取用户设定的模板和参数,按照预定义的规则生成相应的代码。模板通常包含占位符,这些占位符在生成过程中会被具体的值替换。例如,类名、变量名、方法名等。此外,代码生成器还可以...
词法分析器通常基于正则表达式来识别不同的符号,它会忽略掉程序中的注释和空白字符。例如,在Java中,"int" 是一个关键字,"myVariable" 是一个标识符,"5" 是一个整型常量。 2. **语法分析器(Parser 或 Syntax ...
- **Tiny SQL DSL**:介绍Tiny SQL DSL的设计理念、语法特点以及如何通过它编写简洁高效的SQL查询语句。 - **Tiny DBF数据文件处理**:介绍Tiny DBF库的功能,如何使用该库读取、写入DBF文件,适用于需要处理大量...
- **解析器生成器**:如ANTLR等工具可以自动生成LL(1)解析器,大大简化了解析器的编写工作。 在压缩包"Test_ll1"中,可能包含了一些示例代码或测试用例,用于演示如何实现或使用LL(1)文法分析。通过对这些文件的...