总算把各种期中混杂着复变期末全部结束了,摸鱼一会
本文将安利一个开源的纯 JS 的 lc-3 模拟器,并介绍自己改的一个 lc-3 评测姬
源项目地址:https://github.com/wchargin/lc3web
并有 github-page:https://wchargin.github.io/lc3web/
感觉比某大学某 ICS 课程主页安利的某模拟器好用
JavaScript 是世界上最好的语言
Web 版的好处就在于甚至不需要安装(好文明),真正的全平台通用
大概由以下这些这些 JS 文件组成:
js/
├── bootstrap.js
├── bootstrap.min.js
├── jquery-1.11.1.min.js
├── lc3_as.js
├── lc3_core.js
├── lc3_hexbin.js
├── lc3_os.js
├── lc3_ui.js
├── lc3_util.js
├── npm.js
└── Queue.js
上面三个是用到的库,主体逻辑是 lc3_core.js
,主体界面交互放在了 lc3_os.js
,其余是一些供这两个 JS 调用的函数
这命名简洁清晰爱了爱了
所以阅读源代码主要也就看上面提到的两个 JS 文件。
lc-3 评测姬
开发动机
采用一次性 Judge 却不提供提前检验正确性工具的 lab 就好像只能提交一次的 acm, 没有灵魂
具体实现
页面组件
不想破坏原来页面的美感(?),直接看着写就行了
<div class="col-md-6">
<h1>lab 评测</h1>
<p>
说明:本程序基于 GitHub 项目
<a href="https://github.com/wchargin/lc3web">lc3web</a>
修改而来,旨在满足修读 2021 USTC CS1002A 计算系统概论的同学们的可能的需要。
</p>
<p>fork 后仓库: <a href="https://github.com/liuly0322/lc3web">lc3web</a></p>
<p>
请先在本模拟器中载入汇编代码或者机器码(在左侧的 Assemble 和 Raw
选项中载入),机器码需要 0011 0000 0000 0000 开头。
</p>
<p>
然后即可点击下方选择需要评测的实验,目前支持显示测试点正确性以及总执行指令数。评测要求基本参照助教文档。数据是自己随便给的,尽量全一点
</p>
<div class="panel panel-default" id="bench-panel">
<div class="panel-heading"><h3 class="panel-title">选择测试题目</h3></div>
<div class="panel-body">
限制单次指令数:<input
type="number"
id="cycleLimit"
value="100000"
/><br />
lab1 乘数数组:
<input
style="width: 100%;"
type="text"
id="arr1"
value="1, 5, 4000, 65036, 65422, 20211, 25210, 50550, 21098, 1"
/>
lab1 被乘数数组:
<input
style="width: 100%;"
type="text"
id="arr2"
value="1, 4000, 5, 433, 65303, 41016, 60433, 21074, 3, 54"
/>
lab2&3 测试数据:
<input
style="width: 100%;"
type="text"
id="testcase1"
value="1, 2, 3, 24, 144, 456, 1088, 1092, 2096, 4200, 8192, 12000, 14000"
/>
lab5 测试数据:
<input
style="width: 100%;"
type="text"
id="testcase2"
value="2, 3, 4, 7, 456, 993, 997, 1569, 9293, 121, 9339, 1437"
/>
</div>
<div class="panel-footer">
<div class="row" id="scroll-container">
<div class="col-xs-12">
<button class="btn btn-default" type="button" onclick="bench1()">
lab 1
</button>
<button class="btn btn-default" type="button" onclick="bench2()">
lab 2 & 3
</button>
<button class="btn btn-default" type="button" onclick="bench3()">
lab 5
</button>
</div>
</div>
</div>
</div>
</div>
按照原来的格式写好组件,然后加点允许用户自由设置的输入框即可。评测分别采用三个函数 bench1(), bench2(), bench3()
主要是因为一共没几个实验,而且输入输出不固定(直接指定寄存器),所以就直接这样高耦合低内聚的写了()
脚本逻辑
不想破坏原来框架的美感,另开一个 lc3_bench.js
核心是判题逻辑:
在 bench 函数预先处理好寄存器数据后,就要运行判题逻辑了,需要:
- 获取设置的单个 case 最多指令数
- pc 归位 x3000
- 总执行指令数清零
随后就逐条指令执行。终止标志是当前即将执行的指令是 Trap 或者是 x0000(与___助教的判题逻辑相同)
function benchTest(f) {
const limit = document.querySelector("#cycleLimit").value;
lc3.pc = 0x3000;
lc3.totalInstruction = 0;
var cnt = 0;
while (true) {
var op = lc3.decode(lc3.getMemory(lc3.pc));
if ((op.raw >= 61440 && op.raw <= 61695) || op.raw === 0) {
var str = f();
break;
}
cnt++;
if (cnt > limit) {
alert("有测试样例超过单次最高执行指令数,请检查!");
return;
}
lc3.nextInstruction();
}
return str;
}
当评测结束时,传入的回调函数用于处理结果(lc-3 模拟器是一个全局变量,回调函数通过读取这些全局变量的值判断结果是否正确)
下面是每个题目判题脚本的编写。以 lab2&3 为例:
function bench2() {
// r0 是给定的 n, 其余寄存器初始化为 0, 结果存在 r7,
// 计算数列:f(0)=1,f(1)=1,f(2)=2,f(n)=f(n-1)+2*f(n-3) 的第 n 项
// 设置批处理模式,不更新用户显示的界面(见 lc3_ui.js)
window.batchMode = true;
// 获取正确答案的函数(因为测试样例没有很多,直接每次 O(n) 算了)
function fib(x) {
var arr = [1, 1, 2];
for (var i = 3; i <= x; i++) {
arr[i] = (arr[i - 1] + 2 * arr[i - 3]) % 1024;
}
return arr[x];
}
// 获取用户填入的测试样例
const testcase = document
.querySelector("#testcase1")
.value.replace(/\s*/g, "")
.split(",")
.map(Number);
var str = ""; // 最终将显示的结果
var sumInstruction = 0; // 总计命令数(用于计算平均值)
for (var i = 0; i < testcase.length; i++) {
// 每次测试先初始化寄存器
window.lc3.r = [0, 0, 0, 0, 0, 0, 0, 0];
window.lc3.r[0] = testcase[i];
var ans = fib(testcase[i]); // 获取正确答案
str += `测试数据 F(${testcase[i]}) = ${ans} `; // 理论答案
str += benchTest(bench_res); // 实际测试
}
// 所有样例评测完毕
str += "平均指令数 " + sumInstruction / testcase.length; // 显示平均指令
alert(str); // 显示最终将显示的结果
window.gExitBatchMode(); // 刷新界面,退出批处理模式
return;
// 判断结果正确性的函数
function bench_res() {
// 判断结果
var lc3res = window.lc3.r[7];
sumInstruction += window.lc3.totalInstruction;
if (lc3res == ans) {
return `你的回答正确,指令数 ${window.lc3.totalInstruction} \n`;
} else {
return `你的答案是 ${lc3res} \n`;
}
}
}
大概还可以抽象优化一下,摸了
Have fun playing
不知道下一届还需不需要用到这个(希望人没事)