Instead of editing nested textual expressions, edit the whole expression.
[l3full.git] / l3lang / view.py
bloba568931a71e91e0f92c78fc7927057d214650745
2 # Author: Michael H. Hohn (mhhohn@lbl.gov)
4 # Copyright (c) 2006, The Regents of the University of California
5 #
6 # See legal.txt and license.txt
9 """
10 Extra tools for viewing l3 trees in various ways.
11 """
12 import re, pdb, sys, pprint, os
13 from pprint import pprint
14 from l3lang import ast, utils
15 from l3lang.globals import logger
16 # AST types
17 from l3lang.ast import \
18 Call, \
19 Float, \
20 Function, \
21 If, \
22 Immediate, \
23 Inline, \
24 Int, \
25 Labeled, \
26 Let, \
27 List, \
28 Macro, \
29 Map, \
30 Marker, \
31 MarkerTyped, \
32 Member, \
33 Native, \
34 Nested, \
35 Program, \
36 Return, \
37 Set, \
38 String, \
39 Subdir, \
40 Symbol, \
41 Tuple, \
42 aList, \
43 aNone, \
44 astType,\
45 cls_viewList
46 # viewList
48 # Others.
49 from l3lang.ast import TreeWork
51 from types import *
53 #* Misc.
54 def strip_comment(st):
55 # strip comments off a repr() string -- uses \\n etc.
56 return (re.sub(r"#.*?\\n", "", st))
58 def combine_newlines(st):
59 # combine trailing form-feeds off a repr() string -- uses \\l etc.
60 return re.sub(r'(\\l){1,}$', r'\\l', st)
62 def leading_lines(str, num_lines = 4, max_len = 80):
63 # Return the first num_lines empty lines of str, each
64 # truncated to max_len
66 lines = str.split("\n")
67 llen = len(lines)
68 # Remove empty lines.
69 lines = filter(lambda s: len(s) != 0 and (not s.isspace()), lines)
70 # Use first num_lines
71 lines = lines[0:num_lines]
72 # Trim to max_len
73 def shorten(ll):
74 nl = ll[0:max_len]
75 if len(ll) > max_len:
76 return nl + "..."
77 return nl
78 lines = map(shorten, lines)
79 if llen > num_lines:
80 lines.append('...')
81 return lines
84 #* AST printing
85 #** prefix tree dump
87 # Trivial And Ugly -- but useful.
88 # Use a stream with .write() method.
90 # Indentation is handled by Nested() types; Immediates always print.
92 #*** immediates
93 def prefix_dump(self, stream, indent = 0):
94 stream.write(str(self._primary[0]) + " ")
95 Immediate.prefix_dump = prefix_dump
97 def prefix_dump(self, stream, indent = 0):
98 stream.write(repr(self._primary[0]) + " ")
99 String.prefix_dump = prefix_dump
101 def prefix_dump(self, stream, indent = 0):
102 a, b = self._primary
103 a.prefix_dump(stream, indent)
104 stream.write( "." )
105 b.prefix_dump(stream, indent)
106 Member.prefix_dump = prefix_dump
108 #*** nested
109 def prefix_dump(self, stream, indent = 0):
110 # Output format is usually (name args)
111 full_name = self.__class__.__name__
112 # name -> print name
113 name_map = {
114 "Program" : "",
115 "Map" : "map",
116 "Set" : "set",
117 "Call" : "call",
118 "Call" : "",
120 # Head
121 print_name = name_map.get(full_name, full_name)
122 stream.write("(%s " % print_name )
123 # Body
124 indent += 1
125 for sub in (self._primary):
126 stream.write("\n" + "\t" * indent) # nl / indent
127 sub.prefix_dump(stream, indent)
128 # Tail
129 indent -= 1
130 stream.write(")")
131 ## stream.write("\n" + "\t" * indent) # nl / indent
132 Nested.prefix_dump = prefix_dump
134 def prefix_dump(self, stream, indent = 0):
135 # Head.
136 stream.write( "(")
137 # Body.
138 indent += 1
139 for sub in self:
140 stream.write("\n" + "\t" * indent) # nl / indent
141 sub.prefix_dump(stream, indent)
142 # Tail
143 indent -= 1
144 stream.write(")" )
145 ## stream.write("\n" + "\t" * indent) # nl / indent
146 aList.prefix_dump = prefix_dump
149 #** infix tree formation (pretty-printing)
151 # Ignores precedence for now; that can be added in a separate pass
152 # providing annnotations.
154 # output format
155 # ----------------------
156 # Output format defaults to Python syntax; this may lead to invalid
157 # strings if l3's nesting abilities are used heavily (like a
158 # function definition inside an Environment, which in Python would be
159 # a `def` inside a dict ({}), and is invalid.
161 # A l3-syntax output formatter can be added later.
163 # Error handling
164 # ----------------------
165 # To find an error, a printout is needed. Thus, raising
166 # exceptions in printing routines is a bad idea. Instead, errors are
167 # inserted into the output as comments.
169 # Immediate.infix_string includes
170 # Bool
171 # Int
172 # Float
173 # Complex
174 # Symbol
176 # Types that don't (yet) need .infix_string():
177 # Macro
178 # Native
181 #*** pretty-printer imports
182 from l3lang.pretty import \
183 DocNil, \
184 DocCons, \
185 DocText, \
186 DocNest, \
187 DocBreak, \
188 DocGroupFlat, \
189 DocGroupBreak, \
190 DocGroupFill, \
191 DocGroupAuto
193 B = lambda : DocBreak(" ")
194 BW = lambda s : DocBreak(s)
195 C = lambda x,y: DocCons(x, y)
196 T = lambda t : DocText(t)
197 G = lambda x : DocGroupAuto(x)
198 GB = lambda x : DocGroupBreak(x)
199 GF = lambda x : DocGroupFill(x)
200 GFL = lambda x : DocGroupFlat(x)
201 I = lambda r, x : DocNest(r, x)
204 #*** main entry points
205 def get_infix_string(self, width, comment_dct = {}):
206 ''' Return a pretty-printed representation of self.
207 The string width is kept below `width` when possible.
208 If the (tree id -> comment string) dict (comment_dct) is provided,
209 include comments.
212 S = utils.Shared()
213 S._comment = comment_dct
215 # Comment printing requires a .setup() tree.
216 if comment_dct:
217 if not hasattr(self, '_id'):
218 logger.warn("get_infix_string: argument is not set up and "
219 "cannot be printed with comments.")
220 S._comment = {}
221 # Statement / expression distinction.
222 S._in_statement = [True]
223 S.es = lambda : S._in_statement.append(False) # expression start
224 S.ee = lambda : S._in_statement.pop()
225 S.ss = lambda : S._in_statement.append(True) # statement start
226 S.se = lambda : S._in_statement.pop()
227 S.in_statement = lambda : S._in_statement[-1]
229 return self.infix_string(S).toString(width)
230 astType.get_infix_string = get_infix_string
231 aList.get_infix_string = get_infix_string
232 aNone.get_infix_string = get_infix_string
233 Native.get_infix_string = get_infix_string
235 def tree_difference(self, self_tbl, pptree, pptree_tbl):
236 ''' Compares trees (including comments) and returns a difference description.
237 For eql trees, returns "".
239 o_it = self.top_down()
240 pp_it = pptree.top_down()
241 while 1:
242 try: o_nd = o_it.next()
243 except StopIteration: break
244 pp_nd = pp_it.next()
246 # Trees equal?
247 if o_nd.eql_1(pp_nd):
248 if self_tbl.has_key(o_nd._id):
249 if self_tbl[o_nd._id].eql_1(pptree_tbl[pp_nd._id]):
250 continue
251 else:
252 return ("Tree comment difference from line %d, col %d\n"
253 " to line %d, col %d\n"
254 % (o_nd._first_char[0], o_nd._first_char[1],
255 pp_nd._first_char[0], pp_nd._first_char[1]))
256 else:
257 continue
258 else:
259 return ("Tree difference: line %d, col %d\n original:\n%s\n"
260 " new:\n%s\n"
262 (pp_nd._first_char[0], pp_nd._first_char[1],
263 str(o_nd), str(pp_nd)))
264 return ""
265 astType.tree_difference = tree_difference
267 def tree_difference(self, self_tbl, pptree, pptree_tbl):
268 return ""
269 Native.tree_difference = tree_difference
272 #*** comment functions
273 def with_comment(self, S, body):
274 if S._comment:
275 try:
276 if S._comment.has_key(self._id):
277 # Multi-line comments must be properly indented.
278 # Form every line separately and assemble the group.
279 comm_lines = S._comment[self._id].split('\n') # windows?
280 while comm_lines[-1] == '': # Strip trailing blanks
281 comm_lines.pop()
282 comment = DocNil()
283 for line in comm_lines:
284 comment |= T("#" + line) | B()
285 return GB(comment | body)
286 except AttributeError:
287 return body # Ignore missing self._id
288 return body
289 astType.with_comment = with_comment
290 aList.with_comment = with_comment
291 Native.with_comment = with_comment
294 #*** immediates
295 def infix_string(self, S):
296 return self.with_comment(S, T(str(self._primary[0])))
297 Immediate.infix_string = infix_string
299 def infix_string(self, S):
300 # Using repr() introduces escaped escapes ('\\n' instead of '\n')
301 # so that `print repr(a)` == a.
302 # But using repr() inside another string without print is wrong
303 # when PARSING the contents:
304 # aa = "a\nb"
305 # repr(aa)
306 # -> "'a\\nb'"
307 # Parsing this will read \\n instead of \n. The \\n is correct
308 # only within a string-conversion pass.
310 # str(aa) converts unprintables into ascii
311 # repr(aa) converts unprintables into ascii and escapes when needed
313 # Here, we only want unprintables -> ascii plus correct quotes.
315 got = lambda sub : self._primary[0].find(sub) != -1
316 if got('\n'):
317 if got("'''"):
318 if got('"""'):
319 raise Exception("String too complicated for infix_string.")
320 else:
321 pstr = '"""%s"""' % self._primary[0]
322 else:
323 pstr = "'''%s'''" % self._primary[0]
325 return self.with_comment(S, T(pstr))
327 if got('"'):
328 if got("'"):
329 if got("'''"):
330 if got('"""'):
331 raise Exception("String too complicated for infix_string.")
332 else:
333 pstr = '"""%s"""' % self._primary[0]
334 else:
335 pstr = "'''%s'''" % self._primary[0]
336 else:
337 pstr = "'%s'" % self._primary[0]
338 else:
339 pstr = '"%s"' % self._primary[0]
341 return self.with_comment(S, T(pstr))
342 String.infix_string = infix_string
345 #*** nested
346 def infix_string(self, S):
347 return self.with_comment(S, GB(T("program:") |
348 I(4, (B() |
349 self[0].infix_string(S) ))))
350 Program.infix_string = infix_string
352 def infix_string(self, S):
353 return self.with_comment(S, GB(T("subdir:") |
354 I(4, (B() |
355 self[0].infix_string(S) ))))
356 Subdir.infix_string = infix_string
358 def infix_string(self, S):
360 # return
361 # foo
363 # is invalid; requires \ as break character:
365 # return \
366 # foo
367 # Or use GroupFill to force first element onto same line.
368 return self.with_comment(S, GF( T('return ') |
369 self[0].infix_string(S)))
370 Return.infix_string = infix_string
372 def infix_string(self, S):
373 # {|| }
374 S.es()
375 body = (T('{|') | self[0].infix_string(S) | # args
376 T('|') |
377 I(4, (B() |
378 self[1].infix_string(S))) | # body
379 B() |
380 T('}')
382 S.ee()
383 return self.with_comment(S, G(body))
384 Function.infix_string = infix_string
386 def infix_string(self, S):
387 fname = self.calltree_str() # function name.
388 if fname[0].isalpha():
389 # Standard prefix form f(a,b); ignore special cases for f.
390 body = T(fname) | T('(')
391 S.es()
392 body |= (I(4, self[1].infix_string(S)) | # arguments.
393 T(')'))
394 S.ee()
395 return self.with_comment(S, G(body))
397 else:
398 if self.nargs() == 1:
399 # Assume unary prefix operator.
400 S.es()
401 body = GF( T('(') |
402 T(fname) |
403 I(4, self[1].infix_string(S)) | # arguments.
404 T(')'))
405 S.ee()
406 return self.with_comment(S, body)
408 else:
409 # Assume binary infix operator.
410 left, right = self.positional_args()
411 S.es()
412 body = GF( T('(') |
413 left.infix_string(S) |
414 B() |
415 T(fname) |
416 B() |
417 right.infix_string(S) |
418 T(')'))
419 S.ee()
420 return self.with_comment(S, body)
421 Call.infix_string = infix_string
423 def infix_string(self, S):
424 # a.b
425 a, b = self._primary
426 return self.with_comment(S, G(a.infix_string(S) |
427 T(".") |
428 b.infix_string(S)))
429 Member.infix_string = infix_string
431 def infix_string(self, S):
432 # if cond:
433 # yes
434 # else:
435 # no
436 cond, yes, no = self._primary
437 if len(no) > 0:
438 S.ss()
439 the_else = (B()|
440 T("else:") |
441 I(4, B() | no.infix_string(S)))
442 S.se()
443 else:
444 the_else = DocNil()
446 if not S.in_statement():
447 body = T("# ERROR: For printing, If must not be in an expression.") | B()
448 else:
449 body = DocNil()
451 body |= T("if ")
452 S.es()
453 body |= cond.infix_string(S)
454 S.ee()
455 S.ss()
456 body |= T(":") | I(4, B() | yes.infix_string(S)) | the_else
457 S.se()
458 return self.with_comment(S, GB(body))
459 If.infix_string = infix_string
461 def infix_string(self, S):
462 # lhs = rhs
463 # Special case for `def foo(a,b): body`
464 # def LNAME(LARGS):
465 # LBODY
466 ma = ast.Matcher()
467 if ma.match(self,
468 Set(MarkerTyped(Symbol('LNAME'), Symbol('symbol')),
469 Function(MarkerTyped(Symbol('LARGS'), aList([])),
470 MarkerTyped(Symbol('LBODY'), aList([]))))):
471 ma['LNAME']
472 body = T('def ') | ma['LNAME'].infix_string(S) | T('(')
473 S.es()
474 body |= ma['LARGS'].infix_string(S) | T('):')
475 S.ee()
476 body |= I(4, B() | ma['LBODY'].infix_string(S))
477 return self.with_comment(S, GB(body))
479 else:
480 lhs, rhs = self._primary
481 return self.with_comment(S, GF(lhs.infix_string(S) |
482 T('=') |
483 rhs.infix_string(S)))
484 Set.infix_string = infix_string
486 def infix_string(self, S):
487 # [ body ]
488 S.es()
489 body = G( T('[') |
490 I(4, (B() |
491 self[0].infix_string(S)))|
492 B() |
493 T(']'))
494 S.ee()
495 return self.with_comment(S, body)
496 List.infix_string = infix_string
499 def infix_string(self, S):
500 body = GF(T('if ("outline",') | B() |
501 T(repr(self.get_label())) |
502 B() | T("):"))
503 S.ss()
504 body |= I(4, B() | self[0].infix_string(S))
505 S.se()
506 return self.with_comment(S, GB(body))
507 cls_viewList.infix_string = infix_string
510 def infix_string(self, S):
511 # { body }
512 S.es()
513 body = G( T('{') |
514 I(4, (B() |
515 self[0].infix_string(S)))|
516 B() |
517 T('}'))
518 S.ee()
519 return self.with_comment(S, body)
520 Map.infix_string = infix_string
522 def infix_string(self, S):
523 # ( body )
524 S.es()
525 body = GF( T('(') |
526 I(1, self[0].infix_string(S)) |
527 T(')'))
528 S.ee()
529 return self.with_comment(S, body)
530 Tuple.infix_string = infix_string
533 #*** Specials
534 def infix_string(self, S):
535 return DocNil()
536 aNone.infix_string = infix_string
538 def infix_string(self, S):
539 return self.with_comment(S, T(repr(self._primary[0])))
540 Native.infix_string = infix_string
543 def infix_string(self, S):
544 # inline string
545 return self.with_comment(S, GFL( T('inline') | B() | self[0].infix_string(S)))
546 Inline.infix_string = infix_string
548 def infix_string(self, S):
549 body = DocNil()
550 if len(self) > 0:
551 if S.in_statement():
552 # inside program, def: ';' '\n' (no ',')
553 for sub in self:
554 body = body | sub.infix_string(S) | BW(';')
555 body = body.head() # strip BW(';')
556 else:
557 # inside list, dict: ' ' '\n' (always with ',')
558 for sub in self:
559 body = body | sub.infix_string(S) | T(',') | B()
560 body = body.head().head() # strip T(',') | B()
562 return self.with_comment(S, G( body ))
563 aList.infix_string = infix_string
566 #* AST information
567 #** print_references
568 # As of [Fri Sep 17 11:35:26 2004],
569 # A tree's ._id is used in
570 # (astType) self._id
571 # self._primary_ids
572 # child._parent
573 # (RamMem) storage.store / load
574 # storage.get_attribute / set_attributes
575 # (IncEval) storage.ie_.touch_setup / touch / get_timestamp
576 # has_clone
577 # (Env) env.bind_id / lookup_symbol_id
579 def print_references(storage, tree_id, indent = 0):
580 # Print information about references to tree_id, from various
581 # sources.
583 # Use via
584 # print_references(storage, 140163)
585 # and together with
586 # pprint(user_env.dump())
587 # print_info(storage, 140163)
589 tree = storage.load(tree_id)
590 print ' '*indent, 'id / string:', tree_id, tree.source_substring()
591 print ' '*indent, 'tree:', tree
593 # Parent referring to tree.
594 parent_id = tree.parent_id()
595 if parent_id is None:
596 print ' '*indent, "no parent"
597 else:
598 parent = storage.load(parent_id)
599 print ' '*indent, "index %d of parent %d" % (parent.find_child_index(tree_id),
600 parent_id,
602 # Children referring to tree
603 child_id_l = tree.childrens_ids()
604 for ch in child_id_l:
605 print ' '*indent, "has child %d" % ch
606 if storage.load(ch).parent_id() != tree_id:
607 print ' '*indent, "warning: child has other parent"
609 # Tree's Env's
610 for cmd_s in ['tree._arg_env', # block
611 'tree._block_env', # block
612 'tree._def_env', # block
613 'tree._eval_env', # some Nested()s
614 'storage.get_attribute(tree_id, "interp_env")',
616 try:
617 env = eval(cmd_s)
619 if env._program == tree:
620 print ' '*indent, cmd_s, '._program'
622 # name -> value, tree may be value
623 # (name, 'ie_status') -> timestamp
624 the_name = None
625 if isinstance(tree, Symbol):
626 the_name = tree.as_index()
627 for nm, val in env._bindings.iteritems():
628 if val == tree:
629 print ' '*indent, cmd_s, '._bindings[', repr(nm), ']'
630 if nm == the_name:
631 print ' '*indent, cmd_s, '._bindings[', repr(nm), ']'
632 if nm == (the_name, 'ie_status'):
633 print ' '*indent, cmd_s, '._bindings[', \
634 repr((the_name, 'ie_status')), ']'
636 # name -> id, tree_id may == id
637 for nm, id in env._bindings_ids.iteritems():
638 if id == tree_id:
639 print ' '*indent, cmd_s, '._bindings_ids[', repr(nm), ']'
641 except AttributeError:
642 print ' '*indent, 'no ', cmd_s, 'attribute'
643 continue
645 def print_all_references(storage, tree_id, indent = 0):
646 # Traverse all clones (recursively) and call print_references()
647 # for each.
649 print ' '*indent, "---------------------- original data"
650 print_references(storage, tree_id, indent = indent)
652 clone_l = storage.get_attribute(tree_id, "interp_clone")
653 if clone_l != None:
654 print ' '*indent, "---------------------- clone data"
655 for clone in clone_l:
656 print_all_references(storage, clone, indent = indent + 4)
660 #** print_info: prefix format tabular dump
661 def print_info(storage, tree,
662 show_macros = False,
663 lead_indent = 0,
664 node_width = 50,
665 max_node_len = 20,
666 per_level_indent = 4,
667 space = ' ',
668 custom = "nd._id",
671 # `custom` is a caller-chosen expression; may use `nd` for the
672 # current node.
674 # Print id & timestamp with leading parts of the ast, in a prefix
675 # format.
676 # This prefix dump ignores clones.
678 lead = space * lead_indent * per_level_indent
679 # columns are
680 # lead id time [custom] indent node-string
681 print "%s%5s %6s %8s %s %s" % (lead, 'id', 'time', custom, '', 'node')
682 print "%s--------------------------------------------" % lead
683 for nd, indent in tree.top_down_indented():
684 # id = nd._id fails w/o ._id attribute.
685 id = nd.__dict__.get('_id')
687 # Print main tree.
688 nd_str = str(nd) # Inefficient string formation...
690 # Prune nested trees
691 if isinstance(nd, (Nested, aList)):
692 nd_str = nd_str[0:nd_str.find('(')]
694 # Prune long items
695 if isinstance(nd, (String, Symbol)):
696 if len(nd_str) + indent * per_level_indent > node_width and \
697 len(nd_str) > max_node_len:
698 off = nd_str.find('(')
699 cutoff = max(off + 8,
700 abs(node_width - indent * per_level_indent - off))
701 nd_str = nd_str[0 : cutoff] + " ...')"
703 # Print source macro.
704 if show_macros and hasattr(nd, '_macro_id'):
705 macro = storage.load(nd._macro_id)
706 print "\n%s[ From macro:" % lead
707 print_info(storage, macro, show_macros = show_macros,
708 lead_indent = indent, custom = custom)
709 print "%s]" % lead
711 if id is None:
712 print "%s----- ---------- %s %s" % (
713 lead,
714 space * per_level_indent * indent,
715 nd_str,
717 else:
718 try:
719 cus_val = eval(custom)
720 except:
721 cus_val = '--------'
722 print "%s%5d %6s %8s %s %s" % (
723 lead,
725 storage.ie_.get_timestamp(id),
726 cus_val,
727 space * per_level_indent * indent,
728 nd_str,
731 #** print_ast: prefix format dump, needs only ast.
732 def print_ast(tree,
733 lead_indent = 0,
734 node_width = 50,
735 max_node_len = 20,
736 per_level_indent = 4,
737 space = ' ',
739 # Print leading parts of the ast, in a prefix format.
741 lead = space * lead_indent * per_level_indent
742 print "%s%5s %10s %s %s" % (lead, 'id', 'time', '', 'node')
743 print "%s--------------------------------------------" % lead
744 for nd, indent in tree.top_down_indented():
745 # Print main tree.
746 nd_str = str(nd) # Inefficient string formation...
748 # Prune nested trees
749 if isinstance(nd, (Nested, aList)):
750 nd_str = nd_str[0:nd_str.find('(')]
752 # Prune long items
753 if isinstance(nd, (String, Symbol)):
754 if len(nd_str) + indent * per_level_indent > node_width and \
755 len(nd_str) > max_node_len:
756 off = nd_str.find('(')
757 cutoff = max(off + 8,
758 abs(node_width - indent * per_level_indent - off))
759 nd_str = nd_str[0 : cutoff] + " ...')"
761 print "%s----- ---------- %s %s" % (
762 lead,
763 space * per_level_indent * indent,
764 nd_str,
769 #** print_all_info: prefix format tabular dump with clones.
770 def print_all_info(storage, tree,
771 per_level_indent = 4,
772 values = False,
774 # print id, timestamp and clone level information,
775 # with leading parts of the ast.
776 # This provides a long dump of all tree nodes and their clones;
777 # useful for comparing state between runs.
779 printed = {}
780 space = ' '
781 print "%5s %10s %5s %s %s" % ('id', 'time', 'clone', '', 'node')
782 print "--------------------------------------------"
783 geta = storage.get_attribute
784 for nd, clone_lev, indent in tree.prefix_all(storage):
785 id = nd._id
786 # print a clone's nested elements only once, as part of the
787 # clone.
788 if printed.has_key(id): continue
789 printed[id] = 1
791 # Inefficient string formation...
792 nd_str = str(nd)
793 if isinstance(nd, (Nested, aList)):
794 nd_str = nd_str[0:nd_str.find('(')]
796 print "%5d %10s %.5d %s %s" % (
798 storage.ie_.get_timestamp(id),
799 clone_lev,
800 space * per_level_indent * indent,
801 nd_str,
803 if values:
804 val = geta(id, 'interp_result')
805 if val != None:
806 val_str = str(val)
807 print "%5s %10s %5s %s `...... %.30s" % (
808 " ",
809 " ",
810 " ",
811 space * per_level_indent * indent,
812 val_str)
815 #* Call context
816 #** print_calling_context
817 def print_calling_context(self, context, level = 0):
818 geta = self._storage.get_attribute
819 load = self._storage.load
820 gts = self._storage.ie_.get_timestamp
822 for path in context:
823 src, clone = path[0]
824 print " "*level,
825 print src, gts(src), load(src).source_string(), " -> ", \
826 clone, gts(clone), load(clone).source_string()
827 ## print geta(clone, "interp_result")
828 if len(path) > 1:
829 self.print_calling_context( path[1:], level + 1)
830 TreeWork.print_calling_context = print_calling_context
833 #** print_call_ctxt
834 def print_call_ctxt(self, ctxt):
835 geta = self._storage.get_attribute
836 load = self._storage.load
837 gts = self._storage.ie_.get_timestamp
839 def _show_path(path, level):
840 cc = path[0]
841 ## print " "*level,
842 # Calls.
843 if cc.J:
844 print cc.J, gts(cc.J), load(cc.J).source_string(), " -> ", \
845 cc.K, gts(cc.K), load(cc.K).source_string()
846 else:
847 print "program source", " -> ", \
848 cc.K, gts(cc.K), load(cc.K).source_string()
850 # Sources
851 if cc.O:
852 print cc.O, gts(cc.O), '[['
853 print load(cc.O).source_string()
854 print ']]'
856 ## print geta(clone, "interp_result")
857 if len(path) > 1:
858 _show_path( path[1:], level + 1)
860 for path in ctxt:
861 _show_path(path, 0)
862 print
863 TreeWork.print_call_ctxt = print_call_ctxt
866 #** print_call_ctxt_dot
867 def print_call_ctxt_dot(self, ctxt,
868 title = "print_call_ctxt_dot",
869 out = sys.stdout,
870 unique = False):
872 # Produce O -> J -> C locally, and recurse. This omits the
873 # lexical source of O, which is important for nested loops.
875 # K is contained in C, so substitute
876 # C[i] -> J[i+1]
877 # for
878 # K[i] -> J[i+1]
880 # Path leading to goal call goal call
883 # +----+ |
884 # | O | |
885 # +----+ |
886 # | |
887 # V |
888 # +----+ |
889 # | J | |
890 # +----+ +----+ |
891 # | | O | |
892 # V +----+ |
893 # +----+ | |
894 # | C | V |
895 # | | +----+ | +---+
896 # | K----------> | J | . .|. . | C |
897 # +----+ +----+ | +---+
898 # | | |
899 # V | V
900 # +----+ | +---+
901 # | C | | | K |
902 # | | | +---+
903 # | K | |
904 # +----+ |
906 # Flags:
907 # unique
908 # ALL calling paths are shown, so for a target T reached
909 # via N paths passing through P, P will have N incoming and (N-1)
910 # outgoing edges. Use unique = True to get only one.
913 uniq_t = {}
915 geta = self._storage.get_attribute
916 load = self._storage.load
917 gts = self._storage.ie_.get_timestamp
919 def dotify(id):
920 # Get a dot-usable string from the node id.
921 return combine_newlines(
922 strip_comment(repr(load(id).source_string()))[1:-1].\
923 replace(r"\n", "\l") + "\l")
925 def _show_path(path, J_from, level):
926 cc = path[0]
928 # Sources.
929 if cc.O:
930 print >> out, cc.O, '[label="%s"];' % dotify(cc.O)
931 if cc.J:
932 print >> out, cc.J, \
933 '[label="call %d\l%s"];' % (cc.J, dotify(cc.J))
934 if cc.C:
935 # K is in C
936 if level == 0:
937 # not a clone
938 print >> out, cc.C, \
939 '[label="%s"];' % (dotify(cc.C))
940 else:
941 print >> out, cc.C, \
942 '[label="clone %d\l%s"];' % (cc.C, dotify(cc.C))
944 # Calls.
945 if cc.J:
946 if not uniq_t.has_key((cc.O, cc.J)):
947 print >> out, cc.O, '->', cc.J, '[label="used by"];'
948 print >> out, cc.J, '->', cc.C, '[label="produces"];'
949 if unique:
950 uniq_t[(cc.O, cc.J)] = 1
951 uniq_t[(cc.J, cc.C)] = 1
952 if J_from:
953 if not uniq_t.has_key((J_from, cc.J)):
954 print >> out, J_from, '->', cc.J, '[label="contains"];'
955 if unique:
956 uniq_t[(J_from, cc.J)] = 1
958 if len(path) > 1:
959 # Continue down the call chain.
960 _show_path( path[1:], cc.C, level + 1)
961 else:
962 # Show the goal call K.
963 print >> out, cc.K, \
964 '[label="call %d\l%s"];' % (cc.K, dotify(cc.K))
965 print >> out, cc.C, '->', cc.K, '[label="contains"];'
966 # Header.
967 print >> out, '''
968 digraph foo {
969 page = "8.5,11.0"; /* size of single physical page */
970 size="7.5,10.0"; /* graph size */
971 /* rotate=90; */
972 /* ratio=fill; */
973 /* rankdir=LR; */
974 margin="0.5,0.5";
975 fontsize=24;
976 edge [fontname = "Helvetica", fontsize=24];
977 node [style=filled, color=grey90, shape=plaintext,
978 fontname="Helvetica", fontsize=24];
980 if title:
981 print >> out, '''
982 title_node [label="%s", fontname="Helvetica", fontsize=20];
983 ''' % title
984 # Edges and nodes.
985 for path in ctxt:
986 _show_path(path, None, 0)
987 print >> out
989 # Rank alignment for all target clones.
990 print >> out, "{ rank=same;"
991 for path in ctxt:
992 print >> out, path[-1].K, ';'
993 print >> out, "}"
995 # Footer.
996 print >> out, "}"
997 out.flush()
998 TreeWork.print_call_ctxt_dot = print_call_ctxt_dot
1002 #** print_call_chains
1003 def print_call_chains(self, ctxt):
1005 # Show only the dynamic call chains; subset of print_call_ctxt.
1007 geta = self._storage.get_attribute
1008 load = self._storage.load
1009 gts = self._storage.ie_.get_timestamp
1011 def _show_path(path, level):
1012 cc = path[0]
1013 # Calls.
1014 if cc.J:
1015 print cc.J, gts(cc.J), load(cc.J).source_string(), " -> ", \
1016 cc.K, gts(cc.K), load(cc.K).source_string()
1017 else:
1018 print "program source", " -> ", \
1019 cc.K, gts(cc.K), load(cc.K).source_string()
1021 if len(path) > 1:
1022 _show_path( path[1:], level + 1)
1024 for path in ctxt:
1025 _show_path(path, 0)
1026 print
1027 TreeWork.print_call_chains = print_call_chains
1029 #* interpretation-generated environments
1030 def print_env(env):
1031 ol = 0
1032 for (k,v, env, lev) in env.all_bindings_recursive():
1033 if lev > ol:
1034 print " "* 4 * ol, "{" * (lev - ol)
1035 ol = lev
1036 if lev < ol:
1037 print " "* 4 * lev, "}" * (ol - lev)
1038 ol = lev
1039 print "%s%s = %s\n" % (" "* 4 * lev, k, v)
1040 if lev > 0:
1041 print " "* 4 * lev, "}" * (lev - 0)
1045 #* Decoration (title / label / comment) support
1046 #** Find appropriate decoration text
1047 def deco_title_text(self):
1048 return self.__class__.__name__
1049 Nested.deco_title_text = deco_title_text
1051 def deco_title_text(self):
1052 return str(self)
1053 Immediate.deco_title_text = deco_title_text
1055 def deco_title_text(self):
1056 return str(self)
1057 Native.deco_title_text = deco_title_text
1059 def deco_title_text(self):
1060 ma = ast.Matcher()
1061 if ma.match(self,
1062 Call(Marker('func_name'), Marker('rhs'))):
1063 return ma['func_name'].get_infix_string(23)
1064 return ""
1065 Call.deco_title_text = deco_title_text
1067 def deco_title_text(self):
1068 ma = ast.Matcher()
1069 if ma.match(self,
1070 Set(Marker('lhs'), Marker('rhs')) ):
1071 return "define " + ma['lhs'].get_infix_string(23)
1072 return ""
1073 Set.deco_title_text = deco_title_text
1075 def deco_title_text(self):
1076 if self.l_label is None:
1077 return "List"
1078 else:
1079 lbl = self.l_label
1080 if hasattr(lbl, 'get_infix_string'):
1081 return lbl.get_infix_string(23)
1082 else:
1083 return str(lbl)
1084 List.deco_title_text = deco_title_text
1086 def deco_title_text(self):
1087 if self.m_label is None:
1088 if isinstance(self, Subdir):
1089 return "Subdir"
1090 else:
1091 return "Map"
1092 else:
1093 lbl = self.m_label
1094 if hasattr(lbl, 'get_infix_string'):
1095 return lbl.get_infix_string(23)
1096 else:
1097 return str(lbl)
1098 Map.deco_title_text = deco_title_text
1100 def deco_title_text(self):
1101 if self.p_label is None:
1102 return "Program"
1103 else:
1104 lbl = self.p_label
1105 if hasattr(lbl, 'get_infix_string'):
1106 return lbl.get_infix_string(23)
1107 else:
1108 return str(lbl)
1109 Program.deco_title_text = deco_title_text
1111 def deco_title_text(self):
1112 return "Function"
1113 Function.deco_title_text = deco_title_text
1115 def deco_title_text(self):
1116 return "Raw Python"
1117 Inline.deco_title_text = deco_title_text