0

我目前正在分配创建一个带有 8 个关键字(不区分大小写)和 4 个算术运算符的基本解释器。这种语言的程序看起来像这样(实际上类似于 BASIC 的语法):

# (signals start of a comment line)
LET
INTEGER
STRING
PRINT
END

所以无论如何,我目前正在尝试标记要解析的文本行。我已经将所有文本行解析为 ArrayList 并对字符串进行了标记。我现在的问题是 StringTokenizer 提前标记了所有字符串(我使用空格作为分隔符),而我需要的是它找到我的关键字,这始终是代码行开头的第一个单词,并且某些问题使这种情况变得不可取;我认为使用 String.split() 也没有多大帮助。

我计划这样做的方式是让解释器找到我的第一个标记,然后通过 HashMap 从那里转到适当的类(参见我之前关于在此处为我的解释器使用 switch 语句的问题:Switch 或 if 语句在用 java 编写解释器;其他成员建议我使用 Map) 删除关键字标记并执行。专门设置第二个临时 ArrayList 或数组来保存变量是个好主意吗?我不想或不需要它过于复杂。

提前感谢您的建议。

public static void main (String[]args)
    {
        try
        {
            ArrayList<String> demo= new ArrayList <String>();
            FileReader fr= new FileReader("hi.tpl");
            BufferedReader reader= new BufferedReader(fr);
            String line;
            while ((line=reader.readLine()) !=null)//read file line by line
                {
                    //Add to ArrayList
                    demo.add(line);
                }

            reader.close();

            boolean checkEnd= demo.contains("END");//check if arraylist contains END statement
                    if(line=null && checkEnd== false)
                        {
                            System.out.println(" Unexpected end of file: no END statement");
                            System.exit(0);
                        }

            ListIterator<String>arrayListIt=demo.listIterator();
            while (arrayListIt.hasNext())
            for (String file: demo)// begin interpreting the program file here
                {               
                    StringTokenizer st=new StringTokenizer(file);
                    while(st.hasMoreTokens())
                        {

                            int firstWord=file.indexOf();
                            String command = file;
                            if (firstSpace > 0)
                            {
                                command= file.substring(0, firstSpace);
                            }
                            TokenHandler tokens= tokens.get(command.toUpperCase());
                            if(tokens != null)
                            {
                                tokens.execute(file);
                            }

                        }
4

3 回答 3

1

因此,如果我这样做,我会使用更多的 OO 方法。

如果您为所有实现相同接口的命令中的每一个创建一个“类”会怎样?接口——我们称之为 CommandObject 将有一个 execute() 方法。

然后,您可以使用将“Let”之类的命令映射到 Let 类的实例的预加载映射。

现在你的主循环变成了这样(伪):

for(line:lineList)
    CommandObject commandObject=map.get(line.split()[0]) // do this more clearly
    commandObject.execute(variableHash, line) // Parse and execute the line

这些命令对象必须共享一组变量——使单例可以工作,但有点反模式,我建议你将它们作为哈希图(上面的变量哈希)传递。

这种方法的好处是添加一个新的“命令”非常简单并且大部分是独立的。

编辑(重新评论):

您要做的第一件事是创建一个哈希图并“安装”您的每个命令。例如:(仍然是伪代码,我假设您更愿意自己做作业)

map = new HashMap<String, CommandObject>

然后将每个类的实例添加到地图中:

map.put("LET", new LetCommand());
map.put("INTEGER", new Integercommand());

其中右侧类实现“CommandObject”接口。

请注意,由于每个 CommandObject 都是每次找到该关键字时都会重复使用的实例,因此您可能不应该存储任何状态(没有任何实例变量),这意味着您的 CommandObject 只需要一个方法,例如:

execute(String commandLine, HashMap variables);

这可能是最简单的方法(我根据我的原始建议编辑了上面的文本以反映这一点)。

如果这个解析器变得更复杂,那么向“CommandObject”添加更多功能是完全有效的,只要你有一个 reset() 方法,你就可以保留状态变量(我最初的建议,但你正在做的事情似乎过于复杂)

请注意,关键字到命令对象的映射可以用反射代替,但不要尝试在学校作业中这样做,反射的复杂性使其不值得您花时间,而且您可能会因为老师不这样做而被降级不明白。我实现了一个这样的系统,其中每个关键字都链接到一个测试(允许您链接测试、循环测试,甚至定义和携带传递给这些测试并由这些测试操作的变量——在这种情况下,反射是值得的,因为添加一个新测试不需要更新缓存)

于 2012-01-10T19:26:22.927 回答
0

一个不错的方法是使用enum. 而且我不会split限制使用 2 个项目。

enum Command {
    LET {
        @Override
        public void execute(Context context, String args) {
        }
    },
    INTEGER { ... },
    STRING { ... },
    PRINT { ... },
    END { ... };

    public abstract void execute(Context context, String args);
}

private void executeLine(String line) {
    String[] commandAndArgs = line.split("\\s+", 2);
    String command = "";
    String args = "";
    if (commandAndArgs.length > 0)
        command = commandArgs[0].toUpperCase();
    if (commandAndArgs.length > 1)
        args = commandArgs[1];
    Command cmd = Command.valueOf(command);
    Context context = ...;
    cmd.execute(context, args);
}
于 2012-01-10T19:12:50.770 回答
0

听起来正确的设计是将解析这些参数推迟到以后。某些命令,如“STRING”或“PRINT”可能与空格无关,其中 STRING S =“HELLO WORLD”实际上与 STRING S =“HELLOWORLD”在功能上没有什么不同。你不想预先“过度设计”这样的东西——最好只做最简单的事情,写一两个你的命令类,然后弄清楚这些命令类有什么共同点。

如果您后来发现所有(或大部分)命令都希望以某种方式将这些参数解析为列表,则可以将该“列表解析代码”重构为静态实用程序方法(或者可能是非静态实用程序方法)父 Command 类本身,如果您使用继承。)如果您在进行过程中很聪明地创建一套自动化测试,那么风险会小得多,但这种任务可能超出您的任务范围。

于 2012-01-10T18:41:43.270 回答