[英]Design Pattern For Making An Assembler
我正在制作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" */
}
}
對於每個助記符,我必須檢查有效參數,然后創建正確的編譯代碼。 用於檢查每種情況下每個助記符重復的有效參數的非常相似的代碼。
那么是否有改進此代碼的設計模式?
或者只是一種更簡單的方法來實現它?
編輯:我接受了基座的回答,多虧了他。 如果你有這方面的想法,我將很樂意學習它們。 謝謝大家。
多年來我編寫了許多匯編程序來進行手工解析,坦率地說,使用語法語言和解析器生成器可能會更好。
這就是原因 - 典型的裝配線可能看起來像這樣:
[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. */ }
是。 大多數匯編程序使用描述指令的數據表:助記符,操作碼,操作數表等。
我建議查看as
的源代碼。 我找到它時遇到了一些麻煩。 看這里 (感謝Hossein。)
我認為你應該研究訪客模式。 它可能不會使您的代碼更簡單,但會減少耦合並提高可重用性。 SableCC是一個用於構建廣泛使用它的編譯器的Java框架。
你看過“Command Dispatcher”模式嗎?
http://en.wikipedia.org/wiki/Command_pattern
一般的想法是創建一個處理每個指令(命令)的對象,並創建一個將每個指令映射到處理程序類的查找表。 每個命令類都有一個公共接口(例如Command.Execute(* args)),這肯定會給你一個比你當前巨大的switch語句更清晰/更靈活的設計。
當我使用Microcode模擬器工具時,我將所有內容都轉換為Instruction
類的后代。 From Instruction
是類別類,例如Arithmetic_Instruction
和Branch_Instruction
。 我使用工廠模式來創建實例。
您最好的選擇可能是獲得匯編語言語法規范。 寫一個詞法分析器轉換為代幣(**請不要使用if-elseif-else梯子)。 然后基於語義,發出代碼。
很久以前,匯編程序至少有兩次傳遞:第一次解析常量並形成骨架代碼(包括符號表)。 第二步是產生更具體或絕對的價值。
你最近讀過龍書嗎?
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.