Parser, lacking most implementation.
authorJoe Wreschnig <joe.wreschnig@gmail.com>
Tue, 16 Mar 2010 02:32:35 +0000 (19:32 -0700)
committerJoe Wreschnig <joe.wreschnig@gmail.com>
Tue, 16 Mar 2010 02:32:35 +0000 (19:32 -0700)
bulletml/parser.py [new file with mode: 0644]

diff --git a/bulletml/parser.py b/bulletml/parser.py
new file mode 100644 (file)
index 0000000..10459cc
--- /dev/null
@@ -0,0 +1,398 @@
+"""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)