Home | History | Annotate | Download | only in scripts
      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.1 - Pretty Useful for Meta Programming.
     33 
     34 A tool for preprocessor meta programming.  Useful for generating
     35 repetitive boilerplate code.  Especially useful for writing C++
     36 classes, functions, macros, and templates that need to work with
     37 various number of arguments.
     38 
     39 USAGE:
     40        pump.py SOURCE_FILE
     41 
     42 EXAMPLES:
     43        pump.py foo.cc.pump
     44          Converts foo.cc.pump to foo.cc.
     45 
     46 GRAMMAR:
     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 
     65 __author__ = 'wan (at] google.com (Zhanyong Wan)'
     66 
     67 import os
     68 import re
     69 import sys
     70 
     71 
     72 TOKEN_TABLE = [
     73     (re.compile(r'\$var\s+'), '$var'),
     74     (re.compile(r'\$elif\s+'), '$elif'),
     75     (re.compile(r'\$else\s+'), '$else'),
     76     (re.compile(r'\$for\s+'), '$for'),
     77     (re.compile(r'\$if\s+'), '$if'),
     78     (re.compile(r'\$range\s+'), '$range'),
     79     (re.compile(r'\$[_A-Za-z]\w*'), '$id'),
     80     (re.compile(r'\$\(\$\)'), '$($)'),
     81     (re.compile(r'\$\$.*'), '$$'),
     82     (re.compile(r'\$'), '$'),
     83     (re.compile(r'\[\[\n?'), '[['),
     84     (re.compile(r'\]\]\n?'), ']]'),
     85     ]
     86 
     87 
     88 class 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.
    133 def Eof():
    134   """Returns the special cursor to denote the end-of-file."""
    135   return Cursor(-1, -1)
    136 
    137 
    138 class 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 
    164 def 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 
    170 def 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 
    187 def 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 
    209 def 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 
    227 def MakeToken(lines, start, end, token_type):
    228   """Creates a new instance of Token."""
    229 
    230   return Token(start, end, SubString(lines, start, end), token_type)
    231 
    232 
    233 def ParseToken(lines, pos, regex, token_type):
    234   line = lines[pos.line][pos.column:]
    235   m = regex.search(line)
    236   if m and not m.start():
    237     return MakeToken(lines, pos, pos + m.end(), token_type)
    238   else:
    239     print 'ERROR: %s expected at %s.' % (token_type, pos)
    240     sys.exit(1)
    241 
    242 
    243 ID_REGEX = re.compile(r'[_A-Za-z]\w*')
    244 EQ_REGEX = re.compile(r'=')
    245 REST_OF_LINE_REGEX = re.compile(r'.*?(?=$|\$\$)')
    246 OPTIONAL_WHITE_SPACES_REGEX = re.compile(r'\s*')
    247 WHITE_SPACE_REGEX = re.compile(r'\s')
    248 DOT_DOT_REGEX = re.compile(r'\.\.')
    249 
    250 
    251 def Skip(lines, pos, regex):
    252   line = lines[pos.line][pos.column:]
    253   m = re.search(regex, line)
    254   if m and not m.start():
    255     return pos + m.end()
    256   else:
    257     return pos
    258 
    259 
    260 def SkipUntil(lines, pos, regex, token_type):
    261   line = lines[pos.line][pos.column:]
    262   m = re.search(regex, line)
    263   if m:
    264     return pos + m.start()
    265   else:
    266     print ('ERROR: %s expected on line %s after column %s.' %
    267            (token_type, pos.line + 1, pos.column))
    268     sys.exit(1)
    269 
    270 
    271 def ParseExpTokenInParens(lines, pos):
    272   def ParseInParens(pos):
    273     pos = Skip(lines, pos, OPTIONAL_WHITE_SPACES_REGEX)
    274     pos = Skip(lines, pos, r'\(')
    275     pos = Parse(pos)
    276     pos = Skip(lines, pos, r'\)')
    277     return pos
    278 
    279   def Parse(pos):
    280     pos = SkipUntil(lines, pos, r'\(|\)', ')')
    281     if SubString(lines, pos, pos + 1) == '(':
    282       pos = Parse(pos + 1)
    283       pos = Skip(lines, pos, r'\)')
    284       return Parse(pos)
    285     else:
    286       return pos
    287 
    288   start = pos.Clone()
    289   pos = ParseInParens(pos)
    290   return MakeToken(lines, start, pos, 'exp')
    291 
    292 
    293 def RStripNewLineFromToken(token):
    294   if token.value.endswith('\n'):
    295     return Token(token.start, token.end, token.value[:-1], token.token_type)
    296   else:
    297     return token
    298 
    299 
    300 def TokenizeLines(lines, pos):
    301   while True:
    302     found = FindFirst(lines, TOKEN_TABLE, pos)
    303     if not found:
    304       yield MakeToken(lines, pos, Eof(), 'code')
    305       return
    306 
    307     if found.start == pos:
    308       prev_token = None
    309       prev_token_rstripped = None
    310     else:
    311       prev_token = MakeToken(lines, pos, found.start, 'code')
    312       prev_token_rstripped = RStripNewLineFromToken(prev_token)
    313 
    314     if found.token_type == '$$':  # A meta comment.
    315       if prev_token_rstripped:
    316         yield prev_token_rstripped
    317       pos = Cursor(found.end.line + 1, 0)
    318     elif found.token_type == '$var':
    319       if prev_token_rstripped:
    320         yield prev_token_rstripped
    321       yield found
    322       id_token = ParseToken(lines, found.end, ID_REGEX, 'id')
    323       yield id_token
    324       pos = Skip(lines, id_token.end, OPTIONAL_WHITE_SPACES_REGEX)
    325 
    326       eq_token = ParseToken(lines, pos, EQ_REGEX, '=')
    327       yield eq_token
    328       pos = Skip(lines, eq_token.end, r'\s*')
    329 
    330       if SubString(lines, pos, pos + 2) != '[[':
    331         exp_token = ParseToken(lines, pos, REST_OF_LINE_REGEX, 'exp')
    332         yield exp_token
    333         pos = Cursor(exp_token.end.line + 1, 0)
    334     elif found.token_type == '$for':
    335       if prev_token_rstripped:
    336         yield prev_token_rstripped
    337       yield found
    338       id_token = ParseToken(lines, found.end, ID_REGEX, 'id')
    339       yield id_token
    340       pos = Skip(lines, id_token.end, WHITE_SPACE_REGEX)
    341     elif found.token_type == '$range':
    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, OPTIONAL_WHITE_SPACES_REGEX)
    348 
    349       dots_pos = SkipUntil(lines, pos, DOT_DOT_REGEX, '..')
    350       yield MakeToken(lines, pos, dots_pos, 'exp')
    351       yield MakeToken(lines, dots_pos, dots_pos + 2, '..')
    352       pos = dots_pos + 2
    353       new_pos = Cursor(pos.line + 1, 0)
    354       yield MakeToken(lines, pos, new_pos, 'exp')
    355       pos = new_pos
    356     elif found.token_type == '$':
    357       if prev_token:
    358         yield prev_token
    359       yield found
    360       exp_token = ParseExpTokenInParens(lines, found.end)
    361       yield exp_token
    362       pos = exp_token.end
    363     elif (found.token_type == ']]' or found.token_type == '$if' or
    364           found.token_type == '$elif' or found.token_type == '$else'):
    365       if prev_token_rstripped:
    366         yield prev_token_rstripped
    367       yield found
    368       pos = found.end
    369     else:
    370       if prev_token:
    371         yield prev_token
    372       yield found
    373       pos = found.end
    374 
    375 
    376 def Tokenize(s):
    377   lines = s.splitlines(True)
    378   return TokenizeLines(lines, Cursor(0, 0))
    379 
    380 
    381 class CodeNode:
    382   def __init__(self, atomic_code_list=None):
    383     self.atomic_code = atomic_code_list
    384 
    385 
    386 class VarNode:
    387   def __init__(self, identifier=None, atomic_code=None):
    388     self.identifier = identifier
    389     self.atomic_code = atomic_code
    390 
    391 
    392 class RangeNode:
    393   def __init__(self, identifier=None, exp1=None, exp2=None):
    394     self.identifier = identifier
    395     self.exp1 = exp1
    396     self.exp2 = exp2
    397 
    398 
    399 class ForNode:
    400   def __init__(self, identifier=None, sep=None, code=None):
    401     self.identifier = identifier
    402     self.sep = sep
    403     self.code = code
    404 
    405 
    406 class ElseNode:
    407   def __init__(self, else_branch=None):
    408     self.else_branch = else_branch
    409 
    410 
    411 class IfNode:
    412   def __init__(self, exp=None, then_branch=None, else_branch=None):
    413     self.exp = exp
    414     self.then_branch = then_branch
    415     self.else_branch = else_branch
    416 
    417 
    418 class RawCodeNode:
    419   def __init__(self, token=None):
    420     self.raw_code = token
    421 
    422 
    423 class LiteralDollarNode:
    424   def __init__(self, token):
    425     self.token = token
    426 
    427 
    428 class ExpNode:
    429   def __init__(self, token, python_exp):
    430     self.token = token
    431     self.python_exp = python_exp
    432 
    433 
    434 def PopFront(a_list):
    435   head = a_list[0]
    436   a_list[:1] = []
    437   return head
    438 
    439 
    440 def PushFront(a_list, elem):
    441   a_list[:0] = [elem]
    442 
    443 
    444 def PopToken(a_list, token_type=None):
    445   token = PopFront(a_list)
    446   if token_type is not None and token.token_type != token_type:
    447     print 'ERROR: %s expected at %s' % (token_type, token.start)
    448     print 'ERROR: %s found instead' % (token,)
    449     sys.exit(1)
    450 
    451   return token
    452 
    453 
    454 def PeekToken(a_list):
    455   if not a_list:
    456     return None
    457 
    458   return a_list[0]
    459 
    460 
    461 def ParseExpNode(token):
    462   python_exp = re.sub(r'([_A-Za-z]\w*)', r'self.GetValue("\1")', token.value)
    463   return ExpNode(token, python_exp)
    464 
    465 
    466 def ParseElseNode(tokens):
    467   def Pop(token_type=None):
    468     return PopToken(tokens, token_type)
    469 
    470   next = PeekToken(tokens)
    471   if not next:
    472     return None
    473   if next.token_type == '$else':
    474     Pop('$else')
    475     Pop('[[')
    476     code_node = ParseCodeNode(tokens)
    477     Pop(']]')
    478     return code_node
    479   elif next.token_type == '$elif':
    480     Pop('$elif')
    481     exp = Pop('code')
    482     Pop('[[')
    483     code_node = ParseCodeNode(tokens)
    484     Pop(']]')
    485     inner_else_node = ParseElseNode(tokens)
    486     return CodeNode([IfNode(ParseExpNode(exp), code_node, inner_else_node)])
    487   elif not next.value.strip():
    488     Pop('code')
    489     return ParseElseNode(tokens)
    490   else:
    491     return None
    492 
    493 
    494 def ParseAtomicCodeNode(tokens):
    495   def Pop(token_type=None):
    496     return PopToken(tokens, token_type)
    497 
    498   head = PopFront(tokens)
    499   t = head.token_type
    500   if t == 'code':
    501     return RawCodeNode(head)
    502   elif t == '$var':
    503     id_token = Pop('id')
    504     Pop('=')
    505     next = PeekToken(tokens)
    506     if next.token_type == 'exp':
    507       exp_token = Pop()
    508       return VarNode(id_token, ParseExpNode(exp_token))
    509     Pop('[[')
    510     code_node = ParseCodeNode(tokens)
    511     Pop(']]')
    512     return VarNode(id_token, code_node)
    513   elif t == '$for':
    514     id_token = Pop('id')
    515     next_token = PeekToken(tokens)
    516     if next_token.token_type == 'code':
    517       sep_token = next_token
    518       Pop('code')
    519     else:
    520       sep_token = None
    521     Pop('[[')
    522     code_node = ParseCodeNode(tokens)
    523     Pop(']]')
    524     return ForNode(id_token, sep_token, code_node)
    525   elif t == '$if':
    526     exp_token = Pop('code')
    527     Pop('[[')
    528     code_node = ParseCodeNode(tokens)
    529     Pop(']]')
    530     else_node = ParseElseNode(tokens)
    531     return IfNode(ParseExpNode(exp_token), code_node, else_node)
    532   elif t == '$range':
    533     id_token = Pop('id')
    534     exp1_token = Pop('exp')
    535     Pop('..')
    536     exp2_token = Pop('exp')
    537     return RangeNode(id_token, ParseExpNode(exp1_token),
    538                      ParseExpNode(exp2_token))
    539   elif t == '$id':
    540     return ParseExpNode(Token(head.start + 1, head.end, head.value[1:], 'id'))
    541   elif t == '$($)':
    542     return LiteralDollarNode(head)
    543   elif t == '$':
    544     exp_token = Pop('exp')
    545     return ParseExpNode(exp_token)
    546   elif t == '[[':
    547     code_node = ParseCodeNode(tokens)
    548     Pop(']]')
    549     return code_node
    550   else:
    551     PushFront(tokens, head)
    552     return None
    553 
    554 
    555 def ParseCodeNode(tokens):
    556   atomic_code_list = []
    557   while True:
    558     if not tokens:
    559       break
    560     atomic_code_node = ParseAtomicCodeNode(tokens)
    561     if atomic_code_node:
    562       atomic_code_list.append(atomic_code_node)
    563     else:
    564       break
    565   return CodeNode(atomic_code_list)
    566 
    567 
    568 def Convert(file_path):
    569   s = file(file_path, 'r').read()
    570   tokens = []
    571   for token in Tokenize(s):
    572     tokens.append(token)
    573   code_node = ParseCodeNode(tokens)
    574   return code_node
    575 
    576 
    577 class Env:
    578   def __init__(self):
    579     self.variables = []
    580     self.ranges = []
    581 
    582   def Clone(self):
    583     clone = Env()
    584     clone.variables = self.variables[:]
    585     clone.ranges = self.ranges[:]
    586     return clone
    587 
    588   def PushVariable(self, var, value):
    589     # If value looks like an int, store it as an int.
    590     try:
    591       int_value = int(value)
    592       if ('%s' % int_value) == value:
    593         value = int_value
    594     except Exception:
    595       pass
    596     self.variables[:0] = [(var, value)]
    597 
    598   def PopVariable(self):
    599     self.variables[:1] = []
    600 
    601   def PushRange(self, var, lower, upper):
    602     self.ranges[:0] = [(var, lower, upper)]
    603 
    604   def PopRange(self):
    605     self.ranges[:1] = []
    606 
    607   def GetValue(self, identifier):
    608     for (var, value) in self.variables:
    609       if identifier == var:
    610         return value
    611 
    612     print 'ERROR: meta variable %s is undefined.' % (identifier,)
    613     sys.exit(1)
    614 
    615   def EvalExp(self, exp):
    616     try:
    617       result = eval(exp.python_exp)
    618     except Exception, e:
    619       print 'ERROR: caught exception %s: %s' % (e.__class__.__name__, e)
    620       print ('ERROR: failed to evaluate meta expression %s at %s' %
    621              (exp.python_exp, exp.token.start))
    622       sys.exit(1)
    623     return result
    624 
    625   def GetRange(self, identifier):
    626     for (var, lower, upper) in self.ranges:
    627       if identifier == var:
    628         return (lower, upper)
    629 
    630     print 'ERROR: range %s is undefined.' % (identifier,)
    631     sys.exit(1)
    632 
    633 
    634 class Output:
    635   def __init__(self):
    636     self.string = ''
    637 
    638   def GetLastLine(self):
    639     index = self.string.rfind('\n')
    640     if index < 0:
    641       return ''
    642 
    643     return self.string[index + 1:]
    644 
    645   def Append(self, s):
    646     self.string += s
    647 
    648 
    649 def RunAtomicCode(env, node, output):
    650   if isinstance(node, VarNode):
    651     identifier = node.identifier.value.strip()
    652     result = Output()
    653     RunAtomicCode(env.Clone(), node.atomic_code, result)
    654     value = result.string
    655     env.PushVariable(identifier, value)
    656   elif isinstance(node, RangeNode):
    657     identifier = node.identifier.value.strip()
    658     lower = int(env.EvalExp(node.exp1))
    659     upper = int(env.EvalExp(node.exp2))
    660     env.PushRange(identifier, lower, upper)
    661   elif isinstance(node, ForNode):
    662     identifier = node.identifier.value.strip()
    663     if node.sep is None:
    664       sep = ''
    665     else:
    666       sep = node.sep.value
    667     (lower, upper) = env.GetRange(identifier)
    668     for i in range(lower, upper + 1):
    669       new_env = env.Clone()
    670       new_env.PushVariable(identifier, i)
    671       RunCode(new_env, node.code, output)
    672       if i != upper:
    673         output.Append(sep)
    674   elif isinstance(node, RawCodeNode):
    675     output.Append(node.raw_code.value)
    676   elif isinstance(node, IfNode):
    677     cond = env.EvalExp(node.exp)
    678     if cond:
    679       RunCode(env.Clone(), node.then_branch, output)
    680     elif node.else_branch is not None:
    681       RunCode(env.Clone(), node.else_branch, output)
    682   elif isinstance(node, ExpNode):
    683     value = env.EvalExp(node)
    684     output.Append('%s' % (value,))
    685   elif isinstance(node, LiteralDollarNode):
    686     output.Append('$')
    687   elif isinstance(node, CodeNode):
    688     RunCode(env.Clone(), node, output)
    689   else:
    690     print 'BAD'
    691     print node
    692     sys.exit(1)
    693 
    694 
    695 def RunCode(env, code_node, output):
    696   for atomic_code in code_node.atomic_code:
    697     RunAtomicCode(env, atomic_code, output)
    698 
    699 
    700 def IsComment(cur_line):
    701   return '//' in cur_line
    702 
    703 
    704 def IsInPreprocessorDirevative(prev_lines, cur_line):
    705   if cur_line.lstrip().startswith('#'):
    706     return True
    707   return prev_lines != [] and prev_lines[-1].endswith('\\')
    708 
    709 
    710 def WrapComment(line, output):
    711   loc = line.find('//')
    712   before_comment = line[:loc].rstrip()
    713   if before_comment == '':
    714     indent = loc
    715   else:
    716     output.append(before_comment)
    717     indent = len(before_comment) - len(before_comment.lstrip())
    718   prefix = indent*' ' + '// '
    719   max_len = 80 - len(prefix)
    720   comment = line[loc + 2:].strip()
    721   segs = [seg for seg in re.split(r'(\w+\W*)', comment) if seg != '']
    722   cur_line = ''
    723   for seg in segs:
    724     if len((cur_line + seg).rstrip()) < max_len:
    725       cur_line += seg
    726     else:
    727       if cur_line.strip() != '':
    728         output.append(prefix + cur_line.rstrip())
    729       cur_line = seg.lstrip()
    730   if cur_line.strip() != '':
    731     output.append(prefix + cur_line.strip())
    732 
    733 
    734 def WrapCode(line, line_concat, output):
    735   indent = len(line) - len(line.lstrip())
    736   prefix = indent*' '  # Prefix of the current line
    737   max_len = 80 - indent - len(line_concat)  # Maximum length of the current line
    738   new_prefix = prefix + 4*' '  # Prefix of a continuation line
    739   new_max_len = max_len - 4  # Maximum length of a continuation line
    740   # Prefers to wrap a line after a ',' or ';'.
    741   segs = [seg for seg in re.split(r'([^,;]+[,;]?)', line.strip()) if seg != '']
    742   cur_line = ''  # The current line without leading spaces.
    743   for seg in segs:
    744     # If the line is still too long, wrap at a space.
    745     while cur_line == '' and len(seg.strip()) > max_len:
    746       seg = seg.lstrip()
    747       split_at = seg.rfind(' ', 0, max_len)
    748       output.append(prefix + seg[:split_at].strip() + line_concat)
    749       seg = seg[split_at + 1:]
    750       prefix = new_prefix
    751       max_len = new_max_len
    752 
    753     if len((cur_line + seg).rstrip()) < max_len:
    754       cur_line = (cur_line + seg).lstrip()
    755     else:
    756       output.append(prefix + cur_line.rstrip() + line_concat)
    757       prefix = new_prefix
    758       max_len = new_max_len
    759       cur_line = seg.lstrip()
    760   if cur_line.strip() != '':
    761     output.append(prefix + cur_line.strip())
    762 
    763 
    764 def WrapPreprocessorDirevative(line, output):
    765   WrapCode(line, ' \\', output)
    766 
    767 
    768 def WrapPlainCode(line, output):
    769   WrapCode(line, '', output)
    770 
    771 
    772 def IsHeaderGuardOrInclude(line):
    773   return (re.match(r'^#(ifndef|define|endif\s*//)\s*[\w_]+\s*$', line) or
    774           re.match(r'^#include\s', line))
    775 
    776 
    777 def WrapLongLine(line, output):
    778   line = line.rstrip()
    779   if len(line) <= 80:
    780     output.append(line)
    781   elif IsComment(line):
    782     if IsHeaderGuardOrInclude(line):
    783       # The style guide made an exception to allow long header guard lines
    784       # and includes.
    785       output.append(line)
    786     else:
    787       WrapComment(line, output)
    788   elif IsInPreprocessorDirevative(output, line):
    789     if IsHeaderGuardOrInclude(line):
    790       # The style guide made an exception to allow long header guard lines
    791       # and includes.
    792       output.append(line)
    793     else:
    794       WrapPreprocessorDirevative(line, output)
    795   else:
    796     WrapPlainCode(line, output)
    797 
    798 
    799 def BeautifyCode(string):
    800   lines = string.splitlines()
    801   output = []
    802   for line in lines:
    803     WrapLongLine(line, output)
    804   output2 = [line.rstrip() for line in output]
    805   return '\n'.join(output2) + '\n'
    806 
    807 
    808 def main(argv):
    809   if len(argv) == 1:
    810     print __doc__
    811     sys.exit(1)
    812 
    813   file_path = argv[-1]
    814   ast = Convert(file_path)
    815   output = Output()
    816   RunCode(Env(), ast, output)
    817   output_str = BeautifyCode(output.string)
    818   if file_path.endswith('.pump'):
    819     output_file_path = file_path[:-5]
    820   else:
    821     output_file_path = '-'
    822   if output_file_path == '-':
    823     print output_str,
    824   else:
    825     output_file = file(output_file_path, 'w')
    826     output_file.write('// This file was GENERATED by command:\n')
    827     output_file.write('//     %s %s\n' %
    828                       (os.path.basename(__file__), os.path.basename(file_path)))
    829     output_file.write('// DO NOT EDIT BY HAND!!!\n\n')
    830     output_file.write(output_str)
    831     output_file.close()
    832 
    833 
    834 if __name__ == '__main__':
    835   main(sys.argv)
    836