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
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
65__author__ = 'wan@google.com (Zhanyong Wan)'
66
67import os
68import re
69import sys
70
71
72TOKEN_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
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 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
233def 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
243ID_REGEX = re.compile(r'[_A-Za-z]\w*')
244EQ_REGEX = re.compile(r'=')
245REST_OF_LINE_REGEX = re.compile(r'.*?(?=$|\$\$)')
246OPTIONAL_WHITE_SPACES_REGEX = re.compile(r'\s*')
247WHITE_SPACE_REGEX = re.compile(r'\s')
248DOT_DOT_REGEX = re.compile(r'\.\.')
249
250
251def 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
260def 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
271def 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
293def 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
300def 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
376def Tokenize(s):
377  lines = s.splitlines(True)
378  return TokenizeLines(lines, Cursor(0, 0))
379
380
381class CodeNode:
382  def __init__(self, atomic_code_list=None):
383    self.atomic_code = atomic_code_list
384
385
386class VarNode:
387  def __init__(self, identifier=None, atomic_code=None):
388    self.identifier = identifier
389    self.atomic_code = atomic_code
390
391
392class 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
399class 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
406class ElseNode:
407  def __init__(self, else_branch=None):
408    self.else_branch = else_branch
409
410
411class 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
418class RawCodeNode:
419  def __init__(self, token=None):
420    self.raw_code = token
421
422
423class LiteralDollarNode:
424  def __init__(self, token):
425    self.token = token
426
427
428class ExpNode:
429  def __init__(self, token, python_exp):
430    self.token = token
431    self.python_exp = python_exp
432
433
434def PopFront(a_list):
435  head = a_list[0]
436  a_list[:1] = []
437  return head
438
439
440def PushFront(a_list, elem):
441  a_list[:0] = [elem]
442
443
444def 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
454def PeekToken(a_list):
455  if not a_list:
456    return None
457
458  return a_list[0]
459
460
461def 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
466def 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
494def 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
555def 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
568def 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
577class 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
634class 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
649def 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
695def RunCode(env, code_node, output):
696  for atomic_code in code_node.atomic_code:
697    RunAtomicCode(env, atomic_code, output)
698
699
700def IsComment(cur_line):
701  return '//' in cur_line
702
703
704def 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
710def 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
734def 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
764def WrapPreprocessorDirevative(line, output):
765  WrapCode(line, ' \\', output)
766
767
768def WrapPlainCode(line, output):
769  WrapCode(line, '', output)
770
771
772def 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
777def 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
799def 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
808def 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
834if __name__ == '__main__':
835  main(sys.argv)
836