phantasmal-world/src/scripting/assembly.ts

448 lines
14 KiB
TypeScript
Raw Normal View History

2019-07-22 22:03:58 +08:00
import {
Instruction,
OPCODES_BY_MNEMONIC,
Arg,
Type,
Opcode,
Param,
} from "../data_formats/parsing/quest/bin";
2019-07-23 17:03:35 +08:00
export type AssemblyError = {
line_no: number;
2019-07-22 22:03:58 +08:00
col: number;
length: number;
message: string;
2019-07-22 22:03:58 +08:00
};
export function assemble(
2019-07-23 17:03:35 +08:00
assembly: string[],
2019-07-22 22:03:58 +08:00
manual_stack: boolean = false
): {
instructions: Instruction[];
labels: Map<number, number>;
errors: AssemblyError[];
2019-07-22 22:03:58 +08:00
} {
const errors: AssemblyError[] = [];
2019-07-22 22:03:58 +08:00
const instructions: Instruction[] = [];
const labels = new Map<number, number>();
2019-07-23 17:03:35 +08:00
let line_no = 1;
2019-07-22 22:03:58 +08:00
2019-07-23 17:03:35 +08:00
for (const line of assembly) {
const match = line.match(
/^(?<lbl_ws>\s*)(?<lbl>[^\s]+?:)?(?<op_ws>\s*)(?<op>[a-z][a-z0-9_=<>!]*)?(?<args>.*)$/
2019-07-22 22:03:58 +08:00
);
if (!match || !match.groups || (match.groups.lbl == null && match.groups.op == null)) {
2019-07-23 17:03:35 +08:00
const left_trimmed = line.trimLeft();
2019-07-22 22:03:58 +08:00
const trimmed = left_trimmed.trimRight();
if (trimmed.length) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no,
col: 1 + line.length - left_trimmed.length,
2019-07-22 22:03:58 +08:00
length: trimmed.length,
message: "Expected label or instruction.",
2019-07-22 22:03:58 +08:00
});
}
} else {
const { lbl_ws, lbl, op_ws, op, args } = match.groups;
if (lbl != null) {
const label = parseInt(lbl.slice(0, -1), 10);
if (!isFinite(label) || !/^\d+:$/.test(lbl)) {
2019-07-22 22:03:58 +08:00
errors.push({
2019-07-23 17:03:35 +08:00
line_no,
col: 1 + lbl_ws.length,
2019-07-22 22:03:58 +08:00
length: lbl.length,
message: "Invalid label name.",
2019-07-22 22:03:58 +08:00
});
} else if (labels.has(label)) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no,
col: 1 + lbl_ws.length,
2019-07-22 22:03:58 +08:00
length: lbl.length - 1,
message: "Duplicate label.",
2019-07-22 22:03:58 +08:00
});
} else {
labels.set(label, instructions.length);
}
}
if (op != null) {
const opcode = OPCODES_BY_MNEMONIC.get(op);
if (!opcode) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no,
col: 1 + lbl_ws.length + (lbl ? lbl.length : 0) + op_ws.length,
2019-07-22 22:03:58 +08:00
length: op.length,
message: "Unknown instruction.",
2019-07-22 22:03:58 +08:00
});
} else {
const args_col =
1 +
2019-07-22 22:03:58 +08:00
lbl_ws.length +
(lbl ? lbl.length : 0) +
op_ws.length +
(op ? op.length : 0);
const arg_tokens: ArgToken[] = [];
const args_tokenization_ok = tokenize_args(args, args_col, arg_tokens);
const ins_args: Arg[] = [];
if (!args_tokenization_ok) {
const left_trimmed = args.trimLeft();
const trimmed = args.trimRight();
errors.push({
2019-07-23 17:03:35 +08:00
line_no,
col: args_col + args.length - left_trimmed.length,
length: trimmed.length,
message: "Instruction arguments expected.",
});
2019-07-22 22:03:58 +08:00
} else {
const varargs =
opcode.params.findIndex(
p => p.type === Type.U8Var || p.type === Type.U16Var
) !== -1;
2019-07-22 22:03:58 +08:00
const param_count =
opcode.params.length + (manual_stack ? 0 : opcode.stack_params.length);
if (
varargs
? arg_tokens.length < param_count
: arg_tokens.length !== param_count
) {
2019-07-23 17:03:35 +08:00
const left_trimmed = line.trimLeft();
2019-07-22 22:03:58 +08:00
errors.push({
2019-07-23 17:03:35 +08:00
line_no,
col: 1 + line.length - left_trimmed.length,
length: left_trimmed.length,
message: `Expected${
varargs ? " at least" : ""
} ${param_count} argument${param_count === 1 ? "" : "s"}, got ${
arg_tokens.length
}.`,
2019-07-22 22:03:58 +08:00
});
} else if (varargs || arg_tokens.length === opcode.params.length) {
2019-07-23 17:03:35 +08:00
parse_args(opcode.params, arg_tokens, ins_args, line_no, errors);
2019-07-22 22:03:58 +08:00
} else {
const stack_args: Arg[] = [];
2019-07-23 17:03:35 +08:00
parse_args(
opcode.stack_params,
arg_tokens,
stack_args,
line_no,
errors
);
2019-07-22 22:03:58 +08:00
for (let i = 0; i < opcode.stack_params.length; i++) {
const param = opcode.stack_params[i];
const arg = stack_args[i];
const col = arg_tokens[i].col;
const length = arg_tokens[i].arg.length;
if (arg == null) {
continue;
}
2019-07-22 22:03:58 +08:00
switch (param.type) {
case Type.U8:
case Type.Register:
instructions.push(new Instruction(Opcode.arg_pushb, [arg]));
break;
case Type.U16:
instructions.push(new Instruction(Opcode.arg_pushw, [arg]));
break;
case Type.U32:
case Type.I32:
case Type.F32:
instructions.push(new Instruction(Opcode.arg_pushl, [arg]));
break;
case Type.String:
instructions.push(new Instruction(Opcode.arg_pushs, [arg]));
break;
default:
errors.push({
2019-07-23 17:03:35 +08:00
line_no,
col,
length,
message: `Type ${Type[param.type]} not implemented.`,
});
2019-07-22 22:03:58 +08:00
}
}
}
}
instructions.push(new Instruction(opcode, ins_args));
}
}
}
2019-07-23 17:03:35 +08:00
line_no++;
2019-07-22 22:03:58 +08:00
}
return {
instructions,
labels,
errors,
};
}
type ArgToken = {
col: number;
arg: string;
};
function tokenize_args(arg_str: string, col: number, args: ArgToken[]): boolean {
if (arg_str.trim().length === 0) {
2019-07-22 22:03:58 +08:00
return true;
}
let match: RegExpMatchArray | null;
if (args.length === 0) {
match = arg_str.match(/^(?<arg_ws>\s+)(?<arg>"([^"\\]|\\.)*"|[^\s,]+)\s*/);
} else {
match = arg_str.match(/^(?<arg_ws>,\s*)(?<arg>"([^"\\]|\\.)*"|[^\s,]+)\s*/);
}
if (!match || !match.groups) {
return false;
} else {
const { arg_ws, arg } = match.groups;
args.push({
col: col + arg_ws.length,
arg,
});
return tokenize_args(arg_str.slice(match[0].length), col + match[0].length, args);
}
}
function parse_args(
params: Param[],
arg_tokens: ArgToken[],
args: Arg[],
line: number,
errors: AssemblyError[]
): void {
2019-07-22 22:03:58 +08:00
for (let i = 0; i < params.length; i++) {
const param = params[i];
const arg_token = arg_tokens[i];
const arg_str = arg_token.arg;
const col = arg_token.col;
const length = arg_str.length;
2019-07-22 22:03:58 +08:00
switch (param.type) {
case Type.U8:
parse_uint(arg_str, 1, args, line, col, errors);
2019-07-22 22:03:58 +08:00
break;
case Type.U16:
parse_uint(arg_str, 2, args, line, col, errors);
2019-07-22 22:03:58 +08:00
break;
case Type.U32:
parse_uint(arg_str, 4, args, line, col, errors);
break;
2019-07-22 22:03:58 +08:00
case Type.I32:
parse_sint(arg_str, 4, args, line, col, errors);
break;
2019-07-22 22:03:58 +08:00
case Type.F32:
parse_float(arg_str, args, line, col, errors);
2019-07-22 22:03:58 +08:00
break;
case Type.Register:
parse_register(arg_str, args, line, col, errors);
2019-07-22 22:03:58 +08:00
break;
case Type.String:
parse_string(arg_str, args, line, col, errors);
2019-07-22 22:03:58 +08:00
break;
case Type.U8Var:
parse_uint_varargs(arg_tokens, i, 1, args, line, errors);
return;
case Type.U16Var:
parse_uint_varargs(arg_tokens, i, 2, args, line, errors);
return;
2019-07-22 22:03:58 +08:00
default:
errors.push({
2019-07-23 17:03:35 +08:00
line_no: line,
col,
length,
message: `Type ${Type[param.type]} not implemented.`,
});
2019-07-22 22:03:58 +08:00
}
}
}
function parse_uint(
arg_str: string,
size: number,
args: Arg[],
line: number,
col: number,
errors: AssemblyError[]
): void {
const bit_size = 8 * size;
const value = parseInt(arg_str, 10);
const max_value = Math.pow(2, bit_size) - 1;
if (!/^\d+$/.test(arg_str)) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no: line,
col,
length: arg_str.length,
message: `Expected unsigned integer.`,
});
} else if (value > max_value) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no: line,
col,
length: arg_str.length,
message: `${bit_size}-Bit unsigned integer can't be greater than ${max_value}.`,
});
} else {
args.push({
value,
size,
});
}
}
function parse_sint(
arg_str: string,
size: number,
args: Arg[],
line: number,
col: number,
errors: AssemblyError[]
): void {
const bit_size = 8 * size;
const value = parseInt(arg_str, 10);
const min_value = -Math.pow(2, bit_size - 1);
const max_value = Math.pow(2, bit_size - 1) - 1;
if (!/^-?\d+$/.test(arg_str)) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no: line,
col,
length: arg_str.length,
message: `Expected signed integer.`,
});
} else if (value < min_value) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no: line,
col,
length: arg_str.length,
message: `${bit_size}-Bit signed integer can't be less than ${min_value}.`,
});
} else if (value > max_value) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no: line,
col,
length: arg_str.length,
message: `${bit_size}-Bit signed integer can't be greater than ${max_value}.`,
});
} else {
args.push({
value,
size,
});
}
}
function parse_float(
arg_str: string,
args: Arg[],
line: number,
col: number,
errors: AssemblyError[]
): void {
const value = parseFloat(arg_str);
if (!Number.isFinite(value)) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no: line,
col,
length: arg_str.length,
message: `Expected floating point number.`,
});
} else {
args.push({
value,
size: 4,
});
}
}
function parse_register(
arg_str: string,
args: Arg[],
line: number,
col: number,
errors: AssemblyError[]
): void {
const value = parseInt(arg_str.slice(1), 10);
if (!/^r\d+$/.test(arg_str)) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no: line,
col,
length: arg_str.length,
message: `Expected register reference.`,
});
} else if (value > 255) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no: line,
col,
length: arg_str.length,
message: `Invalid register reference, expected r0-r255.`,
});
} else {
args.push({
value,
size: 1,
});
}
}
function parse_string(
arg_str: string,
args: Arg[],
line: number,
col: number,
errors: AssemblyError[]
): void {
if (!/^"([^"\\]|\\.)*"$/.test(arg_str)) {
errors.push({
2019-07-23 17:03:35 +08:00
line_no: line,
col,
length: arg_str.length,
message: `Expected string.`,
});
} else {
const value = JSON.parse(arg_str);
args.push({
value,
size: 2 + 2 * value.length,
});
}
}
function parse_uint_varargs(
arg_tokens: ArgToken[],
index: number,
size: number,
args: Arg[],
line: number,
errors: AssemblyError[]
): void {
for (; index < arg_tokens.length; index++) {
const arg_token = arg_tokens[index];
const col = arg_token.col;
parse_uint(arg_token.arg, size, args, line, col, errors);
}
}