Foreach and Enter blocks.
[dom-editor.git] / Dome / Program.py
blob491f79f72bbc5e0f0254436bf98542197c2c570d
1 from __future__ import nested_scopes
2 import string
3 from constants import DOME_NS
4 from xml.dom import Node
6 def el_named(node, name):
7 for n in node.childNodes:
8 if n.localName == name:
9 return n
10 return None
12 # Converts a DOM <block> node to a Block object.
13 def load(node, parent):
14 #assert node.localName == 'block'
16 block = Block(parent)
17 try:
18 if int(node.getAttributeNS(None, 'foreach')):
19 block.toggle_foreach()
20 if int(node.getAttributeNS(None, 'enter')):
21 block.toggle_enter()
22 prev = block.start
23 except:
24 pass
26 id_hash = {} # id from file -> Op
27 to_link = []
28 def _load(chain, prev, exit):
29 for op_node in chain.childNodes:
30 if str(op_node.localName) == 'node':
31 attr = op_node.getAttributeNS(None, 'action')
32 action = eval(str(attr))
33 if action[0] == 'Start':
34 print "Skipping Start"
35 continue
36 if action[0] == 'chroot':
37 action[0] = 'enter'
38 elif action[0] == 'unchroot':
39 action[0] = 'leave'
40 #elif action[0] == 'set_attrib':
41 # if action[3] == '':
42 # action = ('add_attrib', action[1], action[2])
43 # else:
44 # action = ('set_attrib', action[3])
45 elif action[0] == 'playback':
46 action[0] = 'map'
47 elif action[0] == 'add_attrib':
48 action[1] = "UNUSED"
49 op = Op(action)
50 elif op_node.localName == 'block':
51 op = load(op_node, block)
52 else:
53 if op_node.nodeType == Node.ELEMENT_NODE:
54 print "** WARNING ** Unknown op:", op_node
55 continue
57 try:
58 dx = int(float(op_node.getAttributeNS(None, 'dx')))
59 if dx:
60 op.dx = dx
61 dy = int(float(op_node.getAttributeNS(None, 'dy')))
62 if dy:
63 op.dy = dy
64 except:
65 pass
67 node_id = op_node.getAttributeNS(None, 'id')
68 if node_id:
69 id_hash[node_id] = op
71 prev.link_to(op, exit)
72 exit = 'next'
73 prev = op
75 if op_node.nodeName == 'block':
76 # Block nodes have a special failure child
77 for x in op_node.childNodes:
78 if x.localName == 'fail':
79 _load(x, op, 'fail')
80 break
81 else:
82 # If the new node has children then they are the failure case
83 _load(op_node, op, 'fail')
85 link = op_node.getAttributeNS(None, 'target_fail')
86 if link:
87 to_link.append((op, 'fail', link))
88 link = op_node.getAttributeNS(None, 'target_next')
89 if link:
90 to_link.append((op, 'next', link))
91 _load(node, block.start, 'next')
92 for (op, exit, child) in to_link:
93 try:
94 to = id_hash[child]
95 except:
96 print "**** Not adding link to unknown ID ****"
97 else:
98 op.link_to(to, exit)
99 return block
101 def load_dome_program(prog):
102 "prog should be a DOM 'dome-program' node."
103 #print "Loading", prog
104 if prog.localName != 'dome-program':
105 raise Exception('Not a DOME program: %s!' % prog)
107 new = Program(str(prog.getAttributeNS(None, 'name')))
109 #print "Loading '%s'..." % new.name
110 done_update = 0
112 for node in prog.childNodes:
113 if node.localName == 'node' and not done_update:
114 print "*** Converting from old format ***"
115 new.code = load(prog, new)
116 done_update = 1
117 if node.localName == 'block':
118 assert not done_update
119 new.code = load(node, new)
120 if node.localName == 'dome-program':
121 new.add_sub(load_dome_program(node))
123 return new
125 class Program:
126 "A program contains a code Block and any number of sub-programs."
127 def __init__(self, name):
128 assert '/' not in name
130 self.code = Block(self)
132 self.name = name
133 self.subprograms = {}
134 self.watchers = []
135 self.parent = None
137 def get_path(self):
138 path = ""
139 p = self
140 while p:
141 path = p.name + '/' + path
142 p = p.parent
143 return path[:-1]
145 def changed(self, op = None):
146 if self.parent:
147 self.parent.changed(op)
148 else:
149 for w in self.watchers:
150 w.program_changed(op)
152 def tree_changed(self):
153 if self.parent:
154 self.parent.tree_changed()
155 else:
156 for w in self.watchers:
157 w.prog_tree_changed()
159 def add_sub(self, prog):
160 if prog.parent:
161 raise Exception('%s already has a parent program!' % prog.name)
162 if self.subprograms.has_key(prog.name):
163 raise Exception('%s already has a child called %s!' %
164 (self.name, prog.name))
165 prog.parent = self
166 self.subprograms[prog.name] = prog
167 self.tree_changed()
169 def remove_sub(self, prog):
170 if prog.parent != self:
171 raise Exception('%s is no child of mime!' % prog)
172 prog.parent = None
173 del self.subprograms[prog.name]
174 self.tree_changed()
176 def rename(self, name):
177 p = self.parent
178 if p:
179 if p.subprograms.has_key(name):
180 raise Exception('%s already has a child called %s!' % (p.name, name))
181 p.remove_sub(self)
182 self.name = name
183 if p:
184 p.add_sub(self)
185 else:
186 self.tree_changed()
188 def to_xml(self, doc):
189 node = doc.createElementNS(DOME_NS, 'dome-program')
190 node.setAttributeNS(None, 'name', self.name)
192 node.appendChild(self.code.to_xml(doc))
194 # Keep them in the same order to help with diffs...
195 progs = self.subprograms.keys()
196 progs.sort()
198 for name in progs:
199 p = self.subprograms[name]
200 node.appendChild(p.to_xml(doc))
202 return node
204 def __str__(self):
205 return "Program(%s)" % self.name
207 class Op:
208 "Each node in a chain is an Op. There is no graphical stuff in here."
210 def __init__(self, action = None):
211 "Creates a new node (can be linked into another node later)"
212 if not action:
213 action = ['Start']
214 self.parent = None
215 self.action = action
216 self.next = None
217 self.fail = None
218 self.prev = [] # First parent is used for rendering as a tree
219 self.dx, self.dy = (0, 0)
221 def set_parent(self, parent):
222 if self.parent == parent:
223 return
224 if parent and self.parent:
225 raise Exception('Already got a parent!')
226 nearby = self.prev[:]
227 if self.next:
228 nearby.append(self.next)
229 if self.fail:
230 nearby.append(self.fail)
231 self.parent = parent
232 [x.set_parent(parent) for x in nearby if x.parent is not parent]
234 def changed(self, op = None):
235 self.parent.changed(op or self)
237 def swap_nf(self):
238 assert self.action[0] != 'Start'
239 self.next, self.fail = (self.fail, self.next)
240 self.changed()
242 def link_to(self, child, exit):
243 # Create a link from this exit to this child Op
244 assert self.action[0] != 'Start' or exit == 'next'
245 assert child.action[0] != 'Start'
247 print "Link %s:%s -> %s" % (self, exit, child)
249 if child.parent and child.parent is not self.parent:
250 raise Exception('%s is from a different parent (%s vs %s)!' %
251 (child, child.parent, self.parent))
252 # If we already have something on this exit, and the new node has a
253 # clear next exit, move the rest of the chain there.
254 child.set_parent(self.parent)
255 current = getattr(self, exit)
256 if current:
257 if child.next:
258 raise Exception('%s already has a next exit' % child)
259 self.unlink(exit, may_delete = 0)
260 child.link_to(current, 'next')
261 child.prev.append(self)
262 setattr(self, exit, child)
263 self.changed()
265 def unlink(self, exit, may_delete = 1):
266 "Remove link from us to child"
267 assert exit in ['next', 'fail']
268 self._unlink(exit, may_delete)
269 self.changed()
271 def _unlink(self, exit, may_delete = 1):
272 child = getattr(self, exit)
273 if not child:
274 raise Exception('%s has no child on exit %s' % (self, exit))
275 if self not in child.prev:
276 raise Exception('Internal error: %s not my child!' % child)
278 child.prev.remove(self) # Only remove one copy
279 setattr(self, exit, None)
281 for x in child.prev: print x
283 if may_delete and not child.prev:
284 # There is no way to reach this child now, so unlink its children.
285 child.parent = None
286 if child.next:
287 child._unlink('next')
288 if child.fail:
289 child._unlink('fail')
291 def del_node(self):
292 """Remove this node. It there is exactly one out-going arc and one incoming one,
293 join them together. Error if:"
294 - There are multiple out-going arcs
295 - There is a single out-going arc but multiple parents"""
296 if self.next and self.fail:
297 raise Exception("Can't delete a node with both fail and next exits in use.")
298 if not self.prev:
299 raise Exception("Can't delete a Start node!")
301 prog = self.parent
303 # Find the chain to preserve (can't have both set here)
304 if self.next:
305 exit = 'next'
306 elif self.fail:
307 exit = 'fail'
308 else:
309 exit = None
311 if exit:
312 if len(self.prev) != 1:
313 raise Exception("Deleted node-chain must have a single link in")
314 prev = self.prev[0]
315 preserve = getattr(self, exit)
316 self.unlink(exit, may_delete = 0)
318 # Remove all links to us
319 for p in self.prev:
320 if p.next == self:
321 p.unlink('next')
322 if p.fail == self:
323 p.unlink('fail')
325 if exit:
326 # Relink following nodes to our (single) parent
327 prev.link_to(preserve, exit)
329 assert not self.prev
330 assert not self.next
331 assert not self.fail
332 doc = self.to_doc()
333 self.action = None
334 self.parent = None
336 prog.changed()
337 return doc
339 def to_doc(self):
340 from xml.dom import implementation
341 doc = implementation.createDocument(DOME_NS, 'dome-program', None)
342 self.to_xml_int(doc.documentElement)
343 return doc
345 def to_xml(self, doc):
346 node = doc.createElementNS(DOME_NS, 'node')
347 node.setAttributeNS(None, 'action', `self.action`)
348 node.setAttributeNS(None, 'dx', str(self.dx))
349 node.setAttributeNS(None, 'dy', str(self.dy))
350 return node
352 def to_xml_int(self, parent):
353 """Adds a chain of <Node> elements to 'parent'. Links only followed when node is
354 first parent."""
355 node = self.to_xml(parent.ownerDocument)
356 parent.appendChild(node)
358 if len(self.prev) > 1:
359 node.setAttributeNS(None, 'id', str(id(self)))
361 def add_link(op, parent):
362 node = parent.ownerDocument.createElementNS(DOME_NS, 'link')
363 parent.appendChild(node)
364 node.setAttributeNS(None, 'target', str(id(op)))
366 if self.fail:
367 if self.fail.prev[0] == self:
368 if isinstance(self, Block):
369 fail = parent.ownerDocument.createElementNS(DOME_NS, 'fail')
370 self.fail.to_xml_int(fail)
371 node.appendChild(fail)
372 else:
373 self.fail.to_xml_int(node)
374 else:
375 node.setAttributeNS(None, 'target_fail', str(id(self.fail)))
376 if self.next:
377 if self.next.prev[0] == self:
378 self.next.to_xml_int(parent)
379 else:
380 node.setAttributeNS(None, 'target_next', str(id(self.next)))
382 def __str__(self):
383 return "{" + `self.action` + "}"
385 def get_program(self):
386 p = self.parent
387 while not isinstance(p, Program):
388 p = p.parent
389 return p
391 class Block(Op):
392 """A Block is an Op which contains a group of Ops."""
394 def __init__(self, parent):
395 Op.__init__(self, action = ['Block'])
396 self.parent = parent
397 self.start = Op()
398 self.start.parent = self
399 self.foreach = 0
400 self.enter = 0
402 def set_start(self, start):
403 assert not start.prev
405 start.set_parent(self)
406 self.start = start
407 self.changed()
409 def is_toplevel(self):
410 return not isinstance(self.parent, Block)
412 def link_to(self, child, exit):
413 assert not self.is_toplevel()
414 Op.link_to(self, child, exit)
416 def to_xml(self, doc):
417 node = doc.createElementNS(DOME_NS, 'block')
418 node.setAttributeNS(None, 'foreach', str(self.foreach))
419 node.setAttributeNS(None, 'enter', str(self.enter))
420 assert not self.start.fail
421 if self.start.next:
422 self.start.next.to_xml_int(node)
423 return node
425 def toggle_enter(self):
426 self.enter = not self.enter
427 self.changed()
429 def toggle_foreach(self):
430 self.foreach = not self.foreach
431 self.changed()