Написание более чистого и модульного командного парсера

Я пишу отладчик для эмулятора Z80, который мы пишем в школьном проекте, используя Java. Отладчик читает команду от пользователя, выполняет ее, читает другую команду и т.д.

Команды могут иметь меньше аргументов, иметь необязательные аргументы или принимать неограниченное количество аргументов. Аргументы в основном представляют собой целые числа, но иногда они представляют собой строки.

В настоящее время мы используем класс Scanner для чтения и анализа ввода. Метод чтения выглядит примерно так (пишу навскидку, не обращая внимания на синтаксис и правильность).

Это был кладж, написанный в начале проекта, который быстро вышел из-под контроля по мере того, как мы добавляли все больше и больше команд в отладчик.

Основные проблемы, которые у меня есть с этим кодом, — это большое количество повторений, высокий уровень вложенности if/else и всеобщее уродство.

Хотелось бы предложений, как сделать этот код более красивым и модульным, и какие паттерны подходят для такого рода программ.

Я также хотел бы получить более общие предложения по стилю кода.


person arvidj    schedule 04.04.2009    source источник
comment
Я думаю, что это хороший вопрос, хотя последний фрагмент может быть лучше в комментарии. И мы предпочитаем не подписывать сообщения, так как ваше имя все равно находится прямо под ним. Однако это не помешает мне проголосовать.   -  person Michael Myers    schedule 04.04.2009
comment
Я согласен с mmyers во всем.   -  person erickson    schedule 04.04.2009
comment
Это первый вопрос, который я задаю в Stack Overflow, поэтому я также хотел бы получить ответ на сам вопрос. Я нарушаю какой-то этикет? Вопрос к общему? Это непонятно в каком-либо аспекте?   -  person arvidj    schedule 04.04.2009


Ответы (2)


да, есть более простой/лучший способ, особенно в Java или других языках OO.

Основная идея, во-первых, заключается в том, что ваш анализатор команд представляет собой конечный автомат: состояние START — это пустая строка (или индекс в начале строки).

Давайте подумаем о echo:

$ echo foo bat "bletch quux"
  1. разбить строку на части:

    "эхо" "фу" "бар" "блеч кукс"

  2. в оболочке грамматика обычно следующая: глагол существительное существительное существительное... поэтому интерпретируйте ее таким образом. Вы МОЖЕТЕ сделать это с последовательностью if-else if вещей, но хэш лучше. Вы загружаете хеш строками в качестве индексов и индексируете что-то еще. Это может быть просто число, которое будет входить в переключатель:

(это псевдокод):

  Hashtable cmds = new Hashtable();
  enum cmdindx { ECHO=1, LS=2, ...}
  cmds.add("echo", ECHO); // ...

  // get the token into tok
  switch (cmds.get(tok)) {
     case ECHO: // process it
       // get each successor token and copy it to stdout
       break;
     ...
     default:
        // didn't recognize the token
        // process errors
   }

ДАЖЕ лучше, вы можете применить шаблоны Command и Object Factory. Теперь у вас есть класс Command

  public interface Command {
     public void doThis(String[] nouns) ;
     public Command factory();
  }

  public class Echo implements Command {
     public void doThis(String[] nouns){
         // the code is foreach noun in nouns, echo it
     }
     public Command factory(){
         // this clones the object and returns it
     }
  }

Теперь ваш код становится

  // Load the hash
  Hashtable cmds = new Hashtable();
  cmds.add("echo", new Echo());  // one for each command


  // token is in tok
  // the "nouns" or "arguments are in a String[] nouns
  ((cmds.get(tok)).factory()).doThis(nouns);

Видишь, как это работает? Вы ищете объект в хеше. Вы вызываете метод factory, чтобы получить новую копию. Затем вы вызываете обработку для этой команды, используя метод doThis.

Обновлять

Это может быть немного слишком общим, поскольку используется шаблон Factory. Зачем заводской метод? В основном вы использовали бы это, чтобы каждый раз, когда вы выполняете команду, объект «глагол» (например, экземпляр Echo) мог иметь свое собственное внутреннее состояние. Если вам не нужно, чтобы состояние сохранялось в течение длительного времени, вы можете упростить это до

  (cmds.get(tok)).doThis(nouns);

Теперь он просто получает и использует объект Echo, созданный вами при его создании с помощью cmds.add("echo", new Echo());.

person Charlie Martin    schedule 04.04.2009

Вы смотрели на выполнение диспетчеризации с картой? хэш-карту было бы довольно легко поместить там. Просто сделайте ключ командой и создайте интерфейс или абстрактный класс, который будет такой командой:

interface Commmand {
   void execute(String args);
}

Или, что еще лучше, вы могли бы заранее нарезать аргументы:

interface Commmand {
   void execute(String[] args);
}

Тогда вы должны использовать HashMap‹String, Command>.

person fuzzy-waffle    schedule 04.04.2009