Написание более чистого и модульного парсера команд - PullRequest
8 голосов
/ 04 апреля 2009

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

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

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

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

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

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

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

Ответы [ 2 ]

2 голосов
/ 04 апреля 2009

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

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

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

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

    "echo" "foo" "bar" "bletch quux"

  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.

Обновление

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

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

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

1 голос
/ 04 апреля 2009

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

interface Commmand {
   void execute(String args);
}

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

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

Тогда вы должны использовать HashMap .

...