Fix serious truthiness evaluation flaws. (#1644)
In Lua, only the last expression in an argument list preserves multiple results and all earlier expressions yield exactly one value (their first). For example: ``` f(a(), b(), c()) ``` where ``` a -> (1,2) b -> (3,4) c -> (5,6) ``` becomes ``` f(1, 3, 5, 6) ``` which matches Lua behaviour (tested in version 5.4). Changes: * Function argument lists keep multiple results only from the last argument. * Fixes failing truthiness tests (`true and ret_one_two()`). Sidenote: the `truthiness_test.lua` was not called at all because of my obvious copy/paste typo (the `arithmetical_test.lua` was called twice instead; now fixed too and both test suites pass ok).
This commit is contained in:
+33
-11
@@ -268,7 +268,7 @@ export function evalExpression(
|
||||
return +singleResult(value);
|
||||
}
|
||||
case "not": {
|
||||
return !singleResult(value);
|
||||
return !luaTruthy(singleResult(value));
|
||||
}
|
||||
case "~": {
|
||||
const arg = singleResult(value);
|
||||
@@ -307,7 +307,7 @@ export function evalExpression(
|
||||
return +singleResult(value);
|
||||
}
|
||||
case "not": {
|
||||
return !singleResult(value);
|
||||
return !luaTruthy(singleResult(value));
|
||||
}
|
||||
case "~": {
|
||||
const arg = singleResult(value);
|
||||
@@ -493,7 +493,7 @@ function evalPrefixExpression(
|
||||
}
|
||||
case "FunctionCall": {
|
||||
const prefixValue = evalPrefixExpression(e.prefix, env, sf);
|
||||
if (!prefixValue) {
|
||||
if (prefixValue === null || prefixValue === undefined) {
|
||||
throw new LuaRuntimeError(
|
||||
`Attempting to call nil as a function`,
|
||||
sf.withCtx(e.prefix.ctx),
|
||||
@@ -604,14 +604,16 @@ function evalLogical(
|
||||
const decide = (lv: any) => {
|
||||
if (op === "or") {
|
||||
if (luaTruthy(lv)) {
|
||||
return lv;
|
||||
return singleResult(lv);
|
||||
}
|
||||
return evalExpression(rightExpr, env, sf);
|
||||
const rv = evalExpression(rightExpr, env, sf);
|
||||
return rv instanceof Promise ? rv.then(singleResult) : singleResult(rv);
|
||||
} else {
|
||||
if (!luaTruthy(lv)) {
|
||||
return lv;
|
||||
return singleResult(lv);
|
||||
}
|
||||
return evalExpression(rightExpr, env, sf);
|
||||
const rv = evalExpression(rightExpr, env, sf);
|
||||
return rv instanceof Promise ? rv.then(singleResult) : singleResult(rv);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -901,12 +903,32 @@ function evalExpressions(
|
||||
const argsVal = evalPromiseValues(
|
||||
es.map((arg) => evalExpression(arg, env, sf)),
|
||||
);
|
||||
|
||||
// In Lua multi-returns propagate only in tail position of an expression
|
||||
// list.
|
||||
const finalize = (argsResolved: any[]) => {
|
||||
if (argsResolved.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const out: LuaValue[] = [];
|
||||
// All but last expression produce a single value
|
||||
for (let i = 0; i < argsResolved.length - 1; i++) {
|
||||
out.push(singleResult(argsResolved[i]));
|
||||
}
|
||||
// Last expression preserves multiple results
|
||||
const last = argsResolved[argsResolved.length - 1];
|
||||
if (last instanceof LuaMultiRes) {
|
||||
out.push(...last.flatten().values);
|
||||
} else {
|
||||
out.push(singleResult(last));
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
if (argsVal instanceof Promise) {
|
||||
return argsVal.then((argsResolved) =>
|
||||
new LuaMultiRes(argsResolved).flatten().values
|
||||
);
|
||||
return argsVal.then(finalize);
|
||||
} else {
|
||||
return new LuaMultiRes(argsVal).flatten().values;
|
||||
return finalize(argsVal as any[]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Deno.test("[Lua] Core language", async () => {
|
||||
});
|
||||
|
||||
Deno.test("[Lua] Core language (truthiness)", async () => {
|
||||
await runLuaTest("./arithmetic_test.lua");
|
||||
await runLuaTest("./truthiness_test.lua");
|
||||
});
|
||||
|
||||
Deno.test("[Lua] Core language (arithmetic)", async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
LuaBuiltinFunction,
|
||||
LuaEnv,
|
||||
type LuaEnv,
|
||||
LuaMultiRes,
|
||||
type LuaStackFrame,
|
||||
type LuaValue,
|
||||
|
||||
@@ -226,3 +226,29 @@ assert_eq(s1, 5, "or should see falsy first result and evaluate RHS")
|
||||
local s2 = (truthy_pair() or 5)
|
||||
|
||||
assert_eq(s2, 1, "or should return first result of LHS when truthy")
|
||||
|
||||
-- 12. Only the last expression expands
|
||||
|
||||
local function ret() return "A", "B" end
|
||||
local function a() return "A1", "A2" end
|
||||
local function b() return "B1", "B2" end
|
||||
local function c() return "C1", "C2" end
|
||||
|
||||
assert_eq(string.format("%s-%s", ret()), "A-B",
|
||||
"last-only: single arg expands")
|
||||
|
||||
assert_eq(string.format("%s-%s", ret(), "Z"), "A-Z",
|
||||
"last-only: earlier arg is single")
|
||||
|
||||
assert_eq(string.format("%s-%s-%s-%s", a(), b(), c()), "A1-B1-C1-C2",
|
||||
"last-only: only last expands, earlier args single")
|
||||
|
||||
-- 13. Only false and `nil` are falsey
|
||||
|
||||
assert_false(not 0, "not 0 must be false")
|
||||
assert_false(not -0.0, "not -0.0 must be false")
|
||||
assert_false(not "", "not '' must be false")
|
||||
assert_false(not {}, "not {} must be false")
|
||||
|
||||
assert_true(not nil, "not nil must be true")
|
||||
assert_true(not false, "not false must be true")
|
||||
|
||||
Reference in New Issue
Block a user