feat: converter accepts the constructs of goodluke and SaintlyCorruption

`init -490 screen`, `import x as y` and `from m import A as B`, classes with @renpy.pure, pickle hooks
on classes, `*` items in tuples and lists, generator functions (yield statements; the player runs them
to the end), multi-line expressions that need brackets, Null() and Animation() images, images given as
expressions (`"path/{}.png".format(x)`, f-strings), window statements in init, `offset` in ATL, input
widgets with their values and the focus/confirm actions, `continue` and `break` in screens, `drag`
and DiamondBox-style `fixed` with cell_size, `getattr(...)()` calls, transforms called with arguments,
and the whitelist for the new runtime. JSON files of a game are shipped as data files. Also fixes
blocking_names failing on a call of a call during full conversion.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-26 17:28:13 +03:00
1 parent f2fcd05467
commit e66160cffa
12 files changed
+176 -31

No files matched your search

+12 -4
View File
@@ -32,7 +32,7 @@ OPENERS = {"Show", "ShowTransient", "ToggleScreen", "Hide", "show_screen", "call
CONTAINERS = {"fixed", "frame", "window", "vbox", "hbox", "grid", "vpgrid", "viewport", "button", "side", "nearrect",
"drag"}
LEAVES = {"text", "label", "textbutton", "imagebutton", "add", "image", "null", "bar", "vbar", "mousearea"}
LEAVES = {"text", "label", "textbutton", "imagebutton", "add", "image", "null", "bar", "vbar", "mousearea", "input"}
POSITIONAL = {"side": 1, "text": 1, "label": 1, "textbutton": 1, "add": 1, "image": 1, "grid": 2}
ATL_WORDS = {"pause", "repeat", "parallel", "choice", "block", "contains", "function", "time"}
@@ -50,11 +50,11 @@ BEHAVIOUR = set("""action alternate hovered unhovered sensitive tooltip focus_ma
activate_sound focus keysym alt id modal zorder tag layer variant style style_prefix at value range
adjustment changed mousewheel draggable scrollbars xinitial yinitial child_size edgescroll arrowkeys
text_style properties substitute prefix suffix repeat style_group prefer_top preferred_side
drag_name draggable droppable drag_handle drag_joined drag_offscreen""".split())
drag_name draggable droppable drag_handle drag_joined drag_offscreen cell_size fit start_lower default allow exclude mask length copypaste pixel_width multiline""".split())
# Scrolling widgets share state through adjustments; scrollbars are cosmetic in the browser
IGNORED_PREFIXES = ("scrollbar_", "vscrollbar_", "bar_", "side_", "thumb_", "slider_", "vslider_", "left_gutter",
"right_gutter", "top_gutter", "bottom_gutter")
IGNORED = {"base_bar", "subpixel", "roll_forward", "xadjustment", "yadjustment", "pagekeys", "unscrollable", "arrowkeys", "edgescroll",
IGNORED = {"capture", "axis", "key_events", "base_bar", "subpixel", "roll_forward", "xadjustment", "yadjustment", "pagekeys", "unscrollable", "arrowkeys", "edgescroll",
"child_size", "mouse", "alt", "properties", "substitute", "prefix", "suffix", "keysym"}
STATE_PREFIXES = ("idle", "hover", "insensitive", "selected", "selected_idle", "selected_hover",
"selected_insensitive")
@@ -208,6 +208,8 @@ class ScreenParser:
node = self.use(line, words)
if node:
nodes.append(node)
elif first in ("continue", "break"):
nodes.append({"c": first})
elif first == "transclude":
nodes.append({"c": "transclude"})
elif first == "pass":
@@ -222,8 +224,10 @@ class ScreenParser:
i = len(lines)
elif first == "python" and line.text.strip() == "python:" or first == "$":
nodes.extend(self.python(line, first))
elif first == "default":
elif first == "default" and "=" in text:
self.default(line)
elif first == "default": # `default "Bob"` inside an input: its starting text
props.update(self.properties(words, line))
elif first == "key":
node = self.key(line, words)
if node:
@@ -391,6 +395,8 @@ class ScreenParser:
return None
if kind in ("bar", "vbar"):
props["vertical"] = {"k": "const", "v": kind == "vbar"}
if kind == "fixed" and "cell_size" in props: # the game's DiamondBox (decompiled as a plain `fixed`)
kind = "diamondbox"
node: Node = {"w": kind, "args": args, "props": props, "body": body}
if conditional:
node["propIf"] = conditional
@@ -419,6 +425,8 @@ class ScreenParser:
if cond is not None:
return {"choose": {"c": cond, "then": self.at_ref(ast.unparse(tree.body), line),
"else": self.at_ref(ast.unparse(tree.orelse), line)}}
if text == "None":
return {}
if re.fullmatch(r"[A-Za-z_]\w*(?:\.\w+)*\[[^\]]+\]|[A-Za-z_]\w*\.\w+", text): # `icon["at"]`, `item.at`
dynamic = self.expr(text, line)
if dynamic is not None: