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