Lua stack frame refactor
parent
64c98678bc
commit
8acb112e4e
|
@ -4,6 +4,7 @@ import {
|
|||
LuaEnv,
|
||||
LuaFunction,
|
||||
LuaRuntimeError,
|
||||
LuaStackFrame,
|
||||
} from "$common/space_lua/runtime.ts";
|
||||
import { parse as parseLua } from "$common/space_lua/parse.ts";
|
||||
import { evalStatement } from "$common/space_lua/eval.ts";
|
||||
|
@ -39,10 +40,11 @@ export class SpaceLuaEnvironment {
|
|||
const ast = parseLua(script.script, { ref: script.ref });
|
||||
// We create a local scope for each script
|
||||
const scriptEnv = new LuaEnv(this.env);
|
||||
await evalStatement(ast, scriptEnv);
|
||||
const sf = new LuaStackFrame(new LuaEnv(), ast.ctx);
|
||||
await evalStatement(ast, scriptEnv, sf);
|
||||
} catch (e: any) {
|
||||
if (e instanceof LuaRuntimeError) {
|
||||
const origin = resolveASTReference(e.context);
|
||||
const origin = resolveASTReference(e.sf.astCtx!);
|
||||
if (origin) {
|
||||
console.error(
|
||||
`Error evaluating script: ${e.message} at [[${origin.page}@${origin.pos}]]`,
|
||||
|
@ -62,7 +64,8 @@ export class SpaceLuaEnvironment {
|
|||
if (value instanceof LuaFunction) {
|
||||
console.log("Now registering Lua function", globalName);
|
||||
scriptEnv.registerFunction({ name: globalName }, (...args: any[]) => {
|
||||
return luaValueToJS(value.call(...args.map(jsToLuaValue)));
|
||||
const sf = new LuaStackFrame(new LuaEnv(), value.body.ctx);
|
||||
return luaValueToJS(value.call(sf, ...args.map(jsToLuaValue)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert/equals";
|
|||
import {
|
||||
LuaEnv,
|
||||
LuaNativeJSFunction,
|
||||
LuaStackFrame,
|
||||
luaValueToJS,
|
||||
singleResult,
|
||||
} from "./runtime.ts";
|
||||
|
@ -11,15 +12,19 @@ import { evalExpression, evalStatement } from "./eval.ts";
|
|||
import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts";
|
||||
|
||||
function evalExpr(s: string, e = new LuaEnv()): any {
|
||||
const node = parse(`e(${s})`).statements[0] as LuaFunctionCallStatement;
|
||||
const sf = new LuaStackFrame(e, node.ctx);
|
||||
return evalExpression(
|
||||
(parse(`e(${s})`).statements[0] as LuaFunctionCallStatement).call
|
||||
.args[0],
|
||||
node.call.args[0],
|
||||
e,
|
||||
sf,
|
||||
);
|
||||
}
|
||||
|
||||
function evalBlock(s: string, e = new LuaEnv()): Promise<void> {
|
||||
return evalStatement(parse(s) as LuaBlock, e);
|
||||
const node = parse(s) as LuaBlock;
|
||||
const sf = new LuaStackFrame(e, node.ctx);
|
||||
return evalStatement(node, e, sf);
|
||||
}
|
||||
|
||||
Deno.test("Evaluator test", async () => {
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import type {
|
||||
ASTCtx,
|
||||
LuaExpression,
|
||||
LuaLValue,
|
||||
LuaStatement,
|
||||
} from "$common/space_lua/ast.ts";
|
||||
import { evalPromiseValues } from "$common/space_lua/util.ts";
|
||||
import { luaCall, luaSet } from "$common/space_lua/runtime.ts";
|
||||
import {
|
||||
luaCall,
|
||||
luaSet,
|
||||
type LuaStackFrame,
|
||||
} from "$common/space_lua/runtime.ts";
|
||||
import {
|
||||
type ILuaFunction,
|
||||
type ILuaGettable,
|
||||
|
@ -28,6 +33,7 @@ import {
|
|||
export function evalExpression(
|
||||
e: LuaExpression,
|
||||
env: LuaEnv,
|
||||
sf: LuaStackFrame,
|
||||
): Promise<LuaValue> | LuaValue {
|
||||
try {
|
||||
switch (e.type) {
|
||||
|
@ -41,23 +47,31 @@ export function evalExpression(
|
|||
return null;
|
||||
case "Binary": {
|
||||
const values = evalPromiseValues([
|
||||
evalExpression(e.left, env),
|
||||
evalExpression(e.right, env),
|
||||
evalExpression(e.left, env, sf),
|
||||
evalExpression(e.right, env, sf),
|
||||
]);
|
||||
if (values instanceof Promise) {
|
||||
return values.then(([left, right]) =>
|
||||
luaOp(e.operator, singleResult(left), singleResult(right))
|
||||
luaOp(
|
||||
e.operator,
|
||||
singleResult(left),
|
||||
singleResult(right),
|
||||
e.ctx,
|
||||
sf,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return luaOp(
|
||||
e.operator,
|
||||
singleResult(values[0]),
|
||||
singleResult(values[1]),
|
||||
e.ctx,
|
||||
sf,
|
||||
);
|
||||
}
|
||||
}
|
||||
case "Unary": {
|
||||
const value = evalExpression(e.argument, env);
|
||||
const value = evalExpression(e.argument, env, sf);
|
||||
if (value instanceof Promise) {
|
||||
return value.then((value) => {
|
||||
switch (e.operator) {
|
||||
|
@ -97,29 +111,30 @@ export function evalExpression(
|
|||
case "FunctionCall":
|
||||
case "TableAccess":
|
||||
case "PropertyAccess":
|
||||
return evalPrefixExpression(e, env);
|
||||
return evalPrefixExpression(e, env, sf);
|
||||
case "TableConstructor": {
|
||||
const table = new LuaTable();
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const field of e.fields) {
|
||||
switch (field.type) {
|
||||
case "PropField": {
|
||||
const value = evalExpression(field.value, env);
|
||||
const value = evalExpression(field.value, env, sf);
|
||||
if (value instanceof Promise) {
|
||||
promises.push(value.then((value) => {
|
||||
table.set(
|
||||
field.key,
|
||||
singleResult(value),
|
||||
sf,
|
||||
);
|
||||
}));
|
||||
} else {
|
||||
table.set(field.key, singleResult(value));
|
||||
table.set(field.key, singleResult(value), sf);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "DynamicField": {
|
||||
const key = evalExpression(field.key, env);
|
||||
const value = evalExpression(field.value, env);
|
||||
const key = evalExpression(field.key, env, sf);
|
||||
const value = evalExpression(field.value, env, sf);
|
||||
if (
|
||||
key instanceof Promise || value instanceof Promise
|
||||
) {
|
||||
|
@ -131,6 +146,7 @@ export function evalExpression(
|
|||
table.set(
|
||||
singleResult(key),
|
||||
singleResult(value),
|
||||
sf,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
@ -138,18 +154,20 @@ export function evalExpression(
|
|||
table.set(
|
||||
singleResult(key),
|
||||
singleResult(value),
|
||||
sf,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ExpressionField": {
|
||||
const value = evalExpression(field.value, env);
|
||||
const value = evalExpression(field.value, env, sf);
|
||||
if (value instanceof Promise) {
|
||||
promises.push(value.then((value) => {
|
||||
// +1 because Lua tables are 1-indexed
|
||||
table.set(
|
||||
table.length + 1,
|
||||
singleResult(value),
|
||||
sf,
|
||||
);
|
||||
}));
|
||||
} else {
|
||||
|
@ -157,6 +175,7 @@ export function evalExpression(
|
|||
table.set(
|
||||
table.length + 1,
|
||||
singleResult(value),
|
||||
sf,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
@ -178,7 +197,7 @@ export function evalExpression(
|
|||
} catch (err: any) {
|
||||
// Repackage any non Lua-specific exceptions with some position information
|
||||
if (!err.constructor.name.startsWith("Lua")) {
|
||||
throw new LuaRuntimeError(err.message, e.ctx, err);
|
||||
throw new LuaRuntimeError(err.message, sf.withCtx(e.ctx), err);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
|
@ -188,6 +207,7 @@ export function evalExpression(
|
|||
function evalPrefixExpression(
|
||||
e: LuaExpression,
|
||||
env: LuaEnv,
|
||||
sf: LuaStackFrame,
|
||||
): Promise<LuaValue> | LuaValue {
|
||||
switch (e.type) {
|
||||
case "Variable": {
|
||||
|
@ -199,43 +219,43 @@ function evalPrefixExpression(
|
|||
}
|
||||
}
|
||||
case "Parenthesized":
|
||||
return evalExpression(e.expression, env);
|
||||
return evalExpression(e.expression, env, sf);
|
||||
// <<expr>>[<<expr>>]
|
||||
case "TableAccess": {
|
||||
const values = evalPromiseValues([
|
||||
evalPrefixExpression(e.object, env),
|
||||
evalExpression(e.key, env),
|
||||
evalPrefixExpression(e.object, env, sf),
|
||||
evalExpression(e.key, env, sf),
|
||||
]);
|
||||
if (values instanceof Promise) {
|
||||
return values.then(([table, key]) => {
|
||||
table = singleResult(table);
|
||||
key = singleResult(key);
|
||||
|
||||
return luaGet(table, key, e.ctx);
|
||||
return luaGet(table, key, sf.withCtx(e.ctx));
|
||||
});
|
||||
} else {
|
||||
const table = singleResult(values[0]);
|
||||
const key = singleResult(values[1]);
|
||||
return luaGet(table, singleResult(key), e.ctx);
|
||||
return luaGet(table, singleResult(key), sf.withCtx(e.ctx));
|
||||
}
|
||||
}
|
||||
// <expr>.property
|
||||
case "PropertyAccess": {
|
||||
const obj = evalPrefixExpression(e.object, env);
|
||||
const obj = evalPrefixExpression(e.object, env, sf);
|
||||
if (obj instanceof Promise) {
|
||||
return obj.then((obj) => {
|
||||
return luaGet(obj, e.property, e.ctx);
|
||||
return luaGet(obj, e.property, sf.withCtx(e.ctx));
|
||||
});
|
||||
} else {
|
||||
return luaGet(obj, e.property, e.ctx);
|
||||
return luaGet(obj, e.property, sf.withCtx(e.ctx));
|
||||
}
|
||||
}
|
||||
case "FunctionCall": {
|
||||
let prefixValue = evalPrefixExpression(e.prefix, env);
|
||||
let prefixValue = evalPrefixExpression(e.prefix, env, sf);
|
||||
if (!prefixValue) {
|
||||
throw new LuaRuntimeError(
|
||||
`Attempting to call nil as a function`,
|
||||
e.prefix.ctx,
|
||||
sf.withCtx(e.prefix.ctx),
|
||||
);
|
||||
}
|
||||
if (prefixValue instanceof Promise) {
|
||||
|
@ -243,7 +263,7 @@ function evalPrefixExpression(
|
|||
if (!prefixValue) {
|
||||
throw new LuaRuntimeError(
|
||||
`Attempting to call a nil value`,
|
||||
e.prefix.ctx,
|
||||
sf.withCtx(e.prefix.ctx),
|
||||
);
|
||||
}
|
||||
let selfArgs: LuaValue[] = [];
|
||||
|
@ -251,7 +271,7 @@ function evalPrefixExpression(
|
|||
if (e.name && !prefixValue.get) {
|
||||
throw new LuaRuntimeError(
|
||||
`Attempting to index a non-table: ${prefixValue}`,
|
||||
e.prefix.ctx,
|
||||
sf.withCtx(e.prefix.ctx),
|
||||
);
|
||||
} else if (e.name) {
|
||||
// Two things need to happen: the actual function be called needs to be looked up in the table, and the table itself needs to be passed as the first argument
|
||||
|
@ -261,18 +281,18 @@ function evalPrefixExpression(
|
|||
if (!prefixValue.call) {
|
||||
throw new LuaRuntimeError(
|
||||
`Attempting to call ${prefixValue} as a function`,
|
||||
e.prefix.ctx,
|
||||
sf.withCtx(e.prefix.ctx),
|
||||
);
|
||||
}
|
||||
const args = evalPromiseValues(
|
||||
e.args.map((arg) => evalExpression(arg, env)),
|
||||
e.args.map((arg) => evalExpression(arg, env, sf)),
|
||||
);
|
||||
if (args instanceof Promise) {
|
||||
return args.then((args) =>
|
||||
luaCall(prefixValue, [...selfArgs, ...args], e.ctx)
|
||||
luaCall(prefixValue, [...selfArgs, ...args], e.ctx, sf)
|
||||
);
|
||||
} else {
|
||||
return luaCall(prefixValue, [...selfArgs, ...args], e.ctx);
|
||||
return luaCall(prefixValue, [...selfArgs, ...args], e.ctx, sf);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -281,7 +301,7 @@ function evalPrefixExpression(
|
|||
if (e.name && !prefixValue.get) {
|
||||
throw new LuaRuntimeError(
|
||||
`Attempting to index a non-table: ${prefixValue}`,
|
||||
e.prefix.ctx,
|
||||
sf.withCtx(e.prefix.ctx),
|
||||
);
|
||||
} else if (e.name) {
|
||||
// Two things need to happen: the actual function be called needs to be looked up in the table, and the table itself needs to be passed as the first argument
|
||||
|
@ -291,18 +311,18 @@ function evalPrefixExpression(
|
|||
if (!prefixValue.call) {
|
||||
throw new LuaRuntimeError(
|
||||
`Attempting to call ${prefixValue} as a function`,
|
||||
e.prefix.ctx,
|
||||
sf.withCtx(e.prefix.ctx),
|
||||
);
|
||||
}
|
||||
const args = evalPromiseValues(
|
||||
e.args.map((arg) => evalExpression(arg, env)),
|
||||
e.args.map((arg) => evalExpression(arg, env, sf)),
|
||||
);
|
||||
if (args instanceof Promise) {
|
||||
return args.then((args) =>
|
||||
luaCall(prefixValue, [...selfArgs, ...args], e.ctx)
|
||||
luaCall(prefixValue, [...selfArgs, ...args], e.ctx, sf)
|
||||
);
|
||||
} else {
|
||||
return luaCall(prefixValue, [...selfArgs, ...args], e.ctx);
|
||||
return luaCall(prefixValue, [...selfArgs, ...args], e.ctx, sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -315,7 +335,12 @@ function evalPrefixExpression(
|
|||
|
||||
type LuaMetaMethod = Record<string, {
|
||||
metaMethod?: string;
|
||||
nativeImplementation: (a: LuaValue, b: LuaValue) => LuaValue;
|
||||
nativeImplementation: (
|
||||
a: LuaValue,
|
||||
b: LuaValue,
|
||||
ctx: ASTCtx,
|
||||
sf: LuaStackFrame,
|
||||
) => LuaValue;
|
||||
}>;
|
||||
|
||||
const operatorsMetaMethods: LuaMetaMethod = {
|
||||
|
@ -372,10 +397,10 @@ const operatorsMetaMethods: LuaMetaMethod = {
|
|||
nativeImplementation: (a, b) => a <= b,
|
||||
},
|
||||
">": {
|
||||
nativeImplementation: (a, b) => !luaOp("<=", a, b),
|
||||
nativeImplementation: (a, b, ctx, sf) => !luaOp("<=", a, b, ctx, sf),
|
||||
},
|
||||
">=": {
|
||||
nativeImplementation: (a, b) => !luaOp("<", a, b),
|
||||
nativeImplementation: (a, b, ctx, cf) => !luaOp("<", a, b, ctx, cf),
|
||||
},
|
||||
and: {
|
||||
metaMethod: "__and",
|
||||
|
@ -387,63 +412,59 @@ const operatorsMetaMethods: LuaMetaMethod = {
|
|||
},
|
||||
};
|
||||
|
||||
function luaOp(op: string, left: any, right: any): any {
|
||||
function luaOp(
|
||||
op: string,
|
||||
left: any,
|
||||
right: any,
|
||||
ctx: ASTCtx,
|
||||
sf: LuaStackFrame,
|
||||
): any {
|
||||
const operatorHandler = operatorsMetaMethods[op];
|
||||
if (!operatorHandler) {
|
||||
throw new Error(`Unknown operator ${op}`);
|
||||
throw new LuaRuntimeError(`Unknown operator ${op}`, sf.withCtx(ctx));
|
||||
}
|
||||
if (operatorHandler.metaMethod) {
|
||||
if (left?.metatable?.has(operatorHandler.metaMethod)) {
|
||||
const fn = left.metatable.get(operatorHandler.metaMethod);
|
||||
if (!fn.call) {
|
||||
throw new Error(
|
||||
`Meta method ${operatorHandler.metaMethod} is not callable`,
|
||||
);
|
||||
} else {
|
||||
return fn.call(left, right);
|
||||
}
|
||||
return luaCall(fn, [left, right], ctx, sf);
|
||||
} else if (right?.metatable?.has(operatorHandler.metaMethod)) {
|
||||
const fn = right.metatable.get(operatorHandler.metaMethod);
|
||||
if (!fn.call) {
|
||||
throw new Error(
|
||||
`Meta method ${operatorHandler.metaMethod} is not callable`,
|
||||
);
|
||||
} else {
|
||||
return fn.call(right, left);
|
||||
}
|
||||
return luaCall(fn, [left, right], ctx, sf);
|
||||
}
|
||||
}
|
||||
return operatorHandler.nativeImplementation(left, right);
|
||||
return operatorHandler.nativeImplementation(left, right, ctx, sf);
|
||||
}
|
||||
|
||||
async function evalExpressions(
|
||||
es: LuaExpression[],
|
||||
env: LuaEnv,
|
||||
sf: LuaStackFrame,
|
||||
): Promise<LuaValue[]> {
|
||||
return new LuaMultiRes(
|
||||
await Promise.all(es.map((e) => evalExpression(e, env))),
|
||||
await Promise.all(es.map((e) => evalExpression(e, env, sf))),
|
||||
).flatten().values;
|
||||
}
|
||||
|
||||
export async function evalStatement(
|
||||
s: LuaStatement,
|
||||
env: LuaEnv,
|
||||
sf: LuaStackFrame,
|
||||
): Promise<void> {
|
||||
switch (s.type) {
|
||||
case "Assignment": {
|
||||
const values = await evalExpressions(s.expressions, env);
|
||||
const values = await evalExpressions(s.expressions, env, sf);
|
||||
const lvalues = await evalPromiseValues(s.variables
|
||||
.map((lval) => evalLValue(lval, env)));
|
||||
.map((lval) => evalLValue(lval, env, sf)));
|
||||
|
||||
for (let i = 0; i < lvalues.length; i++) {
|
||||
luaSet(lvalues[i].env, lvalues[i].key, values[i], s.ctx);
|
||||
luaSet(lvalues[i].env, lvalues[i].key, values[i], sf.withCtx(s.ctx));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "Local": {
|
||||
if (s.expressions) {
|
||||
const values = await evalExpressions(s.expressions, env);
|
||||
const values = await evalExpressions(s.expressions, env, sf);
|
||||
for (let i = 0; i < s.names.length; i++) {
|
||||
env.setLocal(s.names[i].name, values[i]);
|
||||
}
|
||||
|
@ -462,25 +483,25 @@ export async function evalStatement(
|
|||
case "Block": {
|
||||
const newEnv = new LuaEnv(env);
|
||||
for (const statement of s.statements) {
|
||||
await evalStatement(statement, newEnv);
|
||||
await evalStatement(statement, newEnv, sf);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "If": {
|
||||
for (const cond of s.conditions) {
|
||||
if (luaTruthy(await evalExpression(cond.condition, env))) {
|
||||
return evalStatement(cond.block, env);
|
||||
if (luaTruthy(await evalExpression(cond.condition, env, sf))) {
|
||||
return evalStatement(cond.block, env, sf);
|
||||
}
|
||||
}
|
||||
if (s.elseBlock) {
|
||||
return evalStatement(s.elseBlock, env);
|
||||
return evalStatement(s.elseBlock, env, sf);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "While": {
|
||||
while (luaTruthy(await evalExpression(s.condition, env))) {
|
||||
while (luaTruthy(await evalExpression(s.condition, env, sf))) {
|
||||
try {
|
||||
await evalStatement(s.block, env);
|
||||
await evalStatement(s.block, env, sf);
|
||||
} catch (e: any) {
|
||||
if (e instanceof LuaBreak) {
|
||||
break;
|
||||
|
@ -494,7 +515,7 @@ export async function evalStatement(
|
|||
case "Repeat": {
|
||||
do {
|
||||
try {
|
||||
await evalStatement(s.block, env);
|
||||
await evalStatement(s.block, env, sf);
|
||||
} catch (e: any) {
|
||||
if (e instanceof LuaBreak) {
|
||||
break;
|
||||
|
@ -502,13 +523,13 @@ export async function evalStatement(
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
} while (!luaTruthy(await evalExpression(s.condition, env)));
|
||||
} while (!luaTruthy(await evalExpression(s.condition, env, sf)));
|
||||
break;
|
||||
}
|
||||
case "Break":
|
||||
throw new LuaBreak();
|
||||
case "FunctionCallStatement": {
|
||||
return evalExpression(s.call, env);
|
||||
return evalExpression(s.call, env, sf);
|
||||
}
|
||||
case "Function": {
|
||||
let body = s.body;
|
||||
|
@ -527,7 +548,7 @@ export async function evalStatement(
|
|||
if (!settable) {
|
||||
throw new LuaRuntimeError(
|
||||
`Cannot find property ${propNames[i]}`,
|
||||
s.name.ctx,
|
||||
sf.withCtx(s.name.ctx),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -549,14 +570,14 @@ export async function evalStatement(
|
|||
// be optimized for the common case later
|
||||
throw new LuaReturn(
|
||||
await evalPromiseValues(
|
||||
s.expressions.map((value) => evalExpression(value, env)),
|
||||
s.expressions.map((value) => evalExpression(value, env, sf)),
|
||||
),
|
||||
);
|
||||
}
|
||||
case "For": {
|
||||
const start = await evalExpression(s.start, env);
|
||||
const end = await evalExpression(s.end, env);
|
||||
const step = s.step ? await evalExpression(s.step, env) : 1;
|
||||
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;
|
||||
const localEnv = new LuaEnv(env);
|
||||
for (
|
||||
let i = start;
|
||||
|
@ -565,7 +586,7 @@ export async function evalStatement(
|
|||
) {
|
||||
localEnv.setLocal(s.name, i);
|
||||
try {
|
||||
await evalStatement(s.block, localEnv);
|
||||
await evalStatement(s.block, localEnv, sf);
|
||||
} catch (e: any) {
|
||||
if (e instanceof LuaBreak) {
|
||||
break;
|
||||
|
@ -579,7 +600,7 @@ export async function evalStatement(
|
|||
case "ForIn": {
|
||||
const iteratorMultiRes = new LuaMultiRes(
|
||||
await evalPromiseValues(
|
||||
s.expressions.map((e) => evalExpression(e, env)),
|
||||
s.expressions.map((e) => evalExpression(e, env, sf)),
|
||||
),
|
||||
).flatten();
|
||||
const iteratorFunction: ILuaFunction | undefined =
|
||||
|
@ -588,7 +609,7 @@ export async function evalStatement(
|
|||
console.error("Cannot iterate over", iteratorMultiRes.values[0]);
|
||||
throw new LuaRuntimeError(
|
||||
`Cannot iterate over ${iteratorMultiRes.values[0]}`,
|
||||
s.ctx,
|
||||
sf.withCtx(s.ctx),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -597,7 +618,7 @@ export async function evalStatement(
|
|||
|
||||
while (true) {
|
||||
const iterResult = new LuaMultiRes(
|
||||
await iteratorFunction.call(state, control),
|
||||
await luaCall(iteratorFunction, [state, control], s.ctx, sf),
|
||||
).flatten();
|
||||
if (
|
||||
iterResult.values[0] === null || iterResult.values[0] === undefined
|
||||
|
@ -609,7 +630,7 @@ export async function evalStatement(
|
|||
localEnv.setLocal(s.names[i], iterResult.values[i]);
|
||||
}
|
||||
try {
|
||||
await evalStatement(s.block, localEnv);
|
||||
await evalStatement(s.block, localEnv, sf);
|
||||
} catch (e: any) {
|
||||
if (e instanceof LuaBreak) {
|
||||
break;
|
||||
|
@ -626,6 +647,7 @@ export async function evalStatement(
|
|||
function evalLValue(
|
||||
lval: LuaLValue,
|
||||
env: LuaEnv,
|
||||
sf: LuaStackFrame,
|
||||
): LuaLValueContainer | Promise<LuaLValueContainer> {
|
||||
switch (lval.type) {
|
||||
case "Variable":
|
||||
|
@ -634,8 +656,9 @@ function evalLValue(
|
|||
const objValue = evalExpression(
|
||||
lval.object,
|
||||
env,
|
||||
sf,
|
||||
);
|
||||
const keyValue = evalExpression(lval.key, env);
|
||||
const keyValue = evalExpression(lval.key, env, sf);
|
||||
if (
|
||||
objValue instanceof Promise ||
|
||||
keyValue instanceof Promise
|
||||
|
@ -658,6 +681,7 @@ function evalLValue(
|
|||
const objValue = evalExpression(
|
||||
lval.object,
|
||||
env,
|
||||
sf,
|
||||
);
|
||||
if (objValue instanceof Promise) {
|
||||
return objValue.then((objValue) => {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { parse } from "$common/space_lua/parse.ts";
|
||||
import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts";
|
||||
import { LuaEnv, type LuaRuntimeError } from "$common/space_lua/runtime.ts";
|
||||
import {
|
||||
LuaEnv,
|
||||
type LuaRuntimeError,
|
||||
LuaStackFrame,
|
||||
} from "$common/space_lua/runtime.ts";
|
||||
import { evalStatement } from "$common/space_lua/eval.ts";
|
||||
import { assert } from "@std/assert/assert";
|
||||
|
||||
Deno.test("Lua language tests", async () => {
|
||||
// Read the Lua file
|
||||
const luaFile = await Deno.readTextFile(
|
||||
|
@ -11,9 +14,10 @@ Deno.test("Lua language tests", async () => {
|
|||
);
|
||||
const chunk = parse(luaFile, {});
|
||||
const env = new LuaEnv(luaBuildStandardEnv());
|
||||
const sf = new LuaStackFrame(new LuaEnv(), chunk.ctx);
|
||||
|
||||
try {
|
||||
await evalStatement(chunk, env);
|
||||
await evalStatement(chunk, env, sf);
|
||||
} catch (e: any) {
|
||||
console.error(`Error evaluating script:`, toPrettyString(e, luaFile));
|
||||
assert(false);
|
||||
|
@ -21,22 +25,32 @@ Deno.test("Lua language tests", async () => {
|
|||
});
|
||||
|
||||
function toPrettyString(err: LuaRuntimeError, code: string): string {
|
||||
if (!err.context || !err.context.from || !err.context.to) {
|
||||
if (!err.sf || !err.sf.astCtx?.from || !err.sf.astCtx?.to) {
|
||||
return err.toString();
|
||||
}
|
||||
const from = err.context.from;
|
||||
// Find the line and column
|
||||
let line = 1;
|
||||
let column = 0;
|
||||
for (let i = 0; i < from; i++) {
|
||||
if (code[i] === "\n") {
|
||||
line++;
|
||||
column = 0;
|
||||
} else {
|
||||
column++;
|
||||
let traceStr = "";
|
||||
let current: LuaStackFrame | undefined = err.sf;
|
||||
while (current) {
|
||||
const ctx = current.astCtx;
|
||||
if (!ctx || !ctx.from || !ctx.to) {
|
||||
break;
|
||||
}
|
||||
// Find the line and column
|
||||
let line = 1;
|
||||
let column = 0;
|
||||
for (let i = 0; i < ctx.from; i++) {
|
||||
if (code[i] === "\n") {
|
||||
line++;
|
||||
column = 0;
|
||||
} else {
|
||||
column++;
|
||||
}
|
||||
}
|
||||
traceStr += `* ${ctx.ref || "(unknown source)"} @ ${line}:${column}:\n ${
|
||||
code.substring(ctx.from, ctx.to)
|
||||
}\n`;
|
||||
current = current.parent;
|
||||
}
|
||||
return `LuaRuntimeError: ${err.message} at ${line}:${column}:\n ${
|
||||
code.substring(from, err.context.to)
|
||||
}`;
|
||||
|
||||
return `LuaRuntimeError: ${err.message} ${traceStr}`;
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ mt = {
|
|||
t = setmetatable({}, mt)
|
||||
t.bar = "bar"
|
||||
assert(t.bar == "bar")
|
||||
assert(t.foo == "Key not found: foo")
|
||||
assert_equal(t.foo, "Key not found: foo")
|
||||
|
||||
-- Test the __newindex metamethod
|
||||
t = setmetatable(
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
jsToLuaValue,
|
||||
luaLen,
|
||||
LuaMultiRes,
|
||||
LuaStackFrame,
|
||||
} from "$common/space_lua/runtime.ts";
|
||||
|
||||
Deno.test("Test Lua Rutime", () => {
|
||||
|
@ -40,5 +41,5 @@ Deno.test("Test Lua Rutime", () => {
|
|||
assertEquals(luaVal.get(2).get("name"), "John");
|
||||
// Functions in objects
|
||||
luaVal = jsToLuaValue({ name: "Pete", first: (l: any[]) => l[0] });
|
||||
assertEquals(luaVal.get("first").call([1, 2, 3]), 1);
|
||||
assertEquals(luaVal.get("first").call(LuaStackFrame.lostFrame, [1, 2, 3]), 1);
|
||||
});
|
||||
|
|
|
@ -17,16 +17,16 @@ export type LuaValue = any;
|
|||
export type JSValue = any;
|
||||
|
||||
export interface ILuaFunction {
|
||||
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue;
|
||||
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export interface ILuaSettable {
|
||||
set(key: LuaValue, value: LuaValue): void;
|
||||
set(key: LuaValue, value: LuaValue, sf?: LuaStackFrame): void;
|
||||
}
|
||||
|
||||
export interface ILuaGettable {
|
||||
get(key: LuaValue): LuaValue | undefined;
|
||||
get(key: LuaValue, sf?: LuaStackFrame): LuaValue | undefined;
|
||||
}
|
||||
|
||||
export class LuaEnv implements ILuaSettable, ILuaGettable {
|
||||
|
@ -39,20 +39,30 @@ export class LuaEnv implements ILuaSettable, ILuaGettable {
|
|||
this.variables.set(name, value);
|
||||
}
|
||||
|
||||
set(key: string, value: LuaValue): void {
|
||||
set(key: string, value: LuaValue, sf?: LuaStackFrame): void {
|
||||
if (this.variables.has(key) || !this.parent) {
|
||||
this.variables.set(key, value);
|
||||
} else {
|
||||
this.parent.set(key, value);
|
||||
this.parent.set(key, value, sf);
|
||||
}
|
||||
}
|
||||
|
||||
get(name: string): LuaValue | undefined {
|
||||
has(key: string): boolean {
|
||||
if (this.variables.has(key)) {
|
||||
return true;
|
||||
}
|
||||
if (this.parent) {
|
||||
return this.parent.has(key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get(name: string, sf?: LuaStackFrame): LuaValue | undefined {
|
||||
if (this.variables.has(name)) {
|
||||
return this.variables.get(name);
|
||||
}
|
||||
if (this.parent) {
|
||||
return this.parent.get(name);
|
||||
return this.parent.get(name, sf);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
@ -69,6 +79,21 @@ export class LuaEnv implements ILuaSettable, ILuaGettable {
|
|||
}
|
||||
}
|
||||
|
||||
export class LuaStackFrame {
|
||||
constructor(
|
||||
readonly threadLocal: LuaEnv,
|
||||
readonly astCtx: ASTCtx | null,
|
||||
readonly parent?: LuaStackFrame,
|
||||
) {
|
||||
}
|
||||
|
||||
withCtx(ctx: ASTCtx): LuaStackFrame {
|
||||
return new LuaStackFrame(this.threadLocal, ctx, this);
|
||||
}
|
||||
|
||||
static lostFrame = new LuaStackFrame(new LuaEnv(), null);
|
||||
}
|
||||
|
||||
export class LuaMultiRes {
|
||||
values: any[];
|
||||
|
||||
|
@ -110,12 +135,16 @@ export function singleResult(value: any): any {
|
|||
}
|
||||
|
||||
export class LuaFunction implements ILuaFunction {
|
||||
constructor(private body: LuaFunctionBody, private closure: LuaEnv) {
|
||||
constructor(readonly body: LuaFunctionBody, private closure: LuaEnv) {
|
||||
}
|
||||
|
||||
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
||||
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
||||
// Create a new environment for this function call
|
||||
const env = new LuaEnv(this.closure);
|
||||
if (!sf) {
|
||||
console.trace(sf);
|
||||
}
|
||||
env.setLocal("_CTX", sf.threadLocal);
|
||||
// Assign the passed arguments to the parameters
|
||||
for (let i = 0; i < this.body.parameters.length; i++) {
|
||||
let arg = args[i];
|
||||
|
@ -124,7 +153,7 @@ export class LuaFunction implements ILuaFunction {
|
|||
}
|
||||
env.setLocal(this.body.parameters[i], arg);
|
||||
}
|
||||
return evalStatement(this.body.block, env).catch((e: any) => {
|
||||
return evalStatement(this.body.block, env, sf).catch((e: any) => {
|
||||
if (e instanceof LuaReturn) {
|
||||
if (e.values.length === 0) {
|
||||
return;
|
||||
|
@ -149,7 +178,7 @@ export class LuaNativeJSFunction implements ILuaFunction {
|
|||
}
|
||||
|
||||
// Performs automatic conversion between Lua and JS values
|
||||
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
||||
call(_sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
||||
const result = this.fn(...args.map(luaValueToJS));
|
||||
if (result instanceof Promise) {
|
||||
return result.then(jsToLuaValue);
|
||||
|
@ -164,11 +193,13 @@ export class LuaNativeJSFunction implements ILuaFunction {
|
|||
}
|
||||
|
||||
export class LuaBuiltinFunction implements ILuaFunction {
|
||||
constructor(readonly fn: (...args: LuaValue[]) => LuaValue) {
|
||||
constructor(
|
||||
readonly fn: (sf: LuaStackFrame, ...args: LuaValue[]) => LuaValue,
|
||||
) {
|
||||
}
|
||||
|
||||
call(...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
||||
return this.fn(...args);
|
||||
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
||||
return this.fn(sf, ...args);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
|
@ -236,14 +267,13 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|||
}
|
||||
}
|
||||
|
||||
set(key: LuaValue, value: LuaValue): void {
|
||||
set(key: LuaValue, value: LuaValue, sf?: LuaStackFrame): void {
|
||||
// New index handling for metatables
|
||||
if (this.metatable && this.metatable.has("__newindex") && !this.has(key)) {
|
||||
const metaValue = this.metatable.get("__newindex");
|
||||
if (metaValue.call) {
|
||||
metaValue.call(this, key, value);
|
||||
return;
|
||||
}
|
||||
const metaValue = this.metatable.get("__newindex", sf);
|
||||
// TODO: This may return a promise, we should handle that
|
||||
luaCall(metaValue, [this, key, value], metaValue.ctx, sf);
|
||||
return;
|
||||
}
|
||||
|
||||
this.rawSet(key, value);
|
||||
|
@ -259,16 +289,16 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|||
}
|
||||
}
|
||||
|
||||
get(key: LuaValue): LuaValue | null {
|
||||
get(key: LuaValue, sf?: LuaStackFrame): LuaValue | null {
|
||||
const value = this.rawGet(key);
|
||||
if (value === undefined || value === null) {
|
||||
// Invoke the meta table
|
||||
if (this.metatable) {
|
||||
const metaValue = this.metatable.get("__index");
|
||||
const metaValue = this.metatable.get("__index", sf);
|
||||
if (metaValue.call) {
|
||||
return metaValue.call(this, key);
|
||||
return metaValue.call(sf, this, key);
|
||||
} else if (metaValue instanceof LuaTable) {
|
||||
return metaValue.get(key);
|
||||
return metaValue.get(key, sf);
|
||||
} else {
|
||||
throw new Error("Meta table __index must be a function or table");
|
||||
}
|
||||
|
@ -285,10 +315,10 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|||
this.arrayPart.splice(pos - 1, 1);
|
||||
}
|
||||
|
||||
async sort(fn?: ILuaFunction) {
|
||||
if (fn) {
|
||||
async sort(fn?: ILuaFunction, sf?: LuaStackFrame) {
|
||||
if (fn && sf) {
|
||||
this.arrayPart = await asyncQuickSort(this.arrayPart, async (a, b) => {
|
||||
return (await fn.call(a, b)) ? -1 : 1;
|
||||
return (await fn.call(sf, a, b)) ? -1 : 1;
|
||||
});
|
||||
} else {
|
||||
this.arrayPart.sort();
|
||||
|
@ -311,7 +341,7 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|||
if (this.metatable?.has("__tostring")) {
|
||||
const metaValue = this.metatable.get("__tostring");
|
||||
if (metaValue.call) {
|
||||
return metaValue.call(this);
|
||||
return metaValue.call(LuaStackFrame.lostFrame, this);
|
||||
} else {
|
||||
throw new Error("Meta table __tostring must be a function");
|
||||
}
|
||||
|
@ -342,37 +372,37 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
|
|||
|
||||
export type LuaLValueContainer = { env: ILuaSettable; key: LuaValue };
|
||||
|
||||
export function luaSet(obj: any, key: any, value: any, ctx: ASTCtx) {
|
||||
export function luaSet(obj: any, key: any, value: any, sf: LuaStackFrame) {
|
||||
if (!obj) {
|
||||
throw new LuaRuntimeError(
|
||||
`Not a settable object: nil`,
|
||||
ctx,
|
||||
sf,
|
||||
);
|
||||
}
|
||||
|
||||
if (obj instanceof LuaTable || obj instanceof LuaEnv) {
|
||||
obj.set(key, value);
|
||||
obj.set(key, value, sf);
|
||||
} else {
|
||||
obj[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
export function luaGet(obj: any, key: any, ctx: ASTCtx): any {
|
||||
export function luaGet(obj: any, key: any, sf: LuaStackFrame): any {
|
||||
if (!obj) {
|
||||
throw new LuaRuntimeError(
|
||||
`Attempting to index a nil value`,
|
||||
ctx,
|
||||
sf,
|
||||
);
|
||||
}
|
||||
if (key === null || key === undefined) {
|
||||
throw new LuaRuntimeError(
|
||||
`Attempting to index with a nil key`,
|
||||
ctx,
|
||||
sf,
|
||||
);
|
||||
}
|
||||
|
||||
if (obj instanceof LuaTable || obj instanceof LuaEnv) {
|
||||
return obj.get(key);
|
||||
return obj.get(key, sf);
|
||||
} else if (typeof key === "number") {
|
||||
return obj[key - 1];
|
||||
} else {
|
||||
|
@ -397,11 +427,16 @@ export function luaLen(obj: any): number {
|
|||
}
|
||||
}
|
||||
|
||||
export function luaCall(fn: any, args: any[], ctx: ASTCtx): any {
|
||||
export function luaCall(
|
||||
fn: any,
|
||||
args: any[],
|
||||
ctx: ASTCtx,
|
||||
sf?: LuaStackFrame,
|
||||
): any {
|
||||
if (!fn) {
|
||||
throw new LuaRuntimeError(
|
||||
`Attempting to call a nil value`,
|
||||
ctx,
|
||||
(sf || LuaStackFrame.lostFrame).withCtx(ctx),
|
||||
);
|
||||
}
|
||||
if (typeof fn === "function") {
|
||||
|
@ -409,7 +444,13 @@ export function luaCall(fn: any, args: any[], ctx: ASTCtx): any {
|
|||
// Native JS function
|
||||
return fn(...jsArgs);
|
||||
}
|
||||
return fn.call(...args);
|
||||
if (!fn.call) {
|
||||
throw new LuaRuntimeError(
|
||||
`Attempting to call a non-callable value`,
|
||||
(sf || LuaStackFrame.lostFrame).withCtx(ctx),
|
||||
);
|
||||
}
|
||||
return fn.call((sf || LuaStackFrame.lostFrame).withCtx(ctx), ...args);
|
||||
}
|
||||
|
||||
export function luaTypeOf(val: any): LuaType {
|
||||
|
@ -445,14 +486,14 @@ export class LuaReturn extends Error {
|
|||
export class LuaRuntimeError extends Error {
|
||||
constructor(
|
||||
override readonly message: string,
|
||||
public context: ASTCtx,
|
||||
public sf: LuaStackFrame,
|
||||
cause?: Error,
|
||||
) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
override toString() {
|
||||
return `LuaRuntimeError: ${this.message} at ${this.context.from}, ${this.context.to}`;
|
||||
return `LuaRuntimeError: ${this.message} at ${this.sf.astCtx?.from}, ${this.sf.astCtx?.to}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import {
|
||||
type ILuaFunction,
|
||||
LuaBuiltinFunction,
|
||||
luaCall,
|
||||
LuaEnv,
|
||||
LuaMultiRes,
|
||||
LuaRuntimeError,
|
||||
type LuaTable,
|
||||
luaToString,
|
||||
luaTypeOf,
|
||||
|
@ -13,19 +15,19 @@ import { tableApi } from "$common/space_lua/stdlib/table.ts";
|
|||
import { osApi } from "$common/space_lua/stdlib/os.ts";
|
||||
import { jsApi } from "$common/space_lua/stdlib/js.ts";
|
||||
|
||||
const printFunction = new LuaBuiltinFunction((...args) => {
|
||||
const printFunction = new LuaBuiltinFunction((_sf, ...args) => {
|
||||
console.log("[Lua]", ...args.map(luaToString));
|
||||
});
|
||||
|
||||
const assertFunction = new LuaBuiltinFunction(
|
||||
async (value: any, message?: string) => {
|
||||
async (sf, value: any, message?: string) => {
|
||||
if (!await value) {
|
||||
throw new Error(`Assertion failed: ${message}`);
|
||||
throw new LuaRuntimeError(`Assertion failed: ${message}`, sf);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const ipairsFunction = new LuaBuiltinFunction((ar: LuaTable) => {
|
||||
const ipairsFunction = new LuaBuiltinFunction((_sf, ar: LuaTable) => {
|
||||
let i = 1;
|
||||
return () => {
|
||||
if (i > ar.length) {
|
||||
|
@ -37,7 +39,7 @@ const ipairsFunction = new LuaBuiltinFunction((ar: LuaTable) => {
|
|||
};
|
||||
});
|
||||
|
||||
const pairsFunction = new LuaBuiltinFunction((t: LuaTable) => {
|
||||
const pairsFunction = new LuaBuiltinFunction((_sf, t: LuaTable) => {
|
||||
const keys = t.keys();
|
||||
let i = 0;
|
||||
return () => {
|
||||
|
@ -50,7 +52,7 @@ const pairsFunction = new LuaBuiltinFunction((t: LuaTable) => {
|
|||
};
|
||||
});
|
||||
|
||||
const unpackFunction = new LuaBuiltinFunction((t: LuaTable) => {
|
||||
const unpackFunction = new LuaBuiltinFunction((_sf, t: LuaTable) => {
|
||||
const values: LuaValue[] = [];
|
||||
for (let i = 1; i <= t.length; i++) {
|
||||
values.push(t.get(i));
|
||||
|
@ -58,26 +60,26 @@ const unpackFunction = new LuaBuiltinFunction((t: LuaTable) => {
|
|||
return new LuaMultiRes(values);
|
||||
});
|
||||
|
||||
const typeFunction = new LuaBuiltinFunction((value: LuaValue): string => {
|
||||
const typeFunction = new LuaBuiltinFunction((_sf, value: LuaValue): string => {
|
||||
return luaTypeOf(value);
|
||||
});
|
||||
|
||||
const tostringFunction = new LuaBuiltinFunction((value: any) => {
|
||||
const tostringFunction = new LuaBuiltinFunction((_sf, value: any) => {
|
||||
return luaToString(value);
|
||||
});
|
||||
|
||||
const tonumberFunction = new LuaBuiltinFunction((value: LuaValue) => {
|
||||
const tonumberFunction = new LuaBuiltinFunction((_sf, value: LuaValue) => {
|
||||
return Number(value);
|
||||
});
|
||||
|
||||
const errorFunction = new LuaBuiltinFunction((message: string) => {
|
||||
const errorFunction = new LuaBuiltinFunction((_sf, message: string) => {
|
||||
throw new Error(message);
|
||||
});
|
||||
|
||||
const pcallFunction = new LuaBuiltinFunction(
|
||||
async (fn: ILuaFunction, ...args) => {
|
||||
async (sf, fn: ILuaFunction, ...args) => {
|
||||
try {
|
||||
return new LuaMultiRes([true, await fn.call(...args)]);
|
||||
return new LuaMultiRes([true, await luaCall(fn, args, sf.astCtx!, sf)]);
|
||||
} catch (e: any) {
|
||||
return new LuaMultiRes([false, e.message]);
|
||||
}
|
||||
|
@ -85,30 +87,33 @@ const pcallFunction = new LuaBuiltinFunction(
|
|||
);
|
||||
|
||||
const xpcallFunction = new LuaBuiltinFunction(
|
||||
async (fn: ILuaFunction, errorHandler: ILuaFunction, ...args) => {
|
||||
async (sf, fn: ILuaFunction, errorHandler: ILuaFunction, ...args) => {
|
||||
try {
|
||||
return new LuaMultiRes([true, await fn.call(...args)]);
|
||||
return new LuaMultiRes([true, await fn.call(sf, ...args)]);
|
||||
} catch (e: any) {
|
||||
return new LuaMultiRes([false, await errorHandler.call(e.message)]);
|
||||
return new LuaMultiRes([
|
||||
false,
|
||||
await luaCall(errorHandler, [e.message], sf.astCtx!, sf),
|
||||
]);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const setmetatableFunction = new LuaBuiltinFunction(
|
||||
(table: LuaTable, metatable: LuaTable) => {
|
||||
(_sf, table: LuaTable, metatable: LuaTable) => {
|
||||
table.metatable = metatable;
|
||||
return table;
|
||||
},
|
||||
);
|
||||
|
||||
const rawsetFunction = new LuaBuiltinFunction(
|
||||
(table: LuaTable, key: LuaValue, value: LuaValue) => {
|
||||
(_sf, table: LuaTable, key: LuaValue, value: LuaValue) => {
|
||||
table.rawSet(key, value);
|
||||
return table;
|
||||
},
|
||||
);
|
||||
|
||||
const getmetatableFunction = new LuaBuiltinFunction((table: LuaTable) => {
|
||||
const getmetatableFunction = new LuaBuiltinFunction((_sf, table: LuaTable) => {
|
||||
return table.metatable;
|
||||
});
|
||||
|
||||
|
|
|
@ -13,13 +13,13 @@ export const jsApi = new LuaTable({
|
|||
);
|
||||
},
|
||||
),
|
||||
importModule: new LuaBuiltinFunction((url) => {
|
||||
importModule: new LuaBuiltinFunction((_sf, url) => {
|
||||
return import(url);
|
||||
}),
|
||||
|
||||
tolua: new LuaBuiltinFunction(jsToLuaValue),
|
||||
tojs: new LuaBuiltinFunction(luaValueToJS),
|
||||
log: new LuaBuiltinFunction((...args) => {
|
||||
tolua: new LuaBuiltinFunction((_sf, val) => jsToLuaValue(val)),
|
||||
tojs: new LuaBuiltinFunction((_sf, val) => luaValueToJS(val)),
|
||||
log: new LuaBuiltinFunction((_sf, ...args) => {
|
||||
console.log(...args);
|
||||
}),
|
||||
// assignGlobal: new LuaBuiltinFunction((name: string, value: any) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { LuaBuiltinFunction, LuaTable } from "$common/space_lua/runtime.ts";
|
||||
|
||||
export const osApi = new LuaTable({
|
||||
time: new LuaBuiltinFunction((tbl?: LuaTable) => {
|
||||
time: new LuaBuiltinFunction((_sf, tbl?: LuaTable) => {
|
||||
if (tbl) {
|
||||
// Build a date object from the table
|
||||
const date = new Date();
|
||||
|
@ -32,7 +32,7 @@ export const osApi = new LuaTable({
|
|||
* If format is not "*t", then date returns the date as a string, formatted according to the same rules as the ISO C function strftime.
|
||||
* If format is absent, it defaults to "%c", which gives a human-readable date and time representation using the current locale.
|
||||
*/
|
||||
date: new LuaBuiltinFunction((format: string, timestamp?: number) => {
|
||||
date: new LuaBuiltinFunction((_sf, format: string, timestamp?: number) => {
|
||||
const date = timestamp ? new Date(timestamp * 1000) : new Date();
|
||||
|
||||
// Default Lua-like format when no format string is provided
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
} from "$common/space_lua/runtime.ts";
|
||||
|
||||
export const stringApi = new LuaTable({
|
||||
byte: new LuaBuiltinFunction((s: string, i?: number, j?: number) => {
|
||||
byte: new LuaBuiltinFunction((_sf, s: string, i?: number, j?: number) => {
|
||||
i = i ?? 1;
|
||||
j = j ?? i;
|
||||
const result = [];
|
||||
|
@ -15,11 +15,11 @@ export const stringApi = new LuaTable({
|
|||
}
|
||||
return new LuaMultiRes(result);
|
||||
}),
|
||||
char: new LuaBuiltinFunction((...args: number[]) => {
|
||||
char: new LuaBuiltinFunction((_sf, ...args: number[]) => {
|
||||
return String.fromCharCode(...args);
|
||||
}),
|
||||
find: new LuaBuiltinFunction(
|
||||
(s: string, pattern: string, init?: number, plain?: boolean) => {
|
||||
(_sf, s: string, pattern: string, init?: number, plain?: boolean) => {
|
||||
init = init ?? 1;
|
||||
plain = plain ?? false;
|
||||
const result = s.slice(init - 1).match(pattern);
|
||||
|
@ -32,7 +32,7 @@ export const stringApi = new LuaTable({
|
|||
]);
|
||||
},
|
||||
),
|
||||
format: new LuaBuiltinFunction((format: string, ...args: any[]) => {
|
||||
format: new LuaBuiltinFunction((_sf, format: string, ...args: any[]) => {
|
||||
return format.replace(/%./g, (match) => {
|
||||
switch (match) {
|
||||
case "%s":
|
||||
|
@ -44,7 +44,7 @@ export const stringApi = new LuaTable({
|
|||
}
|
||||
});
|
||||
}),
|
||||
gmatch: new LuaBuiltinFunction((s: string, pattern: string) => {
|
||||
gmatch: new LuaBuiltinFunction((_sf, s: string, pattern: string) => {
|
||||
const regex = new RegExp(pattern, "g");
|
||||
return () => {
|
||||
const result = regex.exec(s);
|
||||
|
@ -55,7 +55,7 @@ export const stringApi = new LuaTable({
|
|||
};
|
||||
}),
|
||||
gsub: new LuaBuiltinFunction(
|
||||
(s: string, pattern: string, repl: string, n?: number) => {
|
||||
(_sf, s: string, pattern: string, repl: string, n?: number) => {
|
||||
n = n ?? Infinity;
|
||||
const regex = new RegExp(pattern, "g");
|
||||
let result = s;
|
||||
|
@ -70,17 +70,17 @@ export const stringApi = new LuaTable({
|
|||
return result;
|
||||
},
|
||||
),
|
||||
len: new LuaBuiltinFunction((s: string) => {
|
||||
len: new LuaBuiltinFunction((_sf, s: string) => {
|
||||
return s.length;
|
||||
}),
|
||||
lower: new LuaBuiltinFunction((s: string) => {
|
||||
lower: new LuaBuiltinFunction((_sf, s: string) => {
|
||||
return luaToString(s.toLowerCase());
|
||||
}),
|
||||
upper: new LuaBuiltinFunction((s: string) => {
|
||||
upper: new LuaBuiltinFunction((_sf, s: string) => {
|
||||
return luaToString(s.toUpperCase());
|
||||
}),
|
||||
match: new LuaBuiltinFunction(
|
||||
(s: string, pattern: string, init?: number) => {
|
||||
(_sf, s: string, pattern: string, init?: number) => {
|
||||
init = init ?? 1;
|
||||
const result = s.slice(init - 1).match(pattern);
|
||||
if (!result) {
|
||||
|
@ -89,18 +89,18 @@ export const stringApi = new LuaTable({
|
|||
return new LuaMultiRes(result.slice(1));
|
||||
},
|
||||
),
|
||||
rep: new LuaBuiltinFunction((s: string, n: number, sep?: string) => {
|
||||
rep: new LuaBuiltinFunction((_sf, s: string, n: number, sep?: string) => {
|
||||
sep = sep ?? "";
|
||||
return s.repeat(n) + sep;
|
||||
}),
|
||||
reverse: new LuaBuiltinFunction((s: string) => {
|
||||
reverse: new LuaBuiltinFunction((_sf, s: string) => {
|
||||
return s.split("").reverse().join("");
|
||||
}),
|
||||
sub: new LuaBuiltinFunction((s: string, i: number, j?: number) => {
|
||||
sub: new LuaBuiltinFunction((_sf, s: string, i: number, j?: number) => {
|
||||
j = j ?? s.length;
|
||||
return s.slice(i - 1, j);
|
||||
}),
|
||||
split: new LuaBuiltinFunction((s: string, sep: string) => {
|
||||
split: new LuaBuiltinFunction((_sf, s: string, sep: string) => {
|
||||
return s.split(sep);
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
|
||||
export const tableApi = new LuaTable({
|
||||
concat: new LuaBuiltinFunction(
|
||||
(tbl: LuaTable, sep?: string, i?: number, j?: number) => {
|
||||
(_sf, tbl: LuaTable, sep?: string, i?: number, j?: number) => {
|
||||
sep = sep ?? "";
|
||||
i = i ?? 1;
|
||||
j = j ?? tbl.length;
|
||||
|
@ -18,7 +18,7 @@ export const tableApi = new LuaTable({
|
|||
},
|
||||
),
|
||||
insert: new LuaBuiltinFunction(
|
||||
(tbl: LuaTable, posOrValue: number | any, value?: any) => {
|
||||
(_sf, tbl: LuaTable, posOrValue: number | any, value?: any) => {
|
||||
if (value === undefined) {
|
||||
value = posOrValue;
|
||||
posOrValue = tbl.length + 1;
|
||||
|
@ -26,11 +26,11 @@ export const tableApi = new LuaTable({
|
|||
tbl.insert(posOrValue, value);
|
||||
},
|
||||
),
|
||||
remove: new LuaBuiltinFunction((tbl: LuaTable, pos?: number) => {
|
||||
remove: new LuaBuiltinFunction((_sf, tbl: LuaTable, pos?: number) => {
|
||||
pos = pos ?? tbl.length;
|
||||
tbl.remove(pos);
|
||||
}),
|
||||
sort: new LuaBuiltinFunction((tbl: LuaTable, comp?: ILuaFunction) => {
|
||||
return tbl.sort(comp);
|
||||
sort: new LuaBuiltinFunction((sf, tbl: LuaTable, comp?: ILuaFunction) => {
|
||||
return tbl.sort(comp, sf);
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
LuaBuiltinFunction,
|
||||
LuaEnv,
|
||||
LuaNativeJSFunction,
|
||||
LuaStackFrame,
|
||||
LuaTable,
|
||||
} from "$common/space_lua/runtime.ts";
|
||||
import type { System } from "$lib/plugos/system.ts";
|
||||
|
@ -24,17 +25,18 @@ export function buildLuaEnv(system: System<any>, scriptEnv: ScriptEnvironment) {
|
|||
|
||||
function exposeSyscalls(env: LuaEnv, system: System<any>) {
|
||||
// Expose all syscalls to Lua
|
||||
const nativeFs = new LuaStackFrame(env, null);
|
||||
for (const syscallName of system.registeredSyscalls.keys()) {
|
||||
const [ns, fn] = syscallName.split(".");
|
||||
if (!env.get(ns)) {
|
||||
env.set(ns, new LuaTable());
|
||||
if (!env.has(ns)) {
|
||||
env.set(ns, new LuaTable(), nativeFs);
|
||||
}
|
||||
const luaFn = new LuaNativeJSFunction((...args) => {
|
||||
return system.localSyscall(syscallName, args);
|
||||
});
|
||||
// Register the function with the same name as the syscall both in regular and snake_case
|
||||
env.get(ns).set(fn, luaFn);
|
||||
env.get(ns).set(snakeCase(fn), luaFn);
|
||||
env.get(ns, nativeFs).set(fn, luaFn, nativeFs);
|
||||
env.get(ns, nativeFs).set(snakeCase(fn), luaFn, nativeFs);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +49,7 @@ function exposeDefinitions(
|
|||
env.set(
|
||||
"define_command",
|
||||
new LuaBuiltinFunction(
|
||||
(def: LuaTable) => {
|
||||
(_sf, def: LuaTable) => {
|
||||
if (def.get(1) === undefined) {
|
||||
throw new Error("Callback is required");
|
||||
}
|
||||
|
@ -65,10 +67,9 @@ function exposeDefinitions(
|
|||
hide: def.get("hide"),
|
||||
} as CommandDef,
|
||||
async (...args: any[]) => {
|
||||
const sf = new LuaStackFrame(new LuaEnv(), null);
|
||||
try {
|
||||
return await def.get(1).call(
|
||||
...args.map(jsToLuaValue),
|
||||
);
|
||||
return await def.get(1).call(sf, ...args.map(jsToLuaValue));
|
||||
} catch (e: any) {
|
||||
await handleLuaError(e, system);
|
||||
}
|
||||
|
@ -79,7 +80,7 @@ function exposeDefinitions(
|
|||
);
|
||||
env.set(
|
||||
"define_event_listener",
|
||||
new LuaBuiltinFunction((def: LuaTable) => {
|
||||
new LuaBuiltinFunction((_sf, def: LuaTable) => {
|
||||
if (def.get(1) === undefined) {
|
||||
throw new Error("Callback is required");
|
||||
}
|
||||
|
@ -90,10 +91,9 @@ function exposeDefinitions(
|
|||
scriptEnv.registerEventListener(
|
||||
{ name: def.get("event") },
|
||||
async (...args: any[]) => {
|
||||
const sf = new LuaStackFrame(new LuaEnv(), null);
|
||||
try {
|
||||
return await def.get(1).call(
|
||||
...args.map(jsToLuaValue),
|
||||
);
|
||||
return await def.get(1).call(sf, ...args.map(jsToLuaValue));
|
||||
} catch (e: any) {
|
||||
await handleLuaError(e, system);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,11 @@ import type {
|
|||
LuaFunctionCallStatement,
|
||||
} from "$common/space_lua/ast.ts";
|
||||
import { evalExpression } from "$common/space_lua/eval.ts";
|
||||
import { luaValueToJS } from "$common/space_lua/runtime.ts";
|
||||
import {
|
||||
LuaEnv,
|
||||
LuaStackFrame,
|
||||
luaValueToJS,
|
||||
} from "$common/space_lua/runtime.ts";
|
||||
import { LuaRuntimeError } from "$common/space_lua/runtime.ts";
|
||||
import { encodePageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
|
||||
import { resolveASTReference } from "$common/space_lua.ts";
|
||||
|
@ -54,16 +58,18 @@ export function luaDirectivePlugin(client: Client) {
|
|||
(parsedLua.statements[0] as LuaFunctionCallStatement).call
|
||||
.args[0];
|
||||
|
||||
const sf = new LuaStackFrame(new LuaEnv(), expr.ctx);
|
||||
return luaValueToJS(
|
||||
await evalExpression(
|
||||
expr,
|
||||
client.clientSystem.spaceLuaEnv.env,
|
||||
sf,
|
||||
),
|
||||
);
|
||||
} catch (e: any) {
|
||||
if (e instanceof LuaRuntimeError) {
|
||||
if (e.context.ref) {
|
||||
const source = resolveASTReference(e.context);
|
||||
if (e.sf.astCtx) {
|
||||
const source = resolveASTReference(e.sf.astCtx);
|
||||
if (source) {
|
||||
// We know the origin node of the error, let's reference it
|
||||
return {
|
||||
|
|
Loading…
Reference in New Issue