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