Skip to content

Commit

Permalink
Improve assembly error reporting (#323)
Browse files Browse the repository at this point in the history
  • Loading branch information
netalondon authored May 24, 2024
1 parent 27e740a commit e33dd83
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 63 deletions.
7 changes: 5 additions & 2 deletions simulator/src/cpu/alu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ export type COMMANDS_ASM = typeof commandASMValues extends Set<infer S>
? S
: never;

export function isCommandAsm(command: unknown): command is COMMANDS_ASM {
return commandASMValues.has(command as COMMANDS_ASM);
export function isCommandAsm(command: string): command is COMMANDS_ASM {
return (
commandASMValues.has(command as COMMANDS_ASM) ||
commandASMValues.has(command.replace("M", "A") as COMMANDS_ASM)
);
}

export type COMMANDS_OP =
Expand Down
55 changes: 47 additions & 8 deletions simulator/src/languages/asm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
JUMP,
JUMP_ASM,
JUMP_OP,
isAssignAsm,
isCommandAsm,
} from "../cpu/alu.js";
import { KEYBOARD_OFFSET, SCREEN_OFFSET } from "../cpu/memory.js";
import { makeC } from "../util/asm.js";
Expand Down Expand Up @@ -104,19 +106,56 @@ asmSemantics.addAttribute<AsmInstruction>("intermediateInstruction", {
},
});

function getAsmAssign(assignN: ohm.Node) {
let assign = assignN.child(0)?.child(0)?.sourceString ?? "";

// The book (figure 4.5) specifies DM and ADM as the correct forms for destination,
// but since the desktop simulators accept only MD and AMD we have decided to accept both */
if (assign == "DM") {
assign = "MD";
}
if (assign == "ADM") {
assign = "AMD";
}
if (assign != "" && !isAssignAsm(assign)) {
const reversed = assign.split("").reverse().join("");
const suggestion = isAssignAsm(reversed)
? `. Did you mean ${reversed}?`
: "";
throw createError(
`Invalid ASM target: ${assign}${suggestion}`,
span(assignN.source)
);
}

return assign;
}

function getAsmOp(opN: ohm.Node) {
const op = opN.sourceString;

if (!isCommandAsm(op)) {
const reversed = op.split("").reverse().join("");
const suggestion = isCommandAsm(reversed)
? `. Did you mean ${reversed}?`
: "";
throw createError(
`Invalid ASM value: ${opN.sourceString}${suggestion}`,
span(opN.source)
);
}

return op;
}

asmSemantics.addAttribute<AsmInstruction>("instruction", {
aInstruction(_at, name): AsmAInstruction {
return A(name.value, span(this.source));
},
cInstruction(assignN, opN, jmpN): AsmCInstruction {
let assign = assignN.child(0)?.child(0)?.sourceString ?? "";
if (assign == "DM") {
assign = "MD";
}
if (assign == "ADM") {
assign = "AMD";
}
const op = opN.sourceString as COMMANDS_ASM;
const assign = getAsmAssign(assignN);

const op = getAsmOp(opN) as COMMANDS_ASM;
const jmp = (jmpN.child(0)?.child(1)?.sourceString ?? "") as JUMP_ASM;
return C(assign as ASSIGN_ASM, op, jmp, span(this.source));
},
Expand Down
30 changes: 5 additions & 25 deletions simulator/src/languages/grammars/asm.ohm
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,12 @@ ASM <: Base {
label = openParen identifier closeParen
aInstruction = at (identifier | decNumber)
cInstruction = assign? op jmp?

/* The book (figure 4.5) specifies DM and ADM as the correct forms for destination,
but since the desktop simulators accept only MD and AMD we have decided to accept both */
assign = (
"AMD" | "ADM"
| "AM"
| "AD"
| "MD" | "DM"
| "M"
| "D"
| "A"
) equal

op =
| "0" | "1" | "-1"
| "!D" | "!A" | "!M"
| "-D" | "-A" | "-M"
| "D+1" | "A+1" | "M+1"
| "D-1" | "A-1" | "M-1"
| "D+A" | "D+M"
| "D-A" | "D-M"
| "A-D" | "M-D"
| "D&A" | "D&M"
| "D|A" | "D|M"
| "D" | "A" | "M"

assignChar = "A" | "M" | "D"
opChar = assignChar | "0" | "1" | "!" | "-" | "+" | "|" | "&"

assign = assignChar+ equal
op = opChar+

jmp = semi ("JGT" | "JEQ" | "JGE" | "JLT" | "JNE" | "JLE" | "JMP")
}
33 changes: 6 additions & 27 deletions simulator/src/languages/grammars/asm.ohm.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const asm = `
ASM <: Base {
const asm = `ASM <: Base {
Root := ASM
ASM = intermediateInstruction* instruction?
Expand All @@ -9,32 +8,12 @@ ASM <: Base {
label = openParen identifier closeParen
aInstruction = at (identifier | decNumber)
cInstruction = assign? op jmp?
/* The book (figure 4.5) specifies DM and ADM as the correct forms for destination,
but since the desktop simulators accept only MD and AMD we have decided to accept both */
assign = (
"AMD" | "ADM"
| "AM"
| "AD"
| "MD" | "DM"
| "M"
| "D"
| "A"
) equal
op =
| "0" | "1" | "-1"
| "!D" | "!A" | "!M"
| "-D" | "-A" | "-M"
| "D+1" | "A+1" | "M+1"
| "D-1" | "A-1" | "M-1"
| "D+A" | "D+M"
| "D-A" | "D-M"
| "A-D" | "M-D"
| "D&A" | "D&M"
| "D|A" | "D|M"
| "D" | "A" | "M"
assignChar = "A" | "M" | "D"
opChar = assignChar | "0" | "1" | "!" | "-" | "+" | "|" | "&"
assign = assignChar+ equal
op = opChar+
jmp = semi ("JGT" | "JEQ" | "JGE" | "JLT" | "JNE" | "JLE" | "JMP")
}`;
Expand Down
2 changes: 1 addition & 1 deletion simulator/src/util/asm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function cop(asm: string): number {
parts?.[0] != asm || // match is not exhaustive
!isAssignAsm(assign) ||
!isJumpAsm(jump) ||
(!isCommandAsm(operation) && !isCommandAsm(operation.replace("M", "A")))
!isCommandAsm(operation)
) {
// TODO: This should return Result<> instead of throw
throw new Error("Invalid c instruction");
Expand Down

0 comments on commit e33dd83

Please sign in to comment.