Skip to content

Commit 19618b9

Browse files
committed
Fixes #1420
Cleans up vararg handling, previously done really badly by AI...
1 parent a0b1f03 commit 19618b9

File tree

5 files changed

+74
-145
lines changed

5 files changed

+74
-145
lines changed

web/space_lua/eval.ts

Lines changed: 6 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -38,39 +38,6 @@ import {
3838
import { luaValueToJS } from "../space_lua/runtime.ts";
3939
import { jsToLuaValue } from "../space_lua/runtime.ts";
4040

41-
function handleVarargSync(env: LuaEnv): LuaValue[] | Promise<LuaValue[]> {
42-
const varargs = env.get("...");
43-
if (varargs instanceof Promise) {
44-
return handleVarargAsync(varargs);
45-
}
46-
if (varargs instanceof LuaTable) {
47-
const args = [];
48-
for (let i = 1; i <= varargs.length; i++) {
49-
const val = varargs.get(i);
50-
if (val instanceof Promise) {
51-
return handleVarargAsync(varargs);
52-
}
53-
args.push(val);
54-
}
55-
return args;
56-
}
57-
return [];
58-
}
59-
60-
async function handleVarargAsync(
61-
varargs: Promise<LuaValue> | LuaTable,
62-
): Promise<LuaValue[]> {
63-
const resolvedVarargs = await varargs;
64-
if (resolvedVarargs instanceof LuaTable) {
65-
const args = [];
66-
for (let i = 1; i <= resolvedVarargs.length; i++) {
67-
args.push(await resolvedVarargs.get(i));
68-
}
69-
return args;
70-
}
71-
return [];
72-
}
73-
7441
async function handleTableFieldSync(
7542
table: LuaTable,
7643
field: any,
@@ -91,18 +58,13 @@ async function handleTableFieldSync(
9158
break;
9259
}
9360
case "ExpressionField": {
94-
if (field.value.type === "Variable" && field.value.name === "...") {
95-
const varargs = await handleVarargSync(env);
96-
varargs.forEach((val, i) => table.set(i + 1, val, sf));
97-
} else {
98-
const value = await evalExpression(field.value, env, sf);
99-
if (value instanceof LuaMultiRes) {
100-
for (const val of value.values) {
101-
table.set(table.length + 1, val, sf);
102-
}
103-
} else {
104-
table.set(table.length + 1, singleResult(value), sf);
61+
const value = await evalExpression(field.value, env, sf);
62+
if (value instanceof LuaMultiRes) {
63+
for (const val of value.values) {
64+
table.set(table.length + 1, val, sf);
10565
}
66+
} else {
67+
table.set(table.length + 1, singleResult(value), sf);
10668
}
10769
break;
10870
}
@@ -229,16 +191,6 @@ export function evalExpression(
229191
case "TableConstructor": {
230192
return Promise.resolve().then(async () => {
231193
const table = new LuaTable();
232-
if (
233-
e.fields.length === 1 &&
234-
e.fields[0].type === "ExpressionField" &&
235-
e.fields[0].value.type === "Variable" &&
236-
e.fields[0].value.name === "..."
237-
) {
238-
const varargs = await handleVarargSync(env);
239-
varargs.forEach((val, i) => table.set(i + 1, val, sf));
240-
return table;
241-
}
242194

243195
for (const field of e.fields) {
244196
await handleTableFieldSync(table, field, env, sf);
@@ -396,38 +348,6 @@ function evalPrefixExpression(
396348
const handleFunctionCall = (
397349
prefixValue: LuaValue,
398350
): LuaValue | Promise<LuaValue> => {
399-
// Special handling for f(...) - propagate varargs
400-
if (
401-
e.args.length === 1 && e.args[0].type === "Variable" &&
402-
e.args[0].name === "..."
403-
) {
404-
// TODO: Clean this up
405-
const varargs = env.get("...");
406-
const resolveVarargs = async () => {
407-
const resolvedVarargs = await Promise.resolve(varargs);
408-
if (resolvedVarargs instanceof LuaTable) {
409-
const args = [];
410-
for (let i = 1; i <= resolvedVarargs.length; i++) {
411-
const val = await Promise.resolve(resolvedVarargs.get(i));
412-
args.push(val);
413-
}
414-
return args;
415-
}
416-
return [];
417-
};
418-
419-
if (prefixValue instanceof Promise) {
420-
return prefixValue.then(async (resolvedPrefix) => {
421-
const args = await resolveVarargs();
422-
return luaCall(resolvedPrefix, args, e.ctx, sf.withCtx(e.ctx));
423-
});
424-
} else {
425-
return resolveVarargs().then((args) =>
426-
luaCall(prefixValue, args, e.ctx, sf.withCtx(e.ctx))
427-
);
428-
}
429-
}
430-
431351
// Normal argument handling for hello:there(a, b, c) type calls
432352
if (e.name) {
433353
selfArgs = [prefixValue];

web/space_lua/language_core_test.lua

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,4 +559,16 @@ local b, c = select(-2, 1, 2, 3)
559559
assertEqual(b, 2)
560560
assertEqual(c, 3)
561561
-- Special "#" case
562-
assertEqual(select("#", 1, 2, 3), 3)
562+
assertEqual(select("#", 1, 2, 3), 3)
563+
564+
565+
-- Some more vararg verification
566+
function varArgTest(a0, ...)
567+
-- ... gets the multi arg treatment
568+
local a1, a2 = ...
569+
assertEqual(a0, 1)
570+
assertEqual(a1, 2)
571+
assertEqual(a2, 3)
572+
end
573+
574+
varArgTest(1, 2, 3, 4)

web/space_lua/lua.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ Deno.test("[Lua] Global functions tests", async () => {
4242
await runLuaTest("./stdlib/global_test.lua");
4343
});
4444

45+
Deno.test("[Lua] Lua functions tests", async () => {
46+
await runLuaTest("./lume_test.lua");
47+
});
48+
4549
async function runLuaTest(luaPath: string) {
4650
const luaFile = await Deno.readTextFile(
4751
fileURLToPath(new URL(luaPath, import.meta.url)),

web/space_lua/lume_test.lua

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
local function assertEqual(a, b, message)
2+
if a ~= b then
3+
error("Assertion failed: " .. a .. " is not equal to " .. b .. " " .. message)
4+
end
5+
end
6+
7+
lume = lume or {}
8+
9+
local getiter = function(x)
10+
if lume.isarray(x) then
11+
return ipairs
12+
elseif type(x) == "table" then
13+
return pairs
14+
end
15+
error("expected table", 3)
16+
end
17+
18+
function lume.isarray(x)
19+
return type(x) == "table" and x[1] ~= nil
20+
end
21+
22+
function lume.concat(...)
23+
local rtn = {}
24+
for i = 1, select("#", ...) do
25+
local t = select(i, ...)
26+
if t ~= nil then
27+
local iter = getiter(t)
28+
for _, v in iter(t) do
29+
rtn[#rtn + 1] = v
30+
end
31+
end
32+
end
33+
return rtn
34+
end
35+
36+
local r = lume.concat({1, 2}, {3, 4})
37+
assertEqual(#r, 4)
38+
assertEqual(r[1], 1)
39+
assertEqual(r[4], 4)

web/space_lua/runtime.ts

Lines changed: 12 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export class LuaFunction implements ILuaFunction {
164164
this.capturedEnv = closure;
165165
}
166166

167-
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> {
167+
async call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> {
168168
// Create a new environment that chains to the captured environment
169169
const env = new LuaEnv(this.capturedEnv);
170170
if (!sf) {
@@ -173,36 +173,25 @@ export class LuaFunction implements ILuaFunction {
173173
// Set _CTX to the thread local environment from the stack frame
174174
env.setLocal("_CTX", sf.threadLocal);
175175

176-
// Assign the passed arguments to the parameters
176+
// Evaluate all arguments
177+
const resolvedArgs = await evalPromiseValues(args);
178+
179+
// Assign parameter values to variable names in env
180+
let varargs: LuaValue[] = [];
177181
for (let i = 0; i < this.body.parameters.length; i++) {
178182
const paramName = this.body.parameters[i];
179183
if (paramName === "...") {
180-
// Handle varargs by creating a table with all remaining arguments
181-
const varargs = new LuaTable();
182-
// Include all remaining arguments (might be none)
183-
for (let j = i; j < args.length; j++) {
184-
varargs.set(j - i + 1, args[j], sf);
185-
}
186-
env.setLocal("...", varargs);
184+
// Vararg parameter, let's collect the remainder of the resolved args into the varargs array
185+
varargs = resolvedArgs.slice(i);
186+
// Done, break out of this loop
187187
break;
188188
}
189-
let arg = args[i];
190-
if (arg === undefined) {
191-
arg = null;
192-
}
193-
env.setLocal(this.body.parameters[i], arg);
189+
env.setLocal(paramName, resolvedArgs[i] ?? null);
194190
}
195191

196-
// If the function has varargs parameter but it wasn't set above, set an empty varargs table
197-
if (this.body.parameters.includes("...") && !env.has("...")) {
198-
env.setLocal("...", new LuaTable());
199-
}
192+
env.setLocal("...", new LuaMultiRes(varargs));
200193

201-
const resolvedArgs = evalPromiseValues(args);
202-
if (resolvedArgs instanceof Promise) {
203-
return resolvedArgs.then((args) => this.callWithArgs(args, env, sf));
204-
}
205-
return this.callWithArgs(resolvedArgs, env, sf);
194+
return this.evalBody(env, sf);
206195
}
207196

208197
asString(): string {
@@ -213,41 +202,6 @@ export class LuaFunction implements ILuaFunction {
213202
return this.asString();
214203
}
215204

216-
private callWithArgs(
217-
args: LuaValue[],
218-
env: LuaEnv,
219-
sf: LuaStackFrame,
220-
): Promise<LuaValue> {
221-
// Set up parameters and varargs
222-
for (let i = 0; i < this.body.parameters.length; i++) {
223-
const paramName = this.body.parameters[i];
224-
if (paramName === "...") {
225-
const varargs = new LuaTable();
226-
for (let j = i; j < args.length; j++) {
227-
if (args[j] instanceof Promise) {
228-
return Promise.all(args.slice(i)).then((resolvedArgs) => {
229-
const varargs = new LuaTable();
230-
resolvedArgs.forEach((val, idx) => varargs.set(idx + 1, val, sf));
231-
env.setLocal("...", varargs);
232-
return this.evalBody(env, sf);
233-
});
234-
}
235-
varargs.set(j - i + 1, args[j], sf);
236-
}
237-
env.setLocal("...", varargs);
238-
break;
239-
}
240-
env.setLocal(paramName, args[i] ?? null);
241-
}
242-
243-
// Ensure empty varargs table exists if needed
244-
if (this.body.parameters.includes("...") && !env.has("...")) {
245-
env.setLocal("...", new LuaTable());
246-
}
247-
248-
return this.evalBody(env, sf);
249-
}
250-
251205
private async evalBody(
252206
env: LuaEnv,
253207
sf: LuaStackFrame,

0 commit comments

Comments
 (0)