8bea044914be3701d9edf6d99bc447a7cea23fd6
[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 self.horizontal(params, rank)
185 vertical = self.vertical and self.vertical(params, rank)
186 return frames, horizontal, vertical
187
188 def __repr__(self):
189 return "%s(%r, horizontal=%r, vertical=%r)" % (
190 type(self).__name__, self.term, self.horizontal, self.vertical)
191
192 class BulletDef(object):
193 """Bullet definition."""
194
195 direction = None
196 speed = None
197
198 def __init__(self, doc, element):
199 self.actions = []
200 doc.bullets[element.get("label")] = self
201 for subelem in element.getchildren():
202 tag = realtag(subelem)
203 if tag == "direction":
204 self.direction = Direction(doc, subelem)
205 elif tag == "speed":
206 self.speed = Speed(doc, subelem)
207 elif tag == "action":
208 self.actions.append(ActionDef(doc, element))
209 elif tag == "actionRef":
210 self.actions.append(ActionRef(doc, element))
211
212 def __call__(self, params, rank):
213 actions = []
214 for action in self.actions:
215 actions.append(action(params, rank))
216 return (
217 self.direction and self.direction(params, rank),
218 self.speed and self.speed(params, rank),
219 actions)
220
221 def __repr__(self):
222 return "%s(direction=%r, speed=%r, actions=%r)" % (
223 type(self).__name__, self.direction, self.speed, self.actions)
224
225 class BulletRef(object):
226 """Create a bullet by name with parameters."""
227
228 def __init__(self, doc, element):
229 self.bullet = element.get("label")
230 self.params = ParamList(element)
231 doc._bullet_refs.append(self)
232
233 def __call__(self, params, rank):
234 return self.bullet(self.params(params, rank), rank)
235
236 def __repr__(self):
237 return "%s(params=%r, bullet=%r)" % (
238 type(self).__name__, self.params, self.bullet)
239
240 class ActionDef(object):
241 """Action definition."""
242
243 def __init__(self, doc, element):
244 doc.actions[element.get("label")] = self
245 self.actions = []
246 for subelem in element.getchildren():
247 tag = realtag(subelem)
248 try:
249 ctr = dict(
250 repeat=Repeat,
251 fire=FireDef,
252 fireRef=FireRef,
253 changeSpeed=ChangeSpeed,
254 changeDirection=ChangeDirection,
255 accel=Accel,
256 wait=Wait,
257 vanish=Vanish,
258 action=ActionDef,
259 actionRef=ActionRef)[tag]
260 except KeyError:
261 continue
262 else:
263 self.actions.append(ctr(doc, subelem))
264
265 def __call__(self, params, rank):
266 return self.actions, params
267
268 def __repr__(self):
269 return "%s(%r)" % (type(self).__name__, self.actions)
270
271 class ActionRef(object):
272 """Run an action by name with parameters."""
273
274 def __init__(self, doc, element):
275 self.action = element.get("label")
276 self.params = ParamList(element)
277 doc._action_refs.append(self)
278
279 def __call__(self, params, rank):
280 return self.action(self.params(params, rank), rank)
281
282 def __repr__(self):
283 return "%s(params=%r, action=%r)" % (
284 type(self).__name__, self.params, self.action)
285
286 class FireDef(object):
287 """Fire definition (creates a bullet)."""
288
289 direction = None
290 speed = None
291
292 def __init__(self, doc, element):
293 doc.fires[element.get("label")] = self
294 for subelem in element.getchildren():
295 tag = realtag(subelem)
296 if tag == "direction":
297 self.direction = Direction(doc, subelem)
298 elif tag == "speed":
299 self.speed = Speed(doc, subelem)
300 elif tag == "bullet":
301 self.bullet = BulletDef(doc, subelem)
302 elif tag == "bulletRef":
303 self.bullet = BulletRef(doc, subelem)
304 try:
305 self.bullet
306 except AttributeError:
307 raise ParseError
308
309 def __call__(self, params, rank):
310 direction, speed, actions = self.bullet(params, rank)
311 if self.direction:
312 direction = self.direction(params, rank)
313 if self.speed:
314 speed = self.speed(params, rank)
315 return direction, speed, actions
316
317 def __repr__(self):
318 return "%s(direction=%r, speed=%r, bullet=%r)" % (
319 type(self).__name__, self.direction, self.speed, self.bullet)
320
321 class FireRef(object):
322 """Fire a bullet by name with parameters."""
323
324 def __init__(self, doc, element):
325 self.fire = element.get("label")
326 self.params = ParamList(element)
327 doc._fire_refs.append(self)
328
329 def __call__(self, params, rank):
330 """Generate a Bullet from the FireDef and params."""
331 return self.fire(self.params(params, rank), rank)
332
333 def __repr__(self):
334 return "%s(params=%r, fire=%r)" % (
335 type(self).__name__, self.params, self.fire)
336
337 class BulletML(object):
338 """BulletML document.
339
340 A BulletML document is a collection of bullets, actions, and
341 firings, as well as a base game type.
342 """
343
344 CONSTRUCTORS = dict(
345 bullet=BulletDef,
346 action=ActionDef,
347 fire=FireDef,
348 )
349
350 def __init__(self, source):
351 self.bullets = {}
352 self.actions = {}
353 self.fires = {}
354
355 self._bullet_refs = []
356 self._action_refs = []
357 self._fire_refs = []
358
359 if isinstance(source, (str, unicode)):
360 source = StringIO(source)
361
362 tree = ElementTree()
363 root = tree.parse(source)
364
365 self.type = root.get("type", "none")
366
367 for element in root.getchildren():
368 tag = realtag(element)
369 if tag in self.CONSTRUCTORS:
370 self.CONSTRUCTORS[tag](self, element)
371
372 try:
373 for ref in self._bullet_refs:
374 ref.bullet = self.bullets[ref.bullet]
375 for ref in self._fire_refs:
376 ref.fire = self.fires[ref.fire]
377 for ref in self._action_refs:
378 ref.action = self.actions[ref.action]
379 except KeyError as exc:
380 raise ParseError("unknown reference %s" % exc)
381
382 del(self._bullet_refs)
383 del(self._action_refs)
384 del(self._fire_refs)
385
386 @property
387 def top(self):
388 """Get a list of all top-level actions."""
389 return [dfn for name, dfn in self.actions.iteritems()
390 if name and name.startswith("top")]
391
392 def __repr__(self):
393 return "%s(type=%r, bullets=%r, actions=%r, fires=%r)" % (
394 type(self).__name__, self.type, self.bullets, self.actions,
395 self.fires)