在繁華的都市中,小悅作為一名軟體工程師,每天都在這座鋼筋水泥的森林裡忙碌。她的生活似乎被工作和各種瑣碎的事情填滿了,但在這個繁忙的生活中,她總能在工作之餘找到一些小小的樂趣。
這天下班後,小悅收到了一封來自國外同學蘇菲的email。郵件的內容讓她的思緒一下子飄回了那個學習組合語言的大學時代。
蘇菲是一個非常聰明的女孩,她們倆在大學時期成為了要好的朋友。蘇菲對程式設計有著濃厚的興趣,而小悅則是對理論知識情有獨鍾。在大學最後一年的上機考試中,她們倆透過逆波蘭表示式演算法,合作完成了一個數學算式表示式的演算法。
這個算式表示式演算法是小悅在上機考試中實現的,它主要用於解決數學算式計算的問題。但現在回想起來,她覺得這個演算法還有很多可以完善的地方,甚至可以在它基礎上開發出自己的指令碼語言直譯器。於是,她決定利用業餘時間來改進這個演算法。
在眾多實用的程式設計專案中,頁面指令碼直譯器和SQL語言直譯器是兩個比較熱門的選擇。但是這兩個專案的語法也相對複雜,因此小悅決定從更簡單的組合語言直譯器開始研究。
小悅開始仔細研究組合語言的語法和指令集,她想要用cSharp寫一個自己的彙編語法直譯器。她知道,實現一個彙編語法直譯器並不是一件容易的事情,但她也堅信,只要自己不斷努力和探索,一定能夠成功。
在實現彙編語法直譯器的過程中,小悅參考了大量的資料和文件,不斷調整和完善自己的程式碼。她遇到了很多困難和挫折,但她都沒有放棄。每當遇到問題時,她都會想起蘇菲和那個算式表示式演算法,這讓她有了繼續前進的動力。
經過一段時間的努力,小悅終於成功地寫出了一個自己的彙編語法直譯器。這個直譯器能夠解析組合語言語法,並根據程式設計師輸入的語法執行相應的操作。
小悅的閨蜜小欣看到她的成果後,打趣說:“你真是程式設計界的女俠啊!”小悅聽後笑了笑,心中不禁想起了蘇菲。她想:“如果蘇菲還在國內,我們一定會有更多的合作機會。”
然而,生活總是充滿了未知和變數。蘇菲畢業後選擇了留在國外工作和生活,而小悅則在國內繼續著她的軟體工程師生涯。雖然兩人已經很少聯絡,但小悅一直珍藏著她們之間的友誼和那個上機考試中的算式表示式演算法。
在小悅實現彙編語法直譯器的過程中,她不僅提升了自己的程式設計能力,還進一步理解了算式表示式演算法的原理和實現方式。她相信,這個經驗將會成為她未來職業生涯中的一筆寶貴財富。
如今的小悅已經不再是那個單純為了應付考試而程式設計的女孩了。她在程式設計領域有著自己的追求和夢想。她希望透過自己的努力和不斷的學習,成為一個更加優秀的軟體工程師,為這個數字化時代貢獻自己的力量。
小悅需要實現的彙編直譯器語法如下:
mov x, y - 將y(無論是整數還是暫存器的值)複製到暫存器(變數)x中。
inc x - 使暫存器x的內容增加1。
dec x - 使暫存器x的內容減少1。
add x, y - 將暫存器x的內容與y(無論是整數還是暫存器的值)相加,並將結果儲存在x中(即register[x] += y)。
sub x, y - 從暫存器x中減去y(無論是整數還是暫存器的值),並將結果儲存在x中(即register[x] -= y)。
mul x, y - 與乘法相同(即register[x] *= y)。
div x, y - 與整數除法相同(即register[x] /= y)。
label: - 定義函式標籤位置,一般用於函式定位(標籤 = 識別符號 + 冒號,識別符號是與任何其他命令不匹配的字串)。跳轉命令和呼叫針對程式中的這些標籤位置。
jmp lbl - 跳轉到自定義函式標籤lbl。
cmp x, y - 比較x(無論是整數還是暫存器的值)和y(無論是整數還是暫存器的值)。結果用於條件跳轉(jne,je,jge,jg,jle和jl)。
jne lbl - 如果上一個cmp命令的值不相等,則跳轉到標籤lbl。
je lbl - 如果上一個cmp命令的值相等,則跳轉到標籤lbl。
jge lbl - 如果在之前的cmp命令中x大於或等於y,則跳轉到標籤lbl。
jg lbl - 如果在之前的cmp命令中x大於y,則跳轉到標籤lbl。
jle lbl - 如果在之前的cmp命令中x小於或等於y,則跳轉到標籤lbl。
jl lbl - 如果在之前的cmp命令中x小於y,則跳轉到標籤lbl。
call lbl - 呼叫由lbl標識的子程式。當在子程式中找到ret時,指令指標應返回到此call命令之後的指令。
ret - 當在子程式中找到ret時,指令指標應返回到呼叫當前函式的指令。
msg '輸出結果: ',x - 這條指令儲存程式的輸出。它可能包含文字字串(以單引號分隔)和暫存器。引數的數量不限,會因程式而異。
end - 這條指令表示程式正確結束,因此返回儲存的輸出(如果程式在沒有此指令的情況下終止,它應返回預設輸出:參見下文)。
; - comment註釋指令在程式碼執行程式時不執行。
示例語法:
//程式碼定義
var program = @"
; My first program
mov a, 5
inc a
call function
msg '計算結果:(5+1)/2 = ', a ; output message
end
function:
div a, 2
ret
";
//執行程式碼
AssemblerInterpreter.Interpret(program);
//msg命令輸出結果
計算結果:(5+1)/2 = 3
演算法實現1:
1 public class AssemblerInterpreter 2 { 3 public static string Interpret(string input) 4 { 5 // 儲存暫存器的字典 6 var register = new Dictionary<string, int>(); 7 // 儲存比較結果的字串 8 var compare = ""; 9 // 儲存標籤的字典 10 var label = new Dictionary<string, int>(); 11 // 整數型別的棧,儲存程式碼當前位置 12 var stack = new Stack<int>(); 13 // 儲存訊息的字串構建器 14 var messages = new StringBuilder(); 15 16 // 將輸入的程式按行分割 17 var program = input.Split("\n"); 18 19 // 遍歷程式,找到標籤並儲存其位置 20 for(var i = 0; i < program.Length; i++) 21 { 22 var parts = program[i].Split(":"); 23 if (parts.Length == 2) label.Add(parts[0], i); 24 } 25 26 // 遍歷程式的每一行指令 27 for (var i = 0; i < program.Length; i++) 28 { 29 // 將當前行的指令解析為運算子和運算元 30 var token = TokensFrom(program[i]); 31 if (token.Length == 0) continue; // 跳過空行 32 33 // 根據運算子執行相應的操作 34 if (token[0] == "mov") 35 { 36 // 將運算元儲存到暫存器中 37 var r = token[1]; 38 var val = ValueOf(token[2]); 39 if (register.ContainsKey(r)) register[r] = val; 40 else register.Add(r, val); 41 } 42 else if (token[0] == "inc") register[token[1]++; 43 else if (token[0] == "dec") register[token[1]--; 44 else if (token[0] == "add") register[token[1]] += ValueOf(token[2]); 45 else if (token[0] == "sub") register[token[1]] -= ValueOf(token[2]); 46 else if (token[0] == "mul") register[token[1]] *= ValueOf(token[2]); 47 else if (token[0] == "div") register[token[1]] /= ValueOf(token[2]); 48 else if (token[0] == "msg") 49 { 50 // 將訊息內容新增到訊息字串構建器中 51 var args = ParseMsg(program[i]) 52 .Select(s => s.First() == '\'' 53 ? s.Length >= 2 ? s[1..^1] : s 54 : ValueOf(s).ToString()); 55 messages.Append(string.Concat(args)); 56 } 57 else if (token[0] == "call") 58 { 59 // 將當前位置壓入棧中,並跳轉到指定標籤位置 60 stack.Push(i); 61 i = label[token[1]]; 62 } 63 else if (token[0] == "ret") i = stack.Pop(); // 從棧中彈出位置並跳轉 64 else if (token[0] == "cmp") 65 { 66 // 比較兩個值並儲存比較結果 67 var x = ValueOf(token[1]); 68 var y = ValueOf(token[2]); 69 70 if (x == y) compare = "="; 71 else if (x > y) compare = ">"; 72 else if (x < y) compare = "<"; 73 else compare = "!="; 74 } 75 // 根據比較結果進行條件跳轉 76 else if (token[0] == "jmp") i = label[token[1]]; 77 else if (token[0] == "jne") 78 { 79 if (compare != "=") i = label[token[1]]; 80 } 81 else if (token[0] == "je") 82 { 83 if (compare == "=") i = label[token[1]]; 84 } 85 else if (token[0] == "jge") 86 { 87 if (compare == ">" || compare == "=") i = label[token[1]]; 88 } 89 else if (token[0] == "jg") 90 { 91 if (compare == ">") i = label[token[1]]; 92 } 93 else if (token[0] == "jle") 94 { 95 if (compare == "<" || compare == "=") i = label[token[1]]; 96 } 97 else if (token[0] == "jl") 98 { 99 if (compare == "<") i = label[token[1]]; 100 } 101 else if (token[0] == "end") return messages.ToString(); 102 } 103 104 return null; 105 106 // 從輸入的行中提取標記並返回標記陣列 107 string[] TokensFrom(string line) => 108 Regex.Split(RemoveComment(line), "[ ,]+") // 使用正規表示式分割去除註釋後的行,並去除空格 109 .Select(s => s.Trim()) // 去除每個標記的首尾空格 110 .Where(s => !string.IsNullOrEmpty(s)) // 去除空字串 111 .ToArray(); // 轉換為陣列 112 113 // 解析訊息內容並返回訊息引數陣列 114 string[] ParseMsg(string line) 115 { 116 var args = Regex.Replace(line, @"^\s*msg\s*", ""); // 去除訊息指令並獲取訊息內容 117 var result = new List<string>(); // 建立儲存結果的列表 118 119 var token = new StringBuilder(); // 建立 StringBuilder 儲存當前標記 120 var inQuote = false; // 標記是否在引號內 121 foreach (var c in args) // 遍歷訊息內容的每個字元 122 { 123 if (c == '\'' && inQuote) // 如果是引號且在引號內 124 { 125 inQuote = false; // 標記離開引號狀態 126 token.Append(c); // 將引號新增到標記中 127 } 128 else if (c == '\'' && !inQuote) // 如果是引號且不在引號內 129 { 130 inQuote = true; // 標記進入引號狀態 131 token.Append(c); // 將引號新增到標記中 132 } 133 else if (c == ',' && !inQuote) // 如果是逗號且不在引號內 134 { 135 result.Add(token.ToString().Trim()); // 將當前標記去除首尾空格後新增到結果列表 136 token.Clear(); // 清空當前標記 137 } 138 else if (c == ' ' && !inQuote) continue; // 如果是空格且不在引號內,則繼續下一次迴圈 139 else if (c == ';' && !inQuote) break; // 如果是分號且不在引號內,則結束迴圈 140 else token.Append(c); // 其他情況將字元新增到當前標記中 141 } 142 if (token.Length > 0) result.Add(token.ToString().Trim()); // 將最後一個標記去除首尾空格後新增到結果列表 143 return result.ToArray(); // 轉換為陣列並返回 144 } 145 146 // 去除行中的註釋並返回處理後的行 147 string RemoveComment(string line) => Regex.Replace(line, ";.*", ""); // 使用正規表示式去除分號及其後的內容 148 149 // 獲取變數的值,如果是整數則直接返回,否則從暫存器中獲取 150 int ValueOf(string x) => int.TryParse(x, out var r) ? r : register[x]; 151 } 152 }
這段程式碼實現了一個基於指令集的虛擬機器執行演算法直譯器。它解釋並定義了一系列基本的彙編指令,包括mov、inc、dec、add、sub、mul、div、msg、call、ret、cmp、jmp、jne、je、jge、jg、jle、jl和end。這些指令可以操作暫存器、棧、標籤和訊息,並且可以進行條件跳轉和比較操作。
這個演算法是一個彙編直譯器,它模擬了虛擬機器的功能。在演算法中,透過使用字典儲存暫存器的值、比較結果的字串、標籤的位置等資訊,以及使用棧儲存程式碼當前位置,來模擬虛擬機器的處理器、記憶體、暫存器等功能。演算法遍歷輸入的程式,解析每一行指令並執行相應的操作,如將運算元儲存到暫存器中、進行加減乘除運算、比較兩個值等。同時,演算法還實現了條件跳轉和訊息輸出等功能,以模擬虛擬機器的指令集。透過這種方式,演算法實現了對輸入程式的解釋和執行,從而實現了對虛擬機器的模擬。這樣的虛擬機器模擬可以使程式在不同的平臺上執行,提高了程式的靈活性和可移植性。
虛擬機器程式AssemblerInterpreter.Interpret作為一個.net宿主程式,接收並執行這些外部輸入的組合語言程式碼。
該演算法首先接收一個彙編語法表示式的字串作為輸入,然後透過解析字串,逐個獲取運算元和運算子。在解析過程中,演算法會根據運算子的型別和運算元的值,進行相應的計算操作。這些操作包括將指令位置壓入棧中、從棧中彈出指令在暫存器中的位置並進行計算等。
演算法的核心部分是遍歷語法表示式並執行相應的操作。在這個過程中,演算法會逐個解析字串中的字元,並根據解析的結果執行相應的計算步驟。一旦完成計算,演算法會將最終的結果儲存在棧頂,而棧頂的元素就是最終的指令計算結果。
注1:else if (token[0] == "ret") i = stack.Pop(); // 從棧中彈出位置並跳轉
這行程式碼是直譯器中處理ret
指令的部分。當直譯器遇到ret
指令時,它需要從呼叫棧中彈出一個位置並跳轉到該位置。
讓我們來解釋一下這行程式碼的邏輯:
- 首先,直譯器解析到
ret
指令,並將其作為一個運算子token
。 - 接著,直譯器檢查
token[0]
是否等於"ret"
,即檢查標記的第一個元素是否是"ret"
。這是為了確保當前指令是ret
指令。 - 如果
token[0]
等於"ret"
,則執行i = stack.Pop()
,這意味著從呼叫棧stack
中彈出一個位置,並將其賦值給程式計數器i
。這樣程式計數器將跳轉到之前呼叫指令的下一條指令位置,實現了ret
指令的跳轉功能。
注2:在程式碼47行,運算子分支處理中,可以根據實際情況擴充套件新的賦值函式
else if (token[0] == "div") register[token[1]] /= ValueOf(token[2]);
else if (token[0] == "expression") ... //在這裡可擴充套件其他語法功能,除了加減乘除,還可以新增逆波蘭表示式演算法進行變數賦值或其他自定義函式處理等
基於指令集的虛擬機器執行演算法的歷史故事可以追溯到電腦科學和軟體工程的早期發展階段,它經歷了多個關鍵的里程碑和發展趨勢:
1. 早期計算機:在早期的計算機系統中,程式設計師需要直接編寫機器碼指令,這些指令直接操作計算機的硬體。這種方式對於程式設計師來說非常繁瑣和複雜,因此人們開始尋找更高階的抽象方式來編寫程式。這個時期,一些先驅者提出了組合語言,它提供了一種更接近硬體的程式設計方式,使得程式設計師可以更容易地編寫機器碼指令。
2. 組合語言:為了簡化程式設計師編寫機器碼指令的工作,組合語言被引入。組合語言是一種低階的程式語言,它使用助記符(mnemonics)來代替機器碼指令,並提供了一些符號標籤來簡化跳轉和呼叫等操作。這使得程式設計師可以更加高效地編寫程式,減少了出錯的可能性。
高階語言:1957年:IBM的約翰·巴科斯(John Backus)建立全世界第一套高階語言:Fortran(formula translate)。Fortran的跨平臺限制主要是由於其與特定的作業系統和硬體架構的緊密相關性,如windows/linux,以及不同編譯器和平臺的差異和限制。如果需要在不同的作業系統或硬體平臺上執行Fortran程式,可能需要重新編譯或修改程式,以確保它能夠在目標平臺上正確執行。
3. 虛擬機器:為了實現跨平臺的程式執行,虛擬機器概念被引入。虛擬機器是一個軟體實體,它模擬了一臺計算機,包括處理器、記憶體、暫存器等,並提供了一組指令集。程式設計師可以使用這個指令集來編寫程式,並在虛擬機器上執行,如JVM,而不需要關心底層硬體和作業系統。虛擬機器的引入使得程式可以在不同的平臺上執行,提高了程式的靈活性和可移植性。
4. 基於指令集的虛擬機器執行演算法:虛擬機器中的核心部分是基於指令集的虛擬機器執行演算法。它負責解釋和執行程式中的指令,包括資料操作、程式碼跳轉、函式呼叫等。執行演算法通常包括指令解析、暫存器操作、記憶體訪問、跳轉控制等步驟。這個演算法的實現除了完成基本指令需求,還需要考慮效能、安全性和穩定性等方面的問題。
5. 發展趨勢:隨著電腦科學和軟體工程的發展,基於指令集的虛擬機器執行演算法得到了不斷的改進和最佳化。例如,引入了即時編譯(JIT)技術、增強了指令集、最佳化了執行引擎等,使得虛擬機器執行演算法在效能和功能上得到了顯著提升。同時,隨著雲端計算和移動網際網路的普及,基於指令集的虛擬機器執行演算法也在這些領域得到了廣泛的應用和發展。
透過測試用例(5+1)/2,我們將執行AssemblerInterpreter.Interpret(program)
來執行這段程式,並期望msg
命令輸出"計算結果:(5+1)/2 = 3"。
程式的執行步驟如下:
- 首先,
AssemblerInterpreter.Interpret(program)
將直譯器應用於給定的程式字串program
。 - 直譯器將逐行解析程式字串,並執行相應的操作。在直譯器中,會呼叫
TokensFrom
函式來解析每一行的標記。 - 對於每個標記,直譯器將執行相應的操作,比如
mov
指令會移動一個值5到一個暫存器,inc
指令會增加暫存器中的值,即(5+1),call
指令會呼叫一個函式function,即(5+1)/2,msg
指令會輸出常量訊息和a的值3。 - 在執行
msg
指令時,直譯器會呼叫ParseMsg
函式來解析訊息內容,並輸出最終的訊息結果。
在這個測試用例中,當msg
指令被執行時,ParseMsg
函式會解析訊息內容"'計算結果:(5+1)/2 = ', a",並輸出"計算結果:(5+1)/2 = 3"作為最終結果。
因此,透過執行AssemblerInterpreter.Interpret(program)
,程式將按預期輸出"計算結果:(5+1)/2 = 3"。
演算法實現2:
1 public class AssemblerInterpreter 2 { 3 public static string Interpret(string input) 4 { 5 Regex trimmer = new Regex(@"\s\s+"); 6 string[] unparsedInstructions = input.Split("\n"); 7 Dictionary<string, int> registers = new Dictionary<string, int>(); 8 Dictionary<string, int> labels = new Dictionary<string, int>(); 9 IInstruction[] instructions = new IInstruction[unparsedInstructions.Length]; 10 int endOfProgram = 0; 11 //cast to instructions 12 for(int i = 0; i < unparsedInstructions.Length; i++) 13 { 14 string cleanString = unparsedInstructions[i].TrimStart(); 15 cleanString = trimmer.Replace(cleanString, " "); 16 string[] instructionParts = cleanString.Split(" "); 17 switch(instructionParts[0]){ 18 case ";": 19 instructions[i] = new Idle(); 20 break; 21 case "msg": 22 instructions[i] = new Msg(cleanString.Substring(4)); 23 break; 24 case "call": 25 instructions[i] = new Call(instructionParts[1]); 26 break; 27 case "mov": 28 instructions[i] = new Mov(instructionParts[1].Remove(1), new Arguement(instructionParts[2])); 29 break; 30 case "inc": 31 instructions[i] = new Inc(instructionParts[1]); 32 break; 33 case "dec": 34 instructions[i] = new Dec(instructionParts[1]); 35 break; 36 case "add": 37 instructions[i] = new Add(instructionParts[1].Remove(1), new Arguement(instructionParts[2])); 38 break; 39 case "sub": 40 instructions[i] = new Sub(instructionParts[1].Remove(1), new Arguement(instructionParts[2])); 41 break; 42 case "div": 43 instructions[i] = new Div(instructionParts[1].Remove(1), new Arguement(instructionParts[2])); 44 break; 45 case "mul": 46 instructions[i] = new Mul(instructionParts[1].Remove(1), new Arguement(instructionParts[2])); 47 break; 48 case "cmp": 49 instructions[i] = new Cmp(new Arguement(instructionParts[1].Remove(1)), new Arguement(instructionParts[2])); 50 break; 51 case "jmp": 52 instructions[i] = new Jmp(instructionParts[1]); 53 break; 54 case "jne": 55 instructions[i] = new Jne(instructionParts[1]); 56 break; 57 case "je": 58 instructions[i] = new Je(instructionParts[1]); 59 break; 60 case "jge": 61 instructions[i] = new Jge(instructionParts[1]); 62 break; 63 case "jg": 64 instructions[i] = new Jg(instructionParts[1]); 65 break; 66 case "jle": 67 instructions[i] = new Jle(instructionParts[1]); 68 break; 69 case "jl": 70 instructions[i] = new Jl(instructionParts[1]); 71 break; 72 case "ret": 73 instructions[i] = new Ret(); 74 break; 75 case "end": 76 endOfProgram = i; 77 break; 78 default: 79 if(instructionParts[0].Contains(":") && instructionParts[0].IndexOf(":") == instructionParts[0].Length - 1) 80 { 81 labels.Add(instructionParts[0].Remove(instructionParts[0].Length - 1) , i); 82 } 83 instructions[i] = new Idle(); 84 break; 85 } 86 } 87 return RunProgram(registers, labels, instructions, endOfProgram); 88 } 89 90 public static string RunProgram(Dictionary<string, int> registers, Dictionary<string, int> labels, IInstruction[] instructions,int endOfprogram) 91 { 92 bool ended = false; 93 int memoryPointer = 0; 94 string output = string.Empty; 95 string ans = string.Empty; 96 Stack<int> callStack = new Stack<int>(); 97 int comparison = 0; 98 while (memoryPointer < instructions.Length) 99 { 100 if(endOfprogram == memoryPointer) 101 { 102 ended = true; 103 break; 104 } 105 memoryPointer = instructions[memoryPointer].Execute(registers, labels, callStack, memoryPointer, ref comparison, out output); 106 ans = $"{ans}{output}"; 107 } 108 if(ended) 109 { 110 return ans; 111 } 112 else 113 { 114 return null; 115 } 116 } 117 118 class Arguement 119 { 120 int _number; 121 string _reg; 122 123 public Arguement(string value) 124 { 125 if (!int.TryParse(value, out _number)) 126 { 127 _reg = value; 128 } 129 else 130 { 131 _reg = string.Empty; 132 } 133 } 134 135 public int GetArguementValue(Dictionary<string, int> registers) 136 { 137 int value; 138 return registers.TryGetValue(_reg, out value) ? value : _number; 139 } 140 } 141 142 public interface IInstruction 143 { 144 int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output); 145 } 146 147 class Idle : IInstruction 148 { 149 public Idle() { } 150 151 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 152 { 153 output = string.Empty; 154 return pointer + 1; 155 } 156 } 157 158 class Msg : IInstruction 159 { 160 string _msg; 161 public Msg(string msgAndRegs) 162 { 163 _msg = msgAndRegs; 164 } 165 166 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 167 { 168 string[] parts = _msg.Split("'"); 169 170 171 bool insideQoute = false; 172 int QouteStart = 0; 173 Queue<Tuple<string, string>> message = new Queue<Tuple<string, string>>(); 174 for(int i = 0; i < _msg.Length; i++) 175 { 176 string character = _msg[i].ToString(); 177 if(!insideQoute && character.Equals(";")) 178 { 179 break; 180 } 181 if(character.Equals("'")) 182 { 183 if(insideQoute) 184 { 185 message.Enqueue(new Tuple<string, string>("message", _msg.Substring(QouteStart + 1, i - QouteStart - 1))); 186 insideQoute = false; 187 } 188 else 189 { 190 insideQoute = true; 191 QouteStart = i; 192 } 193 } 194 else if(!insideQoute) 195 { 196 if (!character.Equals(" ") && !character.Equals(",")) 197 { 198 message.Enqueue(new Tuple<string, string>("reg", character.ToString())); 199 } 200 } 201 } 202 output = string.Empty; 203 while(message.Count > 0) 204 { 205 Tuple<string, string> temp = message.Dequeue(); 206 if (temp.Item1.Equals("message")) 207 { 208 output = $"{output}{temp.Item2}"; 209 } 210 else 211 { 212 output = $"{output}{registers[temp.Item2]}"; 213 } 214 } 215 return pointer + 1; 216 } 217 } 218 219 class Call : IInstruction 220 { 221 string _label; 222 public Call(string label) 223 { 224 _label = label; 225 } 226 227 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 228 { 229 output = string.Empty; 230 callStack.Push(pointer); 231 return labels[_label] + 1; 232 } 233 } 234 235 class Mov : IInstruction 236 { 237 string _reg; 238 Arguement _arg; 239 public Mov(string reg, Arguement arg){ 240 _reg = reg; 241 _arg = arg; 242 } 243 244 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 245 { 246 output = string.Empty; 247 registers[_reg] = _arg.GetArguementValue(registers); 248 return pointer + 1; 249 } 250 } 251 252 class Inc : IInstruction 253 { 254 string _reg; 255 public Inc(string reg) 256 { 257 _reg = reg; 258 } 259 260 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 261 { 262 output = string.Empty; 263 registers[_reg]++; 264 return pointer + 1; 265 } 266 } 267 268 class Dec : IInstruction 269 { 270 string _reg; 271 public Dec(string reg) 272 { 273 _reg = reg; 274 } 275 276 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 277 { 278 output = string.Empty; 279 registers[_reg]--; 280 return pointer + 1; 281 } 282 } 283 284 class Add : IInstruction 285 { 286 string _reg; 287 Arguement _arg; 288 public Add(string reg, Arguement arg) 289 { 290 _reg = reg; 291 _arg = arg; 292 } 293 294 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 295 { 296 output = string.Empty; 297 registers[_reg] += _arg.GetArguementValue(registers); 298 return pointer + 1; 299 } 300 } 301 302 class Sub : IInstruction 303 { 304 string _reg; 305 Arguement _arg; 306 public Sub(string reg, Arguement arg) 307 { 308 _reg = reg; 309 _arg = arg; 310 } 311 312 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 313 { 314 output = string.Empty; 315 registers[_reg] -= _arg.GetArguementValue(registers); 316 return pointer + 1; 317 } 318 } 319 320 class Mul : IInstruction 321 { 322 string _reg; 323 Arguement _arg; 324 public Mul(string reg, Arguement arg) 325 { 326 _reg = reg; 327 _arg = arg; 328 } 329 330 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 331 { 332 output = string.Empty; 333 registers[_reg] *= _arg.GetArguementValue(registers); 334 return pointer + 1; 335 } 336 } 337 338 class Div : IInstruction 339 { 340 string _reg; 341 Arguement _arg; 342 public Div(string reg, Arguement arg) 343 { 344 _reg = reg; 345 _arg = arg; 346 } 347 348 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 349 { 350 output = string.Empty; 351 registers[_reg] /= _arg.GetArguementValue(registers); 352 return pointer + 1; 353 } 354 } 355 356 class Ret : IInstruction 357 { 358 public Ret() { } 359 360 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 361 { 362 output = string.Empty; 363 return callStack.Pop() + 1; 364 } 365 } 366 367 class Cmp : IInstruction 368 { 369 Arguement _leftArg; 370 Arguement _rightArg; 371 public Cmp(Arguement leftArg, Arguement rightArg) 372 { 373 _leftArg = leftArg; 374 _rightArg = rightArg; 375 } 376 377 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 378 { 379 output = string.Empty; 380 comparison = _leftArg.GetArguementValue(registers) - _rightArg.GetArguementValue(registers); 381 return pointer + 1; 382 } 383 } 384 385 class Jmp : IInstruction 386 { 387 string _label; 388 389 public Jmp(string label) 390 { 391 _label = label; 392 } 393 394 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 395 { 396 output = string.Empty; 397 return labels[_label] + 1; 398 } 399 } 400 401 class Jne : IInstruction 402 { 403 string _label; 404 405 public Jne(string label) 406 { 407 _label = label; 408 } 409 410 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 411 { 412 output = string.Empty; 413 if(comparison != 0) 414 { 415 return labels[_label] + 1; 416 } 417 return pointer + 1; 418 } 419 } 420 421 class Je : IInstruction 422 { 423 string _label; 424 425 public Je(string label) 426 { 427 _label = label; 428 } 429 430 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 431 { 432 output = string.Empty; 433 if (comparison == 0) 434 { 435 return labels[_label] + 1; 436 } 437 return pointer + 1; 438 } 439 } 440 441 class Jge : IInstruction 442 { 443 string _label; 444 445 public Jge(string label) 446 { 447 _label = label; 448 } 449 450 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 451 { 452 output = string.Empty; 453 if (comparison >= 0) 454 { 455 return labels[_label] + 1; 456 } 457 return pointer + 1; 458 } 459 } 460 461 class Jg : IInstruction 462 { 463 string _label; 464 465 public Jg(string label) 466 { 467 _label = label; 468 } 469 470 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 471 { 472 output = string.Empty; 473 if (comparison > 0) 474 { 475 return labels[_label] + 1; 476 } 477 return pointer + 1; 478 } 479 } 480 481 class Jle : IInstruction 482 { 483 string _label; 484 485 public Jle(string label) 486 { 487 _label = label; 488 } 489 490 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 491 { 492 output = string.Empty; 493 if (comparison <= 0) 494 { 495 return labels[_label] + 1; 496 } 497 return pointer + 1; 498 } 499 } 500 501 class Jl : IInstruction 502 { 503 string _label; 504 505 public Jl(string label) 506 { 507 _label = label; 508 } 509 510 public int Execute(Dictionary<string, int> registers, Dictionary<string, int> labels, Stack<int> callStack, int pointer, ref int comparison, out string output) 511 { 512 output = string.Empty; 513 if (comparison < 0) 514 { 515 return labels[_label] + 1; 516 } 517 return pointer + 1; 518 } 519 } 520 }
演算法2與演算法1的實現效果是一樣的:
優點:
1. 使用了正規表示式來清理和解析輸入,使得輸入處理更加靈活和健壯。
2. 使用了字典來儲存暫存器和標籤,提高了程式的執行效率。
3. 使用了介面和多型來執行指令,使得程式碼更加模組化和可擴充套件。
4. 使用了堆疊來跟蹤呼叫和返回地址,使得程式執行流程更加清晰。
缺點:
1. 程式碼相對較長,可能導致可讀性較差。
總體來說,演算法2相比演算法1更加靈活和健壯,但也可能更加複雜。在處理複雜的指令集和程式邏輯時可能更加適用。
測試用例:
1 using NUnit.Framework; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Text.RegularExpressions; 7 8 [TestFixture] 9 public class AssemblerInterpreterTest 10 { 11 12 #region Basic tests 13 [Test] 14 public void TestBasic() 15 { 16 for (int i = 0; i < expected.Length; i++) 17 { 18 Assert.AreEqual(expected[i], AssemblerInterpreter.Interpret(displayProg(progs[i]))); 19 } 20 } 21 22 private static string[] progs = { 23 "\n; My first program\nmov a, 5\ninc a\ncall function\nmsg '(5+1)/2 = ', a ; output message\nend\n\nfunction:\n div a, 2\n ret\n", 24 "\nmov a, 5\nmov b, a\nmov c, a\ncall proc_fact\ncall print\nend\n\nproc_fact:\n dec b\n mul c, b\n cmp b, 1\n jne proc_fact\n ret\n\nprint:\n msg a, '! = ', c ; output text\n ret\n", 25 "\nmov a, 8 ; value\nmov b, 0 ; next\nmov c, 0 ; counter\nmov d, 0 ; first\nmov e, 1 ; second\ncall proc_fib\ncall print\nend\n\nproc_fib:\n cmp c, 2\n jl func_0\n mov b, d\n add b, e\n mov d, e\n mov e, b\n inc c\n cmp c, a\n jle proc_fib\n ret\n\nfunc_0:\n mov b, c\n inc c\n jmp proc_fib\n\nprint:\n msg 'Term ', a, ' of Fibonacci series is: ', b ; output text\n ret\n", 26 "\nmov a, 11 ; value1\nmov b, 3 ; value2\ncall mod_func\nmsg 'mod(', a, ', ', b, ') = ', d ; output\nend\n\n; Mod function\nmod_func:\n mov c, a ; temp1\n div c, b\n mul c, b\n mov d, a ; temp2\n sub d, c\n ret\n", 27 "\nmov a, 81 ; value1\nmov b, 153 ; value2\ncall init\ncall proc_gcd\ncall print\nend\n\nproc_gcd:\n cmp c, d\n jne loop\n ret\n\nloop:\n cmp c, d\n jg a_bigger\n jmp b_bigger\n\na_bigger:\n sub c, d\n jmp proc_gcd\n\nb_bigger:\n sub d, c\n jmp proc_gcd\n\ninit:\n cmp a, 0\n jl a_abs\n cmp b, 0\n jl b_abs\n mov c, a ; temp1\n mov d, b ; temp2\n ret\n\na_abs:\n mul a, -1\n jmp init\n\nb_abs:\n mul b, -1\n jmp init\n\nprint:\n msg 'gcd(', a, ', ', b, ') = ', c\n ret\n", 28 "\ncall func1\ncall print\nend\n\nfunc1:\n call func2\n ret\n\nfunc2:\n ret\n\nprint:\n msg 'This program should return null'\n", 29 "\nmov a, 2 ; value1\nmov b, 10 ; value2\nmov c, a ; temp1\nmov d, b ; temp2\ncall proc_func\ncall print\nend\n\nproc_func:\n cmp d, 1\n je continue\n mul c, a\n dec d\n call proc_func\n\ncontinue:\n ret\n\nprint:\n msg a, '^', b, ' = ', c\n ret\n"}; 30 31 private static string[] expected = {"(5+1)/2 = 3", 32 "5! = 120", 33 "Term 8 of Fibonacci series is: 21", 34 "mod(11, 3) = 2", 35 "gcd(81, 153) = 9", 36 null, 37 "2^10 = 1024"}; 38 #endregion 39 40 #region Random tests 41 [Test] 42 public void RandomTests() 43 { 44 for (int i = 0; i < 1024; i++) 45 { 46 string randProg = GetRandomProg(); 47 string expected = InternalAssembler.Interpret(randProg), 48 actual = AssemblerInterpreter.Interpret(randProg); 49 50 if (expected != null && !expected.Equals(actual) 51 || expected == null && actual != null) 52 displayProg(randProg); 53 54 Assert.AreEqual(expected, actual); 55 } 56 } 57 58 59 private static string[] VARS = { "a", "b", "d", "t", "h", "k", "s", "m", "n", "g", "q", "e", "c", "o", "i", "u" }; 60 private static string[] JUMPS = { "jne", "je", "jge", "jg", "jle", "jl" }; 61 private static string[] OPERATIONS = { "add", "div", "sub", "mul" }; 62 private static string BASE_PROG = "\nmov {0}, {3} ; instruction mov {0}, {3}\nmov {1}, {4} ; instruction mov {1}, {4}\ncall func\nmsg 'Random result: ', {2}\nend\n\nfunc:\n cmp {0}, {1}\n {5} exit\n mov {2}, {0}\n {6} {2}, {1}\n ret\n; Do nothing\nexit:\n msg 'Do nothing'\n"; 63 64 private Random rand = new Random(); 65 66 private string GetRandomProg() 67 { 68 ISet<string> s = new HashSet<string>(); 69 while (s.Count != 3) s.Add(VARS[rand.Next(VARS.Length)]); 70 List<string> vars = new List<string>(s); 71 return string.Format(BASE_PROG, vars[0], 72 vars[1], 73 vars[2], 74 "" + 1 + rand.Next(15), 75 "" + 1 + rand.Next(15), 76 JUMPS[rand.Next(JUMPS.Length)], 77 OPERATIONS[rand.Next(OPERATIONS.Length)]); 78 } 79 80 private static class InternalAssembler 81 { 82 private static Regex TOKENIZER = new Regex(";.*|(?<cmd>('.*?'|-?\\w+))[:,]?\\s*"); 83 84 private static IDictionary<string, Func<int, int, bool>> CMP_FUNCS = new Dictionary<string, Func<int, int, bool>>(); 85 private static Dictionary<string, Func<int, int, int>> MATH_BI_FUNCS = new Dictionary<string, Func<int, int, int>>(), 86 MATH_MONO_FUNCS = new Dictionary<string, Func<int, int, int>>(); 87 private static ISet<string> JUMPS_CMD = new HashSet<string>(CMP_FUNCS.Keys); 88 private static ISet<string> ALL_CMDS = new HashSet<string>(JUMPS_CMD); 89 90 static InternalAssembler() 91 { 92 CMP_FUNCS.Add("jmp", (x, y) => true); 93 CMP_FUNCS.Add("jne", (x, y) => x != y); 94 CMP_FUNCS.Add("je", (x, y) => x == y); 95 CMP_FUNCS.Add("jge", (x, y) => x >= y); 96 CMP_FUNCS.Add("jg", (x, y) => x > y); 97 CMP_FUNCS.Add("jle", (x, y) => x <= y); 98 CMP_FUNCS.Add("jl", (x, y) => x < y); 99 100 MATH_BI_FUNCS.Add("add", (x, y) => x + y); 101 MATH_BI_FUNCS.Add("sub", (x, y) => x - y); 102 MATH_BI_FUNCS.Add("mul", (x, y) => x * y); 103 MATH_BI_FUNCS.Add("div", (x, y) => x / y); 104 105 MATH_MONO_FUNCS.Add("inc", MATH_BI_FUNCS["add"]); 106 MATH_MONO_FUNCS.Add("dec", MATH_BI_FUNCS["sub"]); 107 108 JUMPS_CMD.Add("call"); 109 ALL_CMDS.UnionWith(new[] { "ret", "end", "mov", "cmp", "msg" }); 110 ALL_CMDS.UnionWith(MATH_BI_FUNCS.Keys); 111 ALL_CMDS.UnionWith(MATH_MONO_FUNCS.Keys); 112 } 113 114 private static int pointer; 115 private static Dictionary<string, int> registers, jumpsLbl; 116 private static Dictionary<string, bool> cmpDct; 117 private static StringBuilder output; 118 private static Stack<int> callStackP; 119 private static List<List<string>> instructions; 120 121 public static string Interpret(string input) 122 { 123 pointer = 0; 124 registers = new Dictionary<string, int>(); 125 cmpDct = new Dictionary<string, bool>(); 126 output = new StringBuilder(); 127 callStackP = new Stack<int>(); 128 129 TokenizeProgram(input); 130 SeekJumpLabels(); 131 UpdateCmp("0", "0"); 132 133 while (0 <= pointer && pointer < instructions.Count) 134 { 135 string cmd = instructions[pointer][0]; 136 137 if (CMP_FUNCS.ContainsKey(cmd)) pointer = MoveTo(cmd, Label()); 138 else if (MATH_BI_FUNCS.ContainsKey(cmd)) UpdateRegs(MATH_BI_FUNCS[cmd], x(), y()); 139 else if (MATH_MONO_FUNCS.ContainsKey(cmd)) UpdateRegs(MATH_MONO_FUNCS[cmd], x(), "1"); 140 else if (cmd.Equals("mov")) registers[x()] = IsNum(y()) ? int.Parse(y()) : (registers.ContainsKey(y()) ? registers[y()] : 0); 141 else if (cmd.Equals("cmp")) UpdateCmp(x(), y()); 142 else if (cmd.Equals("call")) { callStackP.Push(pointer); pointer = MoveTo("jmp", Label()); } 143 else if (cmd.Equals("ret")) pointer = callStackP.Pop(); 144 else if (cmd.Equals("msg")) output.Append(FormatMessage(instructions[pointer].GetRange(1, instructions[pointer].Count - 1))); 145 else if (cmd.Equals("end")) return output.ToString(); 146 147 pointer++; 148 } 149 return null; 150 } 151 152 153 private static string Label() { return x(); } 154 private static string x() { return instructions[pointer][1]; } 155 private static string y() { return instructions[pointer][2]; } 156 private static bool IsNum(string s) { return Regex.IsMatch(s, "^-?\\d+$"); } 157 private static int MoveTo(string cmd, string label) { return cmpDct[cmd] ? jumpsLbl[label] : pointer; } 158 159 private static void TokenizeProgram(string input) 160 { 161 instructions = new List<List<string>>(); 162 163 int last = -1; 164 foreach (string line in input.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)) 165 { 166 last++; 167 168 Match mTok = TOKENIZER.Match(line); 169 instructions.Add(new List<string>()); 170 171 while (mTok.Success) 172 { 173 string tok = mTok.Groups["cmd"].Value; 174 if (tok != null && tok.Length != 0) 175 instructions[last].Add(mTok.Groups["cmd"].Value); 176 177 mTok = mTok.NextMatch(); 178 } 179 180 if (instructions[last].Count == 0) 181 { 182 instructions.RemoveAt(last); 183 last--; 184 } 185 } 186 } 187 188 private static void SeekJumpLabels() 189 { 190 jumpsLbl = new Dictionary<string, int>(); 191 int i = -1; 192 foreach (List<string> cmd in instructions) 193 { 194 i++; 195 if (!ALL_CMDS.Contains(cmd[0])) jumpsLbl[cmd[0]] = i; 196 } 197 } 198 199 private static void UpdateCmp(string xS, string yS) 200 { 201 foreach (string f in CMP_FUNCS.Keys) 202 { 203 int x; 204 if (!registers.TryGetValue(xS, out x)) 205 x = 0; 206 207 int yop; 208 if (!registers.TryGetValue(yS, out yop)) 209 yop = 0; 210 211 int y = IsNum(yS) ? int.Parse(yS) : yop; 212 cmpDct[f] = CMP_FUNCS[f](x, y); 213 } 214 } 215 216 private static void UpdateRegs(Func<int, int, int> op, string x, string y) 217 { 218 int yop; 219 if (!registers.TryGetValue(y, out yop)) 220 yop = 0; 221 222 registers[x] = op(registers[x], IsNum(y) ? int.Parse(y) : yop); 223 } 224 225 private static string FormatMessage(List<string> lst) 226 { 227 return string.Join("", lst.Select(s => registers.ContainsKey(s) ? registers[s].ToString() : (IsNum(s) ? s : s.Substring(1, s.Length - 2)))); 228 } 229 } 230 231 232 #endregion 233 234 private string displayProg(string p) 235 { 236 Console.WriteLine("\n----------------------\n"); 237 Console.WriteLine(p); 238 return p; 239 } 240 }
測試用例中的隨機測試演算法RandomTests()是透過生成隨機的程式程式碼來測試直譯器的正確性。具體來說,它生成了一個基礎的程式程式碼模板 `BASE_PROG`,並在其中隨機選擇了三個暫存器變數,一個運算子和一個跳轉條件來隨機生成一個新的程式程式碼。生成的程式程式碼包含了一些基本的指令,如mov、call、msg、cmp、ret等,以及隨機選擇的運算子和跳轉條件。生成的程式程式碼會被傳遞給兩個直譯器(InternalAssembler和AssemblerInterpreter)進行解釋執行,然後比較它們的輸出結果是否一致。
具體來說,生成程式程式碼的過程如下:
1. 從預定義的變數陣列 `VARS` 中隨機選擇三個不同的暫存器變數。
2. 從預定義的運算子陣列 `OPERATIONS` 中隨機選擇一個運算子。
3. 從預定義的跳轉條件陣列 `JUMPS` 中隨機選擇一個跳轉條件。
4. 將隨機選擇的暫存器變數、運算子和跳轉條件填入基礎程式程式碼模板 `BASE_PROG` 中,生成一個新的程式程式碼。
生成的程式程式碼會包含隨機選擇的暫存器變數、運算子和跳轉條件,以及一些固定的指令,如mov、call、msg、cmp、ret等。生成的程式程式碼會被傳遞給兩個直譯器進行解釋執行,並比較它們的輸出結果是否一致。如果兩個直譯器的輸出結果不一致,會輸出生成的程式程式碼,以便進行除錯和分析。
RamdomTests()隨機測試,以確保兩種不同的直譯器(InternalAssembler
和 AssemblerInterpreter
)期望對於相同的組合語言程式能夠產生相同的結果。
- 測試用例:在這段程式碼中,透過一個迴圈(
for (int i = 0; i < 1024; i++)
),生成了1024個隨機的彙編程式(randProg
)。每一個這樣的程式都被用來同時測試兩個直譯器:InternalAssembler
和AssemblerInterpreter
。 - 期望值與實際值的對比:對於每一個隨機生成的程式,都同時得到了兩個結果:一個是測試類中定義的直譯器
InternalAssembler
的解釋結果(expected
),另一個是AssemblerInterpreter
的解釋結果(actual
)。然後,這兩個結果被對比。 - 檢查結果:如果兩個直譯器的結果不同(
expected != actual
),那麼會列印出這個有問題的程式(displayProg(randProg)
)。然後,會使用斷言(Assert.AreEqual(expected, actual)
)來確認兩個結果是否一致。如果兩個結果一致,那麼測試透過;否則,測試失敗。
對比兩個直譯器結果的測試方法的必要性在於:
- 確保正確性:透過對比兩個直譯器的結果,可以確認它們是否都能正確地解釋彙編程式。如果有任何不一致,那麼可以立即發現並調查原因,從而改進直譯器的準確性。
- 效能比較:雖然此程式碼沒有明確地比較執行時間,但透過同時執行兩個直譯器並比較結果,可以在某種程度上間接地比較它們的效能。如果一個直譯器總是比另一個快,那麼可以考慮最佳化它。
- 一致性:即使兩個直譯器的結果是正確的,也可能會因為實現的不同而產生微小的差異。透過對比這些差異,可以確保它們在實現上的一致性,避免潛在的混淆和錯誤。
總的來說,這種對比是一種有效的測試策略,可以確保直譯器的正確性和一致性,同時還可以在某種程度上評估它們的效能。