github.com/jlmucb/cloudproxy@v0.0.0-20170830161738-b5aa0b619bc4/src/third_party/googlemock/gtest/scripts/pump.py (about)

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