]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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) |