More tasteful colouring.
[dom-editor.git] / Dome / Program.py
bloba18c074eaf97553ab52080e61a442e2c5fd3885d
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 prev = block.start
18 try:
19 if int(node.getAttributeNS(None, 'foreach')):
20 block.toggle_foreach()
21 if int(node.getAttributeNS(None, 'enter')):
22 block.toggle_enter()
23 if node.hasAttributeNS(None, 'restore'):
24 if int(node.getAttributeNS(None, 'restore')):
25 block.toggle_restore()
26 comment = node.getAttributeNS(None, 'comment')
27 block.set_comment(comment)
28 except:
29 pass
31 id_hash = {} # id from file -> Op
32 to_link = []
33 def _load(chain, prev, exit):
34 for op_node in chain.childNodes:
35 if str(op_node.localName) == 'node':
36 attr = op_node.getAttributeNS(None, 'action')
37 action = eval(str(attr))
38 if action[0] == 'Start':
39 print "Skipping Start"
40 continue
41 if action[0] == 'chroot':
42 action[0] = 'enter'
43 elif action[0] == 'unchroot':
44 action[0] = 'leave'
45 #elif action[0] == 'set_attrib':
46 # if action[3] == '':
47 # action = ('add_attrib', action[1], action[2])
48 # else:
49 # action = ('set_attrib', action[3])
50 elif action[0] == 'playback':
51 action[0] = 'map'
52 elif action[0] == 'add_attrib':
53 action[1] = "UNUSED"
54 op = Op(action)
55 elif op_node.localName == 'block':
56 op = load(op_node, block)
57 else:
58 if op_node.nodeType == Node.ELEMENT_NODE and op_node.localName != 'fail':
59 print "** WARNING ** Unknown op:", op_node
60 continue
62 try:
63 dx = int(float(op_node.getAttributeNS(None, 'dx')))
64 if dx:
65 op.dx = dx
66 dy = int(float(op_node.getAttributeNS(None, 'dy')))
67 if dy:
68 op.dy = dy
69 except:
70 pass
72 node_id = op_node.getAttributeNS(None, 'id')
73 if node_id:
74 id_hash[node_id] = op
76 prev.link_to(op, exit)
77 exit = 'next'
78 prev = op
80 if op_node.localName == 'block':
81 # Block nodes have a special failure child
82 for x in op_node.childNodes:
83 if x.localName == 'fail':
84 _load(x, op, 'fail')
85 break
86 else:
87 # If the new node has children then they are the failure case
88 _load(op_node, op, 'fail')
90 link = op_node.getAttributeNS(None, 'target_fail')
91 if link:
92 to_link.append((op, 'fail', link))
93 link = op_node.getAttributeNS(None, 'target_next')
94 if link:
95 to_link.append((op, 'next', link))
96 _load(node, block.start, 'next')
97 for (op, exit, child) in to_link:
98 try:
99 to = id_hash[child]
100 except:
101 print "**** Not adding link to unknown ID ****"
102 else:
103 op.link_to(to, exit)
104 return block
106 def load_dome_program(prog):
107 "prog should be a DOM 'dome-program' node."
108 #print "Loading", prog
109 if prog.localName != 'dome-program':
110 raise Exception('Not a DOME program: %s!' % prog)
112 new = Program(str(prog.getAttributeNS(None, 'name')))
114 #print "Loading '%s'..." % new.name
115 done_update = 0
117 for node in prog.childNodes:
118 if node.localName == 'node' and not done_update:
119 print "*** Converting from old format ***"
120 new.code = load(prog, new)
121 done_update = 1
122 if node.localName == 'block':
123 assert not done_update
124 new.code = load(node, new)
125 if node.localName == 'dome-program':
126 new.add_sub(load_dome_program(node))
128 new.modified = 0
129 return new
131 class Program:
132 "A program contains a code Block and any number of sub-programs."
133 def __init__(self, name):
134 assert '/' not in name
136 self.code = Block(self)
138 self.name = name
139 self.subprograms = {}
140 self.watchers = []
141 self.parent = None
142 self.modified = 0
144 def get_path(self):
145 path = ""
146 p = self
147 while p:
148 path = p.name + '/' + path
149 p = p.parent
150 return path[:-1]
152 def changed(self, op = None):
153 self.modified = 1
154 if self.parent:
155 self.parent.changed(op)
156 else:
157 for w in self.watchers:
158 w.program_changed(op)
160 def tree_changed(self):
161 self.modified = 1
162 if self.parent:
163 self.parent.tree_changed()
164 else:
165 for w in self.watchers:
166 w.prog_tree_changed()
168 def add_sub(self, prog):
169 if prog.parent:
170 raise Exception('%s already has a parent program!' % prog.name)
171 if self.subprograms.has_key(prog.name):
172 raise Exception('%s already has a child called %s!' %
173 (self.name, prog.name))
174 prog.parent = self
175 self.subprograms[prog.name] = prog
176 self.tree_changed()
178 def remove_sub(self, prog):
179 if prog.parent != self:
180 raise Exception('%s is no child of mime!' % prog)
181 prog.parent = None
182 del self.subprograms[prog.name]
183 self.tree_changed()
185 def rename(self, name):
186 p = self.parent
187 if p:
188 if p.subprograms.has_key(name):
189 raise Exception('%s already has a child called %s!' % (p.name, name))
190 p.remove_sub(self)
191 self.name = name
192 if p:
193 p.add_sub(self)
194 else:
195 self.tree_changed()
197 def to_xml(self, doc):
198 node = doc.createElementNS(DOME_NS, 'dome-program')
199 node.setAttributeNS(None, 'name', self.name)
201 node.appendChild(self.code.to_xml(doc))
203 # Keep them in the same order to help with diffs...
204 progs = self.subprograms.keys()
205 progs.sort()
207 for name in progs:
208 p = self.subprograms[name]
209 node.appendChild(p.to_xml(doc))
211 return node
213 def __str__(self):
214 return "Program(%s)" % self.name
216 class Op:
217 "Each node in a chain is an Op. There is no graphical stuff in here."
219 def __init__(self, action = None):
220 "Creates a new node (can be linked into another node later)"
221 if not action:
222 action = ['Start']
223 else:
224 action = list(action)
225 self.parent = None
226 self.action = action
227 self.next = None
228 self.fail = None
229 self.prev = [] # First parent is used for rendering as a tree
230 self.dx, self.dy = (0, 0)
232 def set_parent(self, parent):
233 if self.parent == parent:
234 return
235 if parent and self.parent:
236 raise Exception('Already got a parent!')
237 nearby = self.prev[:]
238 if self.next:
239 nearby.append(self.next)
240 if self.fail:
241 nearby.append(self.fail)
242 self.parent = parent
243 [x.set_parent(parent) for x in nearby if x.parent is not parent]
245 def changed(self, op = None):
246 if hasattr(self, 'cached_code'):
247 del self.cached_code
248 print "(remove cached code)"
249 self.parent.changed(op or self)
251 def swap_nf(self):
252 assert self.action[0] != 'Start'
253 self.next, self.fail = (self.fail, self.next)
254 self.changed()
256 def link_to(self, child, exit):
257 # Create a link from this exit to this child Op
258 # Can't link both exits to the same node (bad for tree-walking code in List)
259 assert self.action[0] != 'Start' or exit == 'next'
260 assert child.action[0] != 'Start'
261 assert child is not self
263 if (exit == 'next' and self.fail == child) or \
264 (exit == 'fail' and self.next == child):
265 raise Exception("Can't link both exits (of %s) to the same node!" % self)
267 #print "Link %s:%s -> %s" % (self, exit, child)
269 if child.parent and child.parent is not self.parent:
270 raise Exception('%s is from a different parent (%s vs %s)!' %
271 (child, child.parent, self.parent))
272 # If we already have something on this exit, and the new node has a
273 # clear next exit, move the rest of the chain there.
274 child.set_parent(self.parent)
275 current = getattr(self, exit)
276 if current:
277 if child.next:
278 raise Exception('%s already has a next exit' % child)
279 primary = current.prev[0] == self
280 self.unlink(exit, may_delete = 0)
281 child.link_to(current, 'next')
282 if primary:
283 # We were the primary parent, so we must be again...
284 current.prev.remove(child)
285 current.prev.insert(0, child)
286 child.prev.append(self)
287 setattr(self, exit, child)
288 self.changed()
290 def unlink(self, exit, may_delete = 1):
291 "Remove link from us to child"
292 assert exit in ['next', 'fail']
293 self._unlink(exit, may_delete)
294 self.changed()
296 def _unlink(self, exit, may_delete = 1):
297 child = getattr(self, exit)
298 if not child:
299 raise Exception('%s has no child on exit %s' % (self, exit))
300 if self not in child.prev:
301 raise Exception('Internal error: %s not my child!' % child)
303 child.prev.remove(self) # Only remove one copy
304 setattr(self, exit, None)
306 for x in child.prev: print x
308 if may_delete and not child.prev:
309 # There is no way to reach this child now, so unlink its children.
310 child.parent = None
311 if child.next:
312 child._unlink('next')
313 if child.fail:
314 child._unlink('fail')
316 def del_node(self):
317 """Remove this node. It there is exactly one out-going arc and one incoming one,
318 join them together. Error if:"
319 - There are multiple out-going arcs
320 - There is a single out-going arc but multiple parents"""
321 if self.next and self.fail:
322 raise Exception("Can't delete a node with both fail and next exits in use.")
323 if not self.prev:
324 raise Exception("Can't delete a Start node!")
326 prog = self.parent
328 # Find the chain to preserve (can't have both set here)
329 if self.next:
330 exit = 'next'
331 elif self.fail:
332 exit = 'fail'
333 else:
334 exit = None
336 if exit:
337 if len(self.prev) != 1:
338 raise Exception("Deleted node-chain must have a single link in")
339 prev = self.prev[0]
340 preserve = getattr(self, exit)
341 self.unlink(exit, may_delete = 0)
342 if prev.next == self:
343 exit = 'next'
344 else:
345 exit = 'fail'
347 # Remove all links to us
348 for p in self.prev[:]:
349 if p.next == self:
350 p.unlink('next')
351 if p.fail == self:
352 p.unlink('fail')
354 # Exit is now our parent's exit that leads to us...
355 if exit:
356 # Relink following nodes to our (single) parent
357 prev.link_to(preserve, exit)
359 assert not self.prev
360 assert not self.next
361 assert not self.fail
362 doc = self.to_doc()
363 self.action = None
364 self.parent = None
366 prog.changed()
367 return doc
369 def to_doc(self):
370 from Ft.Xml.cDomlette import implementation
371 doc = implementation.createDocument(DOME_NS, 'dome-program', None)
372 self.to_xml_int(doc.documentElement)
373 return doc
375 def to_xml(self, doc):
376 node = doc.createElementNS(DOME_NS, 'node')
377 node.setAttributeNS(None, 'action', `self.action`)
378 node.setAttributeNS(None, 'dx', str(self.dx))
379 node.setAttributeNS(None, 'dy', str(self.dy))
380 return node
382 def to_xml_int(self, parent):
383 """Adds a chain of <Node> elements to 'parent'. Links only followed when node is
384 first parent."""
385 node = self.to_xml(parent.ownerDocument)
386 parent.appendChild(node)
388 if len(self.prev) > 1:
389 node.setAttributeNS(None, 'id', str(id(self)))
391 def add_link(op, parent):
392 node = parent.ownerDocument.createElementNS(DOME_NS, 'link')
393 parent.appendChild(node)
394 node.setAttributeNS(None, 'target', str(id(op)))
396 if self.fail:
397 if self.fail.prev[0] == self:
398 if isinstance(self, Block):
399 fail = parent.ownerDocument.createElementNS(DOME_NS, 'fail')
400 self.fail.to_xml_int(fail)
401 node.appendChild(fail)
402 else:
403 self.fail.to_xml_int(node)
404 else:
405 node.setAttributeNS(None, 'target_fail', str(id(self.fail)))
406 if self.next:
407 if self.next.prev[0] == self:
408 self.next.to_xml_int(parent)
409 else:
410 node.setAttributeNS(None, 'target_next', str(id(self.next)))
412 def __str__(self):
413 return "{" + `self.action` + "}"
415 def get_program(self):
416 p = self.parent
417 while p and not isinstance(p, Program):
418 p = p.parent
419 return p
421 class Block(Op):
422 """A Block is an Op which contains a group of Ops."""
424 def __init__(self, parent):
425 Op.__init__(self, action = ['Block'])
426 self.parent = parent
427 self.start = Op()
428 self.start.parent = self
429 self.foreach = 0
430 self.enter = 0
431 self.restore = 0
432 self.comment = ''
434 def set_start(self, start):
435 assert not start.prev
437 start.set_parent(self)
438 self.start = start
439 self.changed()
441 def is_toplevel(self):
442 return not isinstance(self.parent, Block)
444 def link_to(self, child, exit):
445 assert not self.is_toplevel()
446 Op.link_to(self, child, exit)
448 def to_xml(self, doc):
449 node = doc.createElementNS(DOME_NS, 'block')
450 node.setAttributeNS(None, 'foreach', str(self.foreach))
451 node.setAttributeNS(None, 'enter', str(self.enter))
452 node.setAttributeNS(None, 'restore', str(self.restore))
453 node.setAttributeNS(None, 'comment', str(self.comment))
454 assert not self.start.fail
455 if self.start.next:
456 self.start.next.to_xml_int(node)
457 return node
459 def toggle_restore(self):
460 self.restore = not self.restore
461 self.changed()
463 def toggle_enter(self):
464 self.enter = not self.enter
465 self.changed()
467 def toggle_foreach(self):
468 self.foreach = not self.foreach
469 self.changed()
471 def set_comment(self, comment):
472 self.comment = comment
473 self.changed()