public class Block { private Sting name; private int xCoord; private int yCoord; // Getters,setters,ctors,etc. public void setCoords(int x,int y) { setXCoord(x); setYCoord(y); } } public BlockController { public static moveBlock(Block block,int newXCoord,int newYCoord) { block.setCooords(newXCoord,newYCoord); } public static stackBlocks(Block under,Block onTop) { // Stack "onTop" on top of "under". // Don't worry about the math here,this is just for an example. onTop.setCoords(under.getXCoord() + onTop.getXCoord(),under.getYCoord()); } }
再次,不要担心数学和(x,y)坐标不准确地表示3D空间中的块的事实.关键是我们有Java代码,编译为JAR,它可以对块执行操作.我现在想要构建一个轻量级的脚本语言,允许非程序员调用各种API API方法和操作块,并且我想用ANTLR(最新版本为4.3)实现其解释器.
脚本语言,我们称之为BlockSpeak,可能如下所示:
block A at (0,10) # Create block "A" at coordinates (0,10) block B at (0,20) # Create block "B" at coordinates (0,20) stack A on B # Stack block A on top of block B
这可能等同于以下Java代码:
Block A,B; A = new Block(0,10); B = new Block(0,20); BlockController.stackBlocks(B,A);
所以这个想法是,ANTLR生成的解释器将使用* .blockspeak脚本作为输入,并使用此脚本中的命令来调用blocks.jar API操作.我读了优秀的Simple Example,它使用ANTLR创建了一个简单的计算器.但是在该链接中,有一个具有eval()方法的ExpParser类:
ExpParser parser = new ExpParser(tokens); parser.eval();
这里的问题是,在计算器的情况下,令牌表示一个求值的数学表达式,eval()返回表达式的求值.在解释器的情况下,令牌将代表我的BlockSpeak脚本,但调用eval()不应该评估任何东西,它应该知道如何将各种BlockSpeak命令映射到Java代码:
BlockSpeak Command: Java code: ========================================== block A at (0,10) ==> Block A = new Block(0,10); block B at (0,20) ==> Block B = new Block(0,20); stack A on B ==> BlockController.stackBlocks(B,A);
所以我的问题是,我在哪里执行这个“映射”?换句话说,当遇到BlockSpeak脚本中的特定语法时,如何指示ANTLR调用各种代码(打包在blocks.jar中)?更重要的是,有人可以给我一个伪代码的例子吗?
解决方法
使用ANTLR 4,强烈建议将语法和目标特定代码彼此分开,并将任何目标特定代码放在树监听器或访问器中.
我将快速演示如何使用监听器.
您的示例输入的语法可能如下所示:
文件:blockspeak / BlockSpeak.g4
grammar BlockSpeak; parse : instruction* EOF ; instruction : create_block | stack_block ; create_block : 'block' NAME 'at' position ; stack_block : 'stack' top=NAME 'on' bottom=NAME ; position : '(' x=INT ',' y=INT ')' ; COMMENT : '#' ~[\r\n]* -> skip ; INT : [0-9]+ ; NAME : [a-zA-Z]+ ; SPACES : [ \t\r\n] -> skip ;
一些支持Java类:
文件:blockspeak / Main.java
package blockspeak; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTreeWalker; import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { Scanner keyboard = new Scanner(System.in); // Some initial input to let the parser have a go at. String input = "block A at (0,10) # Create block \"A\" at coordinates (0,10)\n" + "block B at (0,20) # Create block \"B\" at coordinates (0,20)\n" + "stack A on B # Stack block A on top of block B"; EvalBlockSpeakListener listener = new EvalBlockSpeakListener(); // Keep asking for input until the user presses 'q'. while(!input.equals("q")) { // Create a lexer and parser for `input`. BlockSpeakLexer lexer = new BlockSpeakLexer(new ANTLRInputStream(input)); BlockSpeakParser parser = new BlockSpeakParser(new CommonTokenStream(lexer)); // Now parse the `input` and attach our listener to it. We want to reuse // the same listener because it will hold out Blocks-map. ParseTreeWalker.DEFAULT.walk(listener,parser.parse()); // Let's see if the user wants to continue. System.out.print("Type a command and press return (q to quit) $"); input = keyboard.nextLine(); } System.out.println("Bye!"); } } // You can place this Block class inside Main.java as well. class Block { final String name; int x; int y; Block(String name,int x,int y) { this.name = name; this.x = x; this.y = y; } void onTopOf(Block that) { // TODO } }
这个主要的课程是非常自我解释的内联评论.这个棘手的部分是听众应该看起来像什么.那么这里是:
文件:blockspeak / EvalBlockSpeakListener.java
package blockspeak; import org.antlr.v4.runtime.misc.NotNull; import java.util.HashMap; import java.util.Map; /** * A class extending the `BlockSpeakBaseListener` (which will be generated * by ANTLR) in which we override the methods in which to create blocks,and * in which to stack blocks. */ public class EvalBlockSpeakListener extends BlockSpeakBaseListener { // A map that keeps track of our Blocks. private final Map<String,Block> blocks = new HashMap<String,Block>(); @Override public void enterCreate_block(@NotNull BlockSpeakParser.Create_blockContext ctx) { String name = ctx.NAME().getText(); Integer x = Integer.valueOf(ctx.position().x.getText()); Integer y = Integer.valueOf(ctx.position().y.getText()); Block block = new Block(name,x,y); System.out.printf("creating block: %s\n",name); blocks.put(block.name,block); } @Override public void enterStack_block(@NotNull BlockSpeakParser.Stack_blockContext ctx) { Block bottom = this.blocks.get(ctx.bottom.getText()); Block top = this.blocks.get(ctx.top.getText()); if (bottom == null) { System.out.printf("no such block: %s\n",ctx.bottom.getText()); } else if (top == null) { System.out.printf("no such block: %s\n",ctx.top.getText()); } else { System.out.printf("putting %s on top of %s\n",top.name,bottom.name); top.onTopOf(bottom); } } }
上面的监听器有2个方法定义,映射到以下解析器规则:
create_block : 'block' NAME 'at' position ; stack_block : 'stack' top=NAME 'on' bottom=NAME ;
每当解析器“进入”这样的解析器规则时,将调用侦听器内的相应方法.所以,当调用enterCreate_block(解析器输入create_block规则)时,我们创建(并保存)一个块,当调用enterStack_block时,我们检索该操作涉及的2个块,并将其堆叠在另一个之上.
要查看上述3个类别的操作,请在保存blockspeak /目录下的.g4和.java文件的目录中下载ANTLR 4.4.
打开控制台并执行以下3个步骤:
java -cp antlr-4.4-complete.jar org.antlr.v4.Tool blockspeak/BlockSpeak.g4 -package blockspeak
2.编译所有Java源文件:
javac -cp ./antlr-4.4-complete.jar blockspeak/*.java
3.运行主类:
3.1. Linux的/苹果机
java -cp .:antlr-4.4-complete.jar blockspeak.Main
3.2.视窗
java -cp .;antlr-4.4-complete.jar blockspeak.Main
这是运行Main类的一个示例会话:
bart@hades:~/Temp/demo$java -cp .:antlr-4.4-complete.jar blockspeak.Main creating block: A creating block: B putting A on top of B Type a command and press return (q to quit) $block X at (0,0) creating block: X Type a command and press return (q to quit) $stack Y on X no such block: Y Type a command and press return (q to quit) $stack A on X putting A on top of X Type a command and press return (q to quit) $q Bye! bart@hades:~/Temp/demo$
有关树监听器的更多信息:https://theantlrguy.atlassian.net/wiki/display/ANTLR4/Parse+Tree+Listeners