1#!/usr/bin/env python 2# 3# Copyright 2008, Google Inc. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are 8# met: 9# 10# * Redistributions of source code must retain the above copyright 11# notice, this list of conditions and the following disclaimer. 12# * Redistributions in binary form must reproduce the above 13# copyright notice, this list of conditions and the following disclaimer 14# in the documentation and/or other materials provided with the 15# distribution. 16# * Neither the name of Google Inc. nor the names of its 17# contributors may be used to endorse or promote products derived from 18# this software without specific prior written permission. 19# 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 32"""pump v0.2.0 - Pretty Useful for Meta Programming. 33 34A tool for preprocessor meta programming. Useful for generating 35repetitive boilerplate code. Especially useful for writing C++ 36classes, functions, macros, and templates that need to work with 37various number of arguments. 38 39USAGE: 40 pump.py SOURCE_FILE 41 42EXAMPLES: 43 pump.py foo.cc.pump 44 Converts foo.cc.pump to foo.cc. 45 46GRAMMAR: 47 CODE ::= ATOMIC_CODE* 48 ATOMIC_CODE ::= $var ID = EXPRESSION 49 | $var ID = [[ CODE ]] 50 | $range ID EXPRESSION..EXPRESSION 51 | $for ID SEPARATOR [[ CODE ]] 52 | $($) 53 | $ID 54 | $(EXPRESSION) 55 | $if EXPRESSION [[ CODE ]] ELSE_BRANCH 56 | [[ CODE ]] 57 | RAW_CODE 58 SEPARATOR ::= RAW_CODE | EMPTY 59 ELSE_BRANCH ::= $else [[ CODE ]] 60 | $elif EXPRESSION [[ CODE ]] ELSE_BRANCH 61 | EMPTY 62 EXPRESSION has Python syntax. 63""" 64 65from __future__ import print_function 66 67import io 68import os 69import re 70import sys 71 72 73TOKEN_TABLE = [ 74 (re.compile(r'\$var\s+'), '$var'), 75 (re.compile(r'\$elif\s+'), '$elif'), 76 (re.compile(r'\$else\s+'), '$else'), 77 (re.compile(r'\$for\s+'), '$for'), 78 (re.compile(r'\$if\s+'), '$if'), 79 (re.compile(r'\$range\s+'), '$range'), 80 (re.compile(r'\$[_A-Za-z]\w*'), '$id'), 81 (re.compile(r'\$\(\$\)'), '$($)'), 82 (re.compile(r'\$'), '$'), 83 (re.compile(r'\[\[\n?'), '[['), 84 (re.compile(r'\]\]\n?'), ']]'), 85 ] 86 87 88class Cursor: 89 """Represents a position (line and column) in a text file.""" 90 91 def __init__(self, line=-1, column=-1): 92 self.line = line 93 self.column = column 94 95 def __eq__(self, rhs): 96 return self.line == rhs.line and self.column == rhs.column 97 98 def __ne__(self, rhs): 99 return not self == rhs 100 101 def __lt__(self, rhs): 102 return self.line < rhs.line or ( 103 self.line == rhs.line and self.column < rhs.column) 104 105 def __le__(self, rhs): 106 return self < rhs or self == rhs 107 108 def __gt__(self, rhs): 109 return rhs < self 110 111 def __ge__(self, rhs): 112 return rhs <= self 113 114 def __str__(self): 115 if self == Eof(): 116 return 'EOF' 117 else: 118 return '%s(%s)' % (self.line + 1, self.column) 119 120 def __add__(self, offset): 121 return Cursor(self.line, self.column + offset) 122 123 def __sub__(self, offset): 124 return Cursor(self.line, self.column - offset) 125 126 def Clone(self): 127 """Returns a copy of self.""" 128 129 return Cursor(self.line, self.column) 130 131 132# Special cursor to indicate the end-of-file. 133def Eof(): 134 """Returns the special cursor to denote the end-of-file.""" 135 return Cursor(-1, -1) 136 137 138class Token: 139 """Represents a token in a Pump source file.""" 140 141 def __init__(self, start=None, end=None, value=None, token_type=None): 142 if start is None: 143 self.start = Eof() 144 else: 145 self.start = start 146 if end is None: 147 self.end = Eof() 148 else: 149 self.end = end 150 self.value = value 151 self.token_type = token_type 152 153 def __str__(self): 154 return 'Token @%s: \'%s\' type=%s' % ( 155 self.start, self.value, self.token_type) 156 157 def Clone(self): 158 """Returns a copy of self.""" 159 160 return Token(self.start.Clone(), self.end.Clone(), self.value, 161 self.token_type) 162 163 164def StartsWith(lines, pos, string): 165 """Returns True iff the given position in lines starts with 'string'.""" 166 167 return lines[pos.line][pos.column:].startswith(string) 168 169 170def FindFirstInLine(line, token_table): 171 best_match_start = -1 172 for (regex, token_type) in token_table: 173 m = regex.search(line) 174 if m: 175 # We found regex in lines 176 if best_match_start < 0 or m.start() < best_match_start: 177 best_match_start = m.start() 178 best_match_length = m.end() - m.start() 179 best_match_token_type = token_type 180 181 if best_match_start < 0: 182 return None 183 184 return (best_match_start, best_match_length, best_match_token_type) 185 186 187def FindFirst(lines, token_table, cursor): 188 """Finds the first occurrence of any string in strings in lines.""" 189 190 start = cursor.Clone() 191 cur_line_number = cursor.line 192 for line in lines[start.line:]: 193 if cur_line_number == start.line: 194 line = line[start.column:] 195 m = FindFirstInLine(line, token_table) 196 if m: 197 # We found a regex in line. 198 (start_column, length, token_type) = m 199 if cur_line_number == start.line: 200 start_column += start.column 201 found_start = Cursor(cur_line_number, start_column) 202 found_end = found_start + length 203 return MakeToken(lines, found_start, found_end, token_type) 204 cur_line_number += 1 205 # We failed to find str in lines 206 return None 207 208 209def SubString(lines, start, end): 210 """Returns a substring in lines.""" 211 212 if end == Eof(): 213 end = Cursor(len(lines) - 1, len(lines[-1])) 214 215 if start >= end: 216 return '' 217 218 if start.line == end.line: 219 return lines[start.line][start.column:end.column] 220 221 result_lines = ([lines[start.line][start.column:]] + 222 lines[start.line + 1:end.line] + 223 [lines[end.line][:end.column]]) 224 return ''.join(result_lines) 225 226 227def StripMetaComments(str): 228 """Strip meta comments from each line in the given string.""" 229 230 # First, completely remove lines containing nothing but a meta 231 # comment, including the trailing \n. 232 str = re.sub(r'^\s*\$\$.*\n', '', str) 233 234 # Then, remove meta comments from contentful lines. 235 return re.sub(r'\s*\$\$.*', '', str) 236 237 238def MakeToken(lines, start, end, token_type): 239 """Creates a new instance of Token.""" 240 241 return Token(start, end, SubString(lines, start, end), token_type) 242 243 244def ParseToken(lines, pos, regex, token_type): 245 line = lines[pos.line][pos.column:] 246 m = regex.search(line) 247 if m and not m.start(): 248 return MakeToken(lines, pos, pos + m.end(), token_type) 249 else: 250 print('ERROR: %s expected at %s.' % (token_type, pos)) 251 sys.exit(1) 252 253 254ID_REGEX = re.compile(r'[_A-Za-z]\w*') 255EQ_REGEX = re.compile(r'=') 256REST_OF_LINE_REGEX = re.compile(r'.*?(?=$|\$\$)') 257OPTIONAL_WHITE_SPACES_REGEX = re.compile(r'\s*') 258WHITE_SPACE_REGEX = re.compile(r'\s') 259DOT_DOT_REGEX = re.compile(r'\.\.') 260 261 262def Skip(lines, pos, regex): 263 line = lines[pos.line][pos.column:] 264 m = re.search(regex, line) 265 if m and not m.start(): 266 return pos + m.end() 267 else: 268 return pos 269 270 271def SkipUntil(lines, pos, regex, token_type): 272 line = lines[pos.line][pos.column:] 273 m = re.search(regex, line) 274 if m: 275 return pos + m.start() 276 else: 277 print ('ERROR: %s expected on line %s after column %s.' % 278 (token_type, pos.line + 1, pos.column)) 279 sys.exit(1) 280 281 282def ParseExpTokenInParens(lines, pos): 283 def ParseInParens(pos): 284 pos = Skip(lines, pos, OPTIONAL_WHITE_SPACES_REGEX) 285 pos = Skip(lines, pos, r'\(') 286 pos = Parse(pos) 287 pos = Skip(lines, pos, r'\)') 288 return pos 289 290 def Parse(pos): 291 pos = SkipUntil(lines, pos, r'\(|\)', ')') 292 if SubString(lines, pos, pos + 1) == '(': 293 pos = Parse(pos + 1) 294 pos = Skip(lines, pos, r'\)') 295 return Parse(pos) 296 else: 297 return pos 298 299 start = pos.Clone() 300 pos = ParseInParens(pos) 301 return MakeToken(lines, start, pos, 'exp') 302 303 304def RStripNewLineFromToken(token): 305 if token.value.endswith('\n'): 306 return Token(token.start, token.end, token.value[:-1], token.token_type) 307 else: 308 return token 309 310 311def TokenizeLines(lines, pos): 312 while True: 313 found = FindFirst(lines, TOKEN_TABLE, pos) 314 if not found: 315 yield MakeToken(lines, pos, Eof(), 'code') 316 return 317 318 if found.start == pos: 319 prev_token = None 320 prev_token_rstripped = None 321 else: 322 prev_token = MakeToken(lines, pos, found.start, 'code') 323 prev_token_rstripped = RStripNewLineFromToken(prev_token) 324 325 if found.token_type == '$var': 326 if prev_token_rstripped: 327 yield prev_token_rstripped 328 yield found 329 id_token = ParseToken(lines, found.end, ID_REGEX, 'id') 330 yield id_token 331 pos = Skip(lines, id_token.end, OPTIONAL_WHITE_SPACES_REGEX) 332 333 eq_token = ParseToken(lines, pos, EQ_REGEX, '=') 334 yield eq_token 335 pos = Skip(lines, eq_token.end, r'\s*') 336 337 if SubString(lines, pos, pos + 2) != '[[': 338 exp_token = ParseToken(lines, pos, REST_OF_LINE_REGEX, 'exp') 339 yield exp_token 340 pos = Cursor(exp_token.end.line + 1, 0) 341 elif found.token_type == '$for': 342 if prev_token_rstripped: 343 yield prev_token_rstripped 344 yield found 345 id_token = ParseToken(lines, found.end, ID_REGEX, 'id') 346 yield id_token 347 pos = Skip(lines, id_token.end, WHITE_SPACE_REGEX) 348 elif found.token_type == '$range': 349 if prev_token_rstripped: 350 yield prev_token_rstripped 351 yield found 352 id_token = ParseToken(lines, found.end, ID_REGEX, 'id') 353 yield id_token 354 pos = Skip(lines, id_token.end, OPTIONAL_WHITE_SPACES_REGEX) 355 356 dots_pos = SkipUntil(lines, pos, DOT_DOT_REGEX, '..') 357 yield MakeToken(lines, pos, dots_pos, 'exp') 358 yield MakeToken(lines, dots_pos, dots_pos + 2, '..') 359 pos = dots_pos + 2 360 new_pos = Cursor(pos.line + 1, 0) 361 yield MakeToken(lines, pos, new_pos, 'exp') 362 pos = new_pos 363 elif found.token_type == '$': 364 if prev_token: 365 yield prev_token 366 yield found 367 exp_token = ParseExpTokenInParens(lines, found.end) 368 yield exp_token 369 pos = exp_token.end 370 elif (found.token_type == ']]' or found.token_type == '$if' or 371 found.token_type == '$elif' or found.token_type == '$else'): 372 if prev_token_rstripped: 373 yield prev_token_rstripped 374 yield found 375 pos = found.end 376 else: 377 if prev_token: 378 yield prev_token 379 yield found 380 pos = found.end 381 382 383def Tokenize(s): 384 """A generator that yields the tokens in the given string.""" 385 if s != '': 386 lines = s.splitlines(True) 387 for token in TokenizeLines(lines, Cursor(0, 0)): 388 yield token 389 390 391class CodeNode: 392 def __init__(self, atomic_code_list=None): 393 self.atomic_code = atomic_code_list 394 395 396class VarNode: 397 def __init__(self, identifier=None, atomic_code=None): 398 self.identifier = identifier 399 self.atomic_code = atomic_code 400 401 402class RangeNode: 403 def __init__(self, identifier=None, exp1=None, exp2=None): 404 self.identifier = identifier 405 self.exp1 = exp1 406 self.exp2 = exp2 407 408 409class ForNode: 410 def __init__(self, identifier=None, sep=None, code=None): 411 self.identifier = identifier 412 self.sep = sep 413 self.code = code 414 415 416class ElseNode: 417 def __init__(self, else_branch=None): 418 self.else_branch = else_branch 419 420 421class IfNode: 422 def __init__(self, exp=None, then_branch=None, else_branch=None): 423 self.exp = exp 424 self.then_branch = then_branch 425 self.else_branch = else_branch 426 427 428class RawCodeNode: 429 def __init__(self, token=None): 430 self.raw_code = token 431 432 433class LiteralDollarNode: 434 def __init__(self, token): 435 self.token = token 436 437 438class ExpNode: 439 def __init__(self, token, python_exp): 440 self.token = token 441 self.python_exp = python_exp 442 443 444def PopFront(a_list): 445 head = a_list[0] 446 a_list[:1] = [] 447 return head 448 449 450def PushFront(a_list, elem): 451 a_list[:0] = [elem] 452 453 454def PopToken(a_list, token_type=None): 455 token = PopFront(a_list) 456 if token_type is not None and token.token_type != token_type: 457 print('ERROR: %s expected at %s' % (token_type, token.start)) 458 print('ERROR: %s found instead' % (token,)) 459 sys.exit(1) 460 461 return token 462 463 464def PeekToken(a_list): 465 if not a_list: 466 return None 467 468 return a_list[0] 469 470 471def ParseExpNode(token): 472 python_exp = re.sub(r'([_A-Za-z]\w*)', r'self.GetValue("\1")', token.value) 473 return ExpNode(token, python_exp) 474 475 476def ParseElseNode(tokens): 477 def Pop(token_type=None): 478 return PopToken(tokens, token_type) 479 480 next = PeekToken(tokens) 481 if not next: 482 return None 483 if next.token_type == '$else': 484 Pop('$else') 485 Pop('[[') 486 code_node = ParseCodeNode(tokens) 487 Pop(']]') 488 return code_node 489 elif next.token_type == '$elif': 490 Pop('$elif') 491 exp = Pop('code') 492 Pop('[[') 493 code_node = ParseCodeNode(tokens) 494 Pop(']]') 495 inner_else_node = ParseElseNode(tokens) 496 return CodeNode([IfNode(ParseExpNode(exp), code_node, inner_else_node)]) 497 elif not next.value.strip(): 498 Pop('code') 499 return ParseElseNode(tokens) 500 else: 501 return None 502 503 504def ParseAtomicCodeNode(tokens): 505 def Pop(token_type=None): 506 return PopToken(tokens, token_type) 507 508 head = PopFront(tokens) 509 t = head.token_type 510 if t == 'code': 511 return RawCodeNode(head) 512 elif t == '$var': 513 id_token = Pop('id') 514 Pop('=') 515 next = PeekToken(tokens) 516 if next.token_type == 'exp': 517 exp_token = Pop() 518 return VarNode(id_token, ParseExpNode(exp_token)) 519 Pop('[[') 520 code_node = ParseCodeNode(tokens) 521 Pop(']]') 522 return VarNode(id_token, code_node) 523 elif t == '$for': 524 id_token = Pop('id') 525 next_token = PeekToken(tokens) 526 if next_token.token_type == 'code': 527 sep_token = next_token 528 Pop('code') 529 else: 530 sep_token = None 531 Pop('[[') 532 code_node = ParseCodeNode(tokens) 533 Pop(']]') 534 return ForNode(id_token, sep_token, code_node) 535 elif t == '$if': 536 exp_token = Pop('code') 537 Pop('[[') 538 code_node = ParseCodeNode(tokens) 539 Pop(']]') 540 else_node = ParseElseNode(tokens) 541 return IfNode(ParseExpNode(exp_token), code_node, else_node) 542 elif t == '$range': 543 id_token = Pop('id') 544 exp1_token = Pop('exp') 545 Pop('..') 546 exp2_token = Pop('exp') 547 return RangeNode(id_token, ParseExpNode(exp1_token), 548 ParseExpNode(exp2_token)) 549 elif t == '$id': 550 return ParseExpNode(Token(head.start + 1, head.end, head.value[1:], 'id')) 551 elif t == '$($)': 552 return LiteralDollarNode(head) 553 elif t == '$': 554 exp_token = Pop('exp') 555 return ParseExpNode(exp_token) 556 elif t == '[[': 557 code_node = ParseCodeNode(tokens) 558 Pop(']]') 559 return code_node 560 else: 561 PushFront(tokens, head) 562 return None 563 564 565def ParseCodeNode(tokens): 566 atomic_code_list = [] 567 while True: 568 if not tokens: 569 break 570 atomic_code_node = ParseAtomicCodeNode(tokens) 571 if atomic_code_node: 572 atomic_code_list.append(atomic_code_node) 573 else: 574 break 575 return CodeNode(atomic_code_list) 576 577 578def ParseToAST(pump_src_text): 579 """Convert the given Pump source text into an AST.""" 580 tokens = list(Tokenize(pump_src_text)) 581 code_node = ParseCodeNode(tokens) 582 return code_node 583 584 585class Env: 586 def __init__(self): 587 self.variables = [] 588 self.ranges = [] 589 590 def Clone(self): 591 clone = Env() 592 clone.variables = self.variables[:] 593 clone.ranges = self.ranges[:] 594 return clone 595 596 def PushVariable(self, var, value): 597 # If value looks like an int, store it as an int. 598 try: 599 int_value = int(value) 600 if ('%s' % int_value) == value: 601 value = int_value 602 except Exception: 603 pass 604 self.variables[:0] = [(var, value)] 605 606 def PopVariable(self): 607 self.variables[:1] = [] 608 609 def PushRange(self, var, lower, upper): 610 self.ranges[:0] = [(var, lower, upper)] 611 612 def PopRange(self): 613 self.ranges[:1] = [] 614 615 def GetValue(self, identifier): 616 for (var, value) in self.variables: 617 if identifier == var: 618 return value 619 620 print('ERROR: meta variable %s is undefined.' % (identifier,)) 621 sys.exit(1) 622 623 def EvalExp(self, exp): 624 try: 625 result = eval(exp.python_exp) 626 except Exception as e: # pylint: disable=broad-except 627 print('ERROR: caught exception %s: %s' % (e.__class__.__name__, e)) 628 print('ERROR: failed to evaluate meta expression %s at %s' % 629 (exp.python_exp, exp.token.start)) 630 sys.exit(1) 631 return result 632 633 def GetRange(self, identifier): 634 for (var, lower, upper) in self.ranges: 635 if identifier == var: 636 return (lower, upper) 637 638 print('ERROR: range %s is undefined.' % (identifier,)) 639 sys.exit(1) 640 641 642class Output: 643 def __init__(self): 644 self.string = '' 645 646 def GetLastLine(self): 647 index = self.string.rfind('\n') 648 if index < 0: 649 return '' 650 651 return self.string[index + 1:] 652 653 def Append(self, s): 654 self.string += s 655 656 657def RunAtomicCode(env, node, output): 658 if isinstance(node, VarNode): 659 identifier = node.identifier.value.strip() 660 result = Output() 661 RunAtomicCode(env.Clone(), node.atomic_code, result) 662 value = result.string 663 env.PushVariable(identifier, value) 664 elif isinstance(node, RangeNode): 665 identifier = node.identifier.value.strip() 666 lower = int(env.EvalExp(node.exp1)) 667 upper = int(env.EvalExp(node.exp2)) 668 env.PushRange(identifier, lower, upper) 669 elif isinstance(node, ForNode): 670 identifier = node.identifier.value.strip() 671 if node.sep is None: 672 sep = '' 673 else: 674 sep = node.sep.value 675 (lower, upper) = env.GetRange(identifier) 676 for i in range(lower, upper + 1): 677 new_env = env.Clone() 678 new_env.PushVariable(identifier, i) 679 RunCode(new_env, node.code, output) 680 if i != upper: 681 output.Append(sep) 682 elif isinstance(node, RawCodeNode): 683 output.Append(node.raw_code.value) 684 elif isinstance(node, IfNode): 685 cond = env.EvalExp(node.exp) 686 if cond: 687 RunCode(env.Clone(), node.then_branch, output) 688 elif node.else_branch is not None: 689 RunCode(env.Clone(), node.else_branch, output) 690 elif isinstance(node, ExpNode): 691 value = env.EvalExp(node) 692 output.Append('%s' % (value,)) 693 elif isinstance(node, LiteralDollarNode): 694 output.Append('$') 695 elif isinstance(node, CodeNode): 696 RunCode(env.Clone(), node, output) 697 else: 698 print('BAD') 699 print(node) 700 sys.exit(1) 701 702 703def RunCode(env, code_node, output): 704 for atomic_code in code_node.atomic_code: 705 RunAtomicCode(env, atomic_code, output) 706 707 708def IsSingleLineComment(cur_line): 709 return '//' in cur_line 710 711 712def IsInPreprocessorDirective(prev_lines, cur_line): 713 if cur_line.lstrip().startswith('#'): 714 return True 715 return prev_lines and prev_lines[-1].endswith('\\') 716 717 718def WrapComment(line, output): 719 loc = line.find('//') 720 before_comment = line[:loc].rstrip() 721 if before_comment == '': 722 indent = loc 723 else: 724 output.append(before_comment) 725 indent = len(before_comment) - len(before_comment.lstrip()) 726 prefix = indent*' ' + '// ' 727 max_len = 80 - len(prefix) 728 comment = line[loc + 2:].strip() 729 segs = [seg for seg in re.split(r'(\w+\W*)', comment) if seg != ''] 730 cur_line = '' 731 for seg in segs: 732 if len((cur_line + seg).rstrip()) < max_len: 733 cur_line += seg 734 else: 735 if cur_line.strip() != '': 736 output.append(prefix + cur_line.rstrip()) 737 cur_line = seg.lstrip() 738 if cur_line.strip() != '': 739 output.append(prefix + cur_line.strip()) 740 741 742def WrapCode(line, line_concat, output): 743 indent = len(line) - len(line.lstrip()) 744 prefix = indent*' ' # Prefix of the current line 745 max_len = 80 - indent - len(line_concat) # Maximum length of the current line 746 new_prefix = prefix + 4*' ' # Prefix of a continuation line 747 new_max_len = max_len - 4 # Maximum length of a continuation line 748 # Prefers to wrap a line after a ',' or ';'. 749 segs = [seg for seg in re.split(r'([^,;]+[,;]?)', line.strip()) if seg != ''] 750 cur_line = '' # The current line without leading spaces. 751 for seg in segs: 752 # If the line is still too long, wrap at a space. 753 while cur_line == '' and len(seg.strip()) > max_len: 754 seg = seg.lstrip() 755 split_at = seg.rfind(' ', 0, max_len) 756 output.append(prefix + seg[:split_at].strip() + line_concat) 757 seg = seg[split_at + 1:] 758 prefix = new_prefix 759 max_len = new_max_len 760 761 if len((cur_line + seg).rstrip()) < max_len: 762 cur_line = (cur_line + seg).lstrip() 763 else: 764 output.append(prefix + cur_line.rstrip() + line_concat) 765 prefix = new_prefix 766 max_len = new_max_len 767 cur_line = seg.lstrip() 768 if cur_line.strip() != '': 769 output.append(prefix + cur_line.strip()) 770 771 772def WrapPreprocessorDirective(line, output): 773 WrapCode(line, ' \\', output) 774 775 776def WrapPlainCode(line, output): 777 WrapCode(line, '', output) 778 779 780def IsMultiLineIWYUPragma(line): 781 return re.search(r'/\* IWYU pragma: ', line) 782 783 784def IsHeaderGuardIncludeOrOneLineIWYUPragma(line): 785 return (re.match(r'^#(ifndef|define|endif\s*//)\s*[\w_]+\s*$', line) or 786 re.match(r'^#include\s', line) or 787 # Don't break IWYU pragmas, either; that causes iwyu.py problems. 788 re.search(r'// IWYU pragma: ', line)) 789 790 791def WrapLongLine(line, output): 792 line = line.rstrip() 793 if len(line) <= 80: 794 output.append(line) 795 elif IsSingleLineComment(line): 796 if IsHeaderGuardIncludeOrOneLineIWYUPragma(line): 797 # The style guide made an exception to allow long header guard lines, 798 # includes and IWYU pragmas. 799 output.append(line) 800 else: 801 WrapComment(line, output) 802 elif IsInPreprocessorDirective(output, line): 803 if IsHeaderGuardIncludeOrOneLineIWYUPragma(line): 804 # The style guide made an exception to allow long header guard lines, 805 # includes and IWYU pragmas. 806 output.append(line) 807 else: 808 WrapPreprocessorDirective(line, output) 809 elif IsMultiLineIWYUPragma(line): 810 output.append(line) 811 else: 812 WrapPlainCode(line, output) 813 814 815def BeautifyCode(string): 816 lines = string.splitlines() 817 output = [] 818 for line in lines: 819 WrapLongLine(line, output) 820 output2 = [line.rstrip() for line in output] 821 return '\n'.join(output2) + '\n' 822 823 824def ConvertFromPumpSource(src_text): 825 """Return the text generated from the given Pump source text.""" 826 ast = ParseToAST(StripMetaComments(src_text)) 827 output = Output() 828 RunCode(Env(), ast, output) 829 return BeautifyCode(output.string) 830 831 832def main(argv): 833 if len(argv) == 1: 834 print(__doc__) 835 sys.exit(1) 836 837 file_path = argv[-1] 838 output_str = ConvertFromPumpSource(io.open(file_path, 'r').read()) 839 if file_path.endswith('.pump'): 840 output_file_path = file_path[:-5] 841 else: 842 output_file_path = '-' 843 if output_file_path == '-': 844 print(output_str,) 845 else: 846 output_file = io.open(output_file_path, 'w') 847 output_file.write(u'// This file was GENERATED by command:\n') 848 output_file.write(u'// %s %s\n' % 849 (os.path.basename(__file__), os.path.basename(file_path))) 850 output_file.write(u'// DO NOT EDIT BY HAND!!!\n\n') 851 output_file.write(output_str) 852 output_file.close() 853 854 855if __name__ == '__main__': 856 main(sys.argv) 857