217 lines
10 KiB
TypeScript
217 lines
10 KiB
TypeScript
|
import {Byte} from "./MemoryManager.ts";
|
|||
|
|
|||
|
export class CompileError extends Error {
|
|||
|
constructor(message: string, index: number) {
|
|||
|
super(message);
|
|||
|
this.index = index;
|
|||
|
}
|
|||
|
|
|||
|
index: number;
|
|||
|
}
|
|||
|
|
|||
|
export class Compiler {
|
|||
|
// ["CompileFinished(info)", "CompileError(info,CompileError)"]
|
|||
|
// 感觉这里写的不好 类型限制不够
|
|||
|
_events: Map<string, ((arg0: string, arg1?: any) => void)[]> = new Map<string, ((arg0: string, arg1?: any) => void)[]>();
|
|||
|
|
|||
|
AddEventListener(event: string, callback: (arg0: string, arg1?: any) => void) {
|
|||
|
if (!this._events.has(event))
|
|||
|
this._events.set(event, [callback]);
|
|||
|
else
|
|||
|
this._events.get(event)!.push(callback);
|
|||
|
}
|
|||
|
|
|||
|
Notify(event: string, data?: any) {
|
|||
|
if (this._events.has(event))
|
|||
|
if (data === undefined)
|
|||
|
this._events.get(event)!.forEach(callback => callback(event));
|
|||
|
else
|
|||
|
this._events.get(event)!.forEach(callback => callback(event, data));
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
- 0x00: nop, 0, 0: 无操作
|
|||
|
- 0x01: ldc, n, number: 寄存器n赋值为number
|
|||
|
- 0x10: add, n, m: 寄存器n的值加通用寄存器m的值,结果存回寄存器n中
|
|||
|
- 0x11: div, n, m: 寄存器n中的值除以通用寄存器m的值,结果存回寄存器n中,并将余数存入寄存器1
|
|||
|
- 0x12: and, n, m: 寄存器n中的值和寄存器m中的值按位与,结果存回寄存器n中
|
|||
|
- 0x13: or, n, m: 寄存器n中的值和寄存器m中的值按位或,结果存回寄存器n中
|
|||
|
- 0x14: xor, n, m: 寄存器n中的值和寄存器m中的值按位异或,结果存回寄存器n中
|
|||
|
- 0x15: not, n, 0: 寄存器n中的值按位翻转
|
|||
|
- 0x16: lsl, n, m: 寄存器n中的值左移,位数为寄存器m中的值
|
|||
|
- 0x17: lsr, n, m: 寄存器n中的值右移,位数为寄存器m中的值
|
|||
|
- 0x20: ld, n, addr: 将地址addr处的值加载到寄存器n中
|
|||
|
- 0x21: st, n, addr: 将寄存器n中的值保存到地址addr处
|
|||
|
- 0x23: mov, n, m: 将寄存器m中的值复制到寄存器n中
|
|||
|
- 0x30: cp, src, dst: 将地址为src处的数据复制到dst处,复制的字节数为通用寄存器2的值
|
|||
|
- 0x40: jp, 0, k: 无条件跳转到第k条指令
|
|||
|
- 0x41: ltjp, n, k: 如果寄存器2中的值小于寄存器n中的值,则跳转到第k条指令
|
|||
|
- 0x42: gtjp, n, k: 如果寄存器2中的值大于寄存器n中的值,则跳转到第k条指令
|
|||
|
- 0x43: eqjp, n, k: 如果寄存器2中的值等于寄存器n中的值,则跳转到第k条指令
|
|||
|
- 0x50: call, 0, k: 将7个通用寄存器及下一条指令的序号推入栈中,然后跳转到第k条指令
|
|||
|
- 0x51: ret, 0, 0: 如果栈为空,则程序终止;如果栈不为空,则恢复7个通用寄存器的值,跳转到之前存入的第k条指令
|
|||
|
*/
|
|||
|
static KeyWordsToMachineCode: Map<string, string> = new Map<string, string>([
|
|||
|
["nop", "0x00"],
|
|||
|
["ldc", "0x01"],
|
|||
|
["add", "0x10"],
|
|||
|
["div", "0x11"],
|
|||
|
["and", "0x12"],
|
|||
|
["or", "0x13"],
|
|||
|
["xor", "0x14"],
|
|||
|
["not", "0x15"],
|
|||
|
["lsl", "0x16"],
|
|||
|
["lsr", "0x17"],
|
|||
|
["ld", "0x20"],
|
|||
|
["st", "0x21"],
|
|||
|
["mov", "0x23"],
|
|||
|
["cp", "0x30"],
|
|||
|
["jp", "0x40"],
|
|||
|
["ltjp", "0x41"],
|
|||
|
["gtjp", "0x42"],
|
|||
|
["eqjp", "0x43"],
|
|||
|
["call", "0x50"],
|
|||
|
["ret", "0x51"]
|
|||
|
]);
|
|||
|
static Registers: Map<string, string> = new Map<string, string>([
|
|||
|
["AX", "01"],
|
|||
|
["BX", "02"],
|
|||
|
["CX", "03"],
|
|||
|
["DX", "04"],
|
|||
|
["EX", "05"],
|
|||
|
["FX", "06"],
|
|||
|
["GX", "07"]
|
|||
|
])
|
|||
|
static font8x8: { [key: string]: number[] } = {
|
|||
|
'A': [0x7E, 0x11, 0x11, 0x7E, 0x11, 0x11, 0x11, 0x00],
|
|||
|
'B': [0x7C, 0x12, 0x12, 0x7C, 0x12, 0x12, 0x7C, 0x00],
|
|||
|
'C': [0x3E, 0x41, 0x40, 0x40, 0x40, 0x41, 0x3E, 0x00],
|
|||
|
'D': [0x7C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x7C, 0x00],
|
|||
|
'E': [0x7F, 0x40, 0x40, 0x7C, 0x40, 0x40, 0x7F, 0x00],
|
|||
|
'F': [0x7F, 0x40, 0x40, 0x7C, 0x40, 0x40, 0x40, 0x00],
|
|||
|
'G': [0x3E, 0x41, 0x40, 0x4F, 0x41, 0x41, 0x3E, 0x00],
|
|||
|
'H': [0x41, 0x41, 0x41, 0x7F, 0x41, 0x41, 0x41, 0x00],
|
|||
|
'I': [0x1C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00],
|
|||
|
'J': [0x0F, 0x02, 0x02, 0x02, 0x02, 0x42, 0x3C, 0x00],
|
|||
|
'K': [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11, 0x00],
|
|||
|
'L': [0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7F, 0x00],
|
|||
|
'M': [0x41, 0x63, 0x55, 0x49, 0x41, 0x41, 0x41, 0x00],
|
|||
|
'N': [0x41, 0x61, 0x51, 0x49, 0x45, 0x43, 0x41, 0x00],
|
|||
|
'O': [0x3E, 0x41, 0x41, 0x41, 0x41, 0x41, 0x3E, 0x00],
|
|||
|
'P': [0x7E, 0x11, 0x11, 0x7E, 0x40, 0x40, 0x40, 0x00],
|
|||
|
'Q': [0x3E, 0x41, 0x41, 0x41, 0x45, 0x42, 0x3D, 0x00],
|
|||
|
'R': [0x7E, 0x41, 0x41, 0x7E, 0x44, 0x42, 0x41, 0x00],
|
|||
|
'S': [0x3E, 0x41, 0x40, 0x3E, 0x01, 0x41, 0x3E, 0x00],
|
|||
|
'T': [0x7F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00],
|
|||
|
'U': [0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x3E, 0x00],
|
|||
|
'V': [0x41, 0x41, 0x41, 0x41, 0x22, 0x14, 0x08, 0x00],
|
|||
|
'W': [0x41, 0x41, 0x41, 0x49, 0x55, 0x63, 0x41, 0x00],
|
|||
|
'X': [0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41, 0x00],
|
|||
|
'Y': [0x41, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00],
|
|||
|
'Z': [0x7F, 0x02, 0x04, 0x08, 0x10, 0x20, 0x7F, 0x00],
|
|||
|
'0': [0x3E, 0x41, 0x43, 0x45, 0x49, 0x51, 0x3E, 0x00],
|
|||
|
'1': [0x08, 0x18, 0x28, 0x08, 0x08, 0x08, 0x3E, 0x00],
|
|||
|
'2': [0x3E, 0x41, 0x01, 0x3E, 0x40, 0x40, 0x7F, 0x00],
|
|||
|
'3': [0x3E, 0x41, 0x01, 0x1E, 0x01, 0x41, 0x3E, 0x00],
|
|||
|
'4': [0x10, 0x30, 0x50, 0x90, 0x7F, 0x10, 0x10, 0x00],
|
|||
|
'5': [0x7F, 0x40, 0x7E, 0x01, 0x01, 0x41, 0x3E, 0x00],
|
|||
|
'6': [0x3E, 0x40, 0x7E, 0x41, 0x41, 0x41, 0x3E, 0x00],
|
|||
|
'7': [0x7F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00],
|
|||
|
'8': [0x3E, 0x41, 0x41, 0x3E, 0x41, 0x41, 0x3E, 0x00],
|
|||
|
'9': [0x3E, 0x41, 0x41, 0x3F, 0x01, 0x41, 0x3E, 0x00],
|
|||
|
'+': [0x00, 0x08, 0x08, 0x7F, 0x08, 0x08, 0x00, 0x00],
|
|||
|
'-': [0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00],
|
|||
|
'x': [0x00, 0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41],
|
|||
|
'/': [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00],
|
|||
|
' ': [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
|||
|
};
|
|||
|
Code: string = "";
|
|||
|
CodeChanged: boolean = true;
|
|||
|
PCToIndex: Map<number, number> = new Map<number, number>();
|
|||
|
PCToCode: Map<number, string> = new Map<number, string>();
|
|||
|
PCToMachineCode: Map<number, string> = new Map<number, string>();
|
|||
|
Labels: Map<string, number> = new Map<string, number>();
|
|||
|
MemorySet: Byte[] = [];
|
|||
|
|
|||
|
Clear() {
|
|||
|
this.PCToIndex.clear();
|
|||
|
this.PCToCode.clear();
|
|||
|
this.PCToMachineCode.clear();
|
|||
|
this.Labels.clear();
|
|||
|
this.MemorySet = [];
|
|||
|
}
|
|||
|
|
|||
|
CompileCode(fullCode: string = this.Code) {
|
|||
|
this.Clear();
|
|||
|
this.CodeChanged = false;
|
|||
|
try {
|
|||
|
fullCode.split("\n").forEach((line, index) => {
|
|||
|
//code -> a: b ; c
|
|||
|
let pc = this.PCToCode.size;
|
|||
|
let labelAndCode = (line.includes(";") ? line.slice(0, line.indexOf(";")) : line).split(":");
|
|||
|
let code = "";
|
|||
|
if (labelAndCode.length === 2) {
|
|||
|
if (this.Labels.has(labelAndCode[0].trim()))
|
|||
|
throw new Error("Duplicate label");
|
|||
|
this.Labels.set(labelAndCode[0].trim(), pc);
|
|||
|
code = labelAndCode[1].trim();
|
|||
|
} else if (labelAndCode.length === 1)
|
|||
|
code = labelAndCode[0].trim();
|
|||
|
else
|
|||
|
throw new CompileError("Invalid code", index);
|
|||
|
if (code === "")
|
|||
|
return;
|
|||
|
if (code.startsWith("db")) {
|
|||
|
if (labelAndCode.length === 1)
|
|||
|
throw new CompileError("db must start with a label.", index);
|
|||
|
this.Labels.set(labelAndCode[0].trim(), this.MemorySet.length + 128);
|
|||
|
code.slice(2).split(",").forEach(val => {
|
|||
|
let num = val.toUpperCase().endsWith('H') ? parseInt(val, 16) : parseInt(val);
|
|||
|
if (isNaN(num) || num < 0 || num > 255)
|
|||
|
throw new CompileError("Invalid number", index);
|
|||
|
this.MemorySet.push(new Byte(num));
|
|||
|
});
|
|||
|
return;
|
|||
|
} else if (code.startsWith("dw")) {
|
|||
|
if (labelAndCode.length === 1)
|
|||
|
throw new CompileError("dw must start with a label.", index);
|
|||
|
this.Labels.set(labelAndCode[0].trim(), this.MemorySet.length + 128);
|
|||
|
code.slice(3).toUpperCase().split(",").forEach(val => {
|
|||
|
let str = val.slice(1, -1);
|
|||
|
for (let c of str) {
|
|||
|
if (!(c in Compiler.font8x8))
|
|||
|
throw new CompileError("Invalid char", index);
|
|||
|
Compiler.font8x8[c].forEach(byte => this.MemorySet.push(new Byte(byte)));
|
|||
|
}
|
|||
|
});
|
|||
|
return;
|
|||
|
}
|
|||
|
this.PCToCode.set(pc, code);
|
|||
|
this.PCToIndex.set(pc, index);
|
|||
|
});
|
|||
|
console.log(this.Labels);
|
|||
|
this.PCToCode.forEach((code, pc) => {
|
|||
|
let index = this.PCToIndex.get(pc)!;
|
|||
|
let mnemonicAndVals = code.split(/[\s,]+/);
|
|||
|
if (mnemonicAndVals.length !== 3)
|
|||
|
throw new CompileError("Invalid code", index);
|
|||
|
if (!Compiler.KeyWordsToMachineCode.has(mnemonicAndVals[0]))
|
|||
|
throw new CompileError("Invalid mnemonic", index);
|
|||
|
for (let i = 1; i <= 2; i++)
|
|||
|
if ((Compiler.Registers.has(mnemonicAndVals[i])))
|
|||
|
mnemonicAndVals[i] = Compiler.Registers.get(mnemonicAndVals[i])!;
|
|||
|
else if(this.Labels.has(mnemonicAndVals[i]))
|
|||
|
mnemonicAndVals[i] = this.Labels.get(mnemonicAndVals[i])!.toString(16).padStart(2,'0');
|
|||
|
else
|
|||
|
mnemonicAndVals[i] = parseInt(mnemonicAndVals[i]).toString(16).padStart(2,'0');
|
|||
|
this.PCToMachineCode.set(pc, Compiler.KeyWordsToMachineCode.get(mnemonicAndVals[0])! + mnemonicAndVals[1] + mnemonicAndVals[2]);
|
|||
|
});
|
|||
|
} catch (e) {
|
|||
|
if (e instanceof CompileError)
|
|||
|
this.Notify("CompileError", e);
|
|||
|
throw e;
|
|||
|
}
|
|||
|
this.Notify("CompileFinished");
|
|||
|
}
|
|||
|
|
|||
|
}
|