1# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation. 2# All rights reserved. 3 4"""Tokenization help for Python programs. 5 6generate_tokens(readline) is a generator that breaks a stream of 7text into Python tokens. It accepts a readline-like method which is called 8repeatedly to get the next line of input (or "" for EOF). It generates 95-tuples with these members: 10 11 the token type (see token.py) 12 the token (a string) 13 the starting (row, column) indices of the token (a 2-tuple of ints) 14 the ending (row, column) indices of the token (a 2-tuple of ints) 15 the original line (string) 16 17It is designed to match the working of the Python tokenizer exactly, except 18that it produces COMMENT tokens for comments and gives type OP for all 19operators 20 21Older entry points 22 tokenize_loop(readline, tokeneater) 23 tokenize(readline, tokeneater=printtoken) 24are the same, except instead of generating tokens, tokeneater is a callback 25function to which the 5 fields described above are passed as 5 arguments, 26each time a new token is found.""" 27 28__author__ = 'Ka-Ping Yee <ping@lfw.org>' 29__credits__ = \ 30 'GvR, ESR, Tim Peters, Thomas Wouters, Fred Drake, Skip Montanaro' 31 32import string, re 33from codecs import BOM_UTF8, lookup 34from lib2to3.pgen2.token import * 35 36from . import token 37__all__ = [x for x in dir(token) if x[0] != '_'] + ["tokenize", 38 "generate_tokens", "untokenize"] 39del token 40 41try: 42 bytes 43except NameError: 44 # Support bytes type in Python <= 2.5, so 2to3 turns itself into 45 # valid Python 3 code. 46 bytes = str 47 48def group(*choices): return '(' + '|'.join(choices) + ')' 49def any(*choices): return group(*choices) + '*' 50def maybe(*choices): return group(*choices) + '?' 51def _combinations(*l): 52 return set( 53 x + y for x in l for y in l + ("",) if x.casefold() != y.casefold() 54 ) 55 56Whitespace = r'[ \f\t]*' 57Comment = r'#[^\r\n]*' 58Ignore = Whitespace + any(r'\\\r?\n' + Whitespace) + maybe(Comment) 59Name = r'\w+' 60 61Binnumber = r'0[bB]_?[01]+(?:_[01]+)*' 62Hexnumber = r'0[xX]_?[\da-fA-F]+(?:_[\da-fA-F]+)*[lL]?' 63Octnumber = r'0[oO]?_?[0-7]+(?:_[0-7]+)*[lL]?' 64Decnumber = group(r'[1-9]\d*(?:_\d+)*[lL]?', '0[lL]?') 65Intnumber = group(Binnumber, Hexnumber, Octnumber, Decnumber) 66Exponent = r'[eE][-+]?\d+(?:_\d+)*' 67Pointfloat = group(r'\d+(?:_\d+)*\.(?:\d+(?:_\d+)*)?', r'\.\d+(?:_\d+)*') + maybe(Exponent) 68Expfloat = r'\d+(?:_\d+)*' + Exponent 69Floatnumber = group(Pointfloat, Expfloat) 70Imagnumber = group(r'\d+(?:_\d+)*[jJ]', Floatnumber + r'[jJ]') 71Number = group(Imagnumber, Floatnumber, Intnumber) 72 73# Tail end of ' string. 74Single = r"[^'\\]*(?:\\.[^'\\]*)*'" 75# Tail end of " string. 76Double = r'[^"\\]*(?:\\.[^"\\]*)*"' 77# Tail end of ''' string. 78Single3 = r"[^'\\]*(?:(?:\\.|'(?!''))[^'\\]*)*'''" 79# Tail end of """ string. 80Double3 = r'[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""' 81_litprefix = r"(?:[uUrRbBfF]|[rR][fFbB]|[fFbBuU][rR])?" 82Triple = group(_litprefix + "'''", _litprefix + '"""') 83# Single-line ' or " string. 84String = group(_litprefix + r"'[^\n'\\]*(?:\\.[^\n'\\]*)*'", 85 _litprefix + r'"[^\n"\\]*(?:\\.[^\n"\\]*)*"') 86 87# Because of leftmost-then-longest match semantics, be sure to put the 88# longest operators first (e.g., if = came before ==, == would get 89# recognized as two instances of =). 90Operator = group(r"\*\*=?", r">>=?", r"<<=?", r"<>", r"!=", 91 r"//=?", r"->", 92 r"[+\-*/%&@|^=<>]=?", 93 r"~") 94 95Bracket = '[][(){}]' 96Special = group(r'\r?\n', r':=', r'[:;.,`@]') 97Funny = group(Operator, Bracket, Special) 98 99PlainToken = group(Number, Funny, String, Name) 100Token = Ignore + PlainToken 101 102# First (or only) line of ' or " string. 103ContStr = group(_litprefix + r"'[^\n'\\]*(?:\\.[^\n'\\]*)*" + 104 group("'", r'\\\r?\n'), 105 _litprefix + r'"[^\n"\\]*(?:\\.[^\n"\\]*)*' + 106 group('"', r'\\\r?\n')) 107PseudoExtras = group(r'\\\r?\n', Comment, Triple) 108PseudoToken = Whitespace + group(PseudoExtras, Number, Funny, ContStr, Name) 109 110tokenprog, pseudoprog, single3prog, double3prog = map( 111 re.compile, (Token, PseudoToken, Single3, Double3)) 112 113_strprefixes = ( 114 _combinations('r', 'R', 'f', 'F') | 115 _combinations('r', 'R', 'b', 'B') | 116 {'u', 'U', 'ur', 'uR', 'Ur', 'UR'} 117) 118 119endprogs = {"'": re.compile(Single), '"': re.compile(Double), 120 "'''": single3prog, '"""': double3prog, 121 **{f"{prefix}'''": single3prog for prefix in _strprefixes}, 122 **{f'{prefix}"""': double3prog for prefix in _strprefixes}, 123 **{prefix: None for prefix in _strprefixes}} 124 125triple_quoted = ( 126 {"'''", '"""'} | 127 {f"{prefix}'''" for prefix in _strprefixes} | 128 {f'{prefix}"""' for prefix in _strprefixes} 129) 130single_quoted = ( 131 {"'", '"'} | 132 {f"{prefix}'" for prefix in _strprefixes} | 133 {f'{prefix}"' for prefix in _strprefixes} 134) 135 136tabsize = 8 137 138class TokenError(Exception): pass 139 140class StopTokenizing(Exception): pass 141 142def printtoken(type, token, xxx_todo_changeme, xxx_todo_changeme1, line): # for testing 143 (srow, scol) = xxx_todo_changeme 144 (erow, ecol) = xxx_todo_changeme1 145 print("%d,%d-%d,%d:\t%s\t%s" % \ 146 (srow, scol, erow, ecol, tok_name[type], repr(token))) 147 148def tokenize(readline, tokeneater=printtoken): 149 """ 150 The tokenize() function accepts two parameters: one representing the 151 input stream, and one providing an output mechanism for tokenize(). 152 153 The first parameter, readline, must be a callable object which provides 154 the same interface as the readline() method of built-in file objects. 155 Each call to the function should return one line of input as a string. 156 157 The second parameter, tokeneater, must also be a callable object. It is 158 called once for each token, with five arguments, corresponding to the 159 tuples generated by generate_tokens(). 160 """ 161 try: 162 tokenize_loop(readline, tokeneater) 163 except StopTokenizing: 164 pass 165 166# backwards compatible interface 167def tokenize_loop(readline, tokeneater): 168 for token_info in generate_tokens(readline): 169 tokeneater(*token_info) 170 171class Untokenizer: 172 173 def __init__(self): 174 self.tokens = [] 175 self.prev_row = 1 176 self.prev_col = 0 177 178 def add_whitespace(self, start): 179 row, col = start 180 assert row <= self.prev_row 181 col_offset = col - self.prev_col 182 if col_offset: 183 self.tokens.append(" " * col_offset) 184 185 def untokenize(self, iterable): 186 for t in iterable: 187 if len(t) == 2: 188 self.compat(t, iterable) 189 break 190 tok_type, token, start, end, line = t 191 self.add_whitespace(start) 192 self.tokens.append(token) 193 self.prev_row, self.prev_col = end 194 if tok_type in (NEWLINE, NL): 195 self.prev_row += 1 196 self.prev_col = 0 197 return "".join(self.tokens) 198 199 def compat(self, token, iterable): 200 startline = False 201 indents = [] 202 toks_append = self.tokens.append 203 toknum, tokval = token 204 if toknum in (NAME, NUMBER): 205 tokval += ' ' 206 if toknum in (NEWLINE, NL): 207 startline = True 208 for tok in iterable: 209 toknum, tokval = tok[:2] 210 211 if toknum in (NAME, NUMBER, ASYNC, AWAIT): 212 tokval += ' ' 213 214 if toknum == INDENT: 215 indents.append(tokval) 216 continue 217 elif toknum == DEDENT: 218 indents.pop() 219 continue 220 elif toknum in (NEWLINE, NL): 221 startline = True 222 elif startline and indents: 223 toks_append(indents[-1]) 224 startline = False 225 toks_append(tokval) 226 227cookie_re = re.compile(r'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)', re.ASCII) 228blank_re = re.compile(br'^[ \t\f]*(?:[#\r\n]|$)', re.ASCII) 229 230def _get_normal_name(orig_enc): 231 """Imitates get_normal_name in tokenizer.c.""" 232 # Only care about the first 12 characters. 233 enc = orig_enc[:12].lower().replace("_", "-") 234 if enc == "utf-8" or enc.startswith("utf-8-"): 235 return "utf-8" 236 if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \ 237 enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")): 238 return "iso-8859-1" 239 return orig_enc 240 241def detect_encoding(readline): 242 """ 243 The detect_encoding() function is used to detect the encoding that should 244 be used to decode a Python source file. It requires one argument, readline, 245 in the same way as the tokenize() generator. 246 247 It will call readline a maximum of twice, and return the encoding used 248 (as a string) and a list of any lines (left as bytes) it has read 249 in. 250 251 It detects the encoding from the presence of a utf-8 bom or an encoding 252 cookie as specified in pep-0263. If both a bom and a cookie are present, but 253 disagree, a SyntaxError will be raised. If the encoding cookie is an invalid 254 charset, raise a SyntaxError. Note that if a utf-8 bom is found, 255 'utf-8-sig' is returned. 256 257 If no encoding is specified, then the default of 'utf-8' will be returned. 258 """ 259 bom_found = False 260 encoding = None 261 default = 'utf-8' 262 def read_or_stop(): 263 try: 264 return readline() 265 except StopIteration: 266 return bytes() 267 268 def find_cookie(line): 269 try: 270 line_string = line.decode('ascii') 271 except UnicodeDecodeError: 272 return None 273 match = cookie_re.match(line_string) 274 if not match: 275 return None 276 encoding = _get_normal_name(match.group(1)) 277 try: 278 codec = lookup(encoding) 279 except LookupError: 280 # This behaviour mimics the Python interpreter 281 raise SyntaxError("unknown encoding: " + encoding) 282 283 if bom_found: 284 if codec.name != 'utf-8': 285 # This behaviour mimics the Python interpreter 286 raise SyntaxError('encoding problem: utf-8') 287 encoding += '-sig' 288 return encoding 289 290 first = read_or_stop() 291 if first.startswith(BOM_UTF8): 292 bom_found = True 293 first = first[3:] 294 default = 'utf-8-sig' 295 if not first: 296 return default, [] 297 298 encoding = find_cookie(first) 299 if encoding: 300 return encoding, [first] 301 if not blank_re.match(first): 302 return default, [first] 303 304 second = read_or_stop() 305 if not second: 306 return default, [first] 307 308 encoding = find_cookie(second) 309 if encoding: 310 return encoding, [first, second] 311 312 return default, [first, second] 313 314def untokenize(iterable): 315 """Transform tokens back into Python source code. 316 317 Each element returned by the iterable must be a token sequence 318 with at least two elements, a token number and token value. If 319 only two tokens are passed, the resulting output is poor. 320 321 Round-trip invariant for full input: 322 Untokenized source will match input source exactly 323 324 Round-trip invariant for limited input: 325 # Output text will tokenize the back to the input 326 t1 = [tok[:2] for tok in generate_tokens(f.readline)] 327 newcode = untokenize(t1) 328 readline = iter(newcode.splitlines(1)).next 329 t2 = [tok[:2] for tokin generate_tokens(readline)] 330 assert t1 == t2 331 """ 332 ut = Untokenizer() 333 return ut.untokenize(iterable) 334 335def generate_tokens(readline): 336 """ 337 The generate_tokens() generator requires one argument, readline, which 338 must be a callable object which provides the same interface as the 339 readline() method of built-in file objects. Each call to the function 340 should return one line of input as a string. Alternately, readline 341 can be a callable function terminating with StopIteration: 342 readline = open(myfile).next # Example of alternate readline 343 344 The generator produces 5-tuples with these members: the token type; the 345 token string; a 2-tuple (srow, scol) of ints specifying the row and 346 column where the token begins in the source; a 2-tuple (erow, ecol) of 347 ints specifying the row and column where the token ends in the source; 348 and the line on which the token was found. The line passed is the 349 physical line. 350 """ 351 lnum = parenlev = continued = 0 352 contstr, needcont = '', 0 353 contline = None 354 indents = [0] 355 356 # 'stashed' and 'async_*' are used for async/await parsing 357 stashed = None 358 async_def = False 359 async_def_indent = 0 360 async_def_nl = False 361 362 while 1: # loop over lines in stream 363 try: 364 line = readline() 365 except StopIteration: 366 line = '' 367 lnum = lnum + 1 368 pos, max = 0, len(line) 369 370 if contstr: # continued string 371 if not line: 372 raise TokenError("EOF in multi-line string", strstart) 373 endmatch = endprog.match(line) 374 if endmatch: 375 pos = end = endmatch.end(0) 376 yield (STRING, contstr + line[:end], 377 strstart, (lnum, end), contline + line) 378 contstr, needcont = '', 0 379 contline = None 380 elif needcont and line[-2:] != '\\\n' and line[-3:] != '\\\r\n': 381 yield (ERRORTOKEN, contstr + line, 382 strstart, (lnum, len(line)), contline) 383 contstr = '' 384 contline = None 385 continue 386 else: 387 contstr = contstr + line 388 contline = contline + line 389 continue 390 391 elif parenlev == 0 and not continued: # new statement 392 if not line: break 393 column = 0 394 while pos < max: # measure leading whitespace 395 if line[pos] == ' ': column = column + 1 396 elif line[pos] == '\t': column = (column//tabsize + 1)*tabsize 397 elif line[pos] == '\f': column = 0 398 else: break 399 pos = pos + 1 400 if pos == max: break 401 402 if stashed: 403 yield stashed 404 stashed = None 405 406 if line[pos] in '#\r\n': # skip comments or blank lines 407 if line[pos] == '#': 408 comment_token = line[pos:].rstrip('\r\n') 409 nl_pos = pos + len(comment_token) 410 yield (COMMENT, comment_token, 411 (lnum, pos), (lnum, pos + len(comment_token)), line) 412 yield (NL, line[nl_pos:], 413 (lnum, nl_pos), (lnum, len(line)), line) 414 else: 415 yield ((NL, COMMENT)[line[pos] == '#'], line[pos:], 416 (lnum, pos), (lnum, len(line)), line) 417 continue 418 419 if column > indents[-1]: # count indents or dedents 420 indents.append(column) 421 yield (INDENT, line[:pos], (lnum, 0), (lnum, pos), line) 422 while column < indents[-1]: 423 if column not in indents: 424 raise IndentationError( 425 "unindent does not match any outer indentation level", 426 ("<tokenize>", lnum, pos, line)) 427 indents = indents[:-1] 428 429 if async_def and async_def_indent >= indents[-1]: 430 async_def = False 431 async_def_nl = False 432 async_def_indent = 0 433 434 yield (DEDENT, '', (lnum, pos), (lnum, pos), line) 435 436 if async_def and async_def_nl and async_def_indent >= indents[-1]: 437 async_def = False 438 async_def_nl = False 439 async_def_indent = 0 440 441 else: # continued statement 442 if not line: 443 raise TokenError("EOF in multi-line statement", (lnum, 0)) 444 continued = 0 445 446 while pos < max: 447 pseudomatch = pseudoprog.match(line, pos) 448 if pseudomatch: # scan for tokens 449 start, end = pseudomatch.span(1) 450 spos, epos, pos = (lnum, start), (lnum, end), end 451 token, initial = line[start:end], line[start] 452 453 if initial in string.digits or \ 454 (initial == '.' and token != '.'): # ordinary number 455 yield (NUMBER, token, spos, epos, line) 456 elif initial in '\r\n': 457 newline = NEWLINE 458 if parenlev > 0: 459 newline = NL 460 elif async_def: 461 async_def_nl = True 462 if stashed: 463 yield stashed 464 stashed = None 465 yield (newline, token, spos, epos, line) 466 467 elif initial == '#': 468 assert not token.endswith("\n") 469 if stashed: 470 yield stashed 471 stashed = None 472 yield (COMMENT, token, spos, epos, line) 473 elif token in triple_quoted: 474 endprog = endprogs[token] 475 endmatch = endprog.match(line, pos) 476 if endmatch: # all on one line 477 pos = endmatch.end(0) 478 token = line[start:pos] 479 if stashed: 480 yield stashed 481 stashed = None 482 yield (STRING, token, spos, (lnum, pos), line) 483 else: 484 strstart = (lnum, start) # multiple lines 485 contstr = line[start:] 486 contline = line 487 break 488 elif initial in single_quoted or \ 489 token[:2] in single_quoted or \ 490 token[:3] in single_quoted: 491 if token[-1] == '\n': # continued string 492 strstart = (lnum, start) 493 endprog = (endprogs[initial] or endprogs[token[1]] or 494 endprogs[token[2]]) 495 contstr, needcont = line[start:], 1 496 contline = line 497 break 498 else: # ordinary string 499 if stashed: 500 yield stashed 501 stashed = None 502 yield (STRING, token, spos, epos, line) 503 elif initial.isidentifier(): # ordinary name 504 if token in ('async', 'await'): 505 if async_def: 506 yield (ASYNC if token == 'async' else AWAIT, 507 token, spos, epos, line) 508 continue 509 510 tok = (NAME, token, spos, epos, line) 511 if token == 'async' and not stashed: 512 stashed = tok 513 continue 514 515 if token == 'def': 516 if (stashed 517 and stashed[0] == NAME 518 and stashed[1] == 'async'): 519 520 async_def = True 521 async_def_indent = indents[-1] 522 523 yield (ASYNC, stashed[1], 524 stashed[2], stashed[3], 525 stashed[4]) 526 stashed = None 527 528 if stashed: 529 yield stashed 530 stashed = None 531 532 yield tok 533 elif initial == '\\': # continued stmt 534 # This yield is new; needed for better idempotency: 535 if stashed: 536 yield stashed 537 stashed = None 538 yield (NL, token, spos, (lnum, pos), line) 539 continued = 1 540 else: 541 if initial in '([{': parenlev = parenlev + 1 542 elif initial in ')]}': parenlev = parenlev - 1 543 if stashed: 544 yield stashed 545 stashed = None 546 yield (OP, token, spos, epos, line) 547 else: 548 yield (ERRORTOKEN, line[pos], 549 (lnum, pos), (lnum, pos+1), line) 550 pos = pos + 1 551 552 if stashed: 553 yield stashed 554 stashed = None 555 556 for indent in indents[1:]: # pop remaining indent levels 557 yield (DEDENT, '', (lnum, 0), (lnum, 0), '') 558 yield (ENDMARKER, '', (lnum, 0), (lnum, 0), '') 559 560if __name__ == '__main__': # testing 561 import sys 562 if len(sys.argv) > 1: tokenize(open(sys.argv[1]).readline) 563 else: tokenize(sys.stdin.readline) 564