--- /dev/null
+"""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)
+
+ 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)
+
+ 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, element))
+ elif tag == "actionRef":
+ self.actions.append(ActionRef(doc, element))
+
+ def __call__(self, params, rank):
+ actions = []
+ for action in self.actions:
+ try:
+ actions.append((action.params(params), action.action))
+ except AttributeError:
+ actions.append((params, action))
+ 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)
+ 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)