Lua: fix parenthes evaluation
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { parseBlock } from "./parse.ts";
|
||||
import { luaBuildStandardEnv } from "./stdlib.ts";
|
||||
import { LuaEnv, LuaRuntimeError, LuaStackFrame } from "./runtime.ts";
|
||||
import { LuaEnv, LuaRuntimeError, LuaStackFrame, LuaTable } from "./runtime.ts";
|
||||
import { evalStatement } from "./eval.ts";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname } from "node:path";
|
||||
import { readdirSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { validateObject } from "../plugos/syscalls/jsonschema.ts";
|
||||
import { extractSpaceLuaFromPageText } from "../boot_config.ts";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Core Lua tests (bare stdlib env)
|
||||
const testFiles = readdirSync(__dirname, { recursive: true })
|
||||
.filter((f): f is string => typeof f === "string" && f.endsWith("_test.lua"))
|
||||
.sort();
|
||||
@@ -20,7 +23,54 @@ for (const file of testFiles) {
|
||||
});
|
||||
}
|
||||
|
||||
async function runLuaTest(luaPath: string) {
|
||||
// Library API Lua tests (stdlib + jsonschema + library code pre-loaded)
|
||||
const libraryApisDir = resolve(
|
||||
__dirname,
|
||||
"../../libraries/Library/Std/APIs",
|
||||
);
|
||||
const libraryTestFiles = readdirSync(libraryApisDir, { recursive: true })
|
||||
.filter((f): f is string => typeof f === "string" && f.endsWith("_test.lua"))
|
||||
.sort();
|
||||
|
||||
for (const file of libraryTestFiles) {
|
||||
// A test file may declare its companion library .md explicitly via a header
|
||||
// comment on (any of) its first lines, e.g. "-- companion: Code Widget.md".
|
||||
// This is required for multi-word library pages that the derivation below
|
||||
// cannot reach.
|
||||
const testSource = readFileSync(resolve(libraryApisDir, file), "utf-8");
|
||||
const companionMatch = testSource.match(
|
||||
/^--\s*companion:\s*(.+?)\s*$/m,
|
||||
);
|
||||
let libraryMdPath: string;
|
||||
if (companionMatch) {
|
||||
libraryMdPath = resolve(libraryApisDir, companionMatch[1]);
|
||||
} else {
|
||||
// Derive the companion library .md name from the test file name, e.g.
|
||||
// "widget_sandbox_test.lua" → "Widget.md".
|
||||
// NOTE: only the first snake_case segment is used, so this scheme cannot
|
||||
// reach multi-word library pages (e.g. "Task State.md", "DOM.md"). Such a
|
||||
// test should declare its companion path explicitly via the header comment
|
||||
// above rather than relying on this derivation.
|
||||
const libraryName = file
|
||||
.replace(/_test\.lua$/, "")
|
||||
// Convert snake_case segment to PascalCase for the .md filename, keeping
|
||||
// only the first segment so "widget_sandbox" → "Widget"
|
||||
.split("_")[0]
|
||||
.replace(/^./, (c) => c.toUpperCase());
|
||||
libraryMdPath = resolve(libraryApisDir, `${libraryName}.md`);
|
||||
}
|
||||
test(`[Lua] ${file}`, async () => {
|
||||
await runLuaTest(
|
||||
resolve(libraryApisDir, file),
|
||||
{ isAbsolutePath: true, preloadMdPath: libraryMdPath },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function runLuaTest(
|
||||
luaPath: string,
|
||||
opts: { isAbsolutePath?: boolean; preloadMdPath?: string } = {},
|
||||
) {
|
||||
if (
|
||||
typeof globalThis.client !== "undefined" &&
|
||||
globalThis.client &&
|
||||
@@ -54,12 +104,75 @@ async function runLuaTest(luaPath: string) {
|
||||
};
|
||||
}
|
||||
|
||||
const luaFile = await readFile(
|
||||
fileURLToPath(new URL(luaPath, import.meta.url)),
|
||||
"utf-8",
|
||||
);
|
||||
const resolvedPath = opts.isAbsolutePath
|
||||
? luaPath
|
||||
: fileURLToPath(new URL(luaPath, import.meta.url));
|
||||
const luaFile = await readFile(resolvedPath, "utf-8");
|
||||
const chunk = parseBlock(luaFile, {});
|
||||
const env = new LuaEnv(luaBuildStandardEnv());
|
||||
|
||||
// If a companion .md library file is provided, extract its space-lua block(s)
|
||||
// and run them in the env first, after injecting any required syscall stubs.
|
||||
if (opts.preloadMdPath) {
|
||||
// Provide a minimal jsonschema table so widget.new validation works
|
||||
const jsonschemaTbl = new LuaTable();
|
||||
await jsonschemaTbl.set(
|
||||
"validateObject",
|
||||
(schema: any, obj: any) => validateObject(schema, obj),
|
||||
);
|
||||
env.set("jsonschema", jsonschemaTbl);
|
||||
|
||||
// Provide a minimal in-memory `config` table (normally syscall-backed) so
|
||||
// library APIs that wrap config.get/config.set (e.g. command.define,
|
||||
// codeWidget.define) work in the bare test env. Values are stored as their
|
||||
// raw Lua values (no JS conversion) so e.g. function fields survive a
|
||||
// set/get round trip.
|
||||
const configStore = new Map<string, unknown>();
|
||||
const pathToKey = (path: unknown): string => {
|
||||
if (path instanceof LuaTable) {
|
||||
const parts: string[] = [];
|
||||
for (let i = 1; i <= path.length; i++) {
|
||||
parts.push(String(path.get(i)));
|
||||
}
|
||||
return parts.join("\0");
|
||||
}
|
||||
// string path: support dot notation like the real config API
|
||||
return String(path).split(".").join("\0");
|
||||
};
|
||||
const configTbl = new LuaTable();
|
||||
await configTbl.set(
|
||||
"set",
|
||||
(path: unknown, value: unknown) => {
|
||||
configStore.set(pathToKey(path), value);
|
||||
},
|
||||
);
|
||||
await configTbl.set(
|
||||
"get",
|
||||
(path: unknown, defaultValue: unknown) => {
|
||||
const key = pathToKey(path);
|
||||
return configStore.has(key)
|
||||
? configStore.get(key)
|
||||
: (defaultValue ?? null);
|
||||
},
|
||||
);
|
||||
env.set("config", configTbl);
|
||||
|
||||
let mdText: string;
|
||||
try {
|
||||
mdText = await readFile(opts.preloadMdPath, "utf-8");
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Library preload file not found: ${opts.preloadMdPath}`,
|
||||
);
|
||||
}
|
||||
const libLua = extractSpaceLuaFromPageText(mdText);
|
||||
if (libLua) {
|
||||
const libChunk = parseBlock(libLua, {});
|
||||
const libSf = LuaStackFrame.createWithGlobalEnv(env, libChunk.ctx);
|
||||
await evalStatement(libChunk, env, libSf);
|
||||
}
|
||||
}
|
||||
|
||||
const sf = LuaStackFrame.createWithGlobalEnv(env, chunk.ctx);
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user