【算法】远方来信,从数学表达式算法到汇编语法解释器
在繁华的都市中,小悦作为一名软件工程师,每天都在这座钢筋水泥的森林里忙碌。她的生活似乎被工作和各种琐碎的事情填满了,但在这个繁忙的生活中,她总能在工作之余找到一些小小的乐趣。
这天下班后,小悦收到了一封来自国外同学苏菲的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)
)来确认两个结果是否一致。如果两个结果一致,那么测试通过;否则,测试失败。
对比两个解释器结果的测试方法的必要性在于:
- 确保正确性:通过对比两个解释器的结果,可以确认它们是否都能正确地解释汇编程序。如果有任何不一致,那么可以立即发现并调查原因,从而改进解释器的准确性。
- 性能比较:虽然此代码没有明确地比较执行时间,但通过同时运行两个解释器并比较结果,可以在某种程度上间接地比较它们的性能。如果一个解释器总是比另一个快,那么可以考虑优化它。
- 一致性:即使两个解释器的结果是正确的,也可能会因为实现的不同而产生微小的差异。通过对比这些差异,可以确保它们在实现上的一致性,避免潜在的混淆和错误。
总的来说,这种对比是一种有效的测试策略,可以确保解释器的正确性和一致性,同时还可以在某种程度上评估它们的性能。