From ef50d69288ee60ac2a8fae2ffe5860e80299fd72 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Tue, 16 Mar 2010 21:16:45 -0700 Subject: [PATCH 1/1] Separate element constructors from regular constructors. --- bulletml/expr.py | 7 +- bulletml/impl.py | 3 +- bulletml/parser.py | 286 +++++++++++++++++++++++++++++++-------------- 3 files changed, 206 insertions(+), 90 deletions(-) diff --git a/bulletml/expr.py b/bulletml/expr.py index 7654fed..3c80b06 100644 --- a/bulletml/expr.py +++ b/bulletml/expr.py @@ -31,12 +31,13 @@ class NumberDef(object): """ GLOBALS = dict(random=random.random, __builtins__={}) + def __init__(self, expr): try: - expr = expr.__original + expr = expr.string except AttributeError: pass - self.__original = expr + self.string = expr repl = lambda match: "params[%d]" % (int(match.group()[1:]) - 1) expr = re.sub(r"\$\d+", repl, expr.lower()) self.__expr = expr.replace("$rand", "random()").replace("$rank", "rank") @@ -61,7 +62,7 @@ class NumberDef(object): return eval(self.__expr, self.GLOBALS, variables) def __repr__(self): - return "%s(%r)" % (type(self).__name__, self.__original) + return "%s(%r)" % (type(self).__name__, self.string) class INumberDef(NumberDef): """A NumberDef, but returns rounded integer results.""" diff --git a/bulletml/impl.py b/bulletml/impl.py index 9139f9c..92982db 100644 --- a/bulletml/impl.py +++ b/bulletml/impl.py @@ -7,7 +7,7 @@ from __future__ import division import math -from bulletml import parser, errors +from bulletml import parser # TODO(jfw): This is very non-Pythonic, it's pretty much just the # BulletML reference ActionImpl translated to Python. @@ -297,6 +297,7 @@ class Bullet(object): @classmethod def FromDoc(cls, doc, params=(), x=0, y=0, speed=0, direction=0, target=None, rank=0.5): + """Construct a bullet from top-level actions in a document.""" actions = [act(params, rank) for act in doc.top] return cls(x, y, direction, speed, target, actions, rank=rank) diff --git a/bulletml/parser.py b/bulletml/parser.py index 933be80..bf850a7 100644 --- a/bulletml/parser.py +++ b/bulletml/parser.py @@ -28,12 +28,15 @@ def realtag(element): class ParamList(object): """List of parameter definitions.""" - def __init__(self, element): - self.params = [] - if element: - for subelem in element: - if realtag(subelem) == "param": - self.params.append(NumberDef(subelem.text)) + + def __init__(self, params=[]): + self.params = list(params) + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + return cls([NumberDef(subelem.text) for subelem in element + if realtag(subelem) == "param"]) def __call__(self, params, rank): return [param(params, rank) for param in self.params] @@ -44,9 +47,18 @@ class ParamList(object): class Direction(object): """Raw direction value.""" - def __init__(self, doc, element, type="absolute"): - self.type = element.get("type", type) - self.value = NumberDef(element.text) + VALID_TYPES = ["relative", "absolute", "aim", "sequence"] + + def __init__(self, type, value): + if type not in self.VALID_TYPES: + raise ValueError("invalid type %r" % type) + self.type = type + self.value = value + + @classmethod + def FromElement(cls, doc, element, default="absolute"): + """Construct using an ElementTree-style element.""" + return cls(element.get("type", default), NumberDef(element.text)) def __call__(self, params, rank): return (self.value(params, rank), self.type) @@ -58,17 +70,23 @@ class Direction(object): class ChangeDirection(object): """Direction change over time.""" - def __init__(self, doc, element): + def __init__(self, term, direction): + self.term = term + self.direction = direction + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" for subelem in element.getchildren(): tag = realtag(subelem) if tag == "direction": - self.direction = Direction(doc, subelem) + direction = Direction.FromElement(doc, subelem) elif tag == "term": - self.term = INumberDef(subelem.text) + term = INumberDef(subelem.text) try: - self.term, self.direction - except AttributeError: - raise ParseError + return cls(term, direction) + except UnboundLocalError as exc: + raise ParseError(str(exc)) def __call__(self, params, rank): return self.term(params, rank), self.direction(params, rank) @@ -80,9 +98,18 @@ class ChangeDirection(object): class Speed(object): """Raw speed value.""" - def __init__(self, doc, element, type="absolute"): - self.type = element.get("type", type) - self.value = NumberDef(element.text) + VALID_TYPES = ["relative", "absolute", "sequence"] + + def __init__(self, type, value): + if type not in self.VALID_TYPES: + raise ValueError("invalid type %r" % type) + self.type = type + self.value = value + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + return cls(element.get("type", "absolute"), NumberDef(element.text)) def __call__(self, params, rank): return (self.value(params, rank), self.type) @@ -93,17 +120,23 @@ class Speed(object): class ChangeSpeed(object): """Speed change over time.""" - def __init__(self, doc, element): + def __init__(self, term, speed): + self.term = term + self.speed = speed + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" for subelem in element.getchildren(): tag = realtag(subelem) if tag == "speed": - self.speed = Speed(doc, subelem) + speed = Speed.FromElement(doc, subelem) elif tag == "term": - self.term = INumberDef(subelem.text) + term = INumberDef(subelem.text) try: - self.term, self.speed - except AttributeError: - raise ParseError + return cls(term, speed) + except UnboundLocalError as exc: + raise ParseError(str(exc)) def __call__(self, params, rank): return self.term(params, rank), self.speed(params, rank) @@ -114,8 +147,14 @@ class ChangeSpeed(object): class Wait(object): """Wait for some frames.""" - def __init__(self, doc, element): - self.frames = INumberDef(element.text) + + def __init__(self, frames): + self.frames = frames + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + return cls(INumberDef(element.text)) def __call__(self, params, rank): return self.frames(params, rank) @@ -125,29 +164,39 @@ class Wait(object): class Vanish(object): """Make the owner disappear.""" - def __init__(self, doc, element): + + def __init__(self): pass + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + return cls() + def __repr__(self): return "%s()" % (type(self).__name__) class Repeat(object): """Repeat an action definition.""" - def __init__(self, doc, element): + def __init__(self, times, action): + self.times = times + self.action = action + + @classmethod + def FromElement(cls, doc, element): for subelem in element.getchildren(): tag = realtag(subelem) if tag == "times": - self.times = INumberDef(subelem.text) + times = INumberDef(subelem.text) elif tag == "action": - self.action = ActionDef(doc, subelem) + action = ActionDef.FromElement(doc, subelem) elif tag == "actionRef": - self.action = ActionRef(doc, subelem) - + action = ActionRef.FromElement(doc, subelem) try: - self.times, self.action - except AttributeError: - raise ParseError + return cls(times, action) + except UnboundLocalError as exc: + raise ParseError(str(exc)) def __call__(self, params, rank): return self.times(params, rank), self.action(params, rank) @@ -161,18 +210,28 @@ class Accel(object): horizontal = None vertical = None - def __init__(self, doc, element): + def __init__(self, term, horizontal=None, vertical=None): + self.term = term + self.horizontal = horizontal + self.vertical = vertical + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + horizontal = None + vertical = None + for subelem in element.getchildren(): tag = realtag(subelem) if tag == "term": - self.term = INumberDef(subelem.text) + term = INumberDef(subelem.text) elif tag == "horizontal": - self.horizontal = Speed(doc, subelem) + horizontal = Speed.FromElement(doc, subelem) elif tag == "vertical": - self.vertical = Speed(doc, subelem) + vertical = Speed.FromElement(doc, subelem) try: - self.term + return cls(term, horizontal, vertical) except AttributeError: raise ParseError @@ -192,19 +251,30 @@ class BulletDef(object): direction = None speed = None - def __init__(self, doc, element): - self.actions = [] - doc.bullets[element.get("label")] = self + def __init__(self, actions=[], direction=None, speed=None): + self.direction = direction + self.speed = speed + self.actions = list(actions) + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + actions = [] + speed = None + direction = None for subelem in element.getchildren(): tag = realtag(subelem) if tag == "direction": - self.direction = Direction(doc, subelem) + direction = Direction.FromElement(doc, subelem) elif tag == "speed": - self.speed = Speed(doc, subelem) + speed = Speed.FromElement(doc, subelem) elif tag == "action": - self.actions.append(ActionDef(doc, subelem)) + actions.append(ActionDef.FromElement(doc, subelem)) elif tag == "actionRef": - self.actions.append(ActionRef(doc, subelem)) + actions.append(ActionRef.FromElement(doc, subelem)) + dfn = cls(actions, direction, speed) + doc.bullets[element.get("label")] = dfn + return dfn def __call__(self, params, rank): actions = [action(params, rank) for action in self.actions] @@ -220,10 +290,16 @@ class BulletDef(object): class BulletRef(object): """Create a bullet by name with parameters.""" - def __init__(self, doc, element): - self.bullet = element.get("label") - self.params = ParamList(element) - doc._bullet_refs.append(self) + def __init__(self, bullet, params=None): + self.bullet = bullet + self.params = params or ParamList() + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + bullet = cls(element.get("label"), ParamList.FromElement(doc, element)) + doc._bullet_refs.append(bullet) + return bullet def __call__(self, params, rank): return self.bullet(self.params(params, rank), rank) @@ -235,27 +311,27 @@ class BulletRef(object): class ActionDef(object): """Action definition.""" - def __init__(self, doc, element): - doc.actions[element.get("label")] = self - self.actions = [] + # This is self-referential, so it's filled in later. + CONSTRUCTORS = dict() + + def __init__(self, actions): + self.actions = list(actions) + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + actions = [] for subelem in element.getchildren(): tag = realtag(subelem) try: - ctr = dict( - repeat=Repeat, - fire=FireDef, - fireRef=FireRef, - changeSpeed=ChangeSpeed, - changeDirection=ChangeDirection, - accel=Accel, - wait=Wait, - vanish=Vanish, - action=ActionDef, - actionRef=ActionRef)[tag] + ctr = cls.CONSTRUCTORS[tag] except KeyError: continue else: - self.actions.append(ctr(doc, subelem)) + actions.append(ctr.FromElement(doc, subelem)) + dfn = cls(actions) + doc.actions[element.get("label")] = dfn + return dfn def __call__(self, params, rank): return self.actions, params @@ -266,10 +342,16 @@ class ActionDef(object): class ActionRef(object): """Run an action by name with parameters.""" - def __init__(self, doc, element): - self.action = element.get("label") - self.params = ParamList(element) - doc._action_refs.append(self) + def __init__(self, action, params=None): + self.action = action + self.params = params or ParamList() + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + action = cls(element.get("label"), ParamList.FromElement(doc, element)) + doc._action_refs.append(action) + return action def __call__(self, params, rank): return self.action(self.params(params, rank), rank) @@ -281,25 +363,35 @@ class ActionRef(object): class FireDef(object): """Fire definition (creates a bullet).""" - direction = None - speed = None + def __init__(self, bullet, direction=None, speed=None): + self.bullet = bullet + self.direction = direction + self.speed = speed + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + direction = None + speed = None - def __init__(self, doc, element): - doc.fires[element.get("label")] = self for subelem in element.getchildren(): tag = realtag(subelem) if tag == "direction": - self.direction = Direction(doc, subelem, "aim") + direction = Direction.FromElement(doc, subelem, "aim") elif tag == "speed": - self.speed = Speed(doc, subelem) + speed = Speed.FromElement(doc, subelem) elif tag == "bullet": - self.bullet = BulletDef(doc, subelem) + bullet = BulletDef.FromElement(doc, subelem) elif tag == "bulletRef": - self.bullet = BulletRef(doc, subelem) + bullet = BulletRef.FromElement(doc, subelem) + try: - self.bullet - except AttributeError: - raise ParseError + fire = cls(bullet, direction, speed) + except UnboundLocalError as exc: + raise ParseError(str(exc)) + else: + doc.fires[element.get("label")] = fire + return fire def __call__(self, params, rank): direction, speed, actions = self.bullet(params, rank) @@ -316,10 +408,16 @@ class FireDef(object): class FireRef(object): """Fire a bullet by name with parameters.""" - def __init__(self, doc, element): - self.fire = element.get("label") - self.params = ParamList(element) - doc._fire_refs.append(self) + def __init__(self, fire, params=None): + self.fire = fire + self.params = params or ParamList() + + @classmethod + def FromElement(cls, doc, element): + """Construct using an ElementTree-style element.""" + fired = cls(element.get("label"), ParamList.FromElement(doc, element)) + doc._fire_refs.append(fired) + return fired def __call__(self, params, rank): """Generate a Bullet from the FireDef and params.""" @@ -362,7 +460,7 @@ class BulletML(object): for element in root.getchildren(): tag = realtag(element) if tag in self.CONSTRUCTORS: - self.CONSTRUCTORS[tag](self, element) + self.CONSTRUCTORS[tag].FromElement(self, element) try: for ref in self._bullet_refs: @@ -378,6 +476,10 @@ class BulletML(object): del(self._action_refs) del(self._fire_refs) + self.bullets.pop(None, None) + self.actions.pop(None, None) + self.fires.pop(None, None) + @property def top(self): """Get a list of all top-level actions.""" @@ -388,3 +490,15 @@ class BulletML(object): return "%s(type=%r, bullets=%r, actions=%r, fires=%r)" % ( type(self).__name__, self.type, self.bullets, self.actions, self.fires) + +ActionDef.CONSTRUCTORS = dict( + repeat=Repeat, + fire=FireDef, + fireRef=FireRef, + changeSpeed=ChangeSpeed, + changeDirection=ChangeDirection, + accel=Accel, + wait=Wait, + vanish=Vanish, + action=ActionDef, + actionRef=ActionRef) -- 2.20.1