@craftybones/assembly_simulator v0.2.1
Assembly Simulator
This project is a small simulator that combines BASIC like line numbers and assembly like instructions to help teach the basics of computing.
Installation
npm install https://github.com/step/assembly_simulator.git
Usage
const Machine = require('assembly_simulator');
machine = new Machine();
machine.load('10 START\n20 PRN "HELLO"\n30 STOP');
machine.execute();
console.log(machine.getPrn().join("\n"));For detailed instructions, see documentation
Architecture
The "Machine" has 4 registers A, B, C, D.
There are 4 flags that are set on the compare instruction.
- EQ - this is set when two values are equal
- NE - this is set when two values are not equal
- GT - this is set when the value of the first argument is greater than the second one.
- LT - this is set when the value of the first argument is lesser than the second one.
The "Machine" only recognises positive integers for now.
There is also a stack that you can PUSH and POP from.
Instruction Set
Comments
Comments can be entered by starting a line with
;. The first non-space character has to be a;for it to be considered a comment.Valid uses
; This is a comment ; this is also a commentInvalid uses
10 START ; comments cannot be placed after an instruction this is also not a commentSTART
Starts execution and resets flags. Used at the start of a program, often as
10 START.
MOV
Copies a value into a register. First argument has to be a register. Second argument can either be a register or a positive integer.
Valid uses
10 MOV A,2 ;; Copies 2 into A 20 MOV B,A ;; Copies A's value into B.Invalid uses
10 MOV 2,2 20 MOV 2,AADD
Adds a value with a register and copies the new value into the register. First argument has to be a register. Second argument can either be a register or a positive integer.
Valid uses
10 ADD A,2 ;; Adds 2 to A and puts it back in A 20 ADD B,A ;; Adds A to B and puts it back in BInvalid uses
10 ADD 2,2 20 ADD 2,ASUB
Subtracts a value from a register and copies the new value into the register. First argument has to be a register. Second argument can either be a register or a positive integer.
Valid uses
10 SUB A,2 ;; Subtracts 2 from A and puts it back in A 20 SUB B,A ;; Subtracts A from B and puts it back in BInvalid uses
10 SUB 2,2 20 SUB 2,ANote that one can subtract a greater value from a lesser value and get a negative number even though one can't directly enter negative numbers.
CMP
The compare instruction sets flags. Based on the arguments it sets one or more flags. The first argument has to be a register. The second argument can be either a positive integer or a register.
CMP A,10will set theEQflag if A is 10.CMP A,10will set theNEandGTflag if A is 12.CMP A,10will set theNEandLTflag if A is 8.Valid uses
10 CMP A, 2 ;; will set LT if A<2 or GT if A>2 20 CMP A, B ;; will set LT if A<B or GT if A>BInvalid uses
10 CMP 2,2 20 CMP 2,AJMP
The
JMPinstruction unconditionally continues execution from the line specified.JMPand all other jump instructions leave the registers and flags unmodified.JMPtakes one argument and that argument has to be a line number.Valid uses
70 JMP 80 70 JMP 60Invalid uses
10 JMP A 20 JMP "10"JE
JEis a conditional jump instruction that only jumps to a specified line if theEQflag is set. IfEQis not set, then the execution continues normally.Valid uses
10 JE 50 20 JE 10Invalid uses
10 JE A 20 JE "10"JNE
JNEis a conditional jump instruction that only jumps to a specified line if theNEflag is set. IfNEis not set, then the execution continues normally.Valid uses
10 JNE 50 20 JNE 10Invalid uses
10 JNE A 20 JNE "10"JGT
JGTis a conditional jump instruction that only jumps to a specified line if theGTflag is set. IfGTis not set, then the execution continues normally.Valid uses
10 JGT 50 20 JGT 10Invalid uses
10 JGT A 20 JGT "10"JGE
JGEis a conditional jump instruction that only jumps to a specified line if theGTorEQflags are set. IfGTorEQare not set, then the execution continues normally.Valid uses
10 JGE 50 20 JGE 10Invalid uses
10 JGE A 20 JGE "10"JLT
JLTis a conditional jump instruction that only jumps to a specified line if theLTflag is set. IfLTis not set, then the execution continues normally.Valid uses
10 JLT 50 20 JLT 10Invalid uses
10 JLT A 20 JLT "10"JLE
JLEis a conditional jump instruction that only jumps to a specified line if theLTorEQflags are set. IfLTorEQare not set, then the execution continues normally.Valid uses
10 JLE 50 20 JLE 10Invalid uses
10 JLE A 20 JLE "10"STOP
The
STOPinstruction halts execution entirely. It does not take an argument.Valid uses
100 STOPInvalid uses
100 STOP 10 100 STOP APUSH
The
PUSHinstruction pushes the value of a register onto the stack. It takes exactly one argument that has to be a register.Valid uses
10 PUSH A 20 PUSH BInvalid uses
10 PUSH 20 PUSH 20 30 PUSH A,BPOP
The
POPinstruction pops the stack into the register specified. It takes exactly one argument that has to be a register.Valid uses
10 POP A 20 POP BInvalid uses
10 POP 20 POP 20 30 POP A,BFUNC
The
FUNCinstruction is a special instruction. It has no effect on execution and leaves flags and registers unchanged. It is used to indicate the beginning of a function. It takes one argument that has to be a valid function name.A valid function name begins with an alphabet but can consequently contain alphabets or numbers.
Function names are case insensitive. So,
mulis the same asMUL.The
RETinstruction is used to return to the point of execution from where the previous call was invoked.Valid uses
10 FUNC MUL 20 ADD A,A 30 RETInvalid uses
10 FUNC 1MUL 20 FUNC "ABCD" 30 FUNC A,BCALL
The
CALLinstruction calls the given function. It takes exactly one argument which has to be a valid function name.A valid function name begins with an alphabet but can consequently contain alphabets or numbers.
Valid uses
70 CALL MUL 80 CALL GREATESTInvalid uses
70 CALL 80 CALL 1MULRET
The
RETinstruction is used to return to the point of execution from whereCALLwas last called from.RETis usually the last instruction executed in a function. The instruction accepts no arguments.Valid uses
30 RETInvalid uses
30 RET A 40 RET 20 50 RET "ABC"
Documentation
The simulator can be accessed and controlled using the Machine object. The machine object is instantiated as
const machine = new Machine(); To load a program into the machine, use the load method.
let program = fs.readFileSync("add.asm","utf8");
machine.load(program); To execute a program in the machine, use the execute method.
let program = fs.readFileSync("add.asm","utf8");
machine.load(program);
machine.execute(); Once the machine has finished execution, you can get all the output that has been generated using the PRN instruction by using the method getPrn.
// code to load and execute here
let lines = machine.getPrn();
console.log(lines.join("\n")); getPrn returns an array of lines. This is preferred instead of giving a string as one may wish to use this library either on the command line or on the frontend where having separate lines available makes it easy to format/render
You can also get the trace table of the execution by using the getTable method
// code to load and execute here
let table = machine.getTable();
table.forEach(({CL,NL,A,B,C,D,EQ,NE,GT,LT,SL,INST,PRN})=>{
console.log([CL,NL,SL,INST,A,B,C,D,EQ,NE,GT,LT,PRN],join(", "));
})This table is very useful to help debug and trace execution. Each row of the table consists of:
- The registers
A,B,CandD - The flags
EQ,NE,GTandLT - Print output from the
PRNinstruction. - The line number from the source file or program that was executed in
SL - The actual instruction being executed in
INST The stack as an array contained in
STK
Stepwise Execution
The Machine has the capability of executing stepwise. This feature allows one to 'step through' the program.
const machine = new Machine();
let program = fs.readFileSync("add.asm","utf8");
let callBack = (state) => {
let { A, B, C, D } = state;
let { EQ, NE, GT, LT } = state;
let { PRN } = state;
let { CL, NL } = state;
let { SL, INST } = state;
let { STK } = state;
console.log(`A : ${A}, B : ${B}, C : ${C}, D : ${D}`);
console.log(`EQ : ${EQ}, NE : ${NE}, GT : ${GT}, LT : ${LT}`);
console.log(`CL : ${CL}, NL : ${NL}`);
console.log(`SL : ${SL}, INST : ${INST}`);
console.log(`INST : ${INST}`);
console.log(`STK : ${STK.join(" ")}`);
}
let executor;
try {
machine.load(program);
executor = machine.executeStepWise();
} catch(e) {
console.log("Error on ", e.lineNumber, e.instruction);
}
try {
machine.nextStep();
machine.nextStep();
} catch(e) {
// do whatever with the error
}As shown above, in order to use it, you are required to pass a callback. The callback will be called with the same fields that getTable fetches. The important thing to note here is that once the machine executes the program completely, the callback will no longer be called no matter how many times you call nextStep.
This manner of execution is very useful to debug things like infinite loops.
Error Handling
All errors currently are encapsulated by the InvalidInstructionException class. All exceptions have two pieces of information currently.
- Source line number where the exception occurred in
lineNumber - The offending instruction in
instruction
Always surround your machine's load and execute in try catch blocks.
try {
machine.load(program);
machine.execute();
} catch(e) {
console.log("Error on ", e.lineNumber, e.instruction);
}