Skip to content

Instantly share code, notes, and snippets.

@mousetail
Forked from Steffan153/knight.js
Last active August 24, 2022 08:22
Show Gist options
  • Save mousetail/e4bb725619c8832ef8e3c82084d8ddd8 to your computer and use it in GitHub Desktop.
Save mousetail/e4bb725619c8832ef8e3c82084d8ddd8 to your computer and use it in GitHub Desktop.
Bubbler version of Knight, forked from Seffan
const fs = require('node:fs');
const input = String.fromCharCode(...fs.readFileSync(0)).split('\n');
const source = fs.readFileSync('source.knight', 'utf8');
const Knight = (() => {
let inputs = [];
let output = '';
class KnightError extends Error {}
class ParseError extends KnightError {}
class RuntimeError extends KnightError {}
class Stream {
constructor(source) {
this.source = source;
}
stripWhitespace() {
this.match(/^([\]\[\s(){}:]+|#[^\n]*(\n|$))*/);
}
peek() {
return this.source[0] || null;
}
match(regex, group = 0) {
const match = regex.exec(this.source);
if (match === null) {
return null;
}
this.source = this.source.substr(match[0].length);
return match[group];
}
toString() {
return this.source;
}
}
const TYPES = [];
class Value {
static parse(stream) {
stream.stripWhitespace();
for (var i = 0; i < TYPES.length; i++) {
const match = TYPES[i].parse(stream);
if (match) {
return match;
}
}
return null;
}
run() {
throw new Error();
}
debug() {
throw new Error();
}
toString() {
return this.run().toString();
}
toNumber() {
return this.run().toNumber();
}
toBoolean() {
return this.run().toBoolean();
}
}
class Literal extends Value {
_data;
constructor(data) {
super();
this._data = data;
}
run() {
return this;
}
toString() {
return String(this._data);
}
toNumber() {
return Number(this._data);
}
toBoolean() {
return Boolean(this._data);
}
eql(rhs) {
return rhs instanceof this.constructor && this._data === rhs._data;
}
}
class Bool extends Literal {
static parse(stream) {
const match = stream.match(/^([TF])[A-Z]*/, 1);
return match && new Bool(match === "T");
}
dump() {
return `Boolean(${this})`;
}
lth(rhs) {
return !this._data && rhs.toBoolean();
}
gth(rhs) {
return this._data && !rhs.toBoolean();
}
}
TYPES.push(Bool);
const ENVIRONMENT = {};
class Ident extends Value {
static parse(stream) {
const match = stream.match(/^[a-z_][a-z0-9_]*/);
return match && new Ident(match);
}
constructor(ident) {
super();
this.ident = ident;
}
dump() {
return `Identifier(${this.ident})`;
}
assign(value) {
ENVIRONMENT[this.ident] = value;
}
run() {
const value = ENVIRONMENT[this.ident];
if (value === undefined) {
throw new RuntimeError(`Unknown identifier '${this.ident}'`);
} else {
return value;
}
}
}
TYPES.push(Ident);
class Int extends Literal {
static parse(stream) {
const match = stream.match(/^\d+/);
return match && new Int(Number(match));
}
dump() {
return `Number(${this})`;
}
add(rhs) {
return new Int(this._data + rhs.toNumber());
}
sub(rhs) {
return new Int(this._data - rhs.toNumber());
}
mul(rhs) {
return new Int(this._data * rhs.toNumber());
}
div(rhs) {
const rhsInt = rhs.toNumber();
if (rhsInt === 0) {
throw new RuntimeError("Cannot divide by zero");
} else {
return new Int(Math.trunc(this._data / rhsInt));
}
}
mod(rhs) {
const rhsInt = rhs.toNumber();
if (rhsInt === 0) {
throw new RuntimeError("Cannot modulo by zero");
} else {
return new Int(this._data % rhsInt);
}
}
pow(rhs) {
const rhsInt = rhs.toNumber();
if (this._data === 0 && rhsInt < 0) {
throw new RuntimeError("Cannot exponentiate zero to a negative power");
} else {
return new Int(Math.trunc(this._data ** rhsInt));
}
}
lth(rhs) {
return this._data < rhs.toNumber();
}
gth(rhs) {
return this._data > rhs.toNumber();
}
}
TYPES.push(Int);
class Null extends Literal {
static parse(stream) {
return stream.match(/^N[A-Z]*/) && new Null();
}
constructor() {
super(null);
}
dump() {
return "Null()";
}
eql(rhs) {
return rhs instanceof Null;
}
lth(_rhs) {
throw new RuntimeError("Cannot compare Null.");
}
gth(_rhs) {
throw new RuntimeError("Cannot compare Null.");
}
}
TYPES.push(Null);
class Str extends Literal {
static parse(stream) {
const match = stream.match(/^(["'])([\s\S]*?)\1/, 2);
if (match !== null) {
return new Str(match);
}
const first = stream.peek();
if (first === "'" || first === '"') {
throw new ParseError(`Unterminated quote encountered: ${stream}`);
}
}
toNumber() {
return parseInt(this._data, 10) || 0;
}
dump() {
return `String(${this})`;
}
add(rhs) {
return new Str(`${this}${rhs}`);
}
mul(rhs) {
return new Str(this._data.repeat(rhs.toNumber()));
}
lth(rhs) {
return this._data < rhs.toString();
}
gth(rhs) {
return this._data > rhs.toString();
}
}
TYPES.push(Str);
const FUNCTIONS = {};
class Func extends Value {
static parse(stream) {
const front = stream.peek();
const func = FUNCTIONS[front];
if (func === undefined) {
return null;
}
stream.match(/^(?:[A-Z]+|.)/);
let args = [];
for (let i = 0; i < func.length; i++) {
const arg = Value.parse(stream);
if (!arg) {
throw new ParseError(`Missing argument ${i + 1} for func '${front}'`);
}
args.push(arg);
}
return new Func(func, front, args);
}
constructor(func, name, args) {
super();
this.func = func;
this.name = name;
this.args = args;
}
run() {
return this.func(...this.args);
}
dump() {
let ret = "Function(" + this.name;
for (let val of this.args) {
ret += ", " + val.dump();
}
return ret + ")";
}
eql(rhs) {
return Object.is(this, rhs);
}
}
TYPES.push(Func);
function register(name, func) {
if (name.length !== 1) {
throw new Error("Name must be exactly one character long.");
}
FUNCTIONS[name] = func;
}
register("P", () => {
return inputs.length ? new Str(inputs.shift()) : new Null();
});
register("R", () => new Int(Math.floor(Math.random() * 0x1_0000_0000)));
register("E", (string) => evalRun(string.toString()));
register("A", (a) => {
const b = a.run();
return b instanceof Int
? new Str(String.fromCharCode(b.toNumber()))
: new Int(b.toString().charCodeAt(0));
});
register("~", (a) => new Int(-a.toNumber()));
register("B", (block) => block);
register("C", (block) => block.run().run());
register("`", (command) => {
throw new RuntimeError('Run shell command not supported on DSO.');
});
register("Q", (status) => {
throw new RuntimeError('Exit not supported on DSO.');
});
register("!", (arg) => new Bool(!arg.toBoolean()));
register("L", (str) => new Int(str.toString().length));
register("D", (value) => {
const result = value.run();
output += result.dump();
return result;
});
register("O", (input) => {
const str = input.toString();
if (str.substr(-1) === "\\") {
output += str.substr(0, str.length - 1);
} else {
output += `${str}\n`;
}
return new Null();
});
register("+", (lhs, rhs) => lhs.run().add(rhs.run()));
register("-", (lhs, rhs) => lhs.run().sub(rhs.run()));
register("*", (lhs, rhs) => lhs.run().mul(rhs.run()));
register("/", (lhs, rhs) => lhs.run().div(rhs.run()));
register("%", (lhs, rhs) => lhs.run().mod(rhs.run()));
register("^", (lhs, rhs) => lhs.run().pow(rhs.run()));
register("<", (lhs, rhs) => new Bool(lhs.run().lth(rhs.run())));
register(">", (lhs, rhs) => new Bool(lhs.run().gth(rhs.run())));
register("?", (lhs, rhs) => new Bool(lhs.run().eql(rhs.run())));
register("&", (lhs, rhs) => {
lhs = lhs.run();
return lhs.toBoolean() ? rhs.run() : lhs;
});
register("|", (lhs, rhs) => {
lhs = lhs.run();
return lhs.toBoolean() ? lhs : rhs.run();
});
register(";", (lhs, rhs) => {
lhs.run();
return rhs.run();
});
register("=", (ident, value) => {
if (!(ident instanceof Ident)) {
ident = new Ident(ident.toString());
}
ident.assign((value = value.run()));
return value;
});
register("W", (condition, body) => {
while (condition.toBoolean()) {
body.run();
}
return new Null();
});
register("I", (cond, iftrue, iffalse) => {
return cond.toBoolean() ? iftrue.run() : iffalse.run();
});
register("G", (str, start, len) => {
str = str.toString();
start = start.toNumber();
len = len.toNumber();
return new Str(str.substr(start, len) || "");
});
register("S", (str, start, len, repl) => {
str = str.toString();
start = start.toNumber();
len = len.toNumber();
repl = repl.toString();
if (str.length == start) {
return new Str(str + repl);
}
return new Str(str.substr(0, start) + repl + str.substr(start + len));
});
function evalRun(input) {
const value = Value.parse(new Stream(input.toString()));
if (value === null) {
throw new ParseError("No value could be parsed!");
} else {
return value.run();
}
}
function run(input, inps = []) {
inputs = [...inps];
output = '';
const value = Value.parse(new Stream(input.toString()));
if (value === null) {
throw new ParseError("No value could be parsed!");
} else {
value.run();
return output;
}
}
return { run };
})();
const output = Knight.run(source, input);
fs.writeFileSync(1, output, 'latin1');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment