933be80847c0b31c186173b82c2ee418712f93a2
[python-bulletml.git] / bulletml / parser.py
1 """BulletML parser.
2
3 http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html
4 """
5
6 from __future__ import division
7
8 from xml.etree.ElementTree import ElementTree
9
10 try:
11 from cStringIO import StringIO
12 except ImportError:
13 from StringIO import StringIO
14
15 from bulletml.errors import Error
16 from bulletml.expr import NumberDef, INumberDef
17
18 class ParseError(Error):
19 """Raised when an error occurs parsing the XML structure."""
20 pass
21
22 def realtag(element):
23 """Strip namespace poop off the front of a tag."""
24 try:
25 return element.tag.rsplit('}', 1)[1]
26 except ValueError:
27 return element.tag
28
29 class ParamList(object):
30 """List of parameter definitions."""
31 def __init__(self, element):
32 self.params = []
33 if element:
34 for subelem in element:
35 if realtag(subelem) == "param":
36 self.params.append(NumberDef(subelem.text))
37
38 def __call__(self, params, rank):
39 return [param(params, rank) for param in self.params]
40
41 def __repr__(self):
42 return "%s(%r)" % (type(self).__name__, self.params)
43
44 class Direction(object):
45 """Raw direction value."""
46
47 def __init__(self, doc, element, type="absolute"):
48 self.type = element.get("type", type)
49 self.value = NumberDef(element.text)
50
51 def __call__(self, params, rank):
52 return (self.value(params, rank), self.type)
53
54 def __repr__(self):
55 return "%s(%r, type=%r)" % (
56 type(self).__name__, self.value, self.type)
57
58 class ChangeDirection(object):
59 """Direction change over time."""
60
61 def __init__(self, doc, element):
62 for subelem in element.getchildren():
63 tag = realtag(subelem)
64 if tag == "direction":
65 self.direction = Direction(doc, subelem)
66 elif tag == "term":
67 self.term = INumberDef(subelem.text)
68 try:
69 self.term, self.direction
70 except AttributeError:
71 raise ParseError
72
73 def __call__(self, params, rank):
74 return self.term(params, rank), self.direction(params, rank)
75
76 def __repr__(self):
77 return "%s(term=%r, direction=%r)" % (
78 type(self).__name__, self.term, self.direction)
79
80 class Speed(object):
81 """Raw speed value."""
82
83 def __init__(self, doc, element, type="absolute"):
84 self.type = element.get("type", type)
85 self.value = NumberDef(element.text)
86
87 def __call__(self, params, rank):
88 return (self.value(params, rank), self.type)
89
90 def __repr__(self):
91 return "%s(%r, type=%r)" % (type(self).__name__, self.value, self.type)
92
93 class ChangeSpeed(object):
94 """Speed change over time."""
95
96 def __init__(self, doc, element):
97 for subelem in element.getchildren():
98 tag = realtag(subelem)
99 if tag == "speed":
100 self.speed = Speed(doc, subelem)
101 elif tag == "term":
102 self.term = INumberDef(subelem.text)
103 try:
104 self.term, self.speed
105 except AttributeError:
106 raise ParseError
107
108 def __call__(self, params, rank):
109 return self.term(params, rank), self.speed(params, rank)
110
111 def __repr__(self):
112 return "%s(term=%r, speed=%r)" % (
113 type(self).__name__, self.term, self.speed)
114
115 class Wait(object):
116 """Wait for some frames."""
117 def __init__(self, doc, element):
118 self.frames = INumberDef(element.text)
119
120 def __call__(self, params, rank):
121 return self.frames(params, rank)
122
123 def __repr__(self):
124 return "%s(%r)" % (type(self).__name__, self.frames)
125
126 class Vanish(object):
127 """Make the owner disappear."""
128 def __init__(self, doc, element):
129 pass
130
131 def __repr__(self):
132 return "%s()" % (type(self).__name__)
133
134 class Repeat(object):
135 """Repeat an action definition."""
136
137 def __init__(self, doc, element):
138 for subelem in element.getchildren():
139 tag = realtag(subelem)
140 if tag == "times":
141 self.times = INumberDef(subelem.text)
142 elif tag == "action":
143 self.action = ActionDef(doc, subelem)
144 elif tag == "actionRef":
145 self.action = ActionRef(doc, subelem)
146
147 try:
148 self.times, self.action
149 except AttributeError:
150 raise ParseError
151
152 def __call__(self, params, rank):
153 return self.times(params, rank), self.action(params, rank)
154
155 def __repr__(self):
156 return "%s(%r, %r)" % (type(self).__name__, self.times, self.action)
157
158 class Accel(object):
159 """Accelerate over some time."""
160
161 horizontal = None
162 vertical = None
163
164 def __init__(self, doc, element):
165 for subelem in element.getchildren():
166 tag = realtag(subelem)
167 if tag == "term":
168 self.term = INumberDef(subelem.text)
169 elif tag == "horizontal":
170 self.horizontal = Speed(doc, subelem)
171 elif tag == "vertical":
172 self.vertical = Speed(doc, subelem)
173
174 try:
175 self.term
176 except AttributeError:
177 raise ParseError
178
179 def __call__(self, params, rank):
180 frames = self.term(params, rank)
181 horizontal = self.horizontal and self.horizontal(params, rank)
182 vertical = self.vertical and self.vertical(params, rank)
183 return frames, horizontal, vertical
184
185 def __repr__(self):
186 return "%s(%r, horizontal=%r, vertical=%r)" % (
187 type(self).__name__, self.term, self.horizontal, self.vertical)
188
189 class BulletDef(object):
190 """Bullet definition."""
191
192 direction = None
193 speed = None
194
195 def __init__(self, doc, element):
196 self.actions = []
197 doc.bullets[element.get("label")] = self
198 for subelem in element.getchildren():
199 tag = realtag(subelem)
200 if tag == "direction":
201 self.direction = Direction(doc, subelem)
202 elif tag == "speed":
203 self.speed = Speed(doc, subelem)
204 elif tag == "action":
205 self.actions.append(ActionDef(doc, subelem))
206 elif tag == "actionRef":
207 self.actions.append(ActionRef(doc, subelem))
208
209 def __call__(self, params, rank):
210 actions = [action(params, rank) for action in self.actions]
211 return (
212 self.direction and self.direction(params, rank),
213 self.speed and self.speed(params, rank),
214 actions)
215
216 def __repr__(self):
217 return "%s(direction=%r, speed=%r, actions=%r)" % (
218 type(self).__name__, self.direction, self.speed, self.actions)
219
220 class BulletRef(object):
221 """Create a bullet by name with parameters."""
222
223 def __init__(self, doc, element):
224 self.bullet = element.get("label")
225 self.params = ParamList(element)
226 doc._bullet_refs.append(self)
227
228 def __call__(self, params, rank):
229 return self.bullet(self.params(params, rank), rank)
230
231 def __repr__(self):
232 return "%s(params=%r, bullet=%r)" % (
233 type(self).__name__, self.params, self.bullet)
234
235 class ActionDef(object):
236 """Action definition."""
237
238 def __init__(self, doc, element):
239 doc.actions[element.get("label")] = self
240 self.actions = []
241 for subelem in element.getchildren():
242 tag = realtag(subelem)
243 try:
244 ctr = dict(
245 repeat=Repeat,
246 fire=FireDef,
247 fireRef=FireRef,
248 changeSpeed=ChangeSpeed,
249 changeDirection=ChangeDirection,
250 accel=Accel,
251 wait=Wait,
252 vanish=Vanish,
253 action=ActionDef,
254 actionRef=ActionRef)[tag]
255 except KeyError:
256 continue
257 else:
258 self.actions.append(ctr(doc, subelem))
259
260 def __call__(self, params, rank):
261 return self.actions, params
262
263 def __repr__(self):
264 return "%s(%r)" % (type(self).__name__, self.actions)
265
266 class ActionRef(object):
267 """Run an action by name with parameters."""
268
269 def __init__(self, doc, element):
270 self.action = element.get("label")
271 self.params = ParamList(element)
272 doc._action_refs.append(self)
273
274 def __call__(self, params, rank):
275 return self.action(self.params(params, rank), rank)
276
277 def __repr__(self):
278 return "%s(params=%r, action=%r)" % (
279 type(self).__name__, self.params, self.action)
280
281 class FireDef(object):
282 """Fire definition (creates a bullet)."""
283
284 direction = None
285 speed = None
286
287 def __init__(self, doc, element):
288 doc.fires[element.get("label")] = self
289 for subelem in element.getchildren():
290 tag = realtag(subelem)
291 if tag == "direction":
292 self.direction = Direction(doc, subelem, "aim")
293 elif tag == "speed":
294 self.speed = Speed(doc, subelem)
295 elif tag == "bullet":
296 self.bullet = BulletDef(doc, subelem)
297 elif tag == "bulletRef":
298 self.bullet = BulletRef(doc, subelem)
299 try:
300 self.bullet
301 except AttributeError:
302 raise ParseError
303
304 def __call__(self, params, rank):
305 direction, speed, actions = self.bullet(params, rank)
306 if self.direction:
307 direction = self.direction(params, rank)
308 if self.speed:
309 speed = self.speed(params, rank)
310 return direction, speed, actions
311
312 def __repr__(self):
313 return "%s(direction=%r, speed=%r, bullet=%r)" % (
314 type(self).__name__, self.direction, self.speed, self.bullet)
315
316 class FireRef(object):
317 """Fire a bullet by name with parameters."""
318
319 def __init__(self, doc, element):
320 self.fire = element.get("label")
321 self.params = ParamList(element)
322 doc._fire_refs.append(self)
323
324 def __call__(self, params, rank):
325 """Generate a Bullet from the FireDef and params."""
326 return self.fire(self.params(params, rank), rank)
327
328 def __repr__(self):
329 return "%s(params=%r, fire=%r)" % (
330 type(self).__name__, self.params, self.fire)
331
332 class BulletML(object):
333 """BulletML document.
334
335 A BulletML document is a collection of bullets, actions, and
336 firings, as well as a base game type.
337 """
338
339 CONSTRUCTORS = dict(
340 bullet=BulletDef,
341 action=ActionDef,
342 fire=FireDef,
343 )
344
345 def __init__(self, source):
346 self.bullets = {}
347 self.actions = {}
348 self.fires = {}
349
350 self._bullet_refs = []
351 self._action_refs = []
352 self._fire_refs = []
353
354 if isinstance(source, (str, unicode)):
355 source = StringIO(source)
356
357 tree = ElementTree()
358 root = tree.parse(source)
359
360 self.type = root.get("type", "none")
361
362 for element in root.getchildren():
363 tag = realtag(element)
364 if tag in self.CONSTRUCTORS:
365 self.CONSTRUCTORS[tag](self, element)
366
367 try:
368 for ref in self._bullet_refs:
369 ref.bullet = self.bullets[ref.bullet]
370 for ref in self._fire_refs:
371 ref.fire = self.fires[ref.fire]
372 for ref in self._action_refs:
373 ref.action = self.actions[ref.action]
374 except KeyError as exc:
375 raise ParseError("unknown reference %s" % exc)
376
377 del(self._bullet_refs)
378 del(self._action_refs)
379 del(self._fire_refs)
380
381 @property
382 def top(self):
383 """Get a list of all top-level actions."""
384 return [dfn for name, dfn in self.actions.iteritems()
385 if name and name.startswith("top")]
386
387 def __repr__(self):
388 return "%s(type=%r, bullets=%r, actions=%r, fires=%r)" % (
389 type(self).__name__, self.type, self.bullets, self.actions,
390 self.fires)