8

我在製作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" */ 
    } 
} 

對於每個助記符我必須檢查有效的參數,然後創建正確的編譯代碼。非常類似的代碼用於檢查每種情況下每個助記符的有效參數。

那麼是否有改進此代碼的設計模式?
或者只是一個簡單的方法來實現呢?

編輯:我接受了基林的答案,感謝他。如果你有這方面的想法,我會很樂意學習它們。謝謝大家。

回答

7

多年來我寫了大量的彙編程序來解析並坦率地說,使用語法語言和解析器生成器可能會更好。

這裏的原因 - 一個典型的組裝線可能會是這個樣子:

[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. */ } 
+0

我的記憶大約是200 KiB,我非常高興你用這個替換了以前的面向對象的代碼!現在後續(我知道...)問題:你以前使用'Instruction8051 {public string Mnemonic {get;組; } public List Info {get;組; }}命令模式?是否有任何原因/情況我更喜歡命令模式查找表? – Hossein 2011-04-07 20:35:40

+0

命令模式?不 - 他們兩人都會圍着查找桌子。早期的C#代碼只是一種利用語言對泛型集合和自動屬性的支持的優勢。 – plinth 2011-04-08 12:32:16

3

是的。大多數彙編程序使用描述指令的數據表:助記符,操作碼,操作數形式等。

我建議你看看as的源代碼。 雖然我找到了一些麻煩。 Look here。 (感謝Hossein。)

+0

@wallyk:我已經想到其中包含助記符,參數的數量對於每個助記符,它們的類型等,但速度和內存的查找表這對我來說很重要,因爲它將在具有小內存和慢速處理器的機器上運行。所以我認爲這將是我的最後選擇。 – Hossein 2011-04-07 20:05:29

+0

@Hossein:一個查找表可能不會佔用比當前擁有的多個案例代碼副本更多的內存。至於性能,你可能不會注意到任何差異。你的代碼已經在使用字符串比較和函數調用,所以它不像你在優化一個緊密的內部循環。記住80/20規則。 – Anton 2011-04-07 20:13:55

+1

表驅動的方法在小內存和緩慢的處理器實現方面經受了很大的考驗。考慮在您的方法中設置每個比較需要多少代碼。在表驅動的方法中,只有一點代碼可以進行比較,例如助記符。它在一個不比文字比較序列慢的循環內。試試兩種:我相信你會發現桌子在大多數方面都很出色。 – wallyk 2011-04-07 20:14:45

0

我想你應該看看訪問者模式。它可能不會讓你的代碼變得更簡單,但會減少耦合並提高可重用性。 SableCC是一個構建廣泛使用它的編譯器的java框架。

+0

你確定嗎?我看到訪問者主要用於樹木(如AST中的T)。您不需要在彙編程序中使用樹,而使用更大語言的編譯器。 – delnan 2011-04-07 19:59:16

+0

不,但你可以有一個助記符類,可以通過訪問者模式定義其輸出代碼。你循環助記符,傳遞給訪問者,他們返回他們自己的輸出。我從來沒有設計過一個彙編程序,所以這只是一個猜測。 – 2011-04-07 20:02:25

0

你看過「Command Dispatcher」模式嗎?

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

一般的想法是創建一個處理每一個指令(命令)的對象,並創建每個指令的處理類映射的查找表。每個命令類都有一個通用接口(例如Command.Execute(* args)),它肯定會給你一個比當前巨大的switch語句更清晰/更靈活的設計。

+0

由於性能問題,我寧願遠離「太多」的對象。 – Hossein 2011-04-09 12:23:30

0

當我在玩Microcode模擬器工具時,我將所有東西都轉換成Instruction類的後代。來自Instruction的是類別類別,例如Arithmetic_InstructionBranch_Instruction。我使用工廠模式來創建實例。

最好的辦法可能是弄清楚彙編語言的語法規範。寫一個詞法分析器轉換爲標記(**請不要使用if-elseif-else梯形圖)。然後基於語義,發佈代碼。很久以前,組裝者至少要進行兩次傳遞:第一次解析常量並形成骨架代碼(包括符號表)。第二遍是產生更多的具體或絕​​對值。

最近你讀過龍書嗎?

+0

Dragon Book是否也討論過裝配工?我仍然試圖找到它的可下載版本或獲得翻譯版本,但成功。 – Hossein 2011-04-07 20:12:08