Написание интерпретатора в Java с использованием нескольких массивов или массивов с stringtokenizer
В настоящее время я работаю над заданием по созданию базового интерпретатора с 8 ключевыми словами (без учета регистра) и 4 арифметическими операторами. Программа на этом языке будет выглядеть примерно так (похоже на синтаксис BASIC):
# (signals start of a comment line)
LET
INTEGER
STRING
PRINT
END
Так или иначе, в настоящее время я пытаюсь разбить строки текста на анализ. Я уже проанализировал все строки текста в ArrayList и разбил токены на строки. Моя текущая проблема сейчас заключается в том, что StringTokenizer заранее маркирует все строки (я использую пробел в качестве разделителя), когда мне нужно найти ключевое слово, которое всегда является первым словом в начале строки кода. и некоторые проблемы делают это нежелательным; Я не думаю, что использование String.split() также очень поможет.
Я планировал сделать так, чтобы интерпретатор нашел мой первый токен и перешел оттуда к соответствующему классу через HashMap (см. Мой предыдущий вопрос об использовании операторов switch для моего интерпретатора здесь: Switch или if операторов при написании интерпретатора в Java; другие участники предложили использовать карту), чтобы удалить маркер ключевого слова и выполнить его. Было бы хорошей идеей создать второй временный 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);
}
}
3 ответа
Так что, если бы я делал это, я бы использовал более ОО подход.
Что если вы создали "Класс" для каждой из тех команд, которые реализовали один и тот же интерфейс? Интерфейс - давайте назовем его 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
Эти объекты команд должны иметь общий набор переменных - создание синглтона будет работать, но это своего рода анти-паттерн, я предлагаю вместо этого передавать их как хэш-карту (переменная Hash выше).
Хорошая вещь в этом подходе состоит в том, что добавление новой "Команды" очень просто и в основном независимо.
Редактировать (см. Комментарий):
Первое, что вы должны сделать, это создать hashmap и "Установить" каждую из ваших команд. Например: (все еще psudeo-код, я полагаю, вы бы предпочли сделать это самостоятельно)
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() (мое первоначальное предложение, но оно кажется слишком сложным для того, что вы делаете)
Обратите внимание, что карта ключевых слов для командных объектов может быть заменена отражением, но не пытайтесь сделать это для школьного задания, сложность отражения делает его не стоящим вашего времени, и, вероятно, вы будете понижены, потому что учитель этого не делает. Я не понимаю этого. Я реализовал такую систему, в которой каждое ключевое слово связывалось с тестом (позволяя вам связывать в цепочку тесты, проходить циклы тестов и даже определять и переносить переменные, которые были переданы и обработаны этими тестами - в этом случае отражение того стоило, потому что добавление новый тест не потребовал обновления кеша)
Один хороший способ использования 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);
}
Звучит так, будто правильным решением было бы отложить анализ этих аргументов на потом. Некоторые команды, такие как "STRING" или "PRINT", могут быть независимыми от пробелов, где STRING S = "HELLO WORLD" действительно не будет функционально отличаться от STRING S = "HELLOWORLD". Вы не хотите "перерабатывать" такие вещи заранее - лучше просто сделать простейшую вещь, которая работает сейчас, написать один или два ваших командных класса, а затем выяснить, что общего у этих командных классов.
Если позже вы обнаружите, что все (или большинство) ваших команд хотят, чтобы эти аргументы были проанализированы в списке определенным образом, вы можете реорганизовать этот "код синтаксического анализа списка" в статический служебный метод (или, возможно, нестатический служебный метод в сам родительский класс Command, если вы используете наследование.) Это будет намного менее рискованно, если вы умело создаете набор автоматических тестов по мере продвижения, но такого рода задачи могут выходить за рамки вашего задания.