Шаблон дизайна для создания ассемблера - PullRequest
8 голосов
/ 07 апреля 2011

Я делаю 8051 ассемблер.

Прежде чем все станет токенизатором, который читает следующие токены, устанавливает флаги ошибок, распознает EOF и т. Д.
Затем идет основной цикл компилятора, который читает следующие токены и проверяет правильность мнемоники:

mnemonic= NextToken();
if (mnemonic.Error)
{
    //throw some error
}
else if (mnemonic.Text == "ADD")
{
    ...
}
else if (mnemonic.Text == "ADDC")
{
    ...
}

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

if (mnemonic.Text == "MOV")
{
    arg1 = NextToken();
    if (arg1.Error) { /* throw error */ break; }
    arg2 = NextToken();
    if (arg2.Error) { /* throw error */ break; }

    if (arg1.Text == "A")
    {
        if (arg2.Text == "B")
            output << 0x1234; //Example compiled code
        else if (arg2.Text == "@B")
            output << 0x5678; //Example compiled code
        else
            /* throw "Invalid parameters" */
    }
    else if (arg1.Text == "B")
    {
        if (arg2.Text == "A")
            output << 0x9ABC; //Example compiled code
        else if (arg2.Text == "@A")
            output << 0x0DEF; //Example compiled code
        else
            /* throw "Invalid parameters" */
    }
}

Для каждой мнемоники я должен проверить правильность параметров, а затем создать правильный скомпилированный код. Очень похожие коды для проверки допустимых параметров для каждого мнемонического повтора в каждом случае.

Так есть ли шаблон проектирования для улучшения этого кода?
Или просто более простой способ реализовать это?

Редактировать: Я принял ответ плинтуса, благодаря ему. Тем не менее, если у вас есть идеи по этому поводу, я буду рад выучить их. Спасибо всем.

Ответы [ 5 ]

8 голосов
/ 08 апреля 2011

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

Вот почему - типичная сборочная линия, вероятно,выглядеть примерно так:

[label:] [instruction|directive][newline]

и инструкция будет:

plain-mnemonic|mnemonic-withargs

, а директива будет:

plain-directive|directive-withargs

и т. д.

С приличным генератором парсера, таким как Gold , вы сможете выбить грамматику для 8051 за несколько часов.Преимущество этого анализа перед ручным анализом состоит в том, что вы сможете иметь достаточно сложные выражения в вашем ассемблерном коде, такие как:

.define kMagicNumber 0xdeadbeef
CMPA #(2 * kMagicNumber + 1)

, что может быть настоящим медвежьим делом вручную.

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

enum {
    Implied = 1, Direct = 2, Extended = 4, Indexed = 8 // etc
} AddressingMode; 

/* for a 4 char mnemonic, this struct will be 5 bytes.  A typical small processor
 * has on the order of 100 instructions, making this table come in at ~500 bytes when all
 * is said and done.
 * The time to binary search that will be, worst case 8 compares on the mnemonic.
 * I claim that I/O will take way more time than look up.
 * You will also need a table and/or a routine that given a mnemonic and addressing mode
 * will give you the actual opcode.
 */

struct InstructionInfo {
    char Mnemonic[4];
    char AddessingMode;
}

/* order them by mnemonic */
static InstructionInfo instrs[] = {
    { {'A', 'D', 'D', '\0'}, Direct|Extended|Indexed },
    { {'A', 'D', 'D', 'A'}, Direct|Extended|Indexed },
    { {'S', 'U', 'B', '\0'}, Direct|Extended|Indexed },
    { {'S', 'U', 'B', 'A'}, Direct|Extended|Indexed }
}; /* etc */

static int nInstrs = sizeof(instrs)/sizeof(InstrcutionInfo);

InstructionInfo *GetInstruction(char *mnemonic) {
   /* binary search for mnemonic */
}

int InstructionSize(AddressingMode mode)
{
    switch (mode) {
    case Inplied: return 1;
    / * etc */
    }
 }

Тогда у вас будет список всех инструкций, который, в свою очередь, содержит список всех режимов адресации.

Таким образом, ваш парсер станет примерно таким:

char *line = ReadLine();
int nextStart = 0;
int labelLen;
char *label = GetLabel(line, &labelLen, nextStart, &nextStart); // may be empty
int mnemonicLen;
char *mnemonic = GetMnemonic(line, &mnemonicLen, nextStart, &nextStart); // may be empty
if (IsOpcode(mnemonic, mnemonicLen)) {
    AddressingModeInfo info = GetAddressingModeInfo(line, nextStart, &nextStart);
    if (IsValidInstruction(mnemonic, info)) {
        GenerateCode(mnemonic, info);
    }
    else throw new BadInstructionException(mnemonic, info);
}
else if (IsDirective()) { /* etc. */ }
4 голосов
/ 07 апреля 2011

Да.Большинство ассемблеров используют таблицу данных, в которой описаны инструкции: мнемоника, код операции, формы операндов и т. Д.

Я предлагаю посмотреть на исходный код as. У меня проблемы с поиском. Смотри здесь .(Спасибо Хоссейну.)

0 голосов
/ 08 апреля 2011

Когда я играл с инструментом эмулятора микрокода, я преобразовал все в потомки класса Instruction.Из Instruction были классы категорий, такие как Arithmetic_Instruction и Branch_Instruction.Я использовал шаблон factory для создания экземпляров.

Лучшим вариантом может стать изучение синтаксиса ассемблера.Напишите лексер для преобразования в токены (** пожалуйста, не используйте лестницы if-elseif-else).Затем, основываясь на семантике, выдайте код.

Давным-давно ассемблеры прошли как минимум два прохода: первый для разрешения констант и формирования скелетного кода (включая таблицы символов).Вторым проходом было генерирование более конкретных или абсолютных значений.

Читали ли вы в последнее время Книгу Дракона?

0 голосов
/ 07 апреля 2011

Вы смотрели на шаблон «Диспетчер команд»?

http://en.wikipedia.org/wiki/Command_pattern

Общая идея заключается в создании объекта, который обрабатывает каждую инструкцию (команду), и создании внешнего вида.таблица, которая отображает каждую инструкцию в класс обработчика.Каждый командный класс будет иметь общий интерфейс (например, Command.Execute (* args)), который определенно даст вам более чистый / более гибкий дизайн, чем ваш текущий огромный оператор switch.

0 голосов
/ 07 апреля 2011

Я думаю, вы должны посмотреть на шаблон посетителя.Это может не сделать ваш код намного проще, но уменьшит связность и увеличит возможность повторного использования. SableCC - это Java-инфраструктура для создания компиляторов, которые широко ее используют.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...