Files
mrleo1nidandClaude Sonnet 5.5 962595c925 feat: support user-defined functions and classes in Ren'Py python
- Converter: def/return/global/nonlocal, class (inheritance, property,
  staticmethod, classmethod), lambda, *args/**kwargs and call-site unpacking.
  Unsupported dunder methods, generators and unknown decorators are still refused
- Player: closures, super(), instances that survive save/load, isinstance,
  hasattr/getattr/setattr, map/filter/sorted(key=), recursion limit
- audit_ir accepts calls to names, classes and methods the game defines itself;
  unknown renpy.* calls still block the import. Volume/pan/pause/replay calls
  are accepted as presentation-only no-ops; build.* settings are skipped
- Tests: IR fixture compiled from tests/fixtures/classes.py (tests/make_ir.py)
  and run in the interpreter; converter tests updated

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 12:01:09 +03:00

104 lines
2.2 KiB
Python

# Source of classes.ir.json. Regenerate from backend/ with:
# python -m tests.make_ir ../frontend/tests/fixtures/classes.py ../frontend/tests/fixtures/classes.ir.json
# The Python below is also valid CPython, so expected values can be checked there.
class Perk(object):
count = 0
def __init__(self, name, cost=1):
self.name = name
self.cost = cost
Perk.count += 1
def __str__(self):
return "Perk(%s)" % self.name
class Player(object):
def __init__(self, name, *perks, **extra):
self.name = name
self.perks = list(perks)
self.extra = extra
self._hp = 10
def has_perk(self, name):
return any([p.name == name for p in self.perks])
def add(self, perk):
self.perks.append(perk)
return self
@property
def hp(self):
return self._hp
@hp.setter
def hp(self, value):
self._hp = max(0, value)
@staticmethod
def make(name):
return Player(name)
@classmethod
def label(cls):
return cls is Player
class Hero(Player):
def __init__(self, name):
super().__init__(name, Perk("brave"), role="hero")
self.level = 1
def has_perk(self, name):
return name == "legend" or super().has_perk(name)
total = 0
def bump(step=1, *, scale=2):
global total
total += step * scale
return total
def fact(n):
return 1 if n <= 1 else n * fact(n - 1)
def counter():
n = 0
def inc():
nonlocal n
n += 1
return n
return inc
hero = Hero("Ann")
hero.add(Perk("fast", cost=3))
hero.hp = -5
call_args = [2]
call_kw = {"scale": 10}
result_brave = hero.has_perk("brave")
result_legend = hero.has_perk("legend")
result_missing = hero.has_perk("nope")
result_count = Perk.count
result_str = str(Perk("x"))
result_hp = hero.hp
result_role = hero.extra["role"]
result_static = Player.make("Bob").name
result_cls = Player.label()
result_bump = bump(*call_args, **call_kw)
result_total = total
result_fact = fact(6)
tick = counter()
tick()
result_closure = tick()
result_lambda = sorted([3, 1, 2], key=lambda v: -v)
result_isinstance = [isinstance(hero, Player), isinstance(hero, Perk), isinstance(1, int)]