加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

java – 使ANTLR生成脚本解释器?

发布时间:2020-12-14 05:07:47 所属栏目:Java 来源:网络整理
导读:说我有以下 Java API,它们都被封装成block.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 BlockCon
说我有以下 Java API,它们都被封装成block.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空间中的块的事实.关键是我们有Java代码,编译为JAR,它可以对块执行操作.我现在想要构建一个轻量级的脚本语言,允许非程序员调用各种AP??I 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中)?更重要的是,有人可以给我一个伪代码的例子吗?

解决方法

我会简单地评估脚本,而不是生成需要自己编译的Java源文件.

使用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
 : '#' ~[rn]* -> skip
 ;

INT
 : [0-9]+
 ;

NAME
 : [a-zA-Z]+
 ;

SPACES
 : [ trn] -> 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: %sn",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: %sn",ctx.bottom.getText());
        }
        else if (top == null) {
            System.out.printf("no such block: %sn",ctx.top.getText());
        }
        else {
            System.out.printf("putting %s on top of %sn",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的/苹果机

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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读