Skip to content

Commit

Permalink
More lua progress: JS Interop
Browse files Browse the repository at this point in the history
  • Loading branch information
zefhemel committed Oct 9, 2024
1 parent 7731b28 commit 3319c7f
Show file tree
Hide file tree
Showing 15 changed files with 390 additions and 209 deletions.
9 changes: 4 additions & 5 deletions common/space_lua.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,16 @@ export class SpaceLuaEnvironment {
}),
);
}
const sbApi = new LuaTable();
sbApi.set(
"register_command",
env.set(
"command",
new LuaBuiltinFunction(
(def: LuaTable) => {
if (def.get(1) === undefined) {
throw new Error("Callback is required");
}
console.log("Registering Lua command", def.get("name"));
scriptEnv.registerCommand(
luaValueToJS(def) as any,
def.toJSObject() as any,
async (...args: any[]) => {
try {
return await def.get(1).call(...args.map(jsToLuaValue));
Expand Down Expand Up @@ -90,7 +90,6 @@ export class SpaceLuaEnvironment {
),
);

env.set("silverbullet", sbApi);
for (const script of allScripts) {
try {
const ast = parseLua(script.script, { ref: script.ref });
Expand Down
74 changes: 16 additions & 58 deletions common/space_lua/eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
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 {
type ILuaFunction,
type ILuaGettable,
Expand Down Expand Up @@ -209,59 +210,24 @@ function evalPrefixExpression(
return values.then(([table, key]) => {
table = singleResult(table);
key = singleResult(key);
if (!table) {
throw new LuaRuntimeError(
`Attempting to index a nil value`,
e.object.ctx,
);
}
if (key === null || key === undefined) {
throw new LuaRuntimeError(
`Attempting to index with a nil key`,
e.key.ctx,
);
}
return luaGet(table, key);

return luaGet(table, key, e.ctx);
});
} else {
const table = singleResult(values[0]);
const key = singleResult(values[1]);
if (!table) {
throw new LuaRuntimeError(
`Attempting to index a nil value`,
e.object.ctx,
);
}
if (key === null || key === undefined) {
throw new LuaRuntimeError(
`Attempting to index with a nil key`,
e.key.ctx,
);
}
return luaGet(table, singleResult(key));
return luaGet(table, singleResult(key), e.ctx);
}
}
// <expr>.property
case "PropertyAccess": {
const obj = evalPrefixExpression(e.object, env);
if (obj instanceof Promise) {
return obj.then((obj) => {
if (!obj?.get) {
throw new LuaRuntimeError(
`Attempting to index a nil value`,
e.object.ctx,
);
}
return obj.get(e.property);
return luaGet(obj, e.property, e.ctx);
});
} else {
if (!obj?.get) {
throw new LuaRuntimeError(
`Attempting to index a nil value`,
e.object.ctx,
);
}
return obj.get(e.property);
return luaGet(obj, e.property, e.ctx);
}
}
case "FunctionCall": {
Expand Down Expand Up @@ -295,16 +261,18 @@ function evalPrefixExpression(
if (!prefixValue.call) {
throw new LuaRuntimeError(
`Attempting to call ${prefixValue} as a function`,
prefixValue.ctx,
e.prefix.ctx,
);
}
const args = evalPromiseValues(
e.args.map((arg) => evalExpression(arg, env)),
);
if (args instanceof Promise) {
return args.then((args) => prefixValue.call(...selfArgs, ...args));
return args.then((args) =>
luaCall(prefixValue, [...selfArgs, ...args], e.ctx)
);
} else {
return prefixValue.call(...selfArgs, ...args);
return luaCall(prefixValue, [...selfArgs, ...args], e.ctx);
}
});
} else {
Expand All @@ -330,9 +298,11 @@ function evalPrefixExpression(
e.args.map((arg) => evalExpression(arg, env)),
);
if (args instanceof Promise) {
return args.then((args) => prefixValue.call(...selfArgs, ...args));
return args.then((args) =>
luaCall(prefixValue, [...selfArgs, ...args], e.ctx)
);
} else {
return prefixValue.call(...selfArgs, ...args);
return luaCall(prefixValue, [...selfArgs, ...args], e.ctx);
}
}
}
Expand Down Expand Up @@ -466,7 +436,7 @@ export async function evalStatement(
.map((lval) => evalLValue(lval, env)));

for (let i = 0; i < lvalues.length; i++) {
lvalues[i].env.set(lvalues[i].key, values[i]);
luaSet(lvalues[i].env, lvalues[i].key, values[i], s.ctx);
}

break;
Expand Down Expand Up @@ -691,24 +661,12 @@ function evalLValue(
);
if (objValue instanceof Promise) {
return objValue.then((objValue) => {
if (!objValue.set) {
throw new LuaRuntimeError(
`Not a settable object: ${objValue}`,
lval.object.ctx,
);
}
return {
env: objValue,
key: lval.property,
};
});
} else {
if (!objValue.set) {
throw new LuaRuntimeError(
`Not a settable object: ${objValue}`,
lval.object.ctx,
);
}
return {
env: objValue,
key: lval.property,
Expand Down
2 changes: 1 addition & 1 deletion common/space_lua/language.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Deno.test("Lua language tests", async () => {
});

function toPrettyString(err: LuaRuntimeError, code: string): string {
if (!err.context.from || !err.context.to) {
if (!err.context || !err.context.from || !err.context.to) {
return err.toString();
}
const from = err.context.from;
Expand Down
8 changes: 7 additions & 1 deletion common/space_lua/language_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ local a = 1
local b = 2
assert(a + b == 3)

-- Basic string concatenation
-- Basic string stuff
assert("Hello " .. "world" == "Hello world")
assert_equal([[Hello world]], "Hello world")
assert_equal([==[Hello [[world]]!]==], "Hello [[world]]!")

-- Various forms of function definitions
function f1()
Expand Down Expand Up @@ -303,3 +305,7 @@ table.sort(data, function(a, b)
end)
assert_equal(data[1].name, "Jane")
assert_equal(data[2].name, "John")

-- os functions
assert(os.time() > 0)
assert(os.date("%Y-%m-%d", os.time({ year = 2020, month = 1, day = 1 })) == "2020-01-01")
7 changes: 6 additions & 1 deletion common/space_lua/lua.grammar
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,16 @@ TableConstructor { "{" (field (fieldsep field)* fieldsep?)? "}" }

// Any sequence of characters except two consecutive ]]
longStringContent { (![\]] | $[\]] ![\]])* }

longDelimStringContent {
(![\]] | $[\]] ![=]+ ![\]])*
}

simpleString {
"'" (stringEscape | ![\r\n\\'])* "'" |
'"' (stringEscape | ![\r\n\\"])* '"' |
'[[' longStringContent ']]'
'[[' longStringContent ']]' |
'[' '='+ '[' longDelimStringContent ']' '='+ ']'
}

hex { $[0-9a-fA-F] }
Expand Down
2 changes: 1 addition & 1 deletion common/space_lua/parse-lua.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions common/space_lua/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Deno.test("Test Lua parser", () => {
`e(1, 1.2, -3.8, +4, #lst, true, false, nil, "string", "", "Hello there \x00", ...)`,
);
parse(`e([[hel]lo]], "Grinny face\\u{1F600}")`);
parse(`e([=[Hello page [[index]] end scene]=], [[yo]])`);

parse(`e(10 << 10, 10 >> 10, 10 & 10, 10 | 10, 10 ~ 10)`);

Expand Down
8 changes: 6 additions & 2 deletions common/space_lua/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,11 +331,15 @@ function parseExpList(t: ParseTree, ctx: ASTCtx): LuaExpression[] {
);
}

const delimiterRegex = /^(\[=*\[)([\s\S]*)(\]=*\])$/;

// In case of quoted strings, remove the quotes and unescape the string
// In case of a [[ type ]] literal string, remove the brackets
function parseString(s: string): string {
if (s.startsWith("[[") && s.endsWith("]]")) {
return s.slice(2, -2);
// Handle long strings with delimiters
const delimiterMatch = s.match(delimiterRegex);
if (delimiterMatch) {
return delimiterMatch[2];
}
return s.slice(1, -1).replace(
/\\(x[0-9a-fA-F]{2}|u\{[0-9a-fA-F]+\}|[abfnrtv\\'"n])/g,
Expand Down
53 changes: 49 additions & 4 deletions common/space_lua/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
}
}

toJSObject(): Record<string, any> {
const result: Record<string, any> = {};
for (const key of this.keys()) {
result[key] = luaValueToJS(this.get(key));
}
return result;
}

toString(): string {
if (this.metatable?.has("__tostring")) {
const metaValue = this.metatable.get("__tostring");
Expand Down Expand Up @@ -330,17 +338,39 @@ export class LuaTable implements ILuaSettable, ILuaGettable {

export type LuaLValueContainer = { env: ILuaSettable; key: LuaValue };

export function luaSet(obj: any, key: any, value: any) {
if (obj instanceof LuaTable) {
export function luaSet(obj: any, key: any, value: any, ctx: ASTCtx) {
if (!obj) {
throw new LuaRuntimeError(
`Not a settable object: nil`,
ctx,
);
}

if (obj instanceof LuaTable || obj instanceof LuaEnv) {
obj.set(key, value);
} else {
obj[key] = value;
}
}

export function luaGet(obj: any, key: any): any {
if (obj instanceof LuaTable) {
export function luaGet(obj: any, key: any, ctx: ASTCtx): any {
if (!obj) {
throw new LuaRuntimeError(
`Attempting to index a nil value`,
ctx,
);
}
if (key === null || key === undefined) {
throw new LuaRuntimeError(
`Attempting to index with a nil key`,
ctx,
);
}

if (obj instanceof LuaTable || obj instanceof LuaEnv) {
return obj.get(key);
} else if (typeof key === "number") {
return obj[key - 1];
} else {
return obj[key];
}
Expand All @@ -356,6 +386,21 @@ export function luaLen(obj: any): number {
}
}

export function luaCall(fn: any, args: any[], ctx: ASTCtx): any {
if (!fn) {
throw new LuaRuntimeError(
`Attempting to call a nil value`,
ctx,
);
}
if (typeof fn === "function") {
const jsArgs = args.map(luaValueToJS);
// Native JS function
return fn(...jsArgs);
}
return fn.call(...args);
}

export function luaTypeOf(val: any): LuaType {
if (val === null || val === undefined) {
return "nil";
Expand Down
Loading

0 comments on commit 3319c7f

Please sign in to comment.