IAm*_*aja 12 java grammar interpreter antlr scripting-language
假设我有以下所有打包的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());
}
}
Run Code Online (Sandbox Code Playgroud)
同样,不要担心数学和(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
Run Code Online (Sandbox Code Playgroud)
这可能等同于以下Java代码:
Block A, B;
A = new Block(0, 10);
B = new Block(0, 20);
BlockController.stackBlocks(B, A);
Run Code Online (Sandbox Code Playgroud)
因此,我们的想法是ANTLR生成的解释器将*.blockspeak脚本作为输入,并使用此脚本中的命令来调用blocks.jarAPI操作.我阅读了优秀的简单示例,它使用ANTLR创建了一个简单的计算器.但是在该链接中,有一个ExpParser类,其中包含一个eval()方法:
ExpParser parser = new ExpParser(tokens);
parser.eval();
Run Code Online (Sandbox Code Playgroud)
这里的问题是,在计算器的情况下,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);
Run Code Online (Sandbox Code Playgroud)
所以我的问题是,我在哪里执行这种"映射"?换句话说,blocks.jar当在BlockSpeak脚本中遇到特定的语法时,如何指示ANTLR调用各种代码(打包在内部)? 更重要的是,有人可以给我一个伪代码示例吗?
Bar*_*ers 14
我只是简单地评估脚本,而不是生成需要再次编译的Java源文件.
使用ANTLR 4时,强烈建议保持语法和目标特定代码彼此分离,并将任何特定于目标的代码放在树监听器或检查器中.
我将快速演示如何使用监听器.
您的示例输入的语法可能如下所示:
blockspeak/BlockSpeak.g4grammar 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
;
Run Code Online (Sandbox Code Playgroud)
一些支持Java类:
blockspeak/Main.javapackage 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
}
}
Run Code Online (Sandbox Code Playgroud)
这个主要类对内联注释非常自我解释.棘手的部分是听众应该是什么样子.嗯,这是:
blockspeak/EvalBlockSpeakListener.javapackage 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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
上面的监听器定义了两个映射到以下解析器规则的方法:
create_block
: 'block' NAME 'at' position
;
stack_block
: 'stack' top=NAME 'on' bottom=NAME
;
Run Code Online (Sandbox Code Playgroud)
每当解析器"输入"这样的解析器规则时,将调用侦听器内的相应方法.因此,每当enterCreate_block(解析器进入create_block规则)被调用时,我们创建(并保存)一个块,并且当enterStack_block被调用时,我们检索操作中涉及的2块,并将一个块叠加在另一个块之上.
要查看上面的3个类,请在包含和files 的目录的目录中下载ANTLR 4.4.blockspeak/.g4.java
打开控制台并执行以下3个步骤:
java -cp antlr-4.4-complete.jar org.antlr.v4.Tool blockspeak/BlockSpeak.g4 -package blockspeak
Run Code Online (Sandbox Code Playgroud)
javac -cp ./antlr-4.4-complete.jar blockspeak/*.java
Run Code Online (Sandbox Code Playgroud)
java -cp .:antlr-4.4-complete.jar blockspeak.Main
Run Code Online (Sandbox Code Playgroud)
3.2.视窗
java -cp .;antlr-4.4-complete.jar blockspeak.Main
Run Code Online (Sandbox Code Playgroud)
以下是运行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$
Run Code Online (Sandbox Code Playgroud)
关于树监听器的更多信息:https://theantlrguy.atlassian.net/wiki/display/ANTLR4/Parse+Tree+Listeners
我会亲自编写一个语法来为每个脚本生成一个 Java 程序,然后您可以编译(与您的 jar 一起)并独立运行......即,一个两步过程。
例如,使用类似于以下简单语法的内容(我还没有测试过,我确定您需要扩展和适应),您可以将parser.eval()示例中的语句替换为parser.program();(也将“Exp”替换为“BlockSpeak” ) 并且它应该吐出与脚本匹配的 Java 代码,stdout您可以将其重定向到 .java 文件,编译(与 jar 一起)并运行。
BlockSpeak.g :
grammar BlockSpeak;
program
@init { System.out.println("//import com.whatever.stuff;\n\npublic class BlockProgram {\n public static void main(String[] args) {\n\n"); }
@after { System.out.println("\n } // main()\n} // class BlockProgram\n\n"); }
: inss=instructions { if (null != $inss.insList) for (String ins : $inss.insList) { System.out.println(ins); } }
;
instructions returns [ArrayList<String> insList]
@init { $insList = new ArrayList<String>(); }
: (instruction { $insList.add($instruction.ins); })*
;
instruction returns [String ins]
: ( create { $ins = $create.ins; } | move { $ins = $move.ins; } | stack { $ins = $stack.ins; } ) ';'
;
create returns [String ins]
: 'block' id=BlockId 'at' c=coordinates { $ins = " Block " + $id.text + " = new Block(" + $c.coords + ");\n"; }
;
move returns [String ins]
: 'move' id=BlockId 'to' c=coordinates { $ins = " BlockController.moveBlock(" + $id.text + ", " + $c.coords + ");\n"; }
;
stack returns [String ins]
: 'stack' id1=BlockId 'on' id2=BlockId { $ins = " BlockController.stackBlocks(" + $id1.text + ", " + $id2.text + ");\n"; }
;
coordinates returns [String coords]
: '(' x=PosInt ',' y=PosInt ')' { $coords = $x.text + ", " + $y.text; }
;
BlockId
: ('A'..'Z')+
;
PosInt
: ('0'..'9') ('0'..'9')*
;
WS
: (' ' | '\t' | '\r'| '\n') -> channel(HIDDEN)
;
Run Code Online (Sandbox Code Playgroud)
(请注意,为简单起见,此语法需要分号来分隔每条指令。)
当然还有其他方法可以做这种事情,但这对我来说似乎是最简单的。
祝你好运!
更新
所以我继续“完成”了我的原始帖子(修复了上述语法中的一些错误)并在一个简单的脚本上对其进行了测试。
这是我用来测试上述语法的 .java 文件(取自您上面发布的代码存根)。请注意,在您的情况下,您可能希望将脚本文件名(在我的代码中"script.blockspeak")设为命令行参数。此外,当然Block和BlockController类将来自您的 jar。
块测试.java :
import org.antlr.v4.runtime.*;
class Block {
private String name;
private int xCoord;
private int yCoord;
// Other Getters, setters, ctors, etc.
public Block(int x, int y) { xCoord = x; yCoord = y; }
public int getXCoord() { return xCoord; }
public int getYCoord() { return yCoord; }
public void setXCoord(int x) { xCoord = x; }
public void setYCoord(int y) { yCoord = y; }
public void setCoords(int x, int y) {
setXCoord(x);
setYCoord(y);
}
}
class BlockController {
public static void moveBlock(Block block, int newXCoord, int newYCoord) {
block.setCoords(newXCoord, newYCoord);
}
public static void 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());
}
}
public class BlocksTest {
public static void main(String[] args) throws Exception {
ANTLRFileStream in = new ANTLRFileStream("script.blockspeak");
BlockSpeakLexer lexer = new BlockSpeakLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
BlockSpeakParser parser = new BlockSpeakParser(tokens);
parser.program();
}
}
Run Code Online (Sandbox Code Playgroud)
这是我使用的命令行(在我的 MacBook Pro 上):
> java -jar antlr-4.4-complete.jar BlockSpeak.g
> javac -cp .:antlr-4.4-complete.jar *.java
> java -cp .:antlr-4.4-complete.jar BlocksTest > BlockProgram.java
Run Code Online (Sandbox Code Playgroud)
这是输入脚本:
script.blockspeak :
block A at (0, 10);
block B at (0, 20);
stack A on B;
Run Code Online (Sandbox Code Playgroud)
这是输出:
块程序.java :
//import com.whatever.stuff;
public class BlockProgram {
public static void main(String[] args) {
Block A = new Block(0, 10);
Block B = new Block(0, 20);
BlockController.stackBlocks(A, B);
} // main()
} // class BlockProgram
Run Code Online (Sandbox Code Playgroud)
您当然必须为每个脚本编译和运行 BlockProgram.java。
在回答您的评论(#3)中的一个问题时,我首先考虑了几个更复杂的选项,它们可能会简化您的“用户体验”。
(A) 您可以将调用BlockController直接嵌入到 ANTLR 操作中,而不是使用语法来生成您必须编译和运行的 Java 程序。在我创建字符串并将它们从一个非终端传递到下一个的地方,只要instruction识别出规则,您就可以在那里使用 Java 代码直接执行 Block 命令。这将需要在 ANTLR 语法和导入方面更加复杂,但在技术上是可行的。
(B) 如果您要执行选项 A,那么您可以更进一步并创建一个交互式解释器(“shell”),其中向用户显示一个提示,只需在提示中键入“blockspeak”命令,即然后直接解析和执行,将结果显示给用户。
就复杂性而言,这两个选项都不太难实现,但它们都需要进行更多的编码,这超出了 Stack Overflow 答案的范围。这就是为什么我选择在这里提出一个“更简单”的解决方案。