1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4""" 5This is a very primitive line based preprocessor, for times when using 6a C preprocessor isn't an option. 7 8It currently supports the following grammar for expressions, whitespace is 9ignored: 10 11expression : 12 and_cond ( '||' expression ) ? ; 13and_cond: 14 test ( '&&' and_cond ) ? ; 15test: 16 unary ( ( '==' | '!=' ) unary ) ? ; 17unary : 18 '!'? value ; 19value : 20 [0-9]+ # integer 21 | 'defined(' \w+ ')' 22 | \w+ # string identifier or value; 23""" 24 25import sys 26import os 27import re 28from optparse import OptionParser 29import errno 30from makeutil import Makefile 31 32# hack around win32 mangling our line endings 33# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65443 34if sys.platform == "win32": 35 import msvcrt 36 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) 37 os.linesep = '\n' 38 39 40__all__ = [ 41 'Context', 42 'Expression', 43 'Preprocessor', 44 'preprocess' 45] 46 47 48class Expression: 49 def __init__(self, expression_string): 50 """ 51 Create a new expression with this string. 52 The expression will already be parsed into an Abstract Syntax Tree. 53 """ 54 self.content = expression_string 55 self.offset = 0 56 self.__ignore_whitespace() 57 self.e = self.__get_logical_or() 58 if self.content: 59 raise Expression.ParseError(self) 60 61 def __get_logical_or(self): 62 """ 63 Production: and_cond ( '||' expression ) ? 64 """ 65 if not len(self.content): 66 return None 67 rv = Expression.__AST("logical_op") 68 # test 69 rv.append(self.__get_logical_and()) 70 self.__ignore_whitespace() 71 if self.content[:2] != '||': 72 # no logical op needed, short cut to our prime element 73 return rv[0] 74 # append operator 75 rv.append(Expression.__ASTLeaf('op', self.content[:2])) 76 self.__strip(2) 77 self.__ignore_whitespace() 78 rv.append(self.__get_logical_or()) 79 self.__ignore_whitespace() 80 return rv 81 82 def __get_logical_and(self): 83 """ 84 Production: test ( '&&' and_cond ) ? 85 """ 86 if not len(self.content): 87 return None 88 rv = Expression.__AST("logical_op") 89 # test 90 rv.append(self.__get_equality()) 91 self.__ignore_whitespace() 92 if self.content[:2] != '&&': 93 # no logical op needed, short cut to our prime element 94 return rv[0] 95 # append operator 96 rv.append(Expression.__ASTLeaf('op', self.content[:2])) 97 self.__strip(2) 98 self.__ignore_whitespace() 99 rv.append(self.__get_logical_and()) 100 self.__ignore_whitespace() 101 return rv 102 103 def __get_equality(self): 104 """ 105 Production: unary ( ( '==' | '!=' ) unary ) ? 106 """ 107 if not len(self.content): 108 return None 109 rv = Expression.__AST("equality") 110 # unary 111 rv.append(self.__get_unary()) 112 self.__ignore_whitespace() 113 if not re.match('[=!]=', self.content): 114 # no equality needed, short cut to our prime unary 115 return rv[0] 116 # append operator 117 rv.append(Expression.__ASTLeaf('op', self.content[:2])) 118 self.__strip(2) 119 self.__ignore_whitespace() 120 rv.append(self.__get_unary()) 121 self.__ignore_whitespace() 122 return rv 123 124 def __get_unary(self): 125 """ 126 Production: '!'? value 127 """ 128 # eat whitespace right away, too 129 not_ws = re.match('!\s*', self.content) 130 if not not_ws: 131 return self.__get_value() 132 rv = Expression.__AST('not') 133 self.__strip(not_ws.end()) 134 rv.append(self.__get_value()) 135 self.__ignore_whitespace() 136 return rv 137 138 def __get_value(self): 139 """ 140 Production: ( [0-9]+ | 'defined(' \w+ ')' | \w+ ) 141 Note that the order is important, and the expression is kind-of 142 ambiguous as \w includes 0-9. One could make it unambiguous by 143 removing 0-9 from the first char of a string literal. 144 """ 145 rv = None 146 m = re.match('defined\s*\(\s*(\w+)\s*\)', self.content) 147 if m: 148 word_len = m.end() 149 rv = Expression.__ASTLeaf('defined', m.group(1)) 150 else: 151 word_len = re.match('[0-9]*', self.content).end() 152 if word_len: 153 value = int(self.content[:word_len]) 154 rv = Expression.__ASTLeaf('int', value) 155 else: 156 word_len = re.match('\w*', self.content).end() 157 if word_len: 158 rv = Expression.__ASTLeaf('string', self.content[:word_len]) 159 else: 160 raise Expression.ParseError(self) 161 self.__strip(word_len) 162 self.__ignore_whitespace() 163 return rv 164 165 def __ignore_whitespace(self): 166 ws_len = re.match('\s*', self.content).end() 167 self.__strip(ws_len) 168 return 169 170 def __strip(self, length): 171 """ 172 Remove a given amount of chars from the input and update 173 the offset. 174 """ 175 self.content = self.content[length:] 176 self.offset += length 177 178 def evaluate(self, context): 179 """ 180 Evaluate the expression with the given context 181 """ 182 183 # Helper function to evaluate __get_equality results 184 def eval_equality(tok): 185 left = opmap[tok[0].type](tok[0]) 186 right = opmap[tok[2].type](tok[2]) 187 rv = left == right 188 if tok[1].value == '!=': 189 rv = not rv 190 return rv 191 # Helper function to evaluate __get_logical_and and __get_logical_or results 192 def eval_logical_op(tok): 193 left = opmap[tok[0].type](tok[0]) 194 right = opmap[tok[2].type](tok[2]) 195 if tok[1].value == '&&': 196 return left and right 197 elif tok[1].value == '||': 198 return left or right 199 raise Expression.ParseError(self) 200 201 # Mapping from token types to evaluator functions 202 # Apart from (non-)equality, all these can be simple lambda forms. 203 opmap = { 204 'logical_op': eval_logical_op, 205 'equality': eval_equality, 206 'not': lambda tok: not opmap[tok[0].type](tok[0]), 207 'string': lambda tok: context[tok.value], 208 'defined': lambda tok: tok.value in context, 209 'int': lambda tok: tok.value} 210 211 return opmap[self.e.type](self.e); 212 213 class __AST(list): 214 """ 215 Internal class implementing Abstract Syntax Tree nodes 216 """ 217 def __init__(self, type): 218 self.type = type 219 super(self.__class__, self).__init__(self) 220 221 class __ASTLeaf: 222 """ 223 Internal class implementing Abstract Syntax Tree leafs 224 """ 225 def __init__(self, type, value): 226 self.value = value 227 self.type = type 228 def __str__(self): 229 return self.value.__str__() 230 def __repr__(self): 231 return self.value.__repr__() 232 233 class ParseError(StandardError): 234 """ 235 Error raised when parsing fails. 236 It has two members, offset and content, which give the offset of the 237 error and the offending content. 238 """ 239 def __init__(self, expression): 240 self.offset = expression.offset 241 self.content = expression.content[:3] 242 def __str__(self): 243 return 'Unexpected content at offset {0}, "{1}"'.format(self.offset, 244 self.content) 245 246class Context(dict): 247 """ 248 This class holds variable values by subclassing dict, and while it 249 truthfully reports True and False on 250 251 name in context 252 253 it returns the variable name itself on 254 255 context["name"] 256 257 to reflect the ambiguity between string literals and preprocessor 258 variables. 259 """ 260 def __getitem__(self, key): 261 if key in self: 262 return super(self.__class__, self).__getitem__(key) 263 return key 264 265 266class Preprocessor: 267 """ 268 Class for preprocessing text files. 269 """ 270 class Error(RuntimeError): 271 def __init__(self, cpp, MSG, context): 272 self.file = cpp.context['FILE'] 273 self.line = cpp.context['LINE'] 274 self.key = MSG 275 RuntimeError.__init__(self, (self.file, self.line, self.key, context)) 276 277 def __init__(self, defines=None, marker='#'): 278 self.context = Context() 279 for k,v in {'FILE': '', 280 'LINE': 0, 281 'DIRECTORY': os.path.abspath('.')}.iteritems(): 282 self.context[k] = v 283 self.actionLevel = 0 284 self.disableLevel = 0 285 # ifStates can be 286 # 0: hadTrue 287 # 1: wantsTrue 288 # 2: #else found 289 self.ifStates = [] 290 self.checkLineNumbers = False 291 self.filters = [] 292 self.cmds = {} 293 for cmd, level in {'define': 0, 294 'undef': 0, 295 'if': sys.maxint, 296 'ifdef': sys.maxint, 297 'ifndef': sys.maxint, 298 'else': 1, 299 'elif': 1, 300 'elifdef': 1, 301 'elifndef': 1, 302 'endif': sys.maxint, 303 'expand': 0, 304 'literal': 0, 305 'filter': 0, 306 'unfilter': 0, 307 'include': 0, 308 'includesubst': 0, 309 'error': 0}.iteritems(): 310 self.cmds[cmd] = (level, getattr(self, 'do_' + cmd)) 311 self.out = sys.stdout 312 self.setMarker(marker) 313 self.varsubst = re.compile('@(?P<VAR>\w+)@', re.U) 314 self.includes = set() 315 self.silenceMissingDirectiveWarnings = False 316 if defines: 317 self.context.update(defines) 318 319 def failUnused(self, file): 320 msg = None 321 if self.actionLevel == 0 and not self.silenceMissingDirectiveWarnings: 322 msg = 'no preprocessor directives found' 323 elif self.actionLevel == 1: 324 msg = 'no useful preprocessor directives found' 325 if msg: 326 class Fake(object): pass 327 fake = Fake() 328 fake.context = { 329 'FILE': file, 330 'LINE': None, 331 } 332 raise Preprocessor.Error(fake, msg, None) 333 334 def setMarker(self, aMarker): 335 """ 336 Set the marker to be used for processing directives. 337 Used for handling CSS files, with pp.setMarker('%'), for example. 338 The given marker may be None, in which case no markers are processed. 339 """ 340 self.marker = aMarker 341 if aMarker: 342 self.instruction = re.compile('{0}(?P<cmd>[a-z]+)(?:\s+(?P<args>.*?))?\s*$' 343 .format(aMarker)) 344 self.comment = re.compile(aMarker, re.U) 345 else: 346 class NoMatch(object): 347 def match(self, *args): 348 return False 349 self.instruction = self.comment = NoMatch() 350 351 def setSilenceDirectiveWarnings(self, value): 352 """ 353 Sets whether missing directive warnings are silenced, according to 354 ``value``. The default behavior of the preprocessor is to emit 355 such warnings. 356 """ 357 self.silenceMissingDirectiveWarnings = value 358 359 def addDefines(self, defines): 360 """ 361 Adds the specified defines to the preprocessor. 362 ``defines`` may be a dictionary object or an iterable of key/value pairs 363 (as tuples or other iterables of length two) 364 """ 365 self.context.update(defines) 366 367 def clone(self): 368 """ 369 Create a clone of the current processor, including line ending 370 settings, marker, variable definitions, output stream. 371 """ 372 rv = Preprocessor() 373 rv.context.update(self.context) 374 rv.setMarker(self.marker) 375 rv.out = self.out 376 return rv 377 378 def processFile(self, input, output, depfile=None): 379 """ 380 Preprocesses the contents of the ``input`` stream and writes the result 381 to the ``output`` stream. If ``depfile`` is set, the dependencies of 382 ``output`` file are written to ``depfile`` in Makefile format. 383 """ 384 self.out = output 385 386 self.do_include(input, False) 387 self.failUnused(input.name) 388 389 if depfile: 390 mk = Makefile() 391 mk.create_rule([output.name]).add_dependencies(self.includes) 392 mk.dump(depfile) 393 394 def computeDependencies(self, input): 395 """ 396 Reads the ``input`` stream, and computes the dependencies for that input. 397 """ 398 try: 399 old_out = self.out 400 self.out = None 401 self.do_include(input, False) 402 403 return self.includes 404 finally: 405 self.out = old_out 406 407 def applyFilters(self, aLine): 408 for f in self.filters: 409 aLine = f[1](aLine) 410 return aLine 411 412 def noteLineInfo(self): 413 # Record the current line and file. Called once before transitioning 414 # into or out of an included file and after writing each line. 415 self.line_info = self.context['FILE'], self.context['LINE'] 416 417 def write(self, aLine): 418 """ 419 Internal method for handling output. 420 """ 421 if not self.out: 422 return 423 424 next_line, next_file = self.context['LINE'], self.context['FILE'] 425 if self.checkLineNumbers: 426 expected_file, expected_line = self.line_info 427 expected_line += 1 428 if (expected_line != next_line or 429 expected_file and expected_file != next_file): 430 self.out.write('//@line {line} "{file}"\n'.format(line=next_line, 431 file=next_file)) 432 self.noteLineInfo() 433 434 filteredLine = self.applyFilters(aLine) 435 if filteredLine != aLine: 436 self.actionLevel = 2 437 self.out.write(filteredLine) 438 439 def handleCommandLine(self, args, defaultToStdin = False): 440 """ 441 Parse a commandline into this parser. 442 Uses OptionParser internally, no args mean sys.argv[1:]. 443 """ 444 def get_output_file(path): 445 dir = os.path.dirname(path) 446 if dir: 447 try: 448 os.makedirs(dir) 449 except OSError as error: 450 if error.errno != errno.EEXIST: 451 raise 452 return open(path, 'wb') 453 454 p = self.getCommandLineParser() 455 options, args = p.parse_args(args=args) 456 out = self.out 457 depfile = None 458 459 if options.output: 460 out = get_output_file(options.output) 461 if defaultToStdin and len(args) == 0: 462 args = [sys.stdin] 463 if options.depend: 464 raise Preprocessor.Error(self, "--depend doesn't work with stdin", 465 None) 466 if options.depend: 467 if not options.output: 468 raise Preprocessor.Error(self, "--depend doesn't work with stdout", 469 None) 470 try: 471 from makeutil import Makefile 472 except: 473 raise Preprocessor.Error(self, "--depend requires the " 474 "mozbuild.makeutil module", None) 475 depfile = get_output_file(options.depend) 476 477 if args: 478 for f in args: 479 with open(f, 'rU') as input: 480 self.processFile(input=input, output=out) 481 if depfile: 482 mk = Makefile() 483 mk.create_rule([options.output]).add_dependencies(self.includes) 484 mk.dump(depfile) 485 depfile.close() 486 487 if options.output: 488 out.close() 489 490 def getCommandLineParser(self, unescapeDefines = False): 491 escapedValue = re.compile('".*"$') 492 numberValue = re.compile('\d+$') 493 def handleD(option, opt, value, parser): 494 vals = value.split('=', 1) 495 if len(vals) == 1: 496 vals.append(1) 497 elif unescapeDefines and escapedValue.match(vals[1]): 498 # strip escaped string values 499 vals[1] = vals[1][1:-1] 500 elif numberValue.match(vals[1]): 501 vals[1] = int(vals[1]) 502 self.context[vals[0]] = vals[1] 503 def handleU(option, opt, value, parser): 504 del self.context[value] 505 def handleF(option, opt, value, parser): 506 self.do_filter(value) 507 def handleMarker(option, opt, value, parser): 508 self.setMarker(value) 509 def handleSilenceDirectiveWarnings(option, opt, value, parse): 510 self.setSilenceDirectiveWarnings(True) 511 p = OptionParser() 512 p.add_option('-D', action='callback', callback=handleD, type="string", 513 metavar="VAR[=VAL]", help='Define a variable') 514 p.add_option('-U', action='callback', callback=handleU, type="string", 515 metavar="VAR", help='Undefine a variable') 516 p.add_option('-F', action='callback', callback=handleF, type="string", 517 metavar="FILTER", help='Enable the specified filter') 518 p.add_option('-o', '--output', type="string", default=None, 519 metavar="FILENAME", help='Output to the specified file '+ 520 'instead of stdout') 521 p.add_option('--depend', type="string", default=None, metavar="FILENAME", 522 help='Generate dependencies in the given file') 523 p.add_option('--marker', action='callback', callback=handleMarker, 524 type="string", 525 help='Use the specified marker instead of #') 526 p.add_option('--silence-missing-directive-warnings', action='callback', 527 callback=handleSilenceDirectiveWarnings, 528 help='Don\'t emit warnings about missing directives') 529 return p 530 531 def handleLine(self, aLine): 532 """ 533 Handle a single line of input (internal). 534 """ 535 if self.actionLevel == 0 and self.comment.match(aLine): 536 self.actionLevel = 1 537 m = self.instruction.match(aLine) 538 if m: 539 args = None 540 cmd = m.group('cmd') 541 try: 542 args = m.group('args') 543 except IndexError: 544 pass 545 if cmd not in self.cmds: 546 raise Preprocessor.Error(self, 'INVALID_CMD', aLine) 547 level, cmd = self.cmds[cmd] 548 if (level >= self.disableLevel): 549 cmd(args) 550 if cmd != 'literal': 551 self.actionLevel = 2 552 elif self.disableLevel == 0 and not self.comment.match(aLine): 553 self.write(aLine) 554 555 # Instruction handlers 556 # These are named do_'instruction name' and take one argument 557 558 # Variables 559 def do_define(self, args): 560 m = re.match('(?P<name>\w+)(?:\s(?P<value>.*))?', args, re.U) 561 if not m: 562 raise Preprocessor.Error(self, 'SYNTAX_DEF', args) 563 val = '' 564 if m.group('value'): 565 val = self.applyFilters(m.group('value')) 566 try: 567 val = int(val) 568 except: 569 pass 570 self.context[m.group('name')] = val 571 def do_undef(self, args): 572 m = re.match('(?P<name>\w+)$', args, re.U) 573 if not m: 574 raise Preprocessor.Error(self, 'SYNTAX_DEF', args) 575 if args in self.context: 576 del self.context[args] 577 # Logic 578 def ensure_not_else(self): 579 if len(self.ifStates) == 0 or self.ifStates[-1] == 2: 580 sys.stderr.write('WARNING: bad nesting of #else in %s\n' % self.context['FILE']) 581 def do_if(self, args, replace=False): 582 if self.disableLevel and not replace: 583 self.disableLevel += 1 584 return 585 val = None 586 try: 587 e = Expression(args) 588 val = e.evaluate(self.context) 589 except Exception: 590 # XXX do real error reporting 591 raise Preprocessor.Error(self, 'SYNTAX_ERR', args) 592 if type(val) == str: 593 # we're looking for a number value, strings are false 594 val = False 595 if not val: 596 self.disableLevel = 1 597 if replace: 598 if val: 599 self.disableLevel = 0 600 self.ifStates[-1] = self.disableLevel 601 else: 602 self.ifStates.append(self.disableLevel) 603 pass 604 def do_ifdef(self, args, replace=False): 605 if self.disableLevel and not replace: 606 self.disableLevel += 1 607 return 608 if re.search('\W', args, re.U): 609 raise Preprocessor.Error(self, 'INVALID_VAR', args) 610 if args not in self.context: 611 self.disableLevel = 1 612 if replace: 613 if args in self.context: 614 self.disableLevel = 0 615 self.ifStates[-1] = self.disableLevel 616 else: 617 self.ifStates.append(self.disableLevel) 618 pass 619 def do_ifndef(self, args, replace=False): 620 if self.disableLevel and not replace: 621 self.disableLevel += 1 622 return 623 if re.search('\W', args, re.U): 624 raise Preprocessor.Error(self, 'INVALID_VAR', args) 625 if args in self.context: 626 self.disableLevel = 1 627 if replace: 628 if args not in self.context: 629 self.disableLevel = 0 630 self.ifStates[-1] = self.disableLevel 631 else: 632 self.ifStates.append(self.disableLevel) 633 pass 634 def do_else(self, args, ifState = 2): 635 self.ensure_not_else() 636 hadTrue = self.ifStates[-1] == 0 637 self.ifStates[-1] = ifState # in-else 638 if hadTrue: 639 self.disableLevel = 1 640 return 641 self.disableLevel = 0 642 def do_elif(self, args): 643 if self.disableLevel == 1: 644 if self.ifStates[-1] == 1: 645 self.do_if(args, replace=True) 646 else: 647 self.do_else(None, self.ifStates[-1]) 648 def do_elifdef(self, args): 649 if self.disableLevel == 1: 650 if self.ifStates[-1] == 1: 651 self.do_ifdef(args, replace=True) 652 else: 653 self.do_else(None, self.ifStates[-1]) 654 def do_elifndef(self, args): 655 if self.disableLevel == 1: 656 if self.ifStates[-1] == 1: 657 self.do_ifndef(args, replace=True) 658 else: 659 self.do_else(None, self.ifStates[-1]) 660 def do_endif(self, args): 661 if self.disableLevel > 0: 662 self.disableLevel -= 1 663 if self.disableLevel == 0: 664 self.ifStates.pop() 665 # output processing 666 def do_expand(self, args): 667 lst = re.split('__(\w+)__', args, re.U) 668 do_replace = False 669 def vsubst(v): 670 if v in self.context: 671 return str(self.context[v]) 672 return '' 673 for i in range(1, len(lst), 2): 674 lst[i] = vsubst(lst[i]) 675 lst.append('\n') # add back the newline 676 self.write(reduce(lambda x, y: x+y, lst, '')) 677 def do_literal(self, args): 678 self.write(args + '\n') 679 def do_filter(self, args): 680 filters = [f for f in args.split(' ') if hasattr(self, 'filter_' + f)] 681 if len(filters) == 0: 682 return 683 current = dict(self.filters) 684 for f in filters: 685 current[f] = getattr(self, 'filter_' + f) 686 filterNames = current.keys() 687 filterNames.sort() 688 self.filters = [(fn, current[fn]) for fn in filterNames] 689 return 690 def do_unfilter(self, args): 691 filters = args.split(' ') 692 current = dict(self.filters) 693 for f in filters: 694 if f in current: 695 del current[f] 696 filterNames = current.keys() 697 filterNames.sort() 698 self.filters = [(fn, current[fn]) for fn in filterNames] 699 return 700 # Filters 701 # 702 # emptyLines 703 # Strips blank lines from the output. 704 def filter_emptyLines(self, aLine): 705 if aLine == '\n': 706 return '' 707 return aLine 708 # slashslash 709 # Strips everything after // 710 def filter_slashslash(self, aLine): 711 if (aLine.find('//') == -1): 712 return aLine 713 [aLine, rest] = aLine.split('//', 1) 714 if rest: 715 aLine += '\n' 716 return aLine 717 # spaces 718 # Collapses sequences of spaces into a single space 719 def filter_spaces(self, aLine): 720 return re.sub(' +', ' ', aLine).strip(' ') 721 # substition 722 # helper to be used by both substition and attemptSubstitution 723 def filter_substitution(self, aLine, fatal=True): 724 def repl(matchobj): 725 varname = matchobj.group('VAR') 726 if varname in self.context: 727 return str(self.context[varname]) 728 if fatal: 729 raise Preprocessor.Error(self, 'UNDEFINED_VAR', varname) 730 return matchobj.group(0) 731 return self.varsubst.sub(repl, aLine) 732 def filter_attemptSubstitution(self, aLine): 733 return self.filter_substitution(aLine, fatal=False) 734 # File ops 735 def do_include(self, args, filters=True): 736 """ 737 Preprocess a given file. 738 args can either be a file name, or a file-like object. 739 Files should be opened, and will be closed after processing. 740 """ 741 isName = type(args) == str or type(args) == unicode 742 oldCheckLineNumbers = self.checkLineNumbers 743 self.checkLineNumbers = False 744 if isName: 745 try: 746 args = str(args) 747 if filters: 748 args = self.applyFilters(args) 749 if not os.path.isabs(args): 750 args = os.path.join(self.context['DIRECTORY'], args) 751 args = open(args, 'rU') 752 except Preprocessor.Error: 753 raise 754 except: 755 raise Preprocessor.Error(self, 'FILE_NOT_FOUND', str(args)) 756 self.checkLineNumbers = bool(re.search('\.(js|jsm|java|webidl)(?:\.in)?$', args.name)) 757 oldFile = self.context['FILE'] 758 oldLine = self.context['LINE'] 759 oldDir = self.context['DIRECTORY'] 760 self.noteLineInfo() 761 762 if args.isatty(): 763 # we're stdin, use '-' and '' for file and dir 764 self.context['FILE'] = '-' 765 self.context['DIRECTORY'] = '' 766 else: 767 abspath = os.path.abspath(args.name) 768 self.includes.add(abspath) 769 self.context['FILE'] = abspath 770 self.context['DIRECTORY'] = os.path.dirname(abspath) 771 self.context['LINE'] = 0 772 773 for l in args: 774 self.context['LINE'] += 1 775 self.handleLine(l) 776 if isName: 777 args.close() 778 779 self.context['FILE'] = oldFile 780 self.checkLineNumbers = oldCheckLineNumbers 781 self.context['LINE'] = oldLine 782 self.context['DIRECTORY'] = oldDir 783 def do_includesubst(self, args): 784 args = self.filter_substitution(args) 785 self.do_include(args) 786 def do_error(self, args): 787 raise Preprocessor.Error(self, 'Error: ', str(args)) 788 789 790def preprocess(includes=[sys.stdin], defines={}, 791 output = sys.stdout, 792 marker='#'): 793 pp = Preprocessor(defines=defines, 794 marker=marker) 795 for f in includes: 796 with open(f, 'rU') as input: 797 pp.processFile(input=input, output=output) 798 return pp.includes 799 800 801# Keep this module independently executable. 802if __name__ == "__main__": 803 pp = Preprocessor() 804 pp.handleCommandLine(None, True) 805