UNPKG

whatlang-interpreter

Version:

Interpreter for WhatLang 2024, a stack-based esolang

411 lines (401 loc) 14.1 kB
import { fromBase64, toBase64 } from "@jsonjoy.com/base64" const op : Record<string, (x : any, y : any) => any> = { "+": (x, y) => ( Array.isArray(x) || Array.isArray(y) ? [].concat(x, y) : typeof x === "string" && typeof y !== "string" ? x + formatting(y) : typeof x !== "string" && typeof y === "string" ? formatting(x) + y : x + y ), "-": (x, y) => (x - y), "*": (x, y) => (x * y), "/": (x, y) => (x / y), "%": (x, y) => (x % y), "?": (x, y) => x == y ? 0 : +(x > y) - +(x < y) || NaN, } const relize = (x : string) => Array.isArray(x) ? new RegExp(x[0], x[1]) : x const safeFromBase64 = (x: string) => { x = x.replace(/[^A-Za-z0-9+/]+/g, "") return fromBase64(x.padEnd(Math.ceil(x.length / 4) * 4, "=")) } export var default_var_dict : Record<string, any> = ({ num: (x : any) => Number(x), str: (x : any) => typeof x === "string" ? x : formatting(x), repr: (x : any) => repr_formatting(x), arr: (x : any) => [...x], pow: (x : any, y : any) => x ** y, sin: (x : any) => Math.sin(x), cos: (x : any) => Math.cos(x), tan: (x : any) => Math.tan(x), asin: (x : any) => Math.asin(x), acos: (x : any) => Math.acos(x), atan: (x : any) => Math.atan(x), band: (x : any, y : any) => x & y, bor: (x : any, y : any) => x | y, bxor: (x : any, y : any) => x ^ y, bnot: (x : any) => ~x, rand: () => Math.random(), randint: (x : any, y : any) => Math.floor((Math.random() * (x - y)) + y), flr: (x : any) => Math.floor(x), range: (x : any) => [...Array(x).keys()], len: (s : any[][]) => s.at(-1).at(-1).length ?? null, split: (x : any, y : any) => (typeof x == "string" ? x : formatting(x)).split(relize(y)), join: (x : any, s : any[][]) => ([...s.at(-1).at(-1)] .map(i => typeof i == "string" ? i : formatting(i)) .join(x) ), reverse: (s : any[][]) => [...s.at(-1).at(-1)].reverse(), in: (x : any, s : any[][]) => [...s.at(-1).at(-1)].indexOf(x), filter: async ( x : any, s : any[][], v : Record<string, any>, o : (x : any) => void, ) => { const arr = []; for (const i of s.at(-1).at(-1)) { const result = await exec_what([s.at(-1).concat([i, x])], v, o); if (result || Number.isNaN(result)) arr.push(i); } return arr }, chr: (x : any) => Array.isArray(x) ? String.fromCodePoint(...x) : String.fromCodePoint(x), ord: (x : any) => [...typeof x == "string" ? x : formatting(x)].map(i => i.codePointAt(0)), and: (x : any, y : any) => Number.isNaN(x) ? y : x && y, or: (x : any, y : any) => Number.isNaN(x) ? x : x || y, nan: () => NaN, undef: (s : any[][]) => void s.at(-1).push(undefined), inf: () => Infinity, ninf: () => -Infinity, eq: (x : any, y : any) => +(x === y), stak: (s : any[][]) => s.at(-1), stack: (s : any[][]) => [...s.at(-1)], try: async ( s : any[][], v : Record<string, any>, o : (x : any) => void, ) => { let temp : string[] = [undefined, undefined] let stack = s.at(-1) let temp2 = [stack] try { await exec_what(temp2, v, o) } catch (e) { if (e?.[Symbol.for("whatlang.uncatchable_exception")]) throw e temp = [e.name, e.message] if (temp2.includes(stack)) { while (temp2.at(-1) !== stack) { temp2.at(-2).push(temp2.pop()) } } } return temp }, throw: (x : any) => {throw new Error(x)}, match: (x : any, y : any) => [...x.match(relize(y)) || []], repl: (x : any, y : any, z : any) => x.replace(relize(y), z), time: () => Date.now(), type: (x : any) => x == undefined ? "Undefined" : x.constructor.name, b64: (x: any) => toBase64(x), nb64: (x: any) => [...safeFromBase64(typeof x == "string" ? x : formatting(x))], utf8: (x: any) => [...new TextEncoder().encode(typeof x == "string" ? x : formatting(x))], nutf8: (x: any) => new TextDecoder().decode(new Uint8Array(x)), }) export var need_svo : string[] = "filter try".split(" ") export var need_fstack : string[] = "len join reverse in stak stack undef".split(" ") export const formatting : (x : any) => string = (x : any) => { if (Array.isArray(x)) { return "[" + x.map( i => Array.isArray(i) && i == x ? "[...]" : formatting(i) ).join(", ") + "]" } else if (typeof x == "string") { return '"' + (x .replace(/"/g, '\\"') .replace(/\n/g, '\\n') .replace(/\t/g, '\\t') .replace(/\r/g, '\\r') .replace(/\f/g, '\\f') .replace(/\v/g, '\\v') ) + '"' } else if (x == undefined) { return "undef" } else if (Number.isNaN(x)) { return "NaN" } else if (x == Infinity) { return "Inf" } else if (x == -Infinity) { return "-Inf" } return String(x) } const is_valid_paren_string = (x : string) : boolean => { let depth = 0 for (const c of x) { if (c === "(") depth++ else if (c === ")") depth-- if (depth < 0) return false } return depth === 0 } const repr_formatting : (x : any) => string = (x : any) => { if (Array.isArray(x)) { return "[" + x.map( i => Array.isArray(i) && i == x ? "stack@" : repr_formatting(i) ).join(" ") + "]" } else if (typeof x == "string") { if (/^[a-z][a-z0-9_]*$/.test(x)) return x else if (is_valid_paren_string(x)) return "(" + x + ")" else return '"' + (x .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/\n/g, '\\n') .replace(/\t/g, '\\t') .replace(/\r/g, '\\r') .replace(/\f/g, '\\f') .replace(/\v/g, '\\v') ) + '"' } else if (x === undefined) { return "undef@" } else if (Number.isNaN(x)) { return "nan@" } else if (x == Infinity) { return "inf@" } else if (x == -Infinity) { return "ninf@" } else if (Object.is(x, -0)) { return "(-0)num@" } else if (typeof x == "number") { if ( x < 0 || x >= 1.0e+21 || !Number.isInteger(x) ) return "(" + String(x) + ")num@" return String(x) } return "${" + String(x) + "}" } export const exec_what = async ( fstack : any[][], var_dict : Record<string, any>, output : (x : any) => void, ) => { var stack : any[] = fstack.at(-1) let temp : any, temp2 : any, temp3 : any //I should stop temping temp = stack.pop() if (temp in var_dict && typeof var_dict[temp] === "function") { temp3 = ( need_svo.includes(temp) ? 3 : need_fstack.includes(temp) ? 1 : 0 ) temp = var_dict[temp] temp2 = [fstack, var_dict, output] temp2.splice(temp3) temp2 = (temp.length > temp3 ? stack.splice(temp3 - temp.length) : []).concat(temp2) temp = await temp(...new Array(temp.length - temp2.length), ...temp2) if (temp === null) stack.push(undefined) else if (temp != undefined) stack.push(temp) } else { temp2 = temp in var_dict ? var_dict[temp] : temp await eval_what(temp2, fstack, var_dict, output) } return stack.at(-1) } export const run_what = async ( code : string, var_dict : Record<string, any> = default_var_dict, ) => { let output : string = "" let stack : any = await eval_what( code, [[]], Object.assign({}, var_dict), (x : any) => {output += x}, ) return ({ stack: stack, output: output, }) } export const eval_what = async ( code : string, fstack : any[][], var_dict : Record<string, any>, output : (x : any) => void = (x : any) => console.log(x), ) => { let dead_loop_check = (var_dict as any)[Symbol.for("whatlang.dead_loop_check")] ?? (() => {}) var stack : any[] = fstack.at(-1) let i : number = -1, c : string let temp : any, temp2 : any while (++i < code.length) { if (dead_loop_check()) throw Object.assign( new Error("Execution timeout"), { [Symbol.for("whatlang.uncatchable_exception")]: true }, ) c = code[i] if (/\s/.test(c)) { continue } else if (/[1-9]/.test(c)) { temp = 0 do { temp = temp * 10 + Number(c) c = code[++i] } while (c && /\d/.test(c)) c = code[--i] stack.push(temp) } else if ('0' === c) { stack.push(0) } else if (/[a-zA-Z]/.test(c)) { temp = "" do { temp += c c = code[++i] } while (c && /[a-zA-Z0-9_]/.test(c)) c = code[--i] stack.push(temp.toLowerCase()) } else if ("'" === c) { if (code.codePointAt(++i) > 0xffff) stack.push(code.slice(i, ++i + 1)) else stack.push(code[i]) } else if (/["`]/.test(c)) { temp = "" temp2 = c c = code[++i] while (c) { if ("\\" === c) { c = code[++i] temp += ({ "n": "\n", "t": "\t", "r": "\r", "f": "\f", "v": "\v", [temp2]: temp2 })[c] ?? c } else if (temp2 === c) break else temp += c c = code[++i] } if ('"' === temp2) { stack.push(temp) } else if ('`' === temp2) { output(temp) } } else if (c in op) { temp = stack.pop() stack.push(op[c](stack.pop(), temp)) } else if ('~' === c) { temp = stack.pop() stack.push(Number.isNaN(temp) ? 0 : +!temp) } else if ('[' === c) { stack = [] fstack.push(stack) } else if ('|' === c) { temp = stack.pop() fstack.push(temp) stack = temp } else if (']' === c) { if (fstack.length <= 2) fstack.unshift([]) stack = fstack.at(-2) stack.push(fstack.pop()) } else if ('(' === c) { temp = "" temp2 = 1 c = code[++i] while (true) { if ('(' === c) ++temp2 else if (')' === c) --temp2 if (!c || !temp2) break temp += c c = code[++i] } stack.push(temp) } else if ('.' === c) { temp = stack.at(-1) output(typeof temp == "string" ? temp : formatting(temp)) } else if ('\\' === c) { if (stack.length >= 2) { temp = stack.pop() temp2 = stack.pop() stack.push(temp, temp2) } } else if ('&' === c) { if (stack.length >= 2) stack.unshift(stack.pop()) } else if (':' === c) { if (stack.length >= 1) { temp = stack.pop() stack.push(temp, temp) } } else if ('_' === c) { stack.pop() } else if ('=' === c) { temp = stack.pop() var_dict[temp] = stack.at(-1) } else if ('^' === c) { temp = stack.pop() temp2 = var_dict[temp] stack.push(typeof temp2 == "function" ? temp + "@" : temp2) } else if ('@' === c) { await exec_what(fstack, var_dict, output) stack = fstack.at(-1) } else if ('>' === c) { stack.push(stack.splice(-stack.pop())) } else if ('<' === c) { stack.push(...stack.pop()) } else if ('{' === c) { temp = stack.pop() if (!(Number.isNaN(temp) || temp)) { temp = 1 while (c && temp) { c = code[++i] if ('{' === c) ++temp else if ('}' === c) --temp } } } else if ('}' === c) { temp = stack.pop() if (Number.isNaN(temp) || temp) { temp = -1 while (c && temp) { c = code[--i] if ('{' === c) ++temp else if ('}' === c) --temp } } } else if ('!' === c) { temp = 1 while ('!' === code[++i]) temp++ c = code[--i] while (c && temp) { c = code[++i] if ('{' === c) ++temp else if ('}' === c) --temp } } else if ("#" === c) { temp = stack.pop() const arr = [] for (const x of stack.at(-1)) { const result = await exec_what([stack.concat([x, temp])], var_dict, output) arr.push(result) } stack.push(arr) } else if ("," === c) { temp = stack.pop() stack.push(stack.at(-1).slice(temp)[0]) } else if (";" === c) { temp = stack.pop() temp2 = stack.pop() if ([undefined, +stack.at(-1).length].includes(temp2) || Number.isNaN(temp2)) { stack.at(-1).push(temp) } else { temp2 = +temp2 || 0 if (temp2 < 0) temp2 += stack.at(-1).length if (temp2 >= 0) stack.at(-1).fill(temp, temp2, temp2 + 1) } } else if ("$" === c) { temp = stack.pop() stack.at(-1).splice(temp, 1) } //console.log(stack) temp = void 0, temp2 = void 0 } return stack.at(-1) }