Last active
August 8, 2017 16:50
-
-
Save king1600/70ebc47e38a19ac2ebef60110a6f1dd8 to your computer and use it in GitHub Desktop.
KASM (King Assembly). My small implementation of a custom assembly like language
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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