【演算法】遠方來信,從數學表示式演算法到彙編語法直譯器

lanedm發表於2023-12-04

在繁華的都市中,小悅作為一名軟體工程師,每天都在這座鋼筋水泥的森林裡忙碌。她的生活似乎被工作和各種瑣碎的事情填滿了,但在這個繁忙的生活中,她總能在工作之餘找到一些小小的樂趣。

這天下班後,小悅收到了一封來自國外同學蘇菲的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指令時,它需要從呼叫棧中彈出一個位置並跳轉到該位置。

讓我們來解釋一下這行程式碼的邏輯:

  1. 首先,直譯器解析到ret指令,並將其作為一個運運算元token
  2. 接著,直譯器檢查token[0]是否等於"ret",即檢查標記的第一個元素是否是"ret"。這是為了確保當前指令是ret指令。
  3. 如果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"。

程式的執行步驟如下:

  1. 首先,AssemblerInterpreter.Interpret(program)將直譯器應用於給定的程式字串program
  2. 直譯器將逐行解析程式字串,並執行相應的操作。在直譯器中,會呼叫TokensFrom函式來解析每一行的標記。
  3. 對於每個標記,直譯器將執行相應的操作,比如mov指令會移動一個值5到一個暫存器,inc指令會增加暫存器中的值,即(5+1),call指令會呼叫一個函式function,即(5+1)/2,msg指令會輸出常量訊息和a的值3。
  4. 在執行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)期望對於相同的組合語言程式能夠產生相同的結果。

  1. 測試用例:在這段程式碼中,透過一個迴圈(for (int i = 0; i < 1024; i++)),生成了1024個隨機的彙編程式(randProg)。每一個這樣的程式都被用來同時測試兩個直譯器:InternalAssembler 和 AssemblerInterpreter
  2. 期望值與實際值的對比:對於每一個隨機生成的程式,都同時得到了兩個結果:一個是測試類中定義的直譯器InternalAssembler的解釋結果(expected),另一個是AssemblerInterpreter的解釋結果(actual)。然後,這兩個結果被對比。
  3. 檢查結果:如果兩個直譯器的結果不同(expected != actual),那麼會列印出這個有問題的程式(displayProg(randProg))。然後,會使用斷言(Assert.AreEqual(expected, actual))來確認兩個結果是否一致。如果兩個結果一致,那麼測試透過;否則,測試失敗。

對比兩個直譯器結果的測試方法的必要性在於:

  • 確保正確性:透過對比兩個直譯器的結果,可以確認它們是否都能正確地解釋彙編程式。如果有任何不一致,那麼可以立即發現並調查原因,從而改進直譯器的準確性。
  • 效能比較:雖然此程式碼沒有明確地比較執行時間,但透過同時執行兩個直譯器並比較結果,可以在某種程度上間接地比較它們的效能。如果一個直譯器總是比另一個快,那麼可以考慮最佳化它。
  • 一致性:即使兩個直譯器的結果是正確的,也可能會因為實現的不同而產生微小的差異。透過對比這些差異,可以確保它們在實現上的一致性,避免潛在的混淆和錯誤。

總的來說,這種對比是一種有效的測試策略,可以確保直譯器的正確性和一致性,同時還可以在某種程度上評估它們的效能。 

相關文章