我个人会编写一个语法来为每个脚本生成一个Java程序,然后您可以编译(以及您的jar)并独立运行...即,一个两步过程。
例如,使用类似于以下简单语法的东西(我还没有测试过,我相信你需要扩展和适应),你可以将该示例中的语句替换为(也在整个过程中用“BlockSpeak”代替“Exp”),它应该吐出与脚本匹配的Java代码,你可以将其重定向到.java文件中, 编译(与jar一起)并运行。parser.eval()
parser.program();
stdout
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)
;
(请注意,为简单起见,此语法需要分号来分隔每个指令。
当然还有其他方法可以做这种事情,但这对我来说似乎是最简单的。
祝你好运!
更新
所以我继续“完成”我的原始帖子(修复了上述语法中的一些错误),并在一个简单的脚本上进行测试。
这是我用来测试上述语法.java文件(取自您上面发布的代码存根)。请注意,在您的情况下,您可能希望将脚本文件名(在我的代码中)设置为命令行参数。此外,当然,和类将来自您的罐子。"script.blockspeak"
Block
BlockController
块测试.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();
}
}
以下是我使用的命令行(在我的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
这是输入脚本:
script.blockspeak:
block A at (0, 10);
block B at (0, 20);
stack A on B;
这是输出:
块程序.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
当然,您必须为每个脚本编译并运行BlockProgram.java。
在回答您的评论(#3)中的一个问题时,我首先考虑的几个更复杂的选项可能会简化您的“用户体验”。
(A) 无需使用语法生成 Java 程序,然后必须编译和运行该程序,而是可以将对 的调用直接嵌入到 ANTLR 操作中。在我创建字符串并将它们从一个非终端传递到下一个非终端的地方,只要识别规则,您就可以让java代码直接执行Block命令。这需要在ANTLR语法和导入方面更加复杂,但这在技术上是可行的。BlockController
instruction
(B)如果你要做选项A,你可以更进一步,创建一个交互式解释器(“shell”),其中用户会看到一个提示,并在提示符下输入“blockspeak”命令,然后直接解析和执行,将结果显示回用户。
就复杂性而言,这两个选项都不那么难以完成,但它们都需要执行更多的编码,这超出了Stack Overflow答案的范围。这就是为什么我选择在这里提出一个“更简单”的解决方案。