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 / TOKENIZE.PY < prev    next >
Encoding:
Python Source  |  2000-10-07  |  9.3 KB  |  219 lines

  1. """Tokenization help for Python programs.
  2.  
  3. This module exports a function called 'tokenize()' that breaks a stream of
  4. text into Python tokens.  It accepts a readline-like method which is called
  5. repeatedly to get the next line of input (or "" for EOF) and a "token-eater"
  6. function which is called once for each token found.  The latter function is
  7. passed the token type, a string containing the token, the starting and
  8. ending (row, column) coordinates of the token, and the original line.  It is
  9. designed to match the working of the Python tokenizer exactly, except that
  10. it produces COMMENT tokens for comments and gives type OP for all operators."""
  11.  
  12. __version__ = "Ka-Ping Yee, 26 October 1997; patched, GvR 3/30/98"
  13.  
  14. import string, re
  15. from token import *
  16.  
  17. COMMENT = N_TOKENS
  18. tok_name[COMMENT] = 'COMMENT'
  19. NL = N_TOKENS + 1
  20. tok_name[NL] = 'NL'
  21.  
  22.  
  23. # Changes from 1.3:
  24. #     Ignore now accepts \f as whitespace.  Operator now includes '**'.
  25. #     Ignore and Special now accept \n or \r\n at the end of a line.
  26. #     Imagnumber is new.  Expfloat is corrected to reject '0e4'.
  27. # Note: to quote a backslash in a regex, it must be doubled in a r'aw' string.
  28.  
  29. def group(*choices): return '(' + string.join(choices, '|') + ')'
  30. def any(*choices): return apply(group, choices) + '*'
  31. def maybe(*choices): return apply(group, choices) + '?'
  32.  
  33. Whitespace = r'[ \f\t]*'
  34. Comment = r'#[^\r\n]*'
  35. Ignore = Whitespace + any(r'\\\r?\n' + Whitespace) + maybe(Comment)
  36. Name = r'[a-zA-Z_]\w*'
  37.  
  38. Hexnumber = r'0[xX][\da-fA-F]*[lL]?'
  39. Octnumber = r'0[0-7]*[lL]?'
  40. Decnumber = r'[1-9]\d*[lL]?'
  41. Intnumber = group(Hexnumber, Octnumber, Decnumber)
  42. Exponent = r'[eE][-+]?\d+'
  43. Pointfloat = group(r'\d+\.\d*', r'\.\d+') + maybe(Exponent)
  44. Expfloat = r'[1-9]\d*' + Exponent
  45. Floatnumber = group(Pointfloat, Expfloat)
  46. Imagnumber = group(r'0[jJ]', r'[1-9]\d*[jJ]', Floatnumber + r'[jJ]')
  47. Number = group(Imagnumber, Floatnumber, Intnumber)
  48.  
  49. # Tail end of ' string.
  50. Single = r"[^'\\]*(?:\\.[^'\\]*)*'"
  51. # Tail end of " string.
  52. Double = r'[^"\\]*(?:\\.[^"\\]*)*"'
  53. # Tail end of ''' string.
  54. Single3 = r"[^'\\]*(?:(?:\\.|'(?!''))[^'\\]*)*'''"
  55. # Tail end of """ string.
  56. Double3 = r'[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""'
  57. Triple = group("[rR]?'''", '[rR]?"""')
  58. # Single-line ' or " string.
  59. String = group(r"[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*'",
  60.                r'[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*"')
  61.  
  62. # Because of leftmost-then-longest match semantics, be sure to put the
  63. # longest operators first (e.g., if = came before ==, == would get
  64. # recognized as two instances of =).
  65. Operator = group(r"\*\*=?", r">>=?", r"<<=?", r"<>", r"!=",
  66.                  r"[+\-*/%&|^=<>]=?",
  67.                  r"~")
  68.  
  69. Bracket = '[][(){}]'
  70. Special = group(r'\r?\n', r'[:;.,`]')
  71. Funny = group(Operator, Bracket, Special)
  72.  
  73. PlainToken = group(Number, Funny, String, Name)
  74. Token = Ignore + PlainToken
  75.  
  76. # First (or only) line of ' or " string.
  77. ContStr = group(r"[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*" + group("'", r'\\\r?\n'),
  78.                 r'[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*' + group('"', r'\\\r?\n'))
  79. PseudoExtras = group(r'\\\r?\n', Comment, Triple)
  80. PseudoToken = Whitespace + group(PseudoExtras, Number, Funny, ContStr, Name)
  81.  
  82. tokenprog, pseudoprog, single3prog, double3prog = map(
  83.     re.compile, (Token, PseudoToken, Single3, Double3))
  84. endprogs = {"'": re.compile(Single), '"': re.compile(Double),
  85.             "'''": single3prog, '"""': double3prog,
  86.             "r'''": single3prog, 'r"""': double3prog,
  87.             "R'''": single3prog, 'R"""': double3prog, 'r': None, 'R': None}
  88.  
  89. tabsize = 8
  90.  
  91. class TokenError(Exception):
  92.     pass
  93.  
  94. def printtoken(type, token, (srow, scol), (erow, ecol), line): # for testing
  95.     print "%d,%d-%d,%d:\t%s\t%s" % \
  96.         (srow, scol, erow, ecol, tok_name[type], repr(token))
  97.  
  98. def tokenize(readline, tokeneater=printtoken):
  99.     lnum = parenlev = continued = 0
  100.     namechars, numchars = string.letters + '_', string.digits
  101.     contstr, needcont = '', 0
  102.     contline = None
  103.     indents = [0]
  104.  
  105.     while 1:                                   # loop over lines in stream
  106.         line = readline()
  107.         lnum = lnum + 1
  108.         pos, max = 0, len(line)
  109.  
  110.         if contstr:                            # continued string
  111.             if not line:
  112.                 raise TokenError, ("EOF in multi-line string", strstart)
  113.             endmatch = endprog.match(line)
  114.             if endmatch:
  115.                 pos = end = endmatch.end(0)
  116.                 tokeneater(STRING, contstr + line[:end],
  117.                            strstart, (lnum, end), contline + line)
  118.                 contstr, needcont = '', 0
  119.                 contline = None
  120.             elif needcont and line[-2:] != '\\\n' and line[-3:] != '\\\r\n':
  121.                 tokeneater(ERRORTOKEN, contstr + line,
  122.                            strstart, (lnum, len(line)), contline)
  123.                 contstr = ''
  124.                 contline = None
  125.                 continue
  126.             else:
  127.                 contstr = contstr + line
  128.                 contline = contline + line
  129.                 continue
  130.  
  131.         elif parenlev == 0 and not continued:  # new statement
  132.             if not line: break
  133.             column = 0
  134.             while pos < max:                   # measure leading whitespace
  135.                 if line[pos] == ' ': column = column + 1
  136.                 elif line[pos] == '\t': column = (column/tabsize + 1)*tabsize
  137.                 elif line[pos] == '\f': column = 0
  138.                 else: break
  139.                 pos = pos + 1
  140.             if pos == max: break
  141.  
  142.             if line[pos] in '#\r\n':           # skip comments or blank lines
  143.                 tokeneater((NL, COMMENT)[line[pos] == '#'], line[pos:],
  144.                            (lnum, pos), (lnum, len(line)), line)
  145.                 continue
  146.  
  147.             if column > indents[-1]:           # count indents or dedents
  148.                 indents.append(column)
  149.                 tokeneater(INDENT, line[:pos], (lnum, 0), (lnum, pos), line)
  150.             while column < indents[-1]:
  151.                 indents = indents[:-1]
  152.                 tokeneater(DEDENT, '', (lnum, pos), (lnum, pos), line)
  153.  
  154.         else:                                  # continued statement
  155.             if not line:
  156.                 raise TokenError, ("EOF in multi-line statement", (lnum, 0))
  157.             continued = 0
  158.  
  159.         while pos < max:
  160.             pseudomatch = pseudoprog.match(line, pos)
  161.             if pseudomatch:                                # scan for tokens
  162.                 start, end = pseudomatch.span(1)
  163.                 spos, epos, pos = (lnum, start), (lnum, end), end
  164.                 token, initial = line[start:end], line[start]
  165.  
  166.                 if initial in numchars \
  167.                     or (initial == '.' and token != '.'):  # ordinary number
  168.                     tokeneater(NUMBER, token, spos, epos, line)
  169.                 elif initial in '\r\n':
  170.                     tokeneater(parenlev > 0 and NL or NEWLINE,
  171.                                token, spos, epos, line)
  172.                 elif initial == '#':
  173.                     tokeneater(COMMENT, token, spos, epos, line)
  174.                 elif token in ("'''", '"""',               # triple-quoted
  175.                                "r'''", 'r"""', "R'''", 'R"""'):
  176.                     endprog = endprogs[token]
  177.                     endmatch = endprog.match(line, pos)
  178.                     if endmatch:                           # all on one line
  179.                         pos = endmatch.end(0)
  180.                         token = line[start:pos]
  181.                         tokeneater(STRING, token, spos, (lnum, pos), line)
  182.                     else:
  183.                         strstart = (lnum, start)           # multiple lines
  184.                         contstr = line[start:]
  185.                         contline = line
  186.                         break
  187.                 elif initial in ("'", '"') or \
  188.                     token[:2] in ("r'", 'r"', "R'", 'R"'):
  189.                     if token[-1] == '\n':                  # continued string
  190.                         strstart = (lnum, start)
  191.                         endprog = endprogs[initial] or endprogs[token[1]]
  192.                         contstr, needcont = line[start:], 1
  193.                         contline = line
  194.                         break
  195.                     else:                                  # ordinary string
  196.                         tokeneater(STRING, token, spos, epos, line)
  197.                 elif initial in namechars:                 # ordinary name
  198.                     tokeneater(NAME, token, spos, epos, line)
  199.                 elif initial == '\\':                      # continued stmt
  200.                     continued = 1
  201.                 else:
  202.                     if initial in '([{': parenlev = parenlev + 1
  203.                     elif initial in ')]}': parenlev = parenlev - 1
  204.                     tokeneater(OP, token, spos, epos, line)
  205.             else:
  206.                 tokeneater(ERRORTOKEN, line[pos],
  207.                            (lnum, pos), (lnum, pos+1), line)
  208.                 pos = pos + 1
  209.  
  210.     for indent in indents[1:]:                 # pop remaining indent levels
  211.         tokeneater(DEDENT, '', (lnum, 0), (lnum, 0), '')
  212.     tokeneater(ENDMARKER, '', (lnum, 0), (lnum, 0), '')
  213.  
  214. if __name__ == '__main__':                     # testing
  215.     import sys
  216.     if len(sys.argv) > 1: tokenize(open(sys.argv[1]).readline)
  217.     else: tokenize(sys.stdin.readline)
  218.  
  219.