Evaluator start
parent
00501690cf
commit
4c87d3eb9e
|
@ -0,0 +1,26 @@
|
||||||
|
import { assertEquals } from "@std/assert/equals";
|
||||||
|
import {
|
||||||
|
evalExpression,
|
||||||
|
LuaEnv,
|
||||||
|
LuaNativeJSFunction,
|
||||||
|
singleResult,
|
||||||
|
} from "./eval.ts";
|
||||||
|
import { type LuaFunctionCallStatement, parse } from "./parse.ts";
|
||||||
|
|
||||||
|
function evalExpr(s: string, e = new LuaEnv()): any {
|
||||||
|
return evalExpression(
|
||||||
|
(parse(`e(${s})`).statements[0] as LuaFunctionCallStatement).call
|
||||||
|
.args[0],
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test("Evaluator test", async () => {
|
||||||
|
const env = new LuaEnv();
|
||||||
|
env.set("test", new LuaNativeJSFunction(() => 3));
|
||||||
|
env.set("asyncTest", new LuaNativeJSFunction(() => Promise.resolve(3)));
|
||||||
|
assertEquals(evalExpr(`1 + 2`), 3);
|
||||||
|
|
||||||
|
assertEquals(singleResult(evalExpr(`test()`, env)), 3);
|
||||||
|
assertEquals(singleResult(await evalExpr(`asyncTest() + 1`, env)), 4);
|
||||||
|
});
|
|
@ -0,0 +1,240 @@
|
||||||
|
import type {
|
||||||
|
LuaExpression,
|
||||||
|
LuaFunctionBody,
|
||||||
|
} from "$common/space_lua/parse.ts";
|
||||||
|
import { evalPromiseValues } from "$common/space_lua/util.ts";
|
||||||
|
|
||||||
|
export class LuaEnv {
|
||||||
|
variables = new Map<string, any>();
|
||||||
|
constructor(readonly parent?: LuaEnv) {
|
||||||
|
}
|
||||||
|
|
||||||
|
set(name: string, value: any) {
|
||||||
|
this.variables.set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(name: string): any {
|
||||||
|
if (this.variables.has(name)) {
|
||||||
|
return this.variables.get(name);
|
||||||
|
}
|
||||||
|
if (this.parent) {
|
||||||
|
return this.parent.get(name);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LuaMultiRes {
|
||||||
|
constructor(readonly values: any[]) {
|
||||||
|
}
|
||||||
|
|
||||||
|
unwrap(): any {
|
||||||
|
if (this.values.length !== 1) {
|
||||||
|
throw new Error("Cannot unwrap multiple values");
|
||||||
|
}
|
||||||
|
return this.values[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function singleResult(value: any): any {
|
||||||
|
if (value instanceof LuaMultiRes) {
|
||||||
|
return value.unwrap();
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ILuaFunction {
|
||||||
|
call(...args: any[]): Promise<LuaMultiRes> | LuaMultiRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LuaFunction implements ILuaFunction {
|
||||||
|
constructor(readonly body: LuaFunctionBody) {
|
||||||
|
}
|
||||||
|
|
||||||
|
call(...args: any[]): Promise<LuaMultiRes> | LuaMultiRes {
|
||||||
|
throw new Error("Not yet implemented funciton call");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LuaNativeJSFunction implements ILuaFunction {
|
||||||
|
constructor(readonly fn: (...args: any[]) => any) {
|
||||||
|
}
|
||||||
|
|
||||||
|
call(...args: any[]): Promise<LuaMultiRes> | LuaMultiRes {
|
||||||
|
const result = this.fn(...args);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
return result.then((result) => new LuaMultiRes([result]));
|
||||||
|
} else {
|
||||||
|
return new LuaMultiRes([result]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LuaTable {
|
||||||
|
constructor(readonly entries: Map<any, any> = new Map()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: any): any {
|
||||||
|
return this.entries.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key: any, value: any) {
|
||||||
|
this.entries.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function luaSet(obj: any, key: any, value: any) {
|
||||||
|
if (obj instanceof LuaTable) {
|
||||||
|
obj.set(key, value);
|
||||||
|
} else {
|
||||||
|
obj[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function luaGet(obj: any, key: any): any {
|
||||||
|
if (obj instanceof LuaTable) {
|
||||||
|
return obj.get(key);
|
||||||
|
} else {
|
||||||
|
return obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function evalExpression(
|
||||||
|
e: LuaExpression,
|
||||||
|
env: LuaEnv,
|
||||||
|
): Promise<any> | any {
|
||||||
|
switch (e.type) {
|
||||||
|
case "String":
|
||||||
|
// TODO: Deal with escape sequences
|
||||||
|
return e.value;
|
||||||
|
case "Number":
|
||||||
|
return e.value;
|
||||||
|
case "Boolean":
|
||||||
|
return e.value;
|
||||||
|
case "Nil":
|
||||||
|
return null;
|
||||||
|
case "Binary": {
|
||||||
|
const values = evalPromiseValues([
|
||||||
|
evalExpression(e.left, env),
|
||||||
|
evalExpression(e.right, env),
|
||||||
|
]);
|
||||||
|
if (values instanceof Promise) {
|
||||||
|
return values.then(([left, right]) =>
|
||||||
|
luaOp(e.operator, singleResult(left), singleResult(right))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return luaOp(
|
||||||
|
e.operator,
|
||||||
|
singleResult(values[0]),
|
||||||
|
singleResult(values[1]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "TableAccess": {
|
||||||
|
const values = evalPromiseValues([
|
||||||
|
evalPrefixExpression(e.object, env),
|
||||||
|
evalExpression(e.key, env),
|
||||||
|
]);
|
||||||
|
if (values instanceof Promise) {
|
||||||
|
return values.then(([table, key]) =>
|
||||||
|
luaGet(singleResult(table), singleResult(key))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return luaGet(singleResult(values[0]), singleResult(values[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Variable":
|
||||||
|
case "FunctionCall":
|
||||||
|
return evalPrefixExpression(e, env);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown expression type ${e.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function evalPrefixExpression(
|
||||||
|
e: LuaExpression,
|
||||||
|
env: LuaEnv,
|
||||||
|
): Promise<any> | any {
|
||||||
|
switch (e.type) {
|
||||||
|
case "Variable": {
|
||||||
|
const value = env.get(e.name);
|
||||||
|
if (value === undefined) {
|
||||||
|
throw new Error(`Undefined variable ${e.name}`);
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Parenthesized":
|
||||||
|
return evalExpression(e.expression, env);
|
||||||
|
case "FunctionCall": {
|
||||||
|
const fn = evalPrefixExpression(e.prefix, env);
|
||||||
|
if (fn instanceof Promise) {
|
||||||
|
return fn.then((fn: ILuaFunction) => {
|
||||||
|
if (!fn.call) {
|
||||||
|
throw new Error(`Not a function: ${fn}`);
|
||||||
|
}
|
||||||
|
const args = evalPromiseValues(
|
||||||
|
e.args.map((arg) => evalExpression(arg, env)),
|
||||||
|
);
|
||||||
|
if (args instanceof Promise) {
|
||||||
|
return args.then((args) => fn.call(...args));
|
||||||
|
} else {
|
||||||
|
return fn.call(...args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!fn.call) {
|
||||||
|
throw new Error(`Not a function: ${fn}`);
|
||||||
|
}
|
||||||
|
const args = evalPromiseValues(
|
||||||
|
e.args.map((arg) => evalExpression(arg, env)),
|
||||||
|
);
|
||||||
|
if (args instanceof Promise) {
|
||||||
|
return args.then((args) => fn.call(...args));
|
||||||
|
} else {
|
||||||
|
return fn.call(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown prefix expression type ${e.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function luaOp(op: string, left: any, right: any): any {
|
||||||
|
switch (op) {
|
||||||
|
case "+":
|
||||||
|
return left + right;
|
||||||
|
case "-":
|
||||||
|
return left - right;
|
||||||
|
case "*":
|
||||||
|
return left * right;
|
||||||
|
case "/":
|
||||||
|
return left / right;
|
||||||
|
case "%":
|
||||||
|
return left % right;
|
||||||
|
case "^":
|
||||||
|
return left ** right;
|
||||||
|
case "..":
|
||||||
|
return left + right;
|
||||||
|
case "==":
|
||||||
|
return left === right;
|
||||||
|
case "~=":
|
||||||
|
return left !== right;
|
||||||
|
case "<":
|
||||||
|
return left < right;
|
||||||
|
case "<=":
|
||||||
|
return left <= right;
|
||||||
|
case ">":
|
||||||
|
return left > right;
|
||||||
|
case ">=":
|
||||||
|
return left >= right;
|
||||||
|
case "and":
|
||||||
|
return left && right;
|
||||||
|
case "or":
|
||||||
|
return left || right;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown operator ${op}`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { evalPromiseValues } from "$common/space_lua/util.ts";
|
||||||
|
import { assertEquals } from "@std/assert/equals";
|
||||||
|
import { assert } from "@std/assert";
|
||||||
|
|
||||||
|
Deno.test("Test promise helpers", async () => {
|
||||||
|
const r = evalPromiseValues([1, 2, 3]);
|
||||||
|
// should return the same array not as a promise
|
||||||
|
assertEquals(r, [1, 2, 3]);
|
||||||
|
const asyncR = evalPromiseValues([
|
||||||
|
new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(1);
|
||||||
|
}, 5);
|
||||||
|
}),
|
||||||
|
Promise.resolve(2),
|
||||||
|
3,
|
||||||
|
]);
|
||||||
|
// should return a promise
|
||||||
|
assert(asyncR instanceof Promise);
|
||||||
|
assertEquals(await asyncR, [1, 2, 3]);
|
||||||
|
});
|
|
@ -0,0 +1,16 @@
|
||||||
|
export function evalPromiseValues(vals: any[]): Promise<any[]> | any[] {
|
||||||
|
const promises = [];
|
||||||
|
const promiseResults = new Array(vals.length);
|
||||||
|
for (let i = 0; i < vals.length; i++) {
|
||||||
|
if (vals[i] instanceof Promise) {
|
||||||
|
promises.push(vals[i].then((v: any) => promiseResults[i] = v));
|
||||||
|
} else {
|
||||||
|
promiseResults[i] = vals[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (promises.length === 0) {
|
||||||
|
return promiseResults;
|
||||||
|
} else {
|
||||||
|
return Promise.all(promises).then(() => promiseResults);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue