Last active
August 24, 2022 08:21
-
-
Save Steffan153/860aa37d224aeb8ac37a1b2e7b5c5c5f to your computer and use it in GitHub Desktop.
This file contains hidden or 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
window.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 }; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment