feat: ATL pictures/contains/function/time, colour matrices and more Python for Ren'Py
ATL
- Image blocks become timelines: picture steps with dissolve, parallel, contains,
finite repeat, time, function f(trans, st, at), calls of other transforms with
arguments, on hover/idle events for buttons. Animations stop when their element
leaves the page
- Pictures defined by an expression (get_face_path("base")) are drawn at show time
- Purely graphical hints (shader, mesh, gl_*, u_*) are accepted and dropped
Colour
- matrixcolor and im.matrix.* as 5x5 matrices combined with `*`, applied through SVG
filters; im.MatrixColor/Scale/Crop/Blur/Composite and Text/Image as values
Python
- match statement, re/copy/time/datetime/defaultdict/functools.partial, more
str/dict methods, Character(...) created by code
- narrator("..."), Character calls, renpy.say and renpy.show/hide with expressions in
Python blocks become script nodes
- Audit follows reachability: functions and classes nothing refers to (custom
statement lexers, unused tools) no longer block an import
Docs: README and ARCHITECTURE now describe a multi-engine library (GameMaker and
QSP planned). Tests for the new runtime pieces.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
62ece25902
commit
ea03cebf6a
21 files changed
+1264
-93
No files matched your search
@@ -24,7 +24,8 @@ CMP_OPS = {
|
||||
UNARY_OPS = {ast.Not: "not", ast.USub: "-", ast.UAdd: "+", ast.Invert: "~"}
|
||||
|
||||
# Modules whose import is harmless: the player provides the functions it supports.
|
||||
KNOWN_MODULES = {"random", "math", "renpy", "store", "time", "datetime"}
|
||||
KNOWN_MODULES = {"random", "math", "renpy", "store", "time", "datetime", "re", "copy", "collections",
|
||||
"itertools", "string", "functools", "operator", "json"}
|
||||
|
||||
|
||||
class Unsupported(Exception):
|
||||
@@ -177,6 +178,8 @@ def stmt(node: ast.stmt) -> list[Stmt]:
|
||||
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.Match(subject=subject, cases=cases):
|
||||
return match_statement(subject, cases)
|
||||
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):
|
||||
@@ -273,3 +276,46 @@ 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)}
|
||||
|
||||
|
||||
_match_counter = 0
|
||||
|
||||
|
||||
def match_statement(subject: ast.expr, cases: list[ast.match_case]) -> list[Stmt]:
|
||||
"""`match` becomes an if/elif chain over a temporary holding the subject."""
|
||||
global _match_counter
|
||||
_match_counter += 1
|
||||
temp = f"_pgm_{_match_counter}"
|
||||
name = {"k": "name", "id": temp}
|
||||
|
||||
def pattern(p: ast.pattern) -> tuple[Expr, list[Stmt]]:
|
||||
match p:
|
||||
case ast.MatchValue(value=value):
|
||||
return {"k": "cmp", "l": name, "ops": ["=="], "r": [expr(value)]}, []
|
||||
case ast.MatchSingleton(value=value):
|
||||
return {"k": "cmp", "l": name, "ops": ["is"], "r": [const(value)]}, []
|
||||
case ast.MatchAs(pattern=None, name=None):
|
||||
return const(True), []
|
||||
case ast.MatchAs(pattern=None, name=capture):
|
||||
return const(True), [{"op": "set", "t": {"k": "name", "id": capture}, "v": name}]
|
||||
case ast.MatchAs(pattern=inner, name=capture):
|
||||
cond, binds = pattern(inner)
|
||||
if capture:
|
||||
binds = [*binds, {"op": "set", "t": {"k": "name", "id": capture}, "v": name}]
|
||||
return cond, binds
|
||||
case ast.MatchOr(patterns=alternatives):
|
||||
parts = [pattern(a) for a in alternatives]
|
||||
if any(binds for _, binds in parts):
|
||||
raise Unsupported("match: захват внутри альтернативы")
|
||||
return {"k": "bool", "op": "or", "v": [c for c, _ in parts]}, []
|
||||
raise Unsupported(f"match: шаблон {type(p).__name__}")
|
||||
|
||||
chain: list[Stmt] = []
|
||||
for case in reversed(cases):
|
||||
cond, binds = pattern(case.pattern)
|
||||
if case.guard is not None:
|
||||
if binds:
|
||||
raise Unsupported("match: захват вместе с условием guard")
|
||||
cond = {"k": "bool", "op": "and", "v": [cond, expr(case.guard)]}
|
||||
chain = [{"op": "if", "c": cond, "then": [*binds, *stmts(case.body)], "else": chain}]
|
||||
return [{"op": "set", "t": name, "v": expr(subject)}, *chain]
|
||||
Reference in new issue
Block a user