"""BulletML parser. http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html """ from __future__ import division from xml.etree.ElementTree import ElementTree try: from cStringIO import StringIO except ImportError: from StringIO import StringIO from bulletml.errors import Error from bulletml.expr import NumberDef, INumberDef class ParseError(Error): """Raised when an error occurs parsing the XML structure.""" pass def realtag(element): """Strip namespace poop off the front of a tag.""" try: return element.tag.rsplit('}', 1)[1] except ValueError: return element.tag 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 __call__(self, params, rank): new_params = [param(params, rank) for param in self.params] while len(new_params) < len(params): new_params.append(params[len(new_params)]) return new_params def __repr__(self): return "%s(%r)" % (type(self).__name__, self.params) class Direction(object): """Raw direction value.""" def __init__(self, doc, element, type="absolute"): self.type = element.get("type", type) self.value = NumberDef(element.text) def __call__(self, params, rank): return (self.value(params, rank), self.type) def __repr__(self): return "%s(%r, type=%r)" % ( type(self).__name__, self.value, self.type) class ChangeDirection(object): """Direction change over time.""" def __init__(self, doc, element): for subelem in element.getchildren(): tag = realtag(subelem) if tag == "direction": self.direction = Direction(doc, subelem) elif tag == "term": self.term = INumberDef(subelem.text) try: self.term, self.direction except AttributeError: raise ParseError def __call__(self, params, rank): return self.term(params, rank), self.direction(params, rank) def __repr__(self): return "%s(term=%r, direction=%r)" % ( type(self).__name__, self.term, self.direction) class Speed(object): """Raw speed value.""" def __init__(self, doc, element, type="absolute"): self.type = element.get("type", type) self.value = NumberDef(element.text) def __call__(self, params, rank): return (self.value(params, rank), self.type) def __repr__(self): return "%s(%r, type=%r)" % (type(self).__name__, self.value, self.type) class ChangeSpeed(object): """Speed change over time.""" def __init__(self, doc, element): for subelem in element.getchildren(): tag = realtag(subelem) if tag == "speed": self.speed = Speed(doc, subelem) elif tag == "term": self.term = INumberDef(subelem.text) try: self.term, self.speed except AttributeError: raise ParseError def __call__(self, params, rank): return self.term(params, rank), self.speed(params, rank) def __repr__(self): return "%s(term=%r, speed=%r)" % ( type(self).__name__, self.term, self.speed) class Wait(object): """Wait for some frames.""" def __init__(self, doc, element): self.frames = INumberDef(element.text) def __call__(self, params, rank): return self.frames(params, rank) def __repr__(self): return "%s(%r)" % (type(self).__name__, self.frames) class Vanish(object): """Make the owner disappear.""" def __init__(self, doc, element): pass def __repr__(self): return "%s()" % (type(self).__name__) class Repeat(object): """Repeat an action definition.""" def __init__(self, doc, element): for subelem in element.getchildren(): tag = realtag(subelem) if tag == "times": self.times = INumberDef(subelem.text) elif tag == "action": self.action = ActionDef(doc, subelem) elif tag == "actionRef": self.action = ActionRef(doc, subelem) try: self.times, self.action except AttributeError: raise ParseError def __call__(self, params, rank): return self.times(params, rank), self.action(params, rank) def __repr__(self): return "%s(%r, %r)" % (type(self).__name__, self.times, self.action) class Accel(object): """Accelerate over some time.""" horizontal = None vertical = None def __init__(self, doc, element): for subelem in element.getchildren(): tag = realtag(subelem) if tag == "term": self.term = INumberDef(subelem.text) elif tag == "horizontal": self.horizontal = Speed(doc, subelem) elif tag == "vertical": self.vertical = Speed(doc, subelem) try: self.term except AttributeError: raise ParseError def __call__(self, params, rank): frames = self.term(params, rank) horizontal = self.horizontal and self.horizontal(params, rank) vertical = self.vertical and self.vertical(params, rank) return frames, horizontal, vertical def __repr__(self): return "%s(%r, horizontal=%r, vertical=%r)" % ( type(self).__name__, self.term, self.horizontal, self.vertical) class BulletDef(object): """Bullet definition.""" direction = None speed = None def __init__(self, doc, element): self.actions = [] doc.bullets[element.get("label")] = self for subelem in element.getchildren(): tag = realtag(subelem) if tag == "direction": self.direction = Direction(doc, subelem) elif tag == "speed": self.speed = Speed(doc, subelem) elif tag == "action": self.actions.append(ActionDef(doc, subelem)) elif tag == "actionRef": self.actions.append(ActionRef(doc, subelem)) def __call__(self, params, rank): actions = [] for action in self.actions: actions.append(action(params, rank)) return ( self.direction and self.direction(params, rank), self.speed and self.speed(params, rank), actions) def __repr__(self): return "%s(direction=%r, speed=%r, actions=%r)" % ( type(self).__name__, self.direction, self.speed, self.actions) 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 __call__(self, params, rank): return self.bullet(self.params(params, rank), rank) def __repr__(self): return "%s(params=%r, bullet=%r)" % ( type(self).__name__, self.params, self.bullet) class ActionDef(object): """Action definition.""" def __init__(self, doc, element): doc.actions[element.get("label")] = self self.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] except KeyError: continue else: self.actions.append(ctr(doc, subelem)) def __call__(self, params, rank): return self.actions, params def __repr__(self): return "%s(%r)" % (type(self).__name__, self.actions) 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 __call__(self, params, rank): return self.action(self.params(params, rank), rank) def __repr__(self): return "%s(params=%r, action=%r)" % ( type(self).__name__, self.params, self.action) class FireDef(object): """Fire definition (creates a bullet).""" 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") elif tag == "speed": self.speed = Speed(doc, subelem) elif tag == "bullet": self.bullet = BulletDef(doc, subelem) elif tag == "bulletRef": self.bullet = BulletRef(doc, subelem) try: self.bullet except AttributeError: raise ParseError def __call__(self, params, rank): direction, speed, actions = self.bullet(params, rank) if self.direction: direction = self.direction(params, rank) if self.speed: speed = self.speed(params, rank) return direction, speed, actions def __repr__(self): return "%s(direction=%r, speed=%r, bullet=%r)" % ( type(self).__name__, self.direction, self.speed, self.bullet) 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 __call__(self, params, rank): """Generate a Bullet from the FireDef and params.""" return self.fire(self.params(params, rank), rank) def __repr__(self): return "%s(params=%r, fire=%r)" % ( type(self).__name__, self.params, self.fire) class BulletML(object): """BulletML document. A BulletML document is a collection of bullets, actions, and firings, as well as a base game type. """ CONSTRUCTORS = dict( bullet=BulletDef, action=ActionDef, fire=FireDef, ) def __init__(self, source): self.bullets = {} self.actions = {} self.fires = {} self._bullet_refs = [] self._action_refs = [] self._fire_refs = [] if isinstance(source, (str, unicode)): source = StringIO(source) tree = ElementTree() root = tree.parse(source) self.type = root.get("type", "none") for element in root.getchildren(): tag = realtag(element) if tag in self.CONSTRUCTORS: self.CONSTRUCTORS[tag](self, element) try: for ref in self._bullet_refs: ref.bullet = self.bullets[ref.bullet] for ref in self._fire_refs: ref.fire = self.fires[ref.fire] for ref in self._action_refs: ref.action = self.actions[ref.action] except KeyError as exc: raise ParseError("unknown reference %s" % exc) del(self._bullet_refs) del(self._action_refs) del(self._fire_refs) @property def top(self): """Get a list of all top-level actions.""" return [dfn for name, dfn in self.actions.iteritems() if name and name.startswith("top")] def __repr__(self): return "%s(type=%r, bullets=%r, actions=%r, fires=%r)" % ( type(self).__name__, self.type, self.bullets, self.actions, self.fires)