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