?login_element?

Subversion Repositories NedoOS

Rev

Blame | Last modification | View Log | Download | RSS feed

  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)
  856.