Code for the littler Buttons the Computer used in the Turing Machine portion of the book.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
buttons-computer/src/node_modules/buttons.js

184 lines
4.1 KiB

class ButtonMachine {
constructor(code) {
this.stack = [];
this.ip = 0;
this.code = code;
this.max_ticks = 128;
this.tick = 0;
this.registers = {};
this.error = '';
this.error_line = 0;
this.halted = false;
}
assert(test, message) {
// I should use exceptions but not sure if I want to do that too early in the course
if(!test) {
let display_op = this.cur_op ? this.cur_op.join(' ') : 'NONE';
this.error_line = this.ip;
console.log(`HALT[FIRE]: ${message} at line #${this.ip}: ${display_op}`);
this.error = message;
this.halted = true;
}
return test;
}
static operations() {
return Object.getOwnPropertyNames(ButtonMachine.prototype)
.filter(x => x.startsWith('op_'))
.map(x => x.slice(3));
}
get stack_top() {
return this.stack[0];
}
get cur_op() {
return this.code[this.ip];
}
get register_entries() {
return Object.entries(this.registers);
}
infix(op_name, cb) {
let b = this.stack.pop();
this.assert(b !== undefined, `${op_name} right operand POP empty stack`);
let a = this.stack.pop();
this.assert(b !== undefined, `${op_name} left operand POP empty stack`);
let res = cb(a, b);
this.assert(res != NaN, `${op_name} results in NaN value`);
this.assert(res !== undefined, `${op_name} results in undefined value`);
this.stack.push(res);
this.next();
}
op_ADD() {
this.infix('ADD', (a,b) => a + b);
}
op_SUB() {
this.infix('SUB', (a,b) => a - b);
}
op_DIV() {
this.infix('DIV', (a,b) => a / b);
}
op_MUL() {
this.infix('MUL', (a,b) => a * b);
}
op_MOD() {
this.infix('MOD', (a,b) => a % b);
}
op_POP() {
let val = this.stack.pop();
this.next();
return val;
}
op_PUSH(value) {
this.stack.push(value);
this.next();
}
op_HALT(message) {
this.halted = true;
this.error = message;
}
op_JUMP(line) {
if(!this.assert(line !== undefined, `Invalid jump! You need to give a line number.`)) return;
if(!this.assert(line <= this.code.length, `Invalid jump to line ${line} last line is ${this.code.length}`)) return;
this.ip = line;
}
op_JZ(line) {
if(!this.assert(line !== undefined, `Invalid jump! You need to give a line number.`)) return;
if(!this.assert(line <= this.code.length, `Invalid jump to line ${line} last line is ${this.code.length}`)) return;
if(this.stack_top == 0) {
this.op_JUMP(line);
} else {
this.next();
}
}
op_JNZ(line) {
if(!this.assert(line !== undefined, `Invalid jump! You need to give a line number.`)) return;
if(!this.assert(line <= this.code.length, `Invalid jump to line ${line} last line is ${this.code.length}`)) return;
if(this.stack_top != 0) {
this.op_JUMP(line);
} else {
this.next();
}
}
op_CLR() {
Object.keys(this.registers).forEach(key => delete this.registers[key]); // clears register
this.stack.splice(0, this.stack.length); // clears the stack contents
this.ip = 0;
this.tick = 0;
this.error = '';
this.error_line = 0;
this.halted = false;
}
op_STOR(reg) {
this.registers[reg] = this.stack_top;
this.next();
}
op_RSTOR(reg) {
let val = this.registers[reg];
this.assert(val !== undefined, `Invalid register ${reg}`);
this.stack.push(val);
this.next();
}
get running() {
return this.halted === false && this.tick < this.max_ticks && this.ip < this.code.length && this.cur_op !== undefined;
}
next() {
this.ip++;
}
dump(leader) {
console.log(leader, "TICK", this.tick, "CUR", this.cur_op, "IP", this.ip, "STACK", this.stack);
}
step() {
if(this.running) {
let [op, data] = this.cur_op;
let op_func = this[`op_${op}`];
this.assert(op_func !== undefined, `Invalid operation ${op}`);
op_func.call(this, data);
this.tick++;
}
}
run(debug=false) {
while(this.running === true) {
if(debug) this.dump(">>>");
this.step();
if(debug) this.dump("<<<");
// this.tick is managed by this.step
}
}
}
module.exports = { ButtonMachine };