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
@@ -120,14 +120,11 @@ def expr(node: ast.AST) -> Expr:
|
||||
elif isinstance(v, ast.FormattedValue):
|
||||
parts.append(expr(v.value))
|
||||
return {"k": "fmt", "v": parts}
|
||||
case (ast.ListComp(elt=elt, generators=[ast.comprehension(target=t, iter=it, ifs=ifs,
|
||||
is_async=0)])
|
||||
| ast.GeneratorExp(elt=elt, generators=[ast.comprehension(target=t, iter=it,
|
||||
ifs=ifs, is_async=0)])
|
||||
| ast.SetComp(elt=elt, generators=[ast.comprehension(target=t, iter=it, ifs=ifs,
|
||||
is_async=0)])):
|
||||
return {"k": "comp", "e": expr(elt), "t": target(t), "it": expr(it),
|
||||
"if": [expr(i) for i in ifs]}
|
||||
case ast.ListComp(elt=elt, generators=gens) | ast.GeneratorExp(elt=elt, generators=gens) \
|
||||
| ast.SetComp(elt=elt, generators=gens):
|
||||
return {"k": "comp", "e": expr(elt), "gens": generators(gens)}
|
||||
case ast.DictComp(key=key, value=value, generators=gens):
|
||||
return {"k": "comp", "e": expr(key), "v": expr(value), "gens": generators(gens)}
|
||||
case ast.Lambda(args=args, body=body):
|
||||
return {"k": "lambda", "params": params(args), "body": expr(body)}
|
||||
raise Unsupported(f"выражение {type(node).__name__}")
|
||||
@@ -170,10 +167,18 @@ def stmt(node: ast.stmt) -> list[Stmt]:
|
||||
return [{"op": "eval", "e": expr(value)}]
|
||||
case ast.If(test=test, body=body, orelse=orelse):
|
||||
return [{"op": "if", "c": expr(test), "then": stmts(body), "else": stmts(orelse)}]
|
||||
case ast.While(test=test, body=body, orelse=[]):
|
||||
return [{"op": "while", "c": expr(test), "body": stmts(body)}]
|
||||
case ast.For(target=t, iter=it, body=body, orelse=[]):
|
||||
return [{"op": "for", "t": target(t), "it": expr(it), "body": stmts(body)}]
|
||||
case ast.While(test=test, body=body, orelse=orelse):
|
||||
return [{"op": "while", "c": expr(test), "body": stmts(body), "else": stmts(orelse)}]
|
||||
case ast.For(target=t, iter=it, body=body, orelse=orelse):
|
||||
return [{"op": "for", "t": target(t), "it": expr(it), "body": stmts(body),
|
||||
"else": stmts(orelse)}]
|
||||
case ast.Try(body=body, handlers=handlers, orelse=orelse, finalbody=final):
|
||||
return [{"op": "try", "body": stmts(body), "handlers": [handler(h) for h in handlers],
|
||||
"else": stmts(orelse), "final": stmts(final)}]
|
||||
case ast.Raise(exc=exc):
|
||||
return [{"op": "raise", "v": None if exc is None else expr(exc)}] # `from cause` is dropped
|
||||
case ast.Assert(test=test, msg=msg):
|
||||
return [{"op": "assert", "c": expr(test), "m": None if msg is None else expr(msg)}]
|
||||
case ast.Delete(targets=targets):
|
||||
return [{"op": "del", "t": target(t)} for t in targets]
|
||||
case ast.Pass():
|
||||
@@ -248,3 +253,15 @@ def class_body(name: str, body: list[ast.stmt]) -> list[Stmt]:
|
||||
and n.name not in SUPPORTED_DUNDERS):
|
||||
raise Unsupported(f"специальный метод {name}.{n.name}()")
|
||||
return stmts(body)
|
||||
|
||||
|
||||
def generators(gens: list[ast.comprehension]) -> list[dict[str, Any]]:
|
||||
if any(g.is_async for g in gens):
|
||||
raise Unsupported("async comprehension")
|
||||
return [{"t": target(g.target), "it": expr(g.iter), "if": [expr(i) for i in g.ifs]} for g in gens]
|
||||
|
||||
|
||||
def handler(node: ast.ExceptHandler) -> dict[str, Any]:
|
||||
"""``types`` is None for a bare ``except:``; ``name`` binds the exception."""
|
||||
return {"types": None if node.type is None else expr(node.type), "name": node.name,
|
||||
"body": stmts(node.body)}
|
||||
Reference in new issue
Block a user