home *** CD-ROM | disk | FTP | other *** search
/ PC World 2002 May / PCWorld_2002-05_cd.bin / Software / TemaCD / activepython / ActivePython-2.1.1.msi / Python21_Tools_idle_PyParse.py < prev    next >
Encoding:
Python Source  |  2001-07-26  |  19.4 KB  |  589 lines

  1. import string
  2. import re
  3. import sys
  4.  
  5. # Reason last stmt is continued (or C_NONE if it's not).
  6. C_NONE, C_BACKSLASH, C_STRING, C_BRACKET = range(4)
  7.  
  8. if 0:   # for throwaway debugging output
  9.     def dump(*stuff):
  10.         sys.__stdout__.write(string.join(map(str, stuff), " ") + "\n")
  11.  
  12. # Find what looks like the start of a popular stmt.
  13.  
  14. _synchre = re.compile(r"""
  15.     ^
  16.     [ \t]*
  17.     (?: if
  18.     |   for
  19.     |   while
  20.     |   else
  21.     |   def
  22.     |   return
  23.     |   assert
  24.     |   break
  25.     |   class
  26.     |   continue
  27.     |   elif
  28.     |   try
  29.     |   except
  30.     |   raise
  31.     |   import
  32.     )
  33.     \b
  34. """, re.VERBOSE | re.MULTILINE).search
  35.  
  36. # Match blank line or non-indenting comment line.
  37.  
  38. _junkre = re.compile(r"""
  39.     [ \t]*
  40.     (?: \# \S .* )?
  41.     \n
  42. """, re.VERBOSE).match
  43.  
  44. # Match any flavor of string; the terminating quote is optional
  45. # so that we're robust in the face of incomplete program text.
  46.  
  47. _match_stringre = re.compile(r"""
  48.     \""" [^"\\]* (?:
  49.                      (?: \\. | "(?!"") )
  50.                      [^"\\]*
  51.                  )*
  52.     (?: \""" )?
  53.  
  54. |   " [^"\\\n]* (?: \\. [^"\\\n]* )* "?
  55.  
  56. |   ''' [^'\\]* (?:
  57.                    (?: \\. | '(?!'') )
  58.                    [^'\\]*
  59.                 )*
  60.     (?: ''' )?
  61.  
  62. |   ' [^'\\\n]* (?: \\. [^'\\\n]* )* '?
  63. """, re.VERBOSE | re.DOTALL).match
  64.  
  65. # Match a line that starts with something interesting;
  66. # used to find the first item of a bracket structure.
  67.  
  68. _itemre = re.compile(r"""
  69.     [ \t]*
  70.     [^\s#\\]    # if we match, m.end()-1 is the interesting char
  71. """, re.VERBOSE).match
  72.  
  73. # Match start of stmts that should be followed by a dedent.
  74.  
  75. _closere = re.compile(r"""
  76.     \s*
  77.     (?: return
  78.     |   break
  79.     |   continue
  80.     |   raise
  81.     |   pass
  82.     )
  83.     \b
  84. """, re.VERBOSE).match
  85.  
  86. # Chew up non-special chars as quickly as possible.  If match is
  87. # successful, m.end() less 1 is the index of the last boring char
  88. # matched.  If match is unsuccessful, the string starts with an
  89. # interesting char.
  90.  
  91. _chew_ordinaryre = re.compile(r"""
  92.     [^[\](){}#'"\\]+
  93. """, re.VERBOSE).match
  94.  
  95. # Build translation table to map uninteresting chars to "x", open
  96. # brackets to "(", and close brackets to ")".
  97.  
  98. _tran = ['x'] * 256
  99. for ch in "({[":
  100.     _tran[ord(ch)] = '('
  101. for ch in ")}]":
  102.     _tran[ord(ch)] = ')'
  103. for ch in "\"'\\\n#":
  104.     _tran[ord(ch)] = ch
  105. _tran = string.join(_tran, '')
  106. del ch
  107.  
  108. try:
  109.     UnicodeType = type(unicode(""))
  110. except NameError:
  111.     UnicodeType = None
  112.  
  113. class Parser:
  114.  
  115.     def __init__(self, indentwidth, tabwidth):
  116.         self.indentwidth = indentwidth
  117.         self.tabwidth = tabwidth
  118.  
  119.     def set_str(self, str):
  120.         assert len(str) == 0 or str[-1] == '\n'
  121.         if type(str) is UnicodeType:
  122.             # The parse functions have no idea what to do with Unicode, so
  123.             # replace all Unicode characters with "x".  This is "safe"
  124.             # so long as the only characters germane to parsing the structure
  125.             # of Python are 7-bit ASCII.  It's *necessary* because Unicode
  126.             # strings don't have a .translate() method that supports
  127.             # deletechars.
  128.             uniphooey = str
  129.             str = []
  130.             push = str.append
  131.             for raw in map(ord, uniphooey):
  132.                 push(raw < 127 and chr(raw) or "x")
  133.             str = "".join(str)
  134.         self.str = str
  135.         self.study_level = 0
  136.  
  137.     # Return index of a good place to begin parsing, as close to the
  138.     # end of the string as possible.  This will be the start of some
  139.     # popular stmt like "if" or "def".  Return None if none found:
  140.     # the caller should pass more prior context then, if possible, or
  141.     # if not (the entire program text up until the point of interest
  142.     # has already been tried) pass 0 to set_lo.
  143.     #
  144.     # This will be reliable iff given a reliable is_char_in_string
  145.     # function, meaning that when it says "no", it's absolutely
  146.     # guaranteed that the char is not in a string.
  147.     #
  148.     # Ack, hack: in the shell window this kills us, because there's
  149.     # no way to tell the differences between output, >>> etc and
  150.     # user input.  Indeed, IDLE's first output line makes the rest
  151.     # look like it's in an unclosed paren!:
  152.     # Python 1.5.2 (#0, Apr 13 1999, ...
  153.  
  154.     def find_good_parse_start(self, use_ps1, is_char_in_string=None,
  155.                               _rfind=string.rfind,
  156.                               _synchre=_synchre):
  157.         str, pos = self.str, None
  158.         if use_ps1:
  159.             # shell window
  160.             ps1 = '\n' + sys.ps1
  161.             i = _rfind(str, ps1)
  162.             if i >= 0:
  163.                 pos = i + len(ps1)
  164.                 # make it look like there's a newline instead
  165.                 # of ps1 at the start -- hacking here once avoids
  166.                 # repeated hackery later
  167.                 self.str = str[:pos-1] + '\n' + str[pos:]
  168.             return pos
  169.  
  170.         # File window -- real work.
  171.         if not is_char_in_string:
  172.             # no clue -- make the caller pass everything
  173.             return None
  174.  
  175.         # Peek back from the end for a good place to start,
  176.         # but don't try too often; pos will be left None, or
  177.         # bumped to a legitimate synch point.
  178.         limit = len(str)
  179.         for tries in range(5):
  180.             i = _rfind(str, ":\n", 0, limit)
  181.             if i < 0:
  182.                 break
  183.             i = _rfind(str, '\n', 0, i) + 1  # start of colon line
  184.             m = _synchre(str, i, limit)
  185.             if m and not is_char_in_string(m.start()):
  186.                 pos = m.start()
  187.                 break
  188.             limit = i
  189.         if pos is None:
  190.             # Nothing looks like a block-opener, or stuff does
  191.             # but is_char_in_string keeps returning true; most likely
  192.             # we're in or near a giant string, the colorizer hasn't
  193.             # caught up enough to be helpful, or there simply *aren't*
  194.             # any interesting stmts.  In any of these cases we're
  195.             # going to have to parse the whole thing to be sure, so
  196.             # give it one last try from the start, but stop wasting
  197.             # time here regardless of the outcome.
  198.             m = _synchre(str)
  199.             if m and not is_char_in_string(m.start()):
  200.                 pos = m.start()
  201.             return pos
  202.  
  203.         # Peeking back worked; look forward until _synchre no longer
  204.         # matches.
  205.         i = pos + 1
  206.         while 1:
  207.             m = _synchre(str, i)
  208.             if m:
  209.                 s, i = m.span()
  210.                 if not is_char_in_string(s):
  211.                     pos = s
  212.             else:
  213.                 break
  214.         return pos
  215.  
  216.     # Throw away the start of the string.  Intended to be called with
  217.     # find_good_parse_start's result.
  218.  
  219.     def set_lo(self, lo):
  220.         assert lo == 0 or self.str[lo-1] == '\n'
  221.         if lo > 0:
  222.             self.str = self.str[lo:]
  223.  
  224.     # As quickly as humanly possible <wink>, find the line numbers (0-
  225.     # based) of the non-continuation lines.
  226.     # Creates self.{goodlines, continuation}.
  227.  
  228.     def _study1(self, _replace=string.replace, _find=string.find):
  229.         if self.study_level >= 1:
  230.             return
  231.         self.study_level = 1
  232.  
  233.         # Map all uninteresting characters to "x", all open brackets
  234.         # to "(", all close brackets to ")", then collapse runs of
  235.         # uninteresting characters.  This can cut the number of chars
  236.         # by a factor of 10-40, and so greatly speed the following loop.
  237.         str = self.str
  238.         str = string.translate(str, _tran)
  239.         str = _replace(str, 'xxxxxxxx', 'x')
  240.         str = _replace(str, 'xxxx', 'x')
  241.         str = _replace(str, 'xx', 'x')
  242.         str = _replace(str, 'xx', 'x')
  243.         str = _replace(str, '\nx', '\n')
  244.         # note that replacing x\n with \n would be incorrect, because
  245.         # x may be preceded by a backslash
  246.  
  247.         # March over the squashed version of the program, accumulating
  248.         # the line numbers of non-continued stmts, and determining
  249.         # whether & why the last stmt is a continuation.
  250.         continuation = C_NONE
  251.         level = lno = 0     # level is nesting level; lno is line number
  252.         self.goodlines = goodlines = [0]
  253.         push_good = goodlines.append
  254.         i, n = 0, len(str)
  255.         while i < n:
  256.             ch = str[i]
  257.             i = i+1
  258.  
  259.             # cases are checked in decreasing order of frequency
  260.             if ch == 'x':
  261.                 continue
  262.  
  263.             if ch == '\n':
  264.                 lno = lno + 1
  265.                 if level == 0:
  266.                     push_good(lno)
  267.                     # else we're in an unclosed bracket structure
  268.                 continue
  269.  
  270.             if ch == '(':
  271.                 level = level + 1
  272.                 continue
  273.  
  274.             if ch == ')':
  275.                 if level:
  276.                     level = level - 1
  277.                     # else the program is invalid, but we can't complain
  278.                 continue
  279.  
  280.             if ch == '"' or ch == "'":
  281.                 # consume the string
  282.                 quote = ch
  283.                 if str[i-1:i+2] == quote * 3:
  284.                     quote = quote * 3
  285.                 w = len(quote) - 1
  286.                 i = i+w
  287.                 while i < n:
  288.                     ch = str[i]
  289.                     i = i+1
  290.  
  291.                     if ch == 'x':
  292.                         continue
  293.  
  294.                     if str[i-1:i+w] == quote:
  295.                         i = i+w
  296.                         break
  297.  
  298.                     if ch == '\n':
  299.                         lno = lno + 1
  300.                         if w == 0:
  301.                             # unterminated single-quoted string
  302.                             if level == 0:
  303.                                 push_good(lno)
  304.                             break
  305.                         continue
  306.  
  307.                     if ch == '\\':
  308.                         assert i < n
  309.                         if str[i] == '\n':
  310.                             lno = lno + 1
  311.                         i = i+1
  312.                         continue
  313.  
  314.                     # else comment char or paren inside string
  315.  
  316.                 else:
  317.                     # didn't break out of the loop, so we're still
  318.                     # inside a string
  319.                     continuation = C_STRING
  320.                 continue    # with outer loop
  321.  
  322.             if ch == '#':
  323.                 # consume the comment
  324.                 i = _find(str, '\n', i)
  325.                 assert i >= 0
  326.                 continue
  327.  
  328.             assert ch == '\\'
  329.             assert i < n
  330.             if str[i] == '\n':
  331.                 lno = lno + 1
  332.                 if i+1 == n:
  333.                     continuation = C_BACKSLASH
  334.             i = i+1
  335.  
  336.         # The last stmt may be continued for all 3 reasons.
  337.         # String continuation takes precedence over bracket
  338.         # continuation, which beats backslash continuation.
  339.         if continuation != C_STRING and level > 0:
  340.             continuation = C_BRACKET
  341.         self.continuation = continuation
  342.  
  343.         # Push the final line number as a sentinel value, regardless of
  344.         # whether it's continued.
  345.         assert (continuation == C_NONE) == (goodlines[-1] == lno)
  346.         if goodlines[-1] != lno:
  347.             push_good(lno)
  348.  
  349.     def get_continuation_type(self):
  350.         self._study1()
  351.         return self.continuation
  352.  
  353.     # study1 was sufficient to determine the continuation status,
  354.     # but doing more requires looking at every character.  study2
  355.     # does this for the last interesting statement in the block.
  356.     # Creates:
  357.     #     self.stmt_start, stmt_end
  358.     #         slice indices of last interesting stmt
  359.     #     self.lastch
  360.     #         last non-whitespace character before optional trailing
  361.     #         comment
  362.     #     self.lastopenbracketpos
  363.     #         if continuation is C_BRACKET, index of last open bracket
  364.  
  365.     def _study2(self, _rfind=string.rfind, _find=string.find,
  366.                       _ws=string.whitespace):
  367.         if self.study_level >= 2:
  368.             return
  369.         self._study1()
  370.         self.study_level = 2
  371.  
  372.         # Set p and q to slice indices of last interesting stmt.
  373.         str, goodlines = self.str, self.goodlines
  374.         i = len(goodlines) - 1
  375.         p = len(str)    # index of newest line
  376.         while i:
  377.             assert p
  378.             # p is the index of the stmt at line number goodlines[i].
  379.             # Move p back to the stmt at line number goodlines[i-1].
  380.             q = p
  381.             for nothing in range(goodlines[i-1], goodlines[i]):
  382.                 # tricky: sets p to 0 if no preceding newline
  383.                 p = _rfind(str, '\n', 0, p-1) + 1
  384.             # The stmt str[p:q] isn't a continuation, but may be blank
  385.             # or a non-indenting comment line.
  386.             if  _junkre(str, p):
  387.                 i = i-1
  388.             else:
  389.                 break
  390.         if i == 0:
  391.             # nothing but junk!
  392.             assert p == 0
  393.             q = p
  394.         self.stmt_start, self.stmt_end = p, q
  395.  
  396.         # Analyze this stmt, to find the last open bracket (if any)
  397.         # and last interesting character (if any).
  398.         lastch = ""
  399.         stack = []  # stack of open bracket indices
  400.         push_stack = stack.append
  401.         while p < q:
  402.             # suck up all except ()[]{}'"#\\
  403.             m = _chew_ordinaryre(str, p, q)
  404.             if m:
  405.                 # we skipped at least one boring char
  406.                 newp = m.end()
  407.                 # back up over totally boring whitespace
  408.                 i = newp - 1    # index of last boring char
  409.                 while i >= p and str[i] in " \t\n":
  410.                     i = i-1
  411.                 if i >= p:
  412.                     lastch = str[i]
  413.                 p = newp
  414.                 if p >= q:
  415.                     break
  416.  
  417.             ch = str[p]
  418.  
  419.             if ch in "([{":
  420.                 push_stack(p)
  421.                 lastch = ch
  422.                 p = p+1
  423.                 continue
  424.  
  425.             if ch in ")]}":
  426.                 if stack:
  427.                     del stack[-1]
  428.                 lastch = ch
  429.                 p = p+1
  430.                 continue
  431.  
  432.             if ch == '"' or ch == "'":
  433.                 # consume string
  434.                 # Note that study1 did this with a Python loop, but
  435.                 # we use a regexp here; the reason is speed in both
  436.                 # cases; the string may be huge, but study1 pre-squashed
  437.                 # strings to a couple of characters per line.  study1
  438.                 # also needed to keep track of newlines, and we don't
  439.                 # have to.
  440.                 lastch = ch
  441.                 p = _match_stringre(str, p, q).end()
  442.                 continue
  443.  
  444.             if ch == '#':
  445.                 # consume comment and trailing newline
  446.                 p = _find(str, '\n', p, q) + 1
  447.                 assert p > 0
  448.                 continue
  449.  
  450.             assert ch == '\\'
  451.             p = p+1     # beyond backslash
  452.             assert p < q
  453.             if str[p] != '\n':
  454.                 # the program is invalid, but can't complain
  455.                 lastch = ch + str[p]
  456.             p = p+1     # beyond escaped char
  457.  
  458.         # end while p < q:
  459.  
  460.         self.lastch = lastch
  461.         if stack:
  462.             self.lastopenbracketpos = stack[-1]
  463.  
  464.     # Assuming continuation is C_BRACKET, return the number
  465.     # of spaces the next line should be indented.
  466.  
  467.     def compute_bracket_indent(self, _find=string.find):
  468.         self._study2()
  469.         assert self.continuation == C_BRACKET
  470.         j = self.lastopenbracketpos
  471.         str = self.str
  472.         n = len(str)
  473.         origi = i = string.rfind(str, '\n', 0, j) + 1
  474.         j = j+1     # one beyond open bracket
  475.         # find first list item; set i to start of its line
  476.         while j < n:
  477.             m = _itemre(str, j)
  478.             if m:
  479.                 j = m.end() - 1     # index of first interesting char
  480.                 extra = 0
  481.                 break
  482.             else:
  483.                 # this line is junk; advance to next line
  484.                 i = j = _find(str, '\n', j) + 1
  485.         else:
  486.             # nothing interesting follows the bracket;
  487.             # reproduce the bracket line's indentation + a level
  488.             j = i = origi
  489.             while str[j] in " \t":
  490.                 j = j+1
  491.             extra = self.indentwidth
  492.         return len(string.expandtabs(str[i:j],
  493.                                      self.tabwidth)) + extra
  494.  
  495.     # Return number of physical lines in last stmt (whether or not
  496.     # it's an interesting stmt!  this is intended to be called when
  497.     # continuation is C_BACKSLASH).
  498.  
  499.     def get_num_lines_in_stmt(self):
  500.         self._study1()
  501.         goodlines = self.goodlines
  502.         return goodlines[-1] - goodlines[-2]
  503.  
  504.     # Assuming continuation is C_BACKSLASH, return the number of spaces
  505.     # the next line should be indented.  Also assuming the new line is
  506.     # the first one following the initial line of the stmt.
  507.  
  508.     def compute_backslash_indent(self):
  509.         self._study2()
  510.         assert self.continuation == C_BACKSLASH
  511.         str = self.str
  512.         i = self.stmt_start
  513.         while str[i] in " \t":
  514.             i = i+1
  515.         startpos = i
  516.  
  517.         # See whether the initial line starts an assignment stmt; i.e.,
  518.         # look for an = operator
  519.         endpos = string.find(str, '\n', startpos) + 1
  520.         found = level = 0
  521.         while i < endpos:
  522.             ch = str[i]
  523.             if ch in "([{":
  524.                 level = level + 1
  525.                 i = i+1
  526.             elif ch in ")]}":
  527.                 if level:
  528.                     level = level - 1
  529.                 i = i+1
  530.             elif ch == '"' or ch == "'":
  531.                 i = _match_stringre(str, i, endpos).end()
  532.             elif ch == '#':
  533.                 break
  534.             elif level == 0 and ch == '=' and \
  535.                    (i == 0 or str[i-1] not in "=<>!") and \
  536.                    str[i+1] != '=':
  537.                 found = 1
  538.                 break
  539.             else:
  540.                 i = i+1
  541.  
  542.         if found:
  543.             # found a legit =, but it may be the last interesting
  544.             # thing on the line
  545.             i = i+1     # move beyond the =
  546.             found = re.match(r"\s*\\", str[i:endpos]) is None
  547.  
  548.         if not found:
  549.             # oh well ... settle for moving beyond the first chunk
  550.             # of non-whitespace chars
  551.             i = startpos
  552.             while str[i] not in " \t\n":
  553.                 i = i+1
  554.  
  555.         return len(string.expandtabs(str[self.stmt_start :
  556.                                          i],
  557.                                      self.tabwidth)) + 1
  558.  
  559.     # Return the leading whitespace on the initial line of the last
  560.     # interesting stmt.
  561.  
  562.     def get_base_indent_string(self):
  563.         self._study2()
  564.         i, n = self.stmt_start, self.stmt_end
  565.         j = i
  566.         str = self.str
  567.         while j < n and str[j] in " \t":
  568.             j = j + 1
  569.         return str[i:j]
  570.  
  571.     # Did the last interesting stmt open a block?
  572.  
  573.     def is_block_opener(self):
  574.         self._study2()
  575.         return self.lastch == ':'
  576.  
  577.     # Did the last interesting stmt close a block?
  578.  
  579.     def is_block_closer(self):
  580.         self._study2()
  581.         return _closere(self.str, self.stmt_start) is not None
  582.  
  583.     # index of last open bracket ({[, or None if none
  584.     lastopenbracketpos = None
  585.  
  586.     def get_last_open_bracket_pos(self):
  587.         self._study2()
  588.         return self.lastopenbracketpos
  589.