whatlang-interpreter
Version:
Interpreter for WhatLang 2024, a stack-based esolang
432 lines (431 loc) • 13.9 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.eval_what = exports.run_what = exports.exec_what = exports.formatting = exports.need_fstack = exports.need_svo = exports.default_var_dict = void 0;
const op = {
"+": (x, y) => (Array.isArray(x) || Array.isArray(y) ? [].concat(x, y)
: typeof x === "string" && typeof y !== "string" ? x + (0, exports.formatting)(y)
: typeof x !== "string" && typeof y === "string" ? (0, exports.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) => Array.isArray(x) ? new RegExp(x[0], x[1]) : x;
exports.default_var_dict = ({
num: (x) => Number(x),
str: (x) => typeof x === "string" ? x : (0, exports.formatting)(x),
repr: (x) => repr_formatting(x),
arr: (x) => [...x],
pow: (x, y) => x ** y,
sin: (x) => Math.sin(x),
cos: (x) => Math.cos(x),
tan: (x) => Math.tan(x),
asin: (x) => Math.asin(x),
acos: (x) => Math.acos(x),
atan: (x) => Math.atan(x),
band: (x, y) => x & y,
bor: (x, y) => x | y,
bxor: (x, y) => x ^ y,
bnot: (x) => ~x,
rand: () => Math.random(),
randint: (x, y) => Math.floor((Math.random() * (x - y)) + y),
flr: (x) => Math.floor(x),
range: (x) => [...Array(x).keys()],
len: (s) => s.at(-1).at(-1).length ?? null,
split: (x, y) => (typeof x == "string" ? x : (0, exports.formatting)(x)).split(relize(y)),
join: (x, s) => ([...s.at(-1).at(-1)]
.map(i => typeof i == "string" ? i : (0, exports.formatting)(i))
.join(x)),
reverse: (s) => [...s.at(-1).at(-1)].reverse(),
in: (x, s) => [...s.at(-1).at(-1)].indexOf(x),
filter: async (x, s, v, o) => {
const arr = [];
for (const i of s.at(-1).at(-1)) {
const result = await (0, exports.exec_what)([s.at(-1).concat([i, x])], v, o);
if (result || Number.isNaN(result))
arr.push(i);
}
return arr;
},
chr: (x) => Array.isArray(x) ? String.fromCodePoint(...x) : String.fromCodePoint(x),
ord: (x) => [...typeof x == "string" ? x : (0, exports.formatting)(x)].map(i => i.codePointAt(0)),
and: (x, y) => Number.isNaN(x) ? y : x && y,
or: (x, y) => Number.isNaN(x) ? x : x || y,
nan: () => NaN,
undef: (s) => void s.at(-1).push(undefined),
inf: () => Infinity,
ninf: () => -Infinity,
eq: (x, y) => +(x === y),
stak: (s) => s.at(-1),
stack: (s) => [...s.at(-1)],
try: async (s, v, o) => {
let temp = [undefined, undefined];
let stack = s.at(-1);
let temp2 = [stack];
try {
await (0, exports.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) => { throw new Error(x); },
match: (x, y) => [...x.match(relize(y)) || []],
repl: (x, y, z) => x.replace(relize(y), z),
time: () => Date.now(),
type: (x) => x == undefined ? "Undefined" : x.constructor.name,
});
exports.need_svo = "filter try".split(" ");
exports.need_fstack = "len join reverse in stak stack undef".split(" ");
const formatting = (x) => {
if (Array.isArray(x)) {
return "[" + x.map(i => Array.isArray(i) && i == x ? "[...]" : (0, exports.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);
};
exports.formatting = formatting;
const is_valid_paren_string = (x) => {
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) => {
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(/\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) + "}";
};
const exec_what = async (fstack, var_dict, output) => {
var stack = fstack.at(-1);
let temp, temp2, temp3;
//I should stop temping
temp = stack.pop();
if (temp in var_dict && typeof var_dict[temp] === "function") {
temp3 = (exports.need_svo.includes(temp) ? 3 :
exports.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 (0, exports.eval_what)(temp2, fstack, var_dict, output);
}
return stack.at(-1);
};
exports.exec_what = exec_what;
const run_what = async (code, var_dict = exports.default_var_dict) => {
let output = "";
let stack = await (0, exports.eval_what)(code, [[]], Object.assign({}, var_dict), (x) => { output += x; });
return ({
stack: stack,
output: output,
});
};
exports.run_what = run_what;
const eval_what = async (code, fstack, var_dict, output = (x) => console.log(x)) => {
let dead_loop_check = var_dict[Symbol.for("whatlang.dead_loop_check")] ?? (() => { });
var stack = fstack.at(-1);
let i = -1, c;
let temp, temp2;
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 : (0, exports.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 (0, exports.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 (0, exports.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);
};
exports.eval_what = eval_what;
;