feat: eval/exec of strings, gallery Replay, callable actions; iNSight and SomethingBetter now pass
- pyparse.ts: parser for expressions and simple statements built at run time, producing
the sandbox's own IR; backs eval() and exec() (e.g. exec("persistent." + var + " = 0"))
- Replay/EndReplay and renpy.end_replay: the engine stashes the full game state, runs the
label with the given scope and restores everything when it ends; locked replays are
insensitive. Save loading shared as applySave()
- Screen actions are callable (MainMenu(confirm=False)())
- jump main_menu_screen is Ren'Py's own label: it opens the player's main menu
- Screens: a closing quote ends the word before a letter ("text"size 15) but not before
an operator ("a"+b); Text(...) pictures in image blocks
- README: current capabilities and limits of the Ren'Py support
Strict compatibility check on the local collection: Tomie, Twisted-Thoughts, Hooked,
iNSight-of-you and SomethingBetter pass; TheFixer (custom AlphaMask wardrobe, missing
labels) and That_New_Teacher (custom statements, Render API) do not.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3923ee72fb
commit
3ac4f0371f
11 files changed
+448
-36
No files matched your search
@@ -9,12 +9,12 @@ Object.assign(globalThis, { window, document });
|
||||
globalThis.requestAnimationFrame = (cb) => setTimeout(() => cb(performance.now()), 4);
|
||||
|
||||
const compiled = await build({
|
||||
stdin: { contents: `export * from "./python"; export * from "./atl"; export * from "./colormatrix"; export * from "./pymodules";`,
|
||||
stdin: { contents: `export * from "./python"; export * from "./atl"; export * from "./colormatrix"; export * from "./pymodules"; export * from "./pyparse";`,
|
||||
resolveDir: fileURLToPath(new URL("../src/player/", import.meta.url)), loader: "ts" },
|
||||
bundle: true, write: false, format: "esm", platform: "node", logLevel: "silent",
|
||||
});
|
||||
const m = await import(`data:text/javascript;base64,${Buffer.from(compiled.outputFiles[0].text).toString("base64")}`);
|
||||
const { Python, AtlRunner, ColorMatrix, colorMatrices, legacyMatrices, matrixFilter, pythonModules, PyDict } = m;
|
||||
const { parseExpression, parseStatements, Python, AtlRunner, ColorMatrix, colorMatrices, legacyMatrices, matrixFilter, pythonModules, PyDict } = m;
|
||||
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const name = (id) => ({ k: "name", id });
|
||||
const attr = (o, a) => ({ k: "attr", o, a });
|
||||
@@ -130,3 +130,23 @@ test("on hover handlers run when the button reports the event", async () => {
|
||||
await wait(20);
|
||||
assert.equal(props.alpha, 1);
|
||||
});
|
||||
|
||||
test("eval/exec of source built at run time use the sandbox's own rules", () => {
|
||||
const { py, store } = python();
|
||||
store.score = 7;
|
||||
store.persistent = new PyDict();
|
||||
const evaluate = (source) => py.eval(parseExpression(source));
|
||||
assert.equal(evaluate("score * 2 + 1"), 15);
|
||||
assert.equal(evaluate("'a' + 'b' if score > 5 else 'c'"), "ab");
|
||||
assert.equal(evaluate("not (score < 3) and score in [7, 8]"), true);
|
||||
assert.deepEqual(evaluate("[1, 2, (3, 4)][2]"), [3, 4]);
|
||||
assert.equal(evaluate("score < 10 < 20"), true);
|
||||
py.exec(parseStatements("score += 3; other = score ** 2\npersistent.seen = 1"));
|
||||
assert.equal(store.score, 10);
|
||||
assert.equal(store.other, 100);
|
||||
assert.equal(store.persistent.get("seen"), 1);
|
||||
assert.throws(() => parseExpression("score +"), /Синтаксическая ошибка/);
|
||||
assert.throws(() => parseExpression("lambda: 1"), /не поддерживается/);
|
||||
assert.throws(() => parseStatements("import os"), /не поддерживается/);
|
||||
assert.equal(py.eval(parseExpression("__import__")), null, "dunder names stay unreachable");
|
||||
});
|
||||
Reference in new issue
Block a user