说我有以下Java API,它们都打包为blocks.jar
:
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空间中的块这一事实。关键是我们拥有编译为JAR的Java代码,该代码对块执行操作。我现在想构建一种轻量级的脚本语言,允许非程序员调用各种块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();
这里的问题是,在计算器的情况下,
tokens
表示要评估的数学表达式,而eval()
返回该表达式的评估值。对于解释器,tokens
将代表我的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
中)? 更重要的是,有人可以给我一个伪代码示例吗? 最佳答案
我只是简单地动态评估脚本,而不生成需要再次自行编译的Java源文件。
强烈建议使用ANTLR 4将语法和目标特定代码彼此分开,并将任何目标特定代码放入树侦听器或-visitor中。
我将快速演示如何使用侦听器。
您的示例输入的语法如下所示:
文件: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个步骤:
1.生成ANTLR文件:
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/Mac
java -cp .:antlr-4.4-complete.jar blockspeak.Main
3.2。视窗
java -cp .;antlr-4.4-complete.jar blockspeak.Main
这是运行
Main
类的示例 session :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