whatlang-interpreter
Version:
Interpreter for WhatLang 2024, a stack-based esolang
411 lines (401 loc) • 14.1 kB
text/typescript
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)
}