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