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