2024-09-27 15:11:03 +08:00
|
|
|
import type {
|
2024-10-20 21:06:23 +08:00
|
|
|
ASTCtx,
|
2024-10-03 23:55:51 +08:00
|
|
|
LuaExpression,
|
|
|
|
LuaLValue,
|
|
|
|
LuaStatement,
|
2024-09-27 15:11:03 +08:00
|
|
|
} from "$common/space_lua/ast.ts";
|
2024-09-12 23:01:54 +08:00
|
|
|
import { evalPromiseValues } from "$common/space_lua/util.ts";
|
2024-10-20 21:06:23 +08:00
|
|
|
import {
|
|
|
|
luaCall,
|
|
|
|
luaSet,
|
|
|
|
type LuaStackFrame,
|
|
|
|
} from "$common/space_lua/runtime.ts";
|
2024-09-24 16:15:22 +08:00
|
|
|
import {
|
2024-10-03 23:55:51 +08:00
|
|
|
type ILuaFunction,
|
|
|
|
type ILuaGettable,
|
|
|
|
type ILuaSettable,
|
|
|
|
LuaBreak,
|
|
|
|
LuaEnv,
|
|
|
|
LuaFunction,
|
|
|
|
luaGet,
|
|
|
|
luaLen,
|
|
|
|
type LuaLValueContainer,
|
|
|
|
LuaMultiRes,
|
|
|
|
LuaReturn,
|
|
|
|
LuaRuntimeError,
|
|
|
|
LuaTable,
|
|
|
|
luaToString,
|
|
|
|
luaTruthy,
|
|
|
|
type LuaValue,
|
|
|
|
singleResult,
|
2024-09-24 16:15:22 +08:00
|
|
|
} from "./runtime.ts";
|
2024-09-12 23:01:54 +08:00
|
|
|
|
|
|
|
export function evalExpression(
|
2024-10-03 23:55:51 +08:00
|
|
|
e: LuaExpression,
|
|
|
|
env: LuaEnv,
|
2024-10-20 21:06:23 +08:00
|
|
|
sf: LuaStackFrame,
|
2024-09-27 15:11:03 +08:00
|
|
|
): Promise<LuaValue> | LuaValue {
|
2024-10-04 23:15:50 +08:00
|
|
|
try {
|
|
|
|
switch (e.type) {
|
|
|
|
case "String":
|
|
|
|
return e.value;
|
|
|
|
case "Number":
|
|
|
|
return e.value;
|
|
|
|
case "Boolean":
|
|
|
|
return e.value;
|
|
|
|
case "Nil":
|
|
|
|
return null;
|
|
|
|
case "Binary": {
|
|
|
|
const values = evalPromiseValues([
|
2024-10-20 21:06:23 +08:00
|
|
|
evalExpression(e.left, env, sf),
|
|
|
|
evalExpression(e.right, env, sf),
|
2024-10-04 23:15:50 +08:00
|
|
|
]);
|
|
|
|
if (values instanceof Promise) {
|
|
|
|
return values.then(([left, right]) =>
|
2024-10-20 21:06:23 +08:00
|
|
|
luaOp(
|
|
|
|
e.operator,
|
|
|
|
singleResult(left),
|
|
|
|
singleResult(right),
|
|
|
|
e.ctx,
|
|
|
|
sf,
|
|
|
|
)
|
2024-10-04 23:15:50 +08:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return luaOp(
|
|
|
|
e.operator,
|
|
|
|
singleResult(values[0]),
|
|
|
|
singleResult(values[1]),
|
2024-10-20 21:06:23 +08:00
|
|
|
e.ctx,
|
|
|
|
sf,
|
2024-10-04 23:15:50 +08:00
|
|
|
);
|
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2024-10-04 23:15:50 +08:00
|
|
|
case "Unary": {
|
2024-10-20 21:06:23 +08:00
|
|
|
const value = evalExpression(e.argument, env, sf);
|
2024-10-04 23:15:50 +08:00
|
|
|
if (value instanceof Promise) {
|
|
|
|
return value.then((value) => {
|
|
|
|
switch (e.operator) {
|
|
|
|
case "-":
|
|
|
|
return -singleResult(value);
|
|
|
|
case "+":
|
|
|
|
return +singleResult(value);
|
|
|
|
case "not":
|
|
|
|
return !singleResult(value);
|
|
|
|
case "#":
|
|
|
|
return luaLen(singleResult(value));
|
|
|
|
default:
|
|
|
|
throw new Error(
|
|
|
|
`Unknown unary operator ${e.operator}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
2024-10-03 23:55:51 +08:00
|
|
|
switch (e.operator) {
|
|
|
|
case "-":
|
|
|
|
return -singleResult(value);
|
|
|
|
case "+":
|
|
|
|
return +singleResult(value);
|
|
|
|
case "not":
|
|
|
|
return !singleResult(value);
|
|
|
|
case "#":
|
|
|
|
return luaLen(singleResult(value));
|
|
|
|
default:
|
|
|
|
throw new Error(
|
|
|
|
`Unknown unary operator ${e.operator}`,
|
|
|
|
);
|
|
|
|
}
|
2024-09-12 23:01:54 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2024-10-09 01:53:09 +08:00
|
|
|
|
2024-10-04 23:15:50 +08:00
|
|
|
case "Variable":
|
|
|
|
case "FunctionCall":
|
2024-10-09 01:53:09 +08:00
|
|
|
case "TableAccess":
|
|
|
|
case "PropertyAccess":
|
2024-10-20 21:06:23 +08:00
|
|
|
return evalPrefixExpression(e, env, sf);
|
2024-10-04 23:15:50 +08:00
|
|
|
case "TableConstructor": {
|
|
|
|
const table = new LuaTable();
|
2025-01-08 18:04:33 +08:00
|
|
|
if (
|
|
|
|
e.fields.length === 1 &&
|
|
|
|
e.fields[0].type === "ExpressionField" &&
|
|
|
|
e.fields[0].value.type === "Variable" &&
|
|
|
|
e.fields[0].value.name === "..."
|
|
|
|
) {
|
|
|
|
const varargs = env.get("...");
|
|
|
|
if (varargs instanceof Promise) {
|
|
|
|
return varargs.then((resolvedVarargs) => {
|
|
|
|
if (resolvedVarargs instanceof LuaTable) {
|
|
|
|
const newTable = new LuaTable();
|
|
|
|
for (let i = 1; i <= resolvedVarargs.length; i++) {
|
|
|
|
newTable.set(i, resolvedVarargs.get(i), sf);
|
|
|
|
}
|
|
|
|
return newTable;
|
|
|
|
}
|
|
|
|
return table;
|
|
|
|
});
|
|
|
|
} else if (varargs instanceof LuaTable) {
|
|
|
|
for (let i = 1; i <= varargs.length; i++) {
|
|
|
|
table.set(i, varargs.get(i), sf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return table;
|
|
|
|
}
|
2024-10-04 23:15:50 +08:00
|
|
|
const promises: Promise<void>[] = [];
|
|
|
|
for (const field of e.fields) {
|
|
|
|
switch (field.type) {
|
|
|
|
case "PropField": {
|
2024-10-20 21:06:23 +08:00
|
|
|
const value = evalExpression(field.value, env, sf);
|
2024-10-04 23:15:50 +08:00
|
|
|
if (value instanceof Promise) {
|
|
|
|
promises.push(value.then((value) => {
|
|
|
|
table.set(
|
|
|
|
field.key,
|
|
|
|
singleResult(value),
|
2024-10-20 21:06:23 +08:00
|
|
|
sf,
|
2024-10-04 23:15:50 +08:00
|
|
|
);
|
|
|
|
}));
|
|
|
|
} else {
|
2024-10-20 21:06:23 +08:00
|
|
|
table.set(field.key, singleResult(value), sf);
|
2024-10-04 23:15:50 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "DynamicField": {
|
2024-10-20 21:06:23 +08:00
|
|
|
const key = evalExpression(field.key, env, sf);
|
|
|
|
const value = evalExpression(field.value, env, sf);
|
2024-10-04 23:15:50 +08:00
|
|
|
if (
|
|
|
|
key instanceof Promise || value instanceof Promise
|
|
|
|
) {
|
|
|
|
promises.push(
|
|
|
|
Promise.all([
|
|
|
|
key instanceof Promise ? key : Promise.resolve(key),
|
|
|
|
value instanceof Promise ? value : Promise.resolve(value),
|
|
|
|
]).then(([key, value]) => {
|
|
|
|
table.set(
|
|
|
|
singleResult(key),
|
|
|
|
singleResult(value),
|
2024-10-20 21:06:23 +08:00
|
|
|
sf,
|
2024-10-04 23:15:50 +08:00
|
|
|
);
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
} else {
|
2024-10-03 23:55:51 +08:00
|
|
|
table.set(
|
2024-10-04 23:15:50 +08:00
|
|
|
singleResult(key),
|
2024-10-03 23:55:51 +08:00
|
|
|
singleResult(value),
|
2024-10-20 21:06:23 +08:00
|
|
|
sf,
|
2024-09-12 23:01:54 +08:00
|
|
|
);
|
2024-10-04 23:15:50 +08:00
|
|
|
}
|
|
|
|
break;
|
2024-09-12 23:01:54 +08:00
|
|
|
}
|
2024-10-04 23:15:50 +08:00
|
|
|
case "ExpressionField": {
|
2024-10-20 21:06:23 +08:00
|
|
|
const value = evalExpression(field.value, env, sf);
|
2024-10-04 23:15:50 +08:00
|
|
|
if (value instanceof Promise) {
|
2025-01-08 18:04:33 +08:00
|
|
|
promises.push(value.then(async (value) => {
|
|
|
|
if (
|
|
|
|
field.value.type === "Variable" &&
|
|
|
|
field.value.name === "..."
|
|
|
|
) {
|
|
|
|
// Special handling for {...}
|
|
|
|
const varargs = await Promise.resolve(env.get("..."));
|
|
|
|
if (varargs instanceof LuaTable) {
|
|
|
|
// Copy all values from varargs table
|
|
|
|
for (let i = 1; i <= varargs.length; i++) {
|
|
|
|
const val = await Promise.resolve(varargs.get(i));
|
|
|
|
table.set(i, val, sf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Normal case
|
|
|
|
table.set(table.length + 1, singleResult(value), sf);
|
|
|
|
}
|
2024-10-04 23:15:50 +08:00
|
|
|
}));
|
|
|
|
} else {
|
2025-01-08 18:04:33 +08:00
|
|
|
if (
|
|
|
|
field.value.type === "Variable" && field.value.name === "..."
|
|
|
|
) {
|
|
|
|
// Special handling for {...}
|
|
|
|
const varargs = env.get("...");
|
|
|
|
if (varargs instanceof LuaTable) {
|
|
|
|
for (let i = 1; i <= varargs.length; i++) {
|
|
|
|
const val = varargs.get(i);
|
|
|
|
if (val instanceof Promise) {
|
|
|
|
promises.push(
|
|
|
|
Promise.resolve(val).then((val) => {
|
|
|
|
table.set(i, val, sf);
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
table.set(i, val, sf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Normal case
|
|
|
|
table.set(table.length + 1, singleResult(value), sf);
|
|
|
|
}
|
2024-10-04 23:15:50 +08:00
|
|
|
}
|
|
|
|
break;
|
2024-09-24 16:15:22 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2024-09-24 16:15:22 +08:00
|
|
|
}
|
2024-10-04 23:15:50 +08:00
|
|
|
if (promises.length > 0) {
|
|
|
|
return Promise.all(promises).then(() => table);
|
|
|
|
} else {
|
|
|
|
return table;
|
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2024-10-04 23:15:50 +08:00
|
|
|
case "FunctionDefinition": {
|
|
|
|
return new LuaFunction(e.body, env);
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2024-10-04 23:15:50 +08:00
|
|
|
default:
|
|
|
|
throw new Error(`Unknown expression type ${e.type}`);
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2024-10-04 23:15:50 +08:00
|
|
|
} catch (err: any) {
|
|
|
|
// Repackage any non Lua-specific exceptions with some position information
|
|
|
|
if (!err.constructor.name.startsWith("Lua")) {
|
2024-10-20 21:06:23 +08:00
|
|
|
throw new LuaRuntimeError(err.message, sf.withCtx(e.ctx), err);
|
2024-10-04 23:15:50 +08:00
|
|
|
} else {
|
|
|
|
throw err;
|
2024-09-12 23:01:54 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2024-09-12 23:01:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function evalPrefixExpression(
|
2024-10-03 23:55:51 +08:00
|
|
|
e: LuaExpression,
|
|
|
|
env: LuaEnv,
|
2024-10-20 21:06:23 +08:00
|
|
|
sf: LuaStackFrame,
|
2024-09-27 15:11:03 +08:00
|
|
|
): Promise<LuaValue> | LuaValue {
|
2024-10-03 23:55:51 +08:00
|
|
|
switch (e.type) {
|
|
|
|
case "Variable": {
|
|
|
|
const value = env.get(e.name);
|
|
|
|
if (value === undefined) {
|
2024-10-05 21:37:36 +08:00
|
|
|
return null;
|
2024-10-03 23:55:51 +08:00
|
|
|
} else {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case "Parenthesized":
|
2024-10-20 21:06:23 +08:00
|
|
|
return evalExpression(e.expression, env, sf);
|
2024-10-09 01:53:09 +08:00
|
|
|
// <<expr>>[<<expr>>]
|
|
|
|
case "TableAccess": {
|
|
|
|
const values = evalPromiseValues([
|
2024-10-20 21:06:23 +08:00
|
|
|
evalPrefixExpression(e.object, env, sf),
|
|
|
|
evalExpression(e.key, env, sf),
|
2024-10-09 01:53:09 +08:00
|
|
|
]);
|
|
|
|
if (values instanceof Promise) {
|
|
|
|
return values.then(([table, key]) => {
|
|
|
|
table = singleResult(table);
|
|
|
|
key = singleResult(key);
|
2024-10-10 02:35:07 +08:00
|
|
|
|
2024-10-20 21:06:23 +08:00
|
|
|
return luaGet(table, key, sf.withCtx(e.ctx));
|
2024-10-09 01:53:09 +08:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const table = singleResult(values[0]);
|
|
|
|
const key = singleResult(values[1]);
|
2024-10-20 21:06:23 +08:00
|
|
|
return luaGet(table, singleResult(key), sf.withCtx(e.ctx));
|
2024-10-09 01:53:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// <expr>.property
|
2024-10-03 23:55:51 +08:00
|
|
|
case "PropertyAccess": {
|
2024-10-20 21:06:23 +08:00
|
|
|
const obj = evalPrefixExpression(e.object, env, sf);
|
2024-10-03 23:55:51 +08:00
|
|
|
if (obj instanceof Promise) {
|
|
|
|
return obj.then((obj) => {
|
2024-10-20 21:06:23 +08:00
|
|
|
return luaGet(obj, e.property, sf.withCtx(e.ctx));
|
2024-10-03 23:55:51 +08:00
|
|
|
});
|
|
|
|
} else {
|
2024-10-20 21:06:23 +08:00
|
|
|
return luaGet(obj, e.property, sf.withCtx(e.ctx));
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
case "FunctionCall": {
|
2025-01-10 01:22:12 +08:00
|
|
|
const prefixValue = evalPrefixExpression(e.prefix, env, sf);
|
2024-10-04 23:15:50 +08:00
|
|
|
if (!prefixValue) {
|
2024-10-05 21:37:36 +08:00
|
|
|
throw new LuaRuntimeError(
|
|
|
|
`Attempting to call nil as a function`,
|
2024-10-20 21:06:23 +08:00
|
|
|
sf.withCtx(e.prefix.ctx),
|
2024-10-05 21:37:36 +08:00
|
|
|
);
|
2024-10-04 23:15:50 +08:00
|
|
|
}
|
2025-01-08 18:04:33 +08:00
|
|
|
|
2025-01-09 17:27:41 +08:00
|
|
|
const handleFunctionCall = (prefixValue: LuaValue) => {
|
|
|
|
// Special handling for f(...) - propagate varargs
|
|
|
|
if (
|
|
|
|
e.args.length === 1 && e.args[0].type === "Variable" &&
|
|
|
|
e.args[0].name === "..."
|
|
|
|
) {
|
|
|
|
const varargs = env.get("...");
|
|
|
|
const resolveVarargs = async () => {
|
|
|
|
const resolvedVarargs = await Promise.resolve(varargs);
|
|
|
|
if (resolvedVarargs instanceof LuaTable) {
|
|
|
|
const args = [];
|
|
|
|
for (let i = 1; i <= resolvedVarargs.length; i++) {
|
|
|
|
const val = await Promise.resolve(resolvedVarargs.get(i));
|
|
|
|
args.push(val);
|
|
|
|
}
|
|
|
|
return args;
|
2025-01-08 18:04:33 +08:00
|
|
|
}
|
2025-01-09 17:27:41 +08:00
|
|
|
return [];
|
|
|
|
};
|
|
|
|
|
|
|
|
if (prefixValue instanceof Promise) {
|
|
|
|
return prefixValue.then(async (resolvedPrefix) => {
|
|
|
|
const args = await resolveVarargs();
|
|
|
|
return luaCall(resolvedPrefix, args, e.ctx, sf.withCtx(e.ctx));
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return resolveVarargs().then((args) =>
|
|
|
|
luaCall(prefixValue, args, e.ctx, sf.withCtx(e.ctx))
|
|
|
|
);
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2025-01-09 17:27:41 +08:00
|
|
|
}
|
2025-01-08 18:04:33 +08:00
|
|
|
|
2025-01-09 17:27:41 +08:00
|
|
|
// Normal argument handling
|
|
|
|
let selfArgs: LuaValue[] = [];
|
|
|
|
if (e.name && !prefixValue.get) {
|
|
|
|
throw new LuaRuntimeError(
|
|
|
|
`Attempting to index a non-table: ${prefixValue}`,
|
|
|
|
sf.withCtx(e.prefix.ctx),
|
2024-10-04 23:15:50 +08:00
|
|
|
);
|
2025-01-09 17:27:41 +08:00
|
|
|
} else if (e.name) {
|
|
|
|
selfArgs = [prefixValue];
|
|
|
|
prefixValue = prefixValue.get(e.name);
|
2024-09-12 23:01:54 +08:00
|
|
|
}
|
2025-01-09 17:27:41 +08:00
|
|
|
if (!prefixValue.call) {
|
|
|
|
throw new LuaRuntimeError(
|
|
|
|
`Attempting to call ${prefixValue} as a function`,
|
|
|
|
sf.withCtx(e.prefix.ctx),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
const args = evalPromiseValues(
|
|
|
|
e.args.map((arg) => evalExpression(arg, env, sf)),
|
2025-01-08 18:04:33 +08:00
|
|
|
);
|
2025-01-09 17:27:41 +08:00
|
|
|
if (args instanceof Promise) {
|
|
|
|
return args.then((args) =>
|
|
|
|
luaCall(prefixValue, [...selfArgs, ...args], e.ctx, sf)
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return luaCall(prefixValue, [...selfArgs, ...args], e.ctx, sf);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if (prefixValue instanceof Promise) {
|
|
|
|
return prefixValue.then(handleFunctionCall);
|
2025-01-08 18:04:33 +08:00
|
|
|
} else {
|
2025-01-09 17:27:41 +08:00
|
|
|
return handleFunctionCall(prefixValue);
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2024-09-12 23:01:54 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
default:
|
|
|
|
throw new Error(`Unknown prefix expression type ${e.type}`);
|
|
|
|
}
|
2024-09-12 23:01:54 +08:00
|
|
|
}
|
|
|
|
|
2025-01-08 18:39:00 +08:00
|
|
|
// Helper functions to reduce duplication
|
|
|
|
function evalMetamethod(
|
|
|
|
left: any,
|
|
|
|
right: any,
|
|
|
|
metaMethod: string,
|
|
|
|
ctx: ASTCtx,
|
|
|
|
sf: LuaStackFrame,
|
|
|
|
): LuaValue | undefined {
|
|
|
|
if (left?.metatable?.has(metaMethod)) {
|
|
|
|
const fn = left.metatable.get(metaMethod);
|
|
|
|
return luaCall(fn, [left, right], ctx, sf);
|
|
|
|
} else if (right?.metatable?.has(metaMethod)) {
|
|
|
|
const fn = right.metatable.get(metaMethod);
|
|
|
|
return luaCall(fn, [left, right], ctx, sf);
|
|
|
|
}
|
|
|
|
}
|
2024-10-04 23:15:50 +08:00
|
|
|
|
2025-01-08 18:39:00 +08:00
|
|
|
// Simplified operator definitions
|
|
|
|
const operatorsMetaMethods: Record<string, {
|
2024-10-04 23:15:50 +08:00
|
|
|
metaMethod?: string;
|
2024-10-20 21:06:23 +08:00
|
|
|
nativeImplementation: (
|
|
|
|
a: LuaValue,
|
|
|
|
b: LuaValue,
|
|
|
|
ctx: ASTCtx,
|
|
|
|
sf: LuaStackFrame,
|
|
|
|
) => LuaValue;
|
2025-01-08 18:39:00 +08:00
|
|
|
}> = {
|
|
|
|
"+": { metaMethod: "__add", nativeImplementation: (a, b) => a + b },
|
|
|
|
"-": { metaMethod: "__sub", nativeImplementation: (a, b) => a - b },
|
|
|
|
"*": { metaMethod: "__mul", nativeImplementation: (a, b) => a * b },
|
|
|
|
"/": { metaMethod: "__div", nativeImplementation: (a, b) => a / b },
|
2024-10-04 23:15:50 +08:00
|
|
|
"//": {
|
|
|
|
metaMethod: "__idiv",
|
2025-01-09 00:09:09 +08:00
|
|
|
nativeImplementation: (a, b) => Math.floor(a / b),
|
2024-10-04 23:15:50 +08:00
|
|
|
},
|
2025-01-08 18:39:00 +08:00
|
|
|
"%": { metaMethod: "__mod", nativeImplementation: (a, b) => a % b },
|
|
|
|
"^": { metaMethod: "__pow", nativeImplementation: (a, b) => a ** b },
|
2024-10-04 23:15:50 +08:00
|
|
|
"..": {
|
|
|
|
metaMethod: "__concat",
|
2024-10-26 22:02:37 +08:00
|
|
|
nativeImplementation: (a, b) => {
|
|
|
|
const aString = luaToString(a);
|
|
|
|
const bString = luaToString(b);
|
|
|
|
if (aString instanceof Promise || bString instanceof Promise) {
|
2025-01-08 18:39:00 +08:00
|
|
|
return Promise.all([aString, bString]).then(([a, b]) => a + b);
|
2024-10-26 22:02:37 +08:00
|
|
|
} else {
|
|
|
|
return aString + bString;
|
|
|
|
}
|
|
|
|
},
|
2024-10-04 23:15:50 +08:00
|
|
|
},
|
|
|
|
"==": {
|
|
|
|
metaMethod: "__eq",
|
|
|
|
nativeImplementation: (a, b) => a === b,
|
|
|
|
},
|
|
|
|
"~=": {
|
|
|
|
metaMethod: "__ne",
|
|
|
|
nativeImplementation: (a, b) => a !== b,
|
|
|
|
},
|
|
|
|
"!=": {
|
|
|
|
metaMethod: "__ne",
|
|
|
|
nativeImplementation: (a, b) => a !== b,
|
|
|
|
},
|
2025-01-08 18:39:00 +08:00
|
|
|
"<": { metaMethod: "__lt", nativeImplementation: (a, b) => a < b },
|
|
|
|
"<=": { metaMethod: "__le", nativeImplementation: (a, b) => a <= b },
|
|
|
|
">": { nativeImplementation: (a, b, ctx, sf) => !luaOp("<=", a, b, ctx, sf) },
|
|
|
|
">=": { nativeImplementation: (a, b, ctx, sf) => !luaOp("<", a, b, ctx, sf) },
|
|
|
|
"and": {
|
2024-10-04 23:15:50 +08:00
|
|
|
metaMethod: "__and",
|
|
|
|
nativeImplementation: (a, b) => a && b,
|
|
|
|
},
|
2025-01-08 18:39:00 +08:00
|
|
|
"or": { metaMethod: "__or", nativeImplementation: (a, b) => a || b },
|
2024-10-04 23:15:50 +08:00
|
|
|
};
|
|
|
|
|
2024-10-20 21:06:23 +08:00
|
|
|
function luaOp(
|
|
|
|
op: string,
|
|
|
|
left: any,
|
|
|
|
right: any,
|
|
|
|
ctx: ASTCtx,
|
|
|
|
sf: LuaStackFrame,
|
|
|
|
): any {
|
2024-10-04 23:15:50 +08:00
|
|
|
const operatorHandler = operatorsMetaMethods[op];
|
|
|
|
if (!operatorHandler) {
|
2024-10-20 21:06:23 +08:00
|
|
|
throw new LuaRuntimeError(`Unknown operator ${op}`, sf.withCtx(ctx));
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2025-01-08 18:39:00 +08:00
|
|
|
|
2024-10-04 23:15:50 +08:00
|
|
|
if (operatorHandler.metaMethod) {
|
2025-01-08 18:39:00 +08:00
|
|
|
const result = evalMetamethod(
|
|
|
|
left,
|
|
|
|
right,
|
|
|
|
operatorHandler.metaMethod,
|
|
|
|
ctx,
|
|
|
|
sf,
|
|
|
|
);
|
|
|
|
if (result !== undefined) {
|
|
|
|
return result;
|
2024-10-04 23:15:50 +08:00
|
|
|
}
|
|
|
|
}
|
2025-01-08 18:39:00 +08:00
|
|
|
|
2024-10-20 21:06:23 +08:00
|
|
|
return operatorHandler.nativeImplementation(left, right, ctx, sf);
|
2024-10-04 23:15:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
async function evalExpressions(
|
|
|
|
es: LuaExpression[],
|
|
|
|
env: LuaEnv,
|
2024-10-20 21:06:23 +08:00
|
|
|
sf: LuaStackFrame,
|
2024-10-04 23:15:50 +08:00
|
|
|
): Promise<LuaValue[]> {
|
|
|
|
return new LuaMultiRes(
|
2024-10-20 21:06:23 +08:00
|
|
|
await Promise.all(es.map((e) => evalExpression(e, env, sf))),
|
2024-10-04 23:15:50 +08:00
|
|
|
).flatten().values;
|
2024-09-12 23:01:54 +08:00
|
|
|
}
|
2024-09-27 15:11:03 +08:00
|
|
|
|
|
|
|
export async function evalStatement(
|
2024-10-03 23:55:51 +08:00
|
|
|
s: LuaStatement,
|
|
|
|
env: LuaEnv,
|
2024-10-20 21:06:23 +08:00
|
|
|
sf: LuaStackFrame,
|
2024-09-27 15:11:03 +08:00
|
|
|
): Promise<void> {
|
2024-10-03 23:55:51 +08:00
|
|
|
switch (s.type) {
|
|
|
|
case "Assignment": {
|
2024-10-20 21:06:23 +08:00
|
|
|
const values = await evalExpressions(s.expressions, env, sf);
|
2024-10-03 23:55:51 +08:00
|
|
|
const lvalues = await evalPromiseValues(s.variables
|
2024-10-20 21:06:23 +08:00
|
|
|
.map((lval) => evalLValue(lval, env, sf)));
|
2024-09-27 15:11:03 +08:00
|
|
|
|
2024-10-03 23:55:51 +08:00
|
|
|
for (let i = 0; i < lvalues.length; i++) {
|
2024-10-20 21:06:23 +08:00
|
|
|
luaSet(lvalues[i].env, lvalues[i].key, values[i], sf.withCtx(s.ctx));
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2024-09-27 15:11:03 +08:00
|
|
|
|
2024-10-03 23:55:51 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Local": {
|
2024-10-04 23:15:50 +08:00
|
|
|
if (s.expressions) {
|
2024-10-20 21:06:23 +08:00
|
|
|
const values = await evalExpressions(s.expressions, env, sf);
|
2024-10-04 23:15:50 +08:00
|
|
|
for (let i = 0; i < s.names.length; i++) {
|
|
|
|
env.setLocal(s.names[i].name, values[i]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (let i = 0; i < s.names.length; i++) {
|
2024-10-03 23:55:51 +08:00
|
|
|
env.setLocal(s.names[i].name, null);
|
2024-09-27 15:11:03 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Semicolon":
|
|
|
|
break;
|
|
|
|
case "Label":
|
|
|
|
case "Goto":
|
|
|
|
throw new Error("Labels and gotos are not supported yet");
|
|
|
|
case "Block": {
|
|
|
|
const newEnv = new LuaEnv(env);
|
|
|
|
for (const statement of s.statements) {
|
2024-10-20 21:06:23 +08:00
|
|
|
await evalStatement(statement, newEnv, sf);
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "If": {
|
|
|
|
for (const cond of s.conditions) {
|
2024-10-20 21:06:23 +08:00
|
|
|
if (luaTruthy(await evalExpression(cond.condition, env, sf))) {
|
|
|
|
return evalStatement(cond.block, env, sf);
|
2024-09-27 15:11:03 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
|
|
|
if (s.elseBlock) {
|
2024-10-20 21:06:23 +08:00
|
|
|
return evalStatement(s.elseBlock, env, sf);
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "While": {
|
2024-10-20 21:06:23 +08:00
|
|
|
while (luaTruthy(await evalExpression(s.condition, env, sf))) {
|
2024-10-03 23:55:51 +08:00
|
|
|
try {
|
2024-10-20 21:06:23 +08:00
|
|
|
await evalStatement(s.block, env, sf);
|
2024-10-03 23:55:51 +08:00
|
|
|
} catch (e: any) {
|
|
|
|
if (e instanceof LuaBreak) {
|
2024-09-27 15:11:03 +08:00
|
|
|
break;
|
2024-10-03 23:55:51 +08:00
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
2024-09-27 15:11:03 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Repeat": {
|
|
|
|
do {
|
|
|
|
try {
|
2024-10-20 21:06:23 +08:00
|
|
|
await evalStatement(s.block, env, sf);
|
2024-10-03 23:55:51 +08:00
|
|
|
} catch (e: any) {
|
|
|
|
if (e instanceof LuaBreak) {
|
2024-09-27 15:11:03 +08:00
|
|
|
break;
|
2024-10-03 23:55:51 +08:00
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
2024-09-27 15:11:03 +08:00
|
|
|
}
|
2024-10-20 21:06:23 +08:00
|
|
|
} while (!luaTruthy(await evalExpression(s.condition, env, sf)));
|
2024-10-03 23:55:51 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Break":
|
|
|
|
throw new LuaBreak();
|
|
|
|
case "FunctionCallStatement": {
|
2024-10-20 21:06:23 +08:00
|
|
|
return evalExpression(s.call, env, sf);
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
|
|
|
case "Function": {
|
|
|
|
let body = s.body;
|
|
|
|
let propNames = s.name.propNames;
|
|
|
|
if (s.name.colonName) {
|
|
|
|
// function hello:there() -> function hello.there(self) transformation
|
|
|
|
body = {
|
|
|
|
...s.body,
|
|
|
|
parameters: ["self", ...s.body.parameters],
|
|
|
|
};
|
|
|
|
propNames = [...s.name.propNames, s.name.colonName];
|
|
|
|
}
|
|
|
|
let settable: ILuaSettable & ILuaGettable = env;
|
|
|
|
for (let i = 0; i < propNames.length - 1; i++) {
|
|
|
|
settable = settable.get(propNames[i]);
|
|
|
|
if (!settable) {
|
2024-10-09 01:53:09 +08:00
|
|
|
throw new LuaRuntimeError(
|
2024-10-03 23:55:51 +08:00
|
|
|
`Cannot find property ${propNames[i]}`,
|
2024-10-20 21:06:23 +08:00
|
|
|
sf.withCtx(s.name.ctx),
|
2024-10-03 23:55:51 +08:00
|
|
|
);
|
2024-09-27 15:11:03 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
|
|
|
settable.set(
|
|
|
|
propNames[propNames.length - 1],
|
|
|
|
new LuaFunction(body, env),
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "LocalFunction": {
|
|
|
|
env.setLocal(
|
|
|
|
s.name,
|
|
|
|
new LuaFunction(s.body, env),
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Return": {
|
|
|
|
// A return statement for now is implemented by throwing the value as an exception, this should
|
|
|
|
// be optimized for the common case later
|
|
|
|
throw new LuaReturn(
|
|
|
|
await evalPromiseValues(
|
2024-10-20 21:06:23 +08:00
|
|
|
s.expressions.map((value) => evalExpression(value, env, sf)),
|
2024-10-03 23:55:51 +08:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
case "For": {
|
2024-10-20 21:06:23 +08:00
|
|
|
const start = await evalExpression(s.start, env, sf);
|
|
|
|
const end = await evalExpression(s.end, env, sf);
|
|
|
|
const step = s.step ? await evalExpression(s.step, env, sf) : 1;
|
2024-10-03 23:55:51 +08:00
|
|
|
for (
|
|
|
|
let i = start;
|
|
|
|
step > 0 ? i <= end : i >= end;
|
|
|
|
i += step
|
|
|
|
) {
|
2025-01-08 17:00:46 +08:00
|
|
|
const localEnv = new LuaEnv(env);
|
2024-10-03 23:55:51 +08:00
|
|
|
localEnv.setLocal(s.name, i);
|
|
|
|
try {
|
2024-10-20 21:06:23 +08:00
|
|
|
await evalStatement(s.block, localEnv, sf);
|
2024-10-03 23:55:51 +08:00
|
|
|
} catch (e: any) {
|
|
|
|
if (e instanceof LuaBreak) {
|
2024-09-27 15:11:03 +08:00
|
|
|
break;
|
2024-10-03 23:55:51 +08:00
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
2024-09-27 15:11:03 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "ForIn": {
|
|
|
|
const iteratorMultiRes = new LuaMultiRes(
|
|
|
|
await evalPromiseValues(
|
2024-10-20 21:06:23 +08:00
|
|
|
s.expressions.map((e) => evalExpression(e, env, sf)),
|
2024-10-03 23:55:51 +08:00
|
|
|
),
|
|
|
|
).flatten();
|
|
|
|
const iteratorFunction: ILuaFunction | undefined =
|
|
|
|
iteratorMultiRes.values[0];
|
|
|
|
if (!iteratorFunction?.call) {
|
|
|
|
console.error("Cannot iterate over", iteratorMultiRes.values[0]);
|
|
|
|
throw new LuaRuntimeError(
|
|
|
|
`Cannot iterate over ${iteratorMultiRes.values[0]}`,
|
2024-10-20 21:06:23 +08:00
|
|
|
sf.withCtx(s.ctx),
|
2024-10-03 23:55:51 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const state: LuaValue = iteratorMultiRes.values[1] || null;
|
|
|
|
const control: LuaValue = iteratorMultiRes.values[2] || null;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
const iterResult = new LuaMultiRes(
|
2024-10-20 21:06:23 +08:00
|
|
|
await luaCall(iteratorFunction, [state, control], s.ctx, sf),
|
2024-10-03 23:55:51 +08:00
|
|
|
).flatten();
|
|
|
|
if (
|
|
|
|
iterResult.values[0] === null || iterResult.values[0] === undefined
|
|
|
|
) {
|
|
|
|
break;
|
2024-09-27 15:11:03 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
const localEnv = new LuaEnv(env);
|
|
|
|
for (let i = 0; i < s.names.length; i++) {
|
|
|
|
localEnv.setLocal(s.names[i], iterResult.values[i]);
|
2024-09-27 23:09:25 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
try {
|
2024-10-20 21:06:23 +08:00
|
|
|
await evalStatement(s.block, localEnv, sf);
|
2024-10-03 23:55:51 +08:00
|
|
|
} catch (e: any) {
|
|
|
|
if (e instanceof LuaBreak) {
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
2024-09-27 23:09:25 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
|
|
|
break;
|
2024-09-27 15:11:03 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2024-09-27 15:11:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function evalLValue(
|
2024-10-03 23:55:51 +08:00
|
|
|
lval: LuaLValue,
|
|
|
|
env: LuaEnv,
|
2024-10-20 21:06:23 +08:00
|
|
|
sf: LuaStackFrame,
|
2024-09-27 15:11:03 +08:00
|
|
|
): LuaLValueContainer | Promise<LuaLValueContainer> {
|
2024-10-03 23:55:51 +08:00
|
|
|
switch (lval.type) {
|
|
|
|
case "Variable":
|
|
|
|
return { env, key: lval.name };
|
|
|
|
case "TableAccess": {
|
|
|
|
const objValue = evalExpression(
|
|
|
|
lval.object,
|
|
|
|
env,
|
2024-10-20 21:06:23 +08:00
|
|
|
sf,
|
2024-10-03 23:55:51 +08:00
|
|
|
);
|
2024-10-20 21:06:23 +08:00
|
|
|
const keyValue = evalExpression(lval.key, env, sf);
|
2024-10-03 23:55:51 +08:00
|
|
|
if (
|
|
|
|
objValue instanceof Promise ||
|
|
|
|
keyValue instanceof Promise
|
|
|
|
) {
|
|
|
|
return Promise.all([
|
|
|
|
objValue instanceof Promise ? objValue : Promise.resolve(objValue),
|
|
|
|
keyValue instanceof Promise ? keyValue : Promise.resolve(keyValue),
|
|
|
|
]).then(([objValue, keyValue]) => ({
|
|
|
|
env: singleResult(objValue),
|
|
|
|
key: singleResult(keyValue),
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
env: singleResult(objValue),
|
|
|
|
key: singleResult(keyValue),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case "PropertyAccess": {
|
|
|
|
const objValue = evalExpression(
|
|
|
|
lval.object,
|
|
|
|
env,
|
2024-10-20 21:06:23 +08:00
|
|
|
sf,
|
2024-10-03 23:55:51 +08:00
|
|
|
);
|
|
|
|
if (objValue instanceof Promise) {
|
|
|
|
return objValue.then((objValue) => {
|
|
|
|
return {
|
|
|
|
env: objValue,
|
|
|
|
key: lval.property,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
env: objValue,
|
|
|
|
key: lval.property,
|
|
|
|
};
|
|
|
|
}
|
2024-09-27 15:11:03 +08:00
|
|
|
}
|
2024-10-03 23:55:51 +08:00
|
|
|
}
|
2024-09-27 15:11:03 +08:00
|
|
|
}
|