Skip to content

Instantly share code, notes, and snippets.

@king1600
Last active August 8, 2017 16:50
Show Gist options
  • Save king1600/70ebc47e38a19ac2ebef60110a6f1dd8 to your computer and use it in GitHub Desktop.
Save king1600/70ebc47e38a19ac2ebef60110a6f1dd8 to your computer and use it in GitHub Desktop.
KASM (King Assembly). My small implementation of a custom assembly like language
// Command bit flags
const KSM_FREG = 1;
const KSM_SREG = 2;
const KSM_FSYM = 4;
const KSM_SSYM = 8;
// Byte size constants
const KSM_MX_SM = 2;
const KSM_MX_CM = 8;
const KSM_VER_S = 4;
const KSM_CMD_S = 4;
const KSM_SYM_S = 4;
// Versioning info
const KSM_VERSION_MAJOR = 4;
const KSM_VERSION_MINOR = 0;
const KSM_VERSION_PATCH = 1;
const KSM_VERSION_VALUE =
(KSM_VERSION_MAJOR << 16) |
(KSM_VERSION_MINOR << 8) |
(KSM_VERSION_PATCH << 0);
// keywords / current registers
const KSM_Registers = [
'reg', // the only register u cun use
'rxx', // the result of the last executed command
'$', // the current pointer / command index
'null', // 0 ...
'end' // the ending pointer / command index
];
// Current supported instructions (commands)
const KSM_Commands = [
'set', // create a label for area in code (SET label_name, value)
'mov', // copy one value into another (MOV reg, 5) x = y
'push', // push a value onto the stack (PUSH 5)
'func', // mark remaining commands until "ret" as a function / skip exec (FUNC null)
'call', // call a function, pointer command or label (CALL label_name)
'ret', // return to the last pointer "call" was issued and set RXX value (RET 5)
'pop', // pop a value from stack into variable (POP reg)
'stak', // resize the stack (STACK 15)
'add', // add value into another and set RXX as result (ADD reg, 7) x += y
'sub', // sub value from another and set RXX (SUB reg, 7) x -= y
'div', // div value from another and set RXX (DIV reg, 10) x /= y
'mul', // mul value from another and set RXX (MUL 5, 10) x *= y
'inc', // increment a value and set RXX as result (INC REG) --x
'dec', // same as INC but decrement (DEC REG) ++x
'out', // output the value of a given var (OUT 5)
'exit', // jump to exit / same as "JMP end" (EXIT null)
'jmp', // jump to a pointer / command index / label (JMP 0) (JMP label_name)
'jcp', // same as jump but only if CARRY_BOOL is set from a CMP from down below
'cmp', // compare two values and set CARRY_BOOl (CMP REG, 6) x == y
'ncmp', // same as CMP but not equal (NCMP REG, 6) x != y
'lcmp', // same as CMP but less than (LCMP REG, 6) x < y
'gcmp', // same as CMP but greater than (GCMP REG, 6) x > y
'lore', // same as CMP but LessOREqual to (LORE REG, 6) x <= y
'gore' // same as CMP but GreaterOREqual to (GORE REG, 6) x >= y
];
/**
* Write an unsigned integer into a Uint8Array with given amount of bytes
* @param {Array} arr the array to write into
* @param {Number} offset the offset in the array
* @param {Number} data the integer data to write
* @param {Number} bytes how many bytes to use when writing
*/
function KSM_WriteBytes(arr, offset = 0, data = 0, bytes = 1) {
for (let bits = (8 * bytes) - 8; bits > -1; bits -= 8)
arr[offset++] = (data >> bits) & 0xff;
return offset;
}
/**
* Read an unsigned integer from Uint8Array given amount of bytes
* @param {Array} arr the array to read from
* @param {Number} offset the offset in the array to start reading
* @param {Number} bytes amount of bytes to read
*/
function KSM_ReadBytes(arr, offset = 0, bytes = 1) {
let data = 0;
for (let bits = (8 * bytes) - 8; bits > -1; bits -= 8)
data |= (arr[offset++] & 0xff) << bits;
return [data, offset];
}
/** Command/Instruction object */
class KSM_Command {
constructor() {
this.cmd = 0;
this.arg1 = 0;
this.arg2 = 0;
this.bitmode = 0;
}
}
/**
* Compile KASM string code into kasm byte code (Uint8Array)
* @param {String} code KASM code to compile
*/
function KSM_Compile(code) {
code = code.split('\n');
let i, j, line, cmd, args, command;
// symbols and commands
let sym_offset = 0, cmd_offset = 0;
let symbols = new Array(code.length);
let commands = new Array(code.length);
// iterate through lines of code
try {
for (i = 0; i < code.length; i++) {
line = code[i].trimLeft().trimRight();
if (line.length < 1 || line[0] === ';') continue;
// extract command and arguments
[cmd, ...args] = line.split(' ');
cmd = cmd.toLowerCase();
args = args
.join(' ').split(';')[0].split(',')
.map(i => i.trimLeft().trimRight())
.filter(i => i.length > 0).slice(0, 2);
if (!KSM_Commands.includes(cmd))
throw `Invalid command: ${cmd}`;
// create new ksm_command object
command = new KSM_Command();
command.cmd = KSM_Commands.indexOf(cmd) + 1;
// handle symbol setting
if (command.cmd === 1) {
if (args.length < 1)
throw 'SET Command needs symbol_name and value';
if (args.length < 2)
args.push(0);
command.arg1 = sym_offset;
command.arg2 = args[1];
command.bitmode |= KSM_FSYM;
args[0] = args[0] + '';
if (args[1] === 'null') args[1] = 0;
if (!isNaN(args[0][0]))
throw 'Symbol names cannot start with numbers: ' + args[0];
if (KSM_Registers.includes(args[0]))
throw 'Symbol name cannot be keyword: ' + args[0];
if (symbols.includes(args[0]))
throw 'Symbol ' + args[0] + ' is already defined';
if (isNaN(args[1]))
throw 'Symbol value has to be unsigned 24bit int: ' + args[1];
symbols[sym_offset++] = args[0];
// handle other commands
} else {
if (args.length < 1)
throw 'Cannot have empty arguments';
for (j = 0; j < args.length; j++) {
if (KSM_Registers.includes(args[j])) {
if (j === 0) command.arg1 = KSM_Registers.indexOf(args[j]);
else command.arg2 = KSM_Registers.indexOf(args[j]);
command.bitmode |= (j === 0) ? KSM_FREG : KSM_SREG;
} else if (symbols.includes(args[j])) {
if (j === 0) command.arg1 = symbols.indexOf(args[j]);
else command.arg2 = symbols.indexOf(args[j]);
command.bitmode |= (j === 0) ? KSM_FSYM : KSM_SSYM;
} else {
if (isNaN(args[j]))
throw 'Argument ' + (j+1) + ' is not valid: ' + args[j];
if (j === 0) command.arg1 = parseInt(args[j]);
else command.arg2 = parseInt(args[j]);
}
}
}
// add created command to command list
commands[cmd_offset++] = command;
}
// display any errors with line number
} catch (msg) {
throw new Error(`Error on line ${i}: ` + msg);
}
// start building byte code
let offset;
symbols.length = sym_offset;
commands.length = cmd_offset;
let cmd_data = new Uint8Array(commands.length * KSM_MX_CM);
let symbol_data = new Uint8Array(
symbols.length > 0 ? symbols.map(s => s.length + KSM_MX_SM).reduce((t,s) => t += s) : 0);
let bytecode = new Uint8Array(
KSM_VER_S + KSM_CMD_S + KSM_SYM_S + symbol_data.length + cmd_data.length);
// write symbol data
offset = 0;
for (i = 0; i < symbols.length; i++) {
offset = KSM_WriteBytes(symbol_data, offset, symbols[i].length, KSM_MX_SM);
for (j = 0; j < symbols[i].length; j++)
symbol_data[offset++] = symbols[i][j].charCodeAt(0) & 0xff;
}
// write command data
offset = 0;
for (i = 0; i < commands.length; i++) {
offset = KSM_WriteBytes(cmd_data, offset, commands[i].cmd, 1);
offset = KSM_WriteBytes(cmd_data, offset, commands[i].arg1, 3);
offset = KSM_WriteBytes(cmd_data, offset, commands[i].arg2, 3);
offset = KSM_WriteBytes(cmd_data, offset, commands[i].bitmode, 1);
}
// write final byte code data
offset = 0;
offset = KSM_WriteBytes(bytecode, offset, KSM_VERSION_VALUE, KSM_VER_S);
offset = KSM_WriteBytes(bytecode, offset, commands.length, KSM_CMD_S);
offset = KSM_WriteBytes(bytecode, offset, symbols.length, KSM_SYM_S);
for (i = 0; i < symbol_data.length; i++)
bytecode[offset++] = symbol_data[i];
for (i = 0; i < cmd_data.length; i++)
bytecode[offset++] = cmd_data[i];
// return constructed byte code
return bytecode;
}
/** KASM Runtime Context */
class KSM_Context {
constructor(stdout = console.log, VERBOSE = false, MX_FUNC_DEPTH = 1024) {
this.$ = 0; // current pointer / command index
this.REG = 0; // the only register to work with
this.RXX = 0; // the result of last executed command
this.version = {}; // context versioning info
this.stackPos = 0; // current position in fixed stack
this.stdout = stdout; // console functionoutput to write to
this.funcStackPos = 0; // current position in function queue
this.verbose = VERBOSE; // if runtime is verbose on instructions running
this.carryFlag = false; // carry flag for *CMP operations
this.stack = new Array(256); // the cpu stack memory
this.commands = [], this.symbols = []; // data needed to execute code
this.functionStack = new Array(MX_FUNC_DEPTH); // pointer position for function tree
}
/**
* Resize the context stack
* @param {Number} size the new size of the stack
*/
resizeStack(size) {
this.stack.length = size;
}
/**
* Convert a KSM_Command to KASM string code using context information
* @param {KSM_Command} cmd the command to decompile
*/
decompile(cmd) {
let str = KSM_Commands[cmd.cmd - 1] + ' '
str += cmd.bitmode & KSM_FREG ? KSM_Registers[cmd.arg1] :
(cmd.bitmode & KSM_FSYM ? this.symbols[cmd.arg1] : cmd.arg1);
str += ', ';
str += cmd.bitmode & KSM_SREG ? KSM_Registers[cmd.arg2] :
(cmd.bitmode & KSM_SSYM ? this.symbols[cmd.arg2] : cmd.arg2);
return str;
}
/**
* Get the current value of an argument for a command
* @param {Number} arg_pos 1 or 2 (the argument to use in the command)
* @param {KSM_Command} command the command to fetch its data from
*/
fetch(arg_pos, command) {
let value = arg_pos == 1 ? command.arg1 : command.arg2;
if (command.bitmode & (arg_pos == 1 ? KSM_FSYM : KSM_SSYM)) {
return this.commands.filter(c => c.cmd === 1 && c.arg1 === value)[0].arg2;
} else if (command.bitmode & (arg_pos == 1 ? KSM_FREG : KSM_SREG)) {
if (value === 0) return this.REG;
else if (value === 1) return this.RXX;
else if (value === 2) return this.$;
else if (value === 3) return 0;
else if (value === 4) return this.commands.length;
}
return value;
}
/**
* Modify a instruction/command in the context with a value
* @param {KSM_Command} command the command to modify
* @param {Any} value the value to set the command data to
*/
apply(command, value) {
if (command.bitmode & KSM_FSYM) {
for (let i = 0; i < this.commands.length; i++)
if (this.commands[i].cmd === 1)
if (this.commands[i].arg1 == command.arg1)
this.commands[i].arg2 = value;
} else if (command.bitmode & KSM_FREG) {
if (command.arg1 === 0) this.REG = value;
else if (command.arg1 === 1) this.RXX = value;
else if (command.arg1 === 2) this.$ = value;
else if (command.arg1 === 3) return value;
else if (value === 4) throw 'Exit cannot be assigned';
}
return value;
}
/**
* Start executing the kasm byte code loaded into the context
*/
execute() {
let current = this.$;
try {
for (this.$ = 0; this.$ < this.commands.length; this.$++) {
current = this.$;
this.evaluate(this.commands[this.$]);
if (this.$ < -1) this.$ = 0;
}
} catch (err) {
throw new Error('Error on pointer ' + current + ": " + err);
}
}
/**
* Set the context pointer position to another
* @param {Any} value the value to jump to
* @param {Number} bitmode bit configuration to use to decide how to jump
*/
jump(value, bitmode) {
if (bitmode & KSM_FREG) {
if (value === KSM_Registers.indexOf('end'))
this.$ = this.commands.length;
else
throw 'Register: ' + KSM_Registers[value] + ' cannot be jumped to';
} else if (bitmode & KSM_FSYM) {
for (let i = 0; i < this.commands.length; i++)
if (this.commands[i].cmd === 1)
if (this.commands[i].arg1 === value)
this.$ = i - 1;
} else this.$ = value - 1;
}
/**
* Execute a command using the current context information
* @param {KSM_Command} cmd the command to evaluate / execute
*/
evaluate(cmd) {
let carry_set = false; // for checking if carry bool is set
// pipe stdout decompiled when running
if (this.verbose)
this.stdout('>' + this.decompile(cmd));
// decide what to do on instruction
switch (KSM_Commands[cmd.cmd - 1].toUpperCase()) {
case 'SET': break; // skip setters
// call a function / pointer / command index
case 'CALL': {
if (this.funcStackPos === this.functionStack.length)
throw 'Max function depth reached: ' + this.functionStack.length;
this.functionStack[this.funcStackPos++] = this.$;
if (cmd.bitmode & KSM_FSYM) {
for (let i = 0; i < this.commands.length; i++)
if (this.commands[i].cmd === 1)
if (this.commands[i].arg1 === cmd.arg1)
this.$ = i + 1;
} else if (cmd.bitmode & KSM_FREG) {
if (cmd.arg1 === KSM_Registers.indexOf('end'))
this.$ = this.commands.length;
else throw 'Register: ' + KSM_Registers[cmd.arg1] + ' cannot be called';
} else throw cmd.arg1 + ' is not a function!';
break;
}
// return to last CALL
case 'RET': {
if (this.funcStackPos === 0)
throw 'Function was not called, so cannot return';
this.$ = this.functionStack[--this.funcStackPos];
this.RXX = this.fetch(1, cmd);
break;
}
// pop a value from stack
case 'POP': {
if (this.stackPos === 0)
throw 'Stack is empty';
this.RXX = this.apply(cmd, this.stack[--this.stackPos]);
break;
}
// push a value into the stack
case 'PUSH': {
if (this.stackPos === this.stack.length)
throw 'Stack is full';
this.stack[this.stackPos++] = this.fetch(1, cmd);
break;
}
// define remaining until RET to be a function
case 'FUNC': {
while (KSM_Commands[this.commands[this.$].cmd - 1].toUpperCase() !== 'RET')
this.$++;
break;
}
// set the stack size
case 'STAK': {
this.resizeStack(this.fetch(1, cmd));
break;
}
// increment a variable
case 'INC': {
this.RXX = this.apply(cmd, this.fetch(1, cmd) + 1);
break;
}
// decrement a variable
case 'DEC': {
this.RXX = this.apply(cmd, this.fetch(1, cmd) + 1);
break;
}
// jump to the end of the script
case 'EXIT': {
this.$ = this.commands.length;
break;
}
// copy one value into another
case 'MOV': {
this.RXX = this.apply(cmd, this.fetch(2, cmd));
break;
}
// jump to pointer / command index / symbol / label
case 'JMP': {
this.jump(cmd.arg1, cmd.bitmode);
break;
}
// jump if carry bool is set
case 'JCP': {
if (this.carryFlag)
this.jump(cmd.arg1, cmd.bitmode);
break;
}
// compare two values ==
case 'CMP': {
this.carryFlag = this.fetch(1, cmd) === this.fetch(2, cmd);
carry_set = true;
break;
}
// compare not values !=
case 'NCMP': {
this.carryFlag = this.fetch(1, cmd) !== this.fetch(2, cmd);
carry_set = true;
break;
}
// compare less than <
case 'LCMP': {
this.carryFlag = this.fetch(1, cmd) < this.fetch(2, cmd);
carry_set = true;
break;
}
// compare greater than >
case 'GCMP': {
this.carryFlag = this.fetch(1, cmd) > this.fetch(2, cmd);
carry_set = true;
break;
}
// compare less than or equal to <=
case 'LORE': {
this.carryFlag = this.fetch(1, cmd) <= this.fetch(2, cmd);
carry_set = true;
break;
}
// compare greater than or equal to <=
case 'GORE': {
this.carryFlag = this.fetch(1, cmd) >= this.fetch(2, cmd);
carry_set = true;
break;
}
// add a variable into another
case 'ADD': {
let ret = this.apply(cmd, this.fetch(1, cmd) + this.fetch(2, cmd));
if (!((cmd.bitmode & KSM_FREG) && KSM_Registers[cmd.arg1].toLowerCase() === 'rxx'))
this.RXX = ret;
break;
}
// subtract a variable from another
case 'SUB': {
let ret = this.apply(cmd, this.fetch(1, cmd) - this.fetch(2, cmd));
if (!((cmd.bitmode & KSM_FREG) && KSM_Registers[cmd.arg1].toLowerCase() === 'rxx'))
this.RXX = ret;
break;
}
// multiply a variable into another
case 'MUL': {
let ret = this.apply(cmd, this.fetch(1, cmd) * this.fetch(2, cmd));
if (!((cmd.bitmode & KSM_FREG) && KSM_Registers[cmd.arg1].toLowerCase() === 'rxx'))
this.RXX = ret;
break;
}
// divide a variable from another
case 'DIV': {
let ret = this.apply(cmd, this.fetch(1, cmd) / this.fetch(2, cmd));
if (!((cmd.bitmode & KSM_FREG) && KSM_Registers[cmd.arg1].toLowerCase() === 'rxx'))
this.RXX = ret;
break;
}
// output value of variable to screen
case 'OUT': {
this.stdout(this.fetch(1, cmd));
break;
}
}
// reset carry flag if wasnt set
if (!carry_set) this.carryFlag = false;
}
}
/**
* Create a KSM_Context using kasm byte code
* @param {Uint8Array} bytecode the bytecode data to use
*/
function KSM_CreateContext(bytecode) {
// extract meta data
let i, j, offset, version, cmd_size, sym_size;
let size, symbol, command, sym_offset, cmd_offset;
[version, offset] = KSM_ReadBytes(bytecode, 0, KSM_VER_S);
[cmd_size, offset] = KSM_ReadBytes(bytecode, offset, KSM_CMD_S);
[sym_size, offset] = KSM_ReadBytes(bytecode, offset, KSM_SYM_S);
// parse symbols and commands
sym_offset = 0; cmd_offset = 0;
let commands = new Array(cmd_size);
let symbols = new Array(sym_size);
// parse symbols
for (i = 0; i < symbols.length; i++) {
[size, offset] = KSM_ReadBytes(bytecode, offset, KSM_MX_SM);
symbol = new Array(size);
for (j = 0; j < size; j++)
symbol[j] = bytecode[offset++];
symbols[sym_offset++] = symbol.map(c => String.fromCharCode(c)).join('');
}
// parse commands
for (i = 0; i < commands.length; i++) {
command = new KSM_Command();
[command.cmd, offset] = KSM_ReadBytes(bytecode, offset, 1);
[command.arg1, offset] = KSM_ReadBytes(bytecode, offset, 3);
[command.arg2, offset] = KSM_ReadBytes(bytecode, offset, 3);
[command.bitmode, offset] = KSM_ReadBytes(bytecode, offset, 1);
commands[cmd_offset++] = command;
}
// start executing bytecode
let ctx = new KSM_Context();
ctx.commands = commands;
ctx.symbols = symbols;
ctx.version['major'] = (version >> 16) & 0xff;
ctx.version['minor'] = (version >> 8) & 0xff;
ctx.version['patch'] = (version >> 0) & 0xff;
return ctx;
}
class Scope {
constructor(name) {
this.name = name;
this.caller = null;
this.symbols = {};
this.functions = {};
}
}
class KSM_Context {
constructor(stdout, verbose, debug) {
this.stdout = stdout || console.log;
this.verbose = verbose || false;
this.maxRecursionDepth = 5; this.recursionDepth = 0;
this.debug = debug || ((...args) => this.stdout(...args));
this.scope = new Scope('global'); this.scopes = [this.scope];
this.keywords = ['$','reg','rxx','end', 'null', 'pop'];
this.$ = 0, this.rxx = 0, this.reg = 0; this.null = 0;
this.stack = [], this.context = []; this.skipScope = false;
}
end() {
return this.context.length;
}
push(value) {
this.stack.push(value);
return value;
}
pop() {
if (this.stack.length === 0) throw 'Stack is empty!';
return this.stack.pop();
}
jump(label) {
if (Object.keys(this.scope.functions).includes(label))
this.$ = this.scope.functions[label].pos;
else if (Object.keys(this.scope.symbols).includes(label))
this.$ = this.scope.symbols[label].pos;
else if (!isNaN(label))
this.$ = parseInt(label);
}
exec(code, on_line = (ctx, line)=>{}) {
for (let line of code.split('\n')) {
line = line.split(';')[0].trimLeft().trimRight();
on_line(this, line);
if (line.length < 1 || line[0] === ';') continue;
this.context.push(line);
}
let line = this.$ + 1;
while (this.$ < this.context.length) {
line = this.$ + 1;
try {
this.eval(this.context[this.$]);
this.$++;
} catch (msg) {
throw new Error(`Error on line:${line} (${this.context[line-1]}) ${msg}`);
}
}
}
set(item, value) {
let symbols = Object.keys(this.scope.symbols);
if (item === 'reg')
this.reg = value;
else if (item === '$')
this.$ = isNaN(value) ? this.$ : parseInt(value);
else if (item === 'rxx')
this.rxx = value;
else if (symbols.includes(item))
this.scope.symbols[item].value = value;
else this.rxx = value;
if (this.$ < -1) this.$ = -1;
}
get(value) {
let symbols = Object.keys(this.scope.symbols);
const _get = () => {
if (!value && value !== 0) value = '';
if (this.keywords.includes(value)) {
if (value === 'pop') return this.pop();
else if (value === 'null') return 0;
else if (value === 'end') return this.end();
else if (value === '$') return this.$;
else if (value === 'reg') return this.reg;
else if (value === 'rxx') return this.rxx;
else throw 'Register does not exist!';
} else if (symbols.includes(value)) {
return this.scope.symbols[value].value;
} else return !isNaN(value) ? parseInt(value) :
(value.startsWith('"') && value.endsWith('"') ?
value.split('').slice(1).slice(0, -1).join('') : null);
};
const _value = _get();
if (_value === null) throw `Symbol (${value}) does not exist in scope`;
return _value;
}
eval(line) {
let [cmd, ...args] = line.split(' ');
let [farg, ...sargs] = args.join(' ').match(/("[^"]*")|[^,]+/g);
args = [farg, sargs.join(',')]
.filter(i => i.length > 0).map(i => i.trimLeft().trimRight());
if (args.length < 1) throw 'No arguments provided';
if (cmd.toUpperCase() === 'RET') {
if (this.skipScope)
return (this.skipScope = false);
if (this.verbose) this.debug(cmd, args);
let caller = this.scopes.pop().caller;
this.set('rxx', this.get(args[0]));
this.scope = this.scopes[this.scopes.length - 1];
this.jump(caller);
return this.recursionDepth--;
}
if (this.skipScope) return;
if (this.verbose) this.debug(cmd, args);
switch (cmd.toUpperCase()) {
case 'POP': {
return this.set(this.pop());
}
case 'OUT': {
return this.stdout(this.get(args[0]));
}
case 'PUSH': {
this.push(this.get(args[0]));
return args.length > 1 ? this.push(this.get(args[1])) : null;
}
case 'SET': {
if (args[0] in this.scope.symbols)
throw `Symbol ${args[0]} already exists in scope!`;
return (this.scope.symbols[args[0]] =
{value: this.get(args[1]) || 0, pos: this.$});
}
case 'MOV': {
if (args.length < 2)
throw `Expected 2 arguments, received: ${args[0]}`;
return this.set(args[0], this.get(args[1]));
}
case 'FUNC': {
if (!isNaN(args[0][0]))
throw `Function names cannot begin with numbers! (${args[0]})`;
let scope = new Scope(args[0]);
this.scope.functions[args[0]] =
{scope: scope, pos: this.$};
return (this.skipScope = true);
}
case 'CALL': {
if (!(args[0] in this.scope.functions))
throw `Function (${args[0]}) does not exist in scope!`;
if (this.recursionDepth >= this.maxRecursionDepth)
throw `Max recursion depth (${this.maxRecursionDepth}) exceeded!`;
let caller = this.$;
let pos = this.scope.functions[args[0]].pos;
this.$ = this.scope.functions[args[0]].pos;
this.scopes.push(this.scope.functions[args[0]].scope);
this.scope = this.scopes[this.scopes.length - 1];
this.scope.caller = caller;
this.scope.functions[this.scope.name] =
{scope: new Scope(this.scope.name), pos: pos};
this.scopes[this.scopes.length - 1] = this.scope;
return this.recursionDepth++;
}
case 'DEL': {
if (this.keywords.includes(args[0]))
throw `Cannot delete register ${args[0]}`;
let value = this.get(args[0]);
if (Object.keys(this.scope.symbols).includes(args[0])) {
delete this.scope.symbols[args[0]];
delete this.scopes[this.scopes.length - 1].symbols[args[0]];
}
return value;
}
case 'JMP': {
this.jump(args[0]);
return (this.rxx = 0);
}
case 'JCP': {
this.rxx !== 0 ? this.jump(args[0]) : null;
return (this.rxx = 0);
}
case 'CMP': {
if (args.length < 2)
throw `Expected 2 arguments, received: ${args[0]}`;
return (this.rxx = (this.get(args[0]) === this.get(args[1]) ? 1 : 0));
}
case 'NE': {
if (args.length < 2)
throw `Expected 2 arguments, received: ${args[0]}`;
return (this.rxx = (this.get(args[0]) !== this.get(args[1]) ? 1 : 0));
}
case 'LT': {
if (args.length < 2)
throw `Expected 2 arguments, received: ${args[0]}`;
return (this.rxx = (this.get(args[0]) < this.get(args[1]) ? 1 : 0));
}
case 'LE': {
if (args.length < 2)
throw `Expected 2 arguments, received: ${args[0]}`;
return (this.rxx = (this.get(args[0]) <= this.get(args[1]) ? 1 : 0));
}
case 'GT': {
if (args.length < 2)
throw `Expected 2 arguments, received: ${args[0]}`;
return (this.rxx = (this.get(args[0]) > this.get(args[1]) ? 1 : 0));
}
case 'GE': {
if (args.length < 2)
throw `Expected 2 arguments, received: ${args[0]}`;
return (this.rxx = (this.get(args[0]) >= this.get(args[1]) ? 1 : 0));
}
case 'ADD': {
if (args.length < 2)
throw `Expected 2 arguments, received: ${args[0]}`;
this.rxx = this.get(args[0]) + this.get(args[1]);
return this.set(args[0], this.rxx);
}
case 'SUB': {
if (args.length < 2)
throw `Expected 2 arguments, received: ${args[0]}`;
this.rxx = this.get(args[0]) - this.get(args[1]);
return this.set(args[0], this.rxx);
}
case 'MUL': {
if (args.length < 2)
throw `Expected 2 arguments, received: ${args[0]}`;
this.rxx = this.get(args[0]) * this.get(args[1]);
return this.set(args[0], this.rxx);
}
case 'DIV': {
if (args.length < 2)
throw `Expected 2 arguments, received: ${args[0]}`;
this.rxx = this.get(args[0]) / this.get(args[1]);
return this.set(args[0], this.rxx);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment