feat: add ATL animation timelines and try/raise to the Ren'Py player
Python sandbox - try/except/else/finally, raise (incl. re-raise), assert, for/while ... else - Multi-generator and dict comprehensions - Builtin exception hierarchy; user exceptions subclass it - Inside try, missing keys/indexes, division by zero, attribute access on None and unknown names raise Python exceptions; lookups stay lenient elsewhere - int()/float() raise ValueError on non-numeric strings ATL - converter/atl.py: set/move (linear, ease, easein, easeout), pause, repeat, parallel, choice, block, on show/hide and named transforms become a timeline shipped as game.json "atl"; Move() becomes a linear slide - player/atl.ts: timeline runner wired into the renderer; `on hide` plays before the picture is removed - function, time, contains, matrixcolor and expressions still block the import Tests: exceptions/ATL fixtures and runtime tests, converter ATL tests; README and ARCHITECTURE updated. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
962595c925
commit
ff82d882c0
18 files changed
+1050
-53
No files matched your search
+38
-5
@@ -85,10 +85,24 @@ def expression(value, depth=0):
|
||||
for item in value["kw"].values():
|
||||
expression(item, depth + 1)
|
||||
elif kind == "comp":
|
||||
for key in ("e", "t", "it"):
|
||||
ex(key)
|
||||
for item in seq("if"):
|
||||
expression(item, depth + 1)
|
||||
ex("e")
|
||||
ex("v", True)
|
||||
if "gens" in value:
|
||||
gens = seq("gens")
|
||||
if not gens or len(gens) > 8:
|
||||
raise ValueError("Некорректный генератор")
|
||||
for gen in gens:
|
||||
if not isinstance(gen, dict) or not isinstance(gen.get("if"), list):
|
||||
raise ValueError("Некорректный генератор")
|
||||
expression(gen.get("t"), depth + 1)
|
||||
expression(gen.get("it"), depth + 1)
|
||||
for item in gen["if"]:
|
||||
expression(item, depth + 1)
|
||||
else: # saved by an older converter
|
||||
for key in ("t", "it"):
|
||||
ex(key)
|
||||
for item in seq("if"):
|
||||
expression(item, depth + 1)
|
||||
else:
|
||||
raise ValueError(f"Неизвестное выражение: {kind}")
|
||||
|
||||
@@ -113,7 +127,8 @@ def statements(code, depth=0):
|
||||
keys = {"set": ("t", "v"), "aug": ("t", "v"), "eval": ("e",),
|
||||
"if": ("c",), "while": ("c",), "for": ("t", "it"),
|
||||
"del": ("t",), "break": (), "continue": (), "def": (), "class": (),
|
||||
"return": (), "global": (), "nonlocal": ()}
|
||||
"return": (), "global": (), "nonlocal": (), "try": (), "raise": (),
|
||||
"assert": ("c",)}
|
||||
if op not in keys:
|
||||
raise ValueError(f"Неизвестный оператор AST: {op}")
|
||||
for key in keys[op]:
|
||||
@@ -123,6 +138,24 @@ def statements(code, depth=0):
|
||||
statements(s.get("else"), depth + 1)
|
||||
if op in ("while", "for"):
|
||||
statements(s.get("body"), depth + 1)
|
||||
statements(s.get("else", []), depth + 1)
|
||||
if op == "try":
|
||||
statements(s.get("body"), depth + 1)
|
||||
statements(s.get("else", []), depth + 1)
|
||||
statements(s.get("final", []), depth + 1)
|
||||
handlers = s.get("handlers")
|
||||
if not isinstance(handlers, list) or len(handlers) > 32:
|
||||
raise ValueError("Некорректные обработчики исключений")
|
||||
for h in handlers:
|
||||
if not isinstance(h, dict) or not (h.get("name") is None or isinstance(h.get("name"), str)):
|
||||
raise ValueError("Некорректный обработчик исключения")
|
||||
if h.get("types") is not None:
|
||||
expression(h["types"], depth + 1)
|
||||
statements(h.get("body"), depth + 1)
|
||||
if op == "raise" and s.get("v") is not None:
|
||||
expression(s["v"], depth + 1)
|
||||
if op == "assert" and s.get("m") is not None:
|
||||
expression(s["m"], depth + 1)
|
||||
if op == "def":
|
||||
if not isinstance(s.get("name"), str):
|
||||
raise ValueError("У функции нет имени")
|
||||
|
||||
Reference in new issue
Block a user