За многие годы я написал несколько ассемблеров, выполняющих ручной анализ, и, честно говоря, вам, вероятно, лучше использовать язык грамматики и генератор синтаксических анализаторов.
Вот почему - типичная сборочная линия, вероятно,выглядеть примерно так:
[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. */ }