X-Git-Url: https://git.yukkurigames.com/?p=python-bulletml.git;a=blobdiff_plain;f=bulletml%2Fparser.py;h=51e15add66b82eac1e0d60671301c5b0e0b37dc2;hp=a89d1c5b8ce207c7f8a5a37db780f0d38ba43d7f;hb=e753939f30c6c07a68363c7819acf8c0e57a6951;hpb=4f0e7df685d8d25cd0f1bbce7d6330f87c8cf178 diff --git a/bulletml/parser.py b/bulletml/parser.py index a89d1c5..51e15ad 100644 --- a/bulletml/parser.py +++ b/bulletml/parser.py @@ -24,6 +24,7 @@ except ImportError: from bulletml.errors import Error from bulletml.expr import NumberDef, INumberDef + __all_ = ["ParseError", "BulletML"] class ParseError(Error): @@ -44,20 +45,11 @@ class ParamList(object): self.params = list(params) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" return cls([NumberDef(subelem.text) for subelem in element if realtag(subelem) == "param"]) - def __getstate__(self): - if self.params: - return dict(params=[param.expr for param in self.params]) - else: - return {} - - def __setstate__(self, state): - self.__init__(NumberDef(param) for param in state.get("params", [])) - def __call__(self, params, rank): return [param(params, rank) for param in self.params] @@ -76,13 +68,14 @@ class Direction(object): self.value = value def __getstate__(self): - return dict(type=self.type, value=self.value.expr) + return [('type', self.type), ('value', self.value.expr)] def __setstate__(self, state): + state = dict(state) self.__init__(state["type"], NumberDef(state["value"])) @classmethod - def FromElement(cls, doc, element, default="absolute"): + def FromXML(cls, doc, element, default="absolute"): """Construct using an ElementTree-style element.""" return cls(element.get("type", default), NumberDef(element.text)) @@ -101,21 +94,22 @@ class ChangeDirection(object): self.direction = direction def __getstate__(self): - return dict(frames=self.term.expr, - type=self.direction.type, - value=self.direction.value.expr) + return [('frames', self.term.expr), + ('type', self.direction.type), + ('value', self.direction.value.expr)] def __setstate__(self, state): + state = dict(state) self.__init__(INumberDef(state["frames"]), Direction(state["type"], NumberDef(state["value"]))) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" for subelem in element.getchildren(): tag = realtag(subelem) if tag == "direction": - direction = Direction.FromElement(doc, subelem) + direction = Direction.FromXML(doc, subelem) elif tag == "term": term = INumberDef(subelem.text) try: @@ -142,13 +136,14 @@ class Speed(object): self.value = value def __getstate__(self): - return dict(type=self.type, value=self.value.expr) + return [('type', self.type), ('value', self.value.expr)] def __setstate__(self, state): + state = dict(state) self.__init__(state["type"], NumberDef(state["value"])) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" return cls(element.get("type", "absolute"), NumberDef(element.text)) @@ -166,21 +161,22 @@ class ChangeSpeed(object): self.speed = speed def __getstate__(self): - return dict(frames=self.term.expr, - type=self.speed.type, - value=self.speed.value.expr) + return [('frames', self.term.expr), + ('type', self.speed.type), + ('value', self.speed.value.expr)] def __setstate__(self, state): + state = dict(state) self.__init__(INumberDef(state["frames"]), Speed(state["type"], NumberDef(state["value"]))) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" for subelem in element.getchildren(): tag = realtag(subelem) if tag == "speed": - speed = Speed.FromElement(doc, subelem) + speed = Speed.FromXML(doc, subelem) elif tag == "term": term = INumberDef(subelem.text) try: @@ -208,7 +204,7 @@ class Wait(object): self.__init__(INumberDef(state["frames"])) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" return cls(INumberDef(element.text)) @@ -231,7 +227,7 @@ class Tag(object): self.__init__(state["tag"]) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" return cls(element.text) @@ -248,7 +244,7 @@ class Untag(object): self.__init__(state["tag"]) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" return cls(element.text) @@ -259,7 +255,7 @@ class Vanish(object): pass @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" return cls() @@ -274,22 +270,23 @@ class Repeat(object): self.action = action def __getstate__(self): - return dict(times=self.times.expr, action=self.action) + return [('times', self.times.expr), ('action', self.action)] def __setstate__(self, state): + state = dict(state) self.__init__(INumberDef(state["times"]), state["action"]) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" for subelem in element.getchildren(): tag = realtag(subelem) if tag == "times": times = INumberDef(subelem.text) elif tag == "action": - action = ActionDef.FromElement(doc, subelem) + action = ActionDef.FromXML(doc, subelem) elif tag == "actionRef": - action = ActionRef.FromElement(doc, subelem) + action = ActionRef.FromXML(doc, subelem) try: return cls(times, action) except UnboundLocalError as exc: @@ -313,19 +310,20 @@ class Accel(object): self.vertical = vertical def __getstate__(self): - state = dict(frames=self.term.expr) + state = [('frames', self.term.expr)] if self.horizontal: - state["horizontal"] = self.horizontal + state.append(('horizontal', self.horizontal)) if self.vertical: - state["vertical"] = self.vertical + state.append(('vertical', self.vertical)) return state def __setstate__(self, state): - self.__init__(INumberDef(state["term"]), state.get("horizontal"), + state = dict(state) + self.__init__(INumberDef(state["frames"]), state.get("horizontal"), state.get("vertical")) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" horizontal = None vertical = None @@ -335,9 +333,9 @@ class Accel(object): if tag == "term": term = INumberDef(subelem.text) elif tag == "horizontal": - horizontal = Speed.FromElement(doc, subelem) + horizontal = Speed.FromXML(doc, subelem) elif tag == "vertical": - vertical = Speed.FromElement(doc, subelem) + vertical = Speed.FromXML(doc, subelem) try: return cls(term, horizontal, vertical) @@ -366,20 +364,21 @@ class BulletDef(object): self.actions = list(actions) def __getstate__(self): - state = dict() + state = [] if self.direction: - state["direction"] = self.direction + state.append(("direction", self.direction)) if self.speed: - state["speed"] = self.speed + state.append(("speed", self.speed)) if self.actions: - state["actions"] = self.actions + state.append(("actions", self.actions)) return state def __setstate__(self, state): + state = dict(state) self.__init__(**state) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" actions = [] speed = None @@ -387,13 +386,13 @@ class BulletDef(object): for subelem in element.getchildren(): tag = realtag(subelem) if tag == "direction": - direction = Direction.FromElement(doc, subelem) + direction = Direction.FromXML(doc, subelem) elif tag == "speed": - speed = Speed.FromElement(doc, subelem) + speed = Speed.FromXML(doc, subelem) elif tag == "action": - actions.append(ActionDef.FromElement(doc, subelem)) + actions.append(ActionDef.FromXML(doc, subelem)) elif tag == "actionRef": - actions.append(ActionRef.FromElement(doc, subelem)) + actions.append(ActionRef.FromXML(doc, subelem)) dfn = cls(actions, direction, speed) doc.bullets[element.get("label")] = dfn return dfn @@ -417,20 +416,23 @@ class BulletRef(object): self.params = ParamList() if params is None else params def __getstate__(self): - state = dict(bullet=self.bullet) + state = [] if self.params.params: - state["params"] = self.params.__getstate__() + params = [param.expr for param in self.params.params] + state.append(("params", params)) + state.append(('bullet', self.bullet)) return state def __setstate__(self, state): + state = dict(state) bullet = state["bullet"] params = [NumberDef(param) for param in state.get("params", [])] - self.__init__(bullet, params) + self.__init__(bullet, ParamList(params)) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" - bullet = cls(element.get("label"), ParamList.FromElement(doc, element)) + bullet = cls(element.get("label"), ParamList.FromXML(doc, element)) doc._bullet_refs.append(bullet) return bullet @@ -446,7 +448,7 @@ class ActionDef(object): To support parsing new actions, add tags to ActionDef.CONSTRUCTORS. It maps tag names to classes with a - FromElement classmethod, which take the BulletML instance and + FromXML classmethod, which take the BulletML instance and ElementTree element as arguments. """ @@ -460,10 +462,11 @@ class ActionDef(object): return dict(actions=self.actions) def __setstate__(self, state): - self.__init__(state) + state = dict(state) + self.__init__(state["actions"]) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" actions = [] for subelem in element.getchildren(): @@ -473,7 +476,7 @@ class ActionDef(object): except KeyError: continue else: - actions.append(ctr.FromElement(doc, subelem)) + actions.append(ctr.FromXML(doc, subelem)) dfn = cls(actions) doc.actions[element.get("label")] = dfn return dfn @@ -492,20 +495,23 @@ class ActionRef(object): self.params = params or ParamList() def __getstate__(self): - state = dict(action=self.action) + state = [] if self.params.params: - state["params"] = self.params.__getstate__() + params = [param.expr for param in self.params.params] + state.append(("params", params)) + state.append(('action', self.action)) return state def __setstate__(self, state): + state = dict(state) action = state["action"] params = [NumberDef(param) for param in state.get("params", [])] - self.__init__(action, params) + self.__init__(action, ParamList(params)) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" - action = cls(element.get("label"), ParamList.FromElement(doc, element)) + action = cls(element.get("label"), ParamList.FromXML(doc, element)) doc._action_refs.append(action) return action @@ -529,18 +535,19 @@ class Offset(object): self.y = y def __getstate__(self): - state = dict(type=self.type) + state = [('type', self.type)] if self.x: - state["x"] = self.x.expr + state.append(('x', self.x.expr)) if self.y: - state["y"] = self.y.expr + state.append(('y', self.y.expr)) return state def __setstate__(self, state): + state = dict(state) self.__init__(state["type"], state.get("x"), state.get("y")) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" type = element.get("type", "relative") x = None @@ -567,25 +574,31 @@ class FireDef(object): self.offset = offset def __getstate__(self): + state = [] + if self.direction: + state.append(("direction", self.direction)) + if self.speed: + state.append(("speed", self.speed)) + if self.offset: + state.append(("offset", self.offset)) try: params = self.bullet.params except AttributeError: state = dict(bullet=self.bullet) else: if params.params: - state = dict(bullet=self.bullet) + state.append(('bullet', self.bullet)) else: - state = dict(bullet=self.bullet.bullet) - if self.direction: - state["direction"] = self.direction - if self.speed: - state["speed"] = self.speed - if self.offset: - state["offset"] = self.offset + # Strip out empty BulletRefs. + state.append(('bullet', self.bullet.bullet)) return state + def __setstate__(self, state): + state = dict(state) + self.__init__(**state) + @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" direction = None speed = None @@ -594,15 +607,15 @@ class FireDef(object): for subelem in element.getchildren(): tag = realtag(subelem) if tag == "direction": - direction = Direction.FromElement(doc, subelem, "aim") + direction = Direction.FromXML(doc, subelem, "aim") elif tag == "speed": - speed = Speed.FromElement(doc, subelem) + speed = Speed.FromXML(doc, subelem) elif tag == "bullet": - bullet = BulletDef.FromElement(doc, subelem) + bullet = BulletDef.FromXML(doc, subelem) elif tag == "bulletRef": - bullet = BulletRef.FromElement(doc, subelem) + bullet = BulletRef.FromXML(doc, subelem) elif tag == "offset": - offset = Offset.FromElement(doc, subelem) + offset = Offset.FromXML(doc, subelem) try: fire = cls(bullet, direction, speed, offset) except UnboundLocalError as exc: @@ -631,20 +644,23 @@ class FireRef(object): self.params = params or ParamList() def __getstate__(self): - state = dict(fire=self.fire) + state = [] if self.params.params: - state["params"] = self.params.__getstate__() + params = [param.expr for param in self.params.params] + state.append(("params", params)) + state.append(('fire', self.fire)) return state def __setstate__(self, state): + state = dict(state) fire = state["fire"] params = [NumberDef(param) for param in state.get("params", [])] - self.__init__(fire, params) + self.__init__(fire, ParamList(params)) @classmethod - def FromElement(cls, doc, element): + def FromXML(cls, doc, element): """Construct using an ElementTree-style element.""" - fired = cls(element.get("label"), ParamList.FromElement(doc, element)) + fired = cls(element.get("label"), ParamList.FromXML(doc, element)) doc._fire_refs.append(fired) return fired @@ -662,7 +678,7 @@ class BulletML(object): firings, as well as a base game type. You can add tags to the BulletML.CONSTRUCTORS dictionary to extend - its parsing. It maps tag names to classes with a FromElement + its parsing. It maps tag names to classes with a FromXML classmethod, which take the BulletML instance and ElementTree element as arguments. @@ -681,14 +697,15 @@ class BulletML(object): self.fires = {} if fires is None else fires def __getstate__(self): - return dict(type=self.type, actions=self.actions) + return [('type', self.type), ('actions', self.actions)] def __setstate__(self, state): - self.__init__(state["type"], state.get("actions")) + state = dict(state) + self.__init__(state["type"], actions=state.get("actions")) @classmethod - def FromDocument(cls, source): - """Return a BulletML instance based on a string or file-like.""" + def FromXML(cls, source): + """Return a BulletML instance based on XML.""" if not hasattr(source, 'read'): source = StringIO(source) @@ -704,7 +721,7 @@ class BulletML(object): for element in root.getchildren(): tag = realtag(element) if tag in self.CONSTRUCTORS: - self.CONSTRUCTORS[tag].FromElement(self, element) + self.CONSTRUCTORS[tag].FromXML(self, element) try: for ref in self._bullet_refs: @@ -726,6 +743,39 @@ class BulletML(object): return self + @classmethod + def FromYAML(cls, source): + """Create a BulletML instance based on YAML.""" + + # Late import to avoid a circular dependency. + try: + import bulletml.bulletyaml + import yaml + except ImportError: + raise ParseError("PyYAML is not available") + else: + try: + return yaml.load(source) + except Exception, exc: + raise ParseError(str(exc)) + + @classmethod + def FromDocument(cls, source): + """Create a BulletML instance based on a seekable file or string. + + This attempts to autodetect if the stream is XML or YAML. + """ + if not hasattr(source, 'read'): + source = StringIO(source) + start = source.read(1) + source.seek(0) + if start == "<": + return cls.FromXML(source) + elif start == "!" or start == "#": + return cls.FromYAML(source) + else: + raise ParseError("unknown initial character %r" % start) + @property def top(self): """Get a list of all top-level actions."""