diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /python/mozbuild/mozbuild/preprocessor.py | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'python/mozbuild/mozbuild/preprocessor.py')
-rw-r--r-- | python/mozbuild/mozbuild/preprocessor.py | 938 |
1 files changed, 938 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/preprocessor.py b/python/mozbuild/mozbuild/preprocessor.py new file mode 100644 index 0000000000..c81357efa4 --- /dev/null +++ b/python/mozbuild/mozbuild/preprocessor.py @@ -0,0 +1,938 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +r""" +This is a very primitive line based preprocessor, for times when using +a C preprocessor isn't an option. + +It currently supports the following grammar for expressions, whitespace is +ignored: + +expression : + and_cond ( '||' expression ) ? ; +and_cond: + test ( '&&' and_cond ) ? ; +test: + unary ( ( '==' | '!=' ) unary ) ? ; +unary : + '!'? value ; +value : + [0-9]+ # integer + | 'defined(' \w+ ')' + | \w+ # string identifier or value; +""" + +import errno +import io +import os +import re +import sys +from optparse import OptionParser + +import six +from mozpack.path import normsep + +from mozbuild.makeutil import Makefile + +# hack around win32 mangling our line endings +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65443 +if sys.platform == "win32": + import msvcrt + + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + os.linesep = "\n" + + +__all__ = ["Context", "Expression", "Preprocessor", "preprocess"] + + +def _to_text(a): + # We end up converting a lot of different types (text_type, binary_type, + # int, etc.) to Unicode in this script. This function handles all of those + # possibilities. + if isinstance(a, (six.text_type, six.binary_type)): + return six.ensure_text(a) + return six.text_type(a) + + +def path_starts_with(path, prefix): + if os.altsep: + prefix = prefix.replace(os.altsep, os.sep) + path = path.replace(os.altsep, os.sep) + prefix = [os.path.normcase(p) for p in prefix.split(os.sep)] + path = [os.path.normcase(p) for p in path.split(os.sep)] + return path[: len(prefix)] == prefix + + +class Expression: + def __init__(self, expression_string): + """ + Create a new expression with this string. + The expression will already be parsed into an Abstract Syntax Tree. + """ + self.content = expression_string + self.offset = 0 + self.__ignore_whitespace() + self.e = self.__get_logical_or() + if self.content: + raise Expression.ParseError(self) + + def __get_logical_or(self): + """ + Production: and_cond ( '||' expression ) ? + """ + if not len(self.content): + return None + rv = Expression.__AST("logical_op") + # test + rv.append(self.__get_logical_and()) + self.__ignore_whitespace() + if self.content[:2] != "||": + # no logical op needed, short cut to our prime element + return rv[0] + # append operator + rv.append(Expression.__ASTLeaf("op", self.content[:2])) + self.__strip(2) + self.__ignore_whitespace() + rv.append(self.__get_logical_or()) + self.__ignore_whitespace() + return rv + + def __get_logical_and(self): + """ + Production: test ( '&&' and_cond ) ? + """ + if not len(self.content): + return None + rv = Expression.__AST("logical_op") + # test + rv.append(self.__get_equality()) + self.__ignore_whitespace() + if self.content[:2] != "&&": + # no logical op needed, short cut to our prime element + return rv[0] + # append operator + rv.append(Expression.__ASTLeaf("op", self.content[:2])) + self.__strip(2) + self.__ignore_whitespace() + rv.append(self.__get_logical_and()) + self.__ignore_whitespace() + return rv + + def __get_equality(self): + """ + Production: unary ( ( '==' | '!=' ) unary ) ? + """ + if not len(self.content): + return None + rv = Expression.__AST("equality") + # unary + rv.append(self.__get_unary()) + self.__ignore_whitespace() + if not re.match("[=!]=", self.content): + # no equality needed, short cut to our prime unary + return rv[0] + # append operator + rv.append(Expression.__ASTLeaf("op", self.content[:2])) + self.__strip(2) + self.__ignore_whitespace() + rv.append(self.__get_unary()) + self.__ignore_whitespace() + return rv + + def __get_unary(self): + """ + Production: '!'? value + """ + # eat whitespace right away, too + not_ws = re.match(r"!\s*", self.content) + if not not_ws: + return self.__get_value() + rv = Expression.__AST("not") + self.__strip(not_ws.end()) + rv.append(self.__get_value()) + self.__ignore_whitespace() + return rv + + def __get_value(self): + r""" + Production: ( [0-9]+ | 'defined(' \w+ ')' | \w+ ) + Note that the order is important, and the expression is kind-of + ambiguous as \w includes 0-9. One could make it unambiguous by + removing 0-9 from the first char of a string literal. + """ + rv = None + m = re.match(r"defined\s*\(\s*(\w+)\s*\)", self.content) + if m: + word_len = m.end() + rv = Expression.__ASTLeaf("defined", m.group(1)) + else: + word_len = re.match("[0-9]*", self.content).end() + if word_len: + value = int(self.content[:word_len]) + rv = Expression.__ASTLeaf("int", value) + else: + word_len = re.match(r"\w*", self.content).end() + if word_len: + rv = Expression.__ASTLeaf("string", self.content[:word_len]) + else: + raise Expression.ParseError(self) + self.__strip(word_len) + self.__ignore_whitespace() + return rv + + def __ignore_whitespace(self): + ws_len = re.match(r"\s*", self.content).end() + self.__strip(ws_len) + return + + def __strip(self, length): + """ + Remove a given amount of chars from the input and update + the offset. + """ + self.content = self.content[length:] + self.offset += length + + def evaluate(self, context): + """ + Evaluate the expression with the given context + """ + + # Helper function to evaluate __get_equality results + def eval_equality(tok): + left = opmap[tok[0].type](tok[0]) + right = opmap[tok[2].type](tok[2]) + rv = left == right + if tok[1].value == "!=": + rv = not rv + return rv + + # Helper function to evaluate __get_logical_and and __get_logical_or results + def eval_logical_op(tok): + left = opmap[tok[0].type](tok[0]) + right = opmap[tok[2].type](tok[2]) + if tok[1].value == "&&": + return left and right + elif tok[1].value == "||": + return left or right + raise Expression.ParseError(self) + + # Mapping from token types to evaluator functions + # Apart from (non-)equality, all these can be simple lambda forms. + opmap = { + "logical_op": eval_logical_op, + "equality": eval_equality, + "not": lambda tok: not opmap[tok[0].type](tok[0]), + "string": lambda tok: context[tok.value], + "defined": lambda tok: tok.value in context, + "int": lambda tok: tok.value, + } + + return opmap[self.e.type](self.e) + + class __AST(list): + """ + Internal class implementing Abstract Syntax Tree nodes + """ + + def __init__(self, type): + self.type = type + super(self.__class__, self).__init__(self) + + class __ASTLeaf: + """ + Internal class implementing Abstract Syntax Tree leafs + """ + + def __init__(self, type, value): + self.value = value + self.type = type + + def __str__(self): + return self.value.__str__() + + def __repr__(self): + return self.value.__repr__() + + class ParseError(Exception): + """ + Error raised when parsing fails. + It has two members, offset and content, which give the offset of the + error and the offending content. + """ + + def __init__(self, expression): + self.offset = expression.offset + self.content = expression.content[:3] + + def __str__(self): + return 'Unexpected content at offset {0}, "{1}"'.format( + self.offset, self.content + ) + + +class Context(dict): + """ + This class holds variable values by subclassing dict, and while it + truthfully reports True and False on + + name in context + + it returns the variable name itself on + + context["name"] + + to reflect the ambiguity between string literals and preprocessor + variables. + """ + + def __getitem__(self, key): + if key in self: + return super(self.__class__, self).__getitem__(key) + return key + + +class Preprocessor: + """ + Class for preprocessing text files. + """ + + class Error(RuntimeError): + def __init__(self, cpp, MSG, context): + self.file = cpp.context["FILE"] + self.line = cpp.context["LINE"] + self.key = MSG + RuntimeError.__init__(self, (self.file, self.line, self.key, context)) + + def __init__(self, defines=None, marker="#"): + self.context = Context() + self.context.update({"FILE": "", "LINE": 0, "DIRECTORY": os.path.abspath(".")}) + try: + # Can import globally because of bootstrapping issues. + from buildconfig import topobjdir, topsrcdir + except ImportError: + # Allow this script to still work independently of a configured objdir. + topsrcdir = topobjdir = None + self.topsrcdir = topsrcdir + self.topobjdir = topobjdir + self.curdir = "." + self.actionLevel = 0 + self.disableLevel = 0 + # ifStates can be + # 0: hadTrue + # 1: wantsTrue + # 2: #else found + self.ifStates = [] + self.checkLineNumbers = False + + # A list of (filter_name, filter_function) pairs. + self.filters = [] + + self.cmds = {} + for cmd, level in ( + ("define", 0), + ("undef", 0), + ("if", sys.maxsize), + ("ifdef", sys.maxsize), + ("ifndef", sys.maxsize), + ("else", 1), + ("elif", 1), + ("elifdef", 1), + ("elifndef", 1), + ("endif", sys.maxsize), + ("expand", 0), + ("literal", 0), + ("filter", 0), + ("unfilter", 0), + ("include", 0), + ("includesubst", 0), + ("error", 0), + ): + self.cmds[cmd] = (level, getattr(self, "do_" + cmd)) + self.out = sys.stdout + self.setMarker(marker) + self.varsubst = re.compile(r"@(?P<VAR>\w+)@", re.U) + self.includes = set() + self.silenceMissingDirectiveWarnings = False + if defines: + self.context.update(defines) + + def failUnused(self, file): + msg = None + if self.actionLevel == 0 and not self.silenceMissingDirectiveWarnings: + msg = "no preprocessor directives found" + elif self.actionLevel == 1: + msg = "no useful preprocessor directives found" + if msg: + + class Fake(object): + pass + + fake = Fake() + fake.context = { + "FILE": file, + "LINE": None, + } + raise Preprocessor.Error(fake, msg, None) + + def setMarker(self, aMarker): + """ + Set the marker to be used for processing directives. + Used for handling CSS files, with pp.setMarker('%'), for example. + The given marker may be None, in which case no markers are processed. + """ + self.marker = aMarker + if aMarker: + instruction_prefix = r"\s*{0}" + instruction_cmd = r"(?P<cmd>[a-z]+)(?:\s+(?P<args>.*?))?\s*$" + instruction_fmt = instruction_prefix + instruction_cmd + ambiguous_fmt = instruction_prefix + r"\s+" + instruction_cmd + + self.instruction = re.compile(instruction_fmt.format(aMarker)) + self.comment = re.compile(aMarker, re.U) + self.ambiguous_comment = re.compile(ambiguous_fmt.format(aMarker)) + else: + + class NoMatch(object): + def match(self, *args): + return False + + self.instruction = self.comment = NoMatch() + + def setSilenceDirectiveWarnings(self, value): + """ + Sets whether missing directive warnings are silenced, according to + ``value``. The default behavior of the preprocessor is to emit + such warnings. + """ + self.silenceMissingDirectiveWarnings = value + + def addDefines(self, defines): + """ + Adds the specified defines to the preprocessor. + ``defines`` may be a dictionary object or an iterable of key/value pairs + (as tuples or other iterables of length two) + """ + self.context.update(defines) + + def clone(self): + """ + Create a clone of the current processor, including line ending + settings, marker, variable definitions, output stream. + """ + rv = Preprocessor() + rv.context.update(self.context) + rv.setMarker(self.marker) + rv.out = self.out + return rv + + def processFile(self, input, output, depfile=None): + """ + Preprocesses the contents of the ``input`` stream and writes the result + to the ``output`` stream. If ``depfile`` is set, the dependencies of + ``output`` file are written to ``depfile`` in Makefile format. + """ + self.out = output + + self.do_include(input, False) + self.failUnused(input.name) + + if depfile: + mk = Makefile() + mk.create_rule([output.name]).add_dependencies(self.includes) + mk.dump(depfile) + + def computeDependencies(self, input): + """ + Reads the ``input`` stream, and computes the dependencies for that input. + """ + try: + old_out = self.out + self.out = None + self.do_include(input, False) + + return self.includes + finally: + self.out = old_out + + def applyFilters(self, aLine): + for f in self.filters: + aLine = f[1](aLine) + return aLine + + def noteLineInfo(self): + # Record the current line and file. Called once before transitioning + # into or out of an included file and after writing each line. + self.line_info = self.context["FILE"], self.context["LINE"] + + def write(self, aLine): + """ + Internal method for handling output. + """ + if not self.out: + return + + next_line, next_file = self.context["LINE"], self.context["FILE"] + if self.checkLineNumbers: + expected_file, expected_line = self.line_info + expected_line += 1 + if ( + expected_line != next_line + or expected_file + and expected_file != next_file + ): + self.out.write( + '//@line {line} "{file}"\n'.format(line=next_line, file=next_file) + ) + self.noteLineInfo() + + filteredLine = self.applyFilters(aLine) + if filteredLine != aLine: + self.actionLevel = 2 + self.out.write(filteredLine) + + def handleCommandLine(self, args, defaultToStdin=False): + """ + Parse a commandline into this parser. + Uses OptionParser internally, no args mean sys.argv[1:]. + """ + + def get_output_file(path, encoding=None): + if encoding is None: + encoding = "utf-8" + dir = os.path.dirname(path) + if dir: + try: + os.makedirs(dir) + except OSError as error: + if error.errno != errno.EEXIST: + raise + return io.open(path, "w", encoding=encoding, newline="\n") + + p = self.getCommandLineParser() + options, args = p.parse_args(args=args) + out = self.out + depfile = None + + if options.output: + out = get_output_file(options.output, options.output_encoding) + elif options.output_encoding: + raise Preprocessor.Error( + self, "--output-encoding doesn't work without --output", None + ) + if defaultToStdin and len(args) == 0: + args = [sys.stdin] + if options.depend: + raise Preprocessor.Error(self, "--depend doesn't work with stdin", None) + if options.depend: + if not options.output: + raise Preprocessor.Error( + self, "--depend doesn't work with stdout", None + ) + depfile = get_output_file(options.depend) + + if args: + for f in args: + if not isinstance(f, io.TextIOBase): + f = io.open(f, "r", encoding="utf-8") + with f as input_: + self.processFile(input=input_, output=out) + if depfile: + mk = Makefile() + mk.create_rule([six.ensure_text(options.output)]).add_dependencies( + self.includes + ) + mk.dump(depfile) + depfile.close() + + if options.output: + out.close() + + def getCommandLineParser(self, unescapeDefines=False): + escapedValue = re.compile('".*"$') + numberValue = re.compile(r"\d+$") + + def handleD(option, opt, value, parser): + vals = value.split("=", 1) + if len(vals) == 1: + vals.append(1) + elif unescapeDefines and escapedValue.match(vals[1]): + # strip escaped string values + vals[1] = vals[1][1:-1] + elif numberValue.match(vals[1]): + vals[1] = int(vals[1]) + self.context[vals[0]] = vals[1] + + def handleU(option, opt, value, parser): + del self.context[value] + + def handleF(option, opt, value, parser): + self.do_filter(value) + + def handleMarker(option, opt, value, parser): + self.setMarker(value) + + def handleSilenceDirectiveWarnings(option, opt, value, parse): + self.setSilenceDirectiveWarnings(True) + + p = OptionParser() + p.add_option( + "-D", + action="callback", + callback=handleD, + type="string", + metavar="VAR[=VAL]", + help="Define a variable", + ) + p.add_option( + "-U", + action="callback", + callback=handleU, + type="string", + metavar="VAR", + help="Undefine a variable", + ) + p.add_option( + "-F", + action="callback", + callback=handleF, + type="string", + metavar="FILTER", + help="Enable the specified filter", + ) + p.add_option( + "-o", + "--output", + type="string", + default=None, + metavar="FILENAME", + help="Output to the specified file instead of stdout", + ) + p.add_option( + "--depend", + type="string", + default=None, + metavar="FILENAME", + help="Generate dependencies in the given file", + ) + p.add_option( + "--marker", + action="callback", + callback=handleMarker, + type="string", + help="Use the specified marker instead of #", + ) + p.add_option( + "--silence-missing-directive-warnings", + action="callback", + callback=handleSilenceDirectiveWarnings, + help="Don't emit warnings about missing directives", + ) + p.add_option( + "--output-encoding", + type="string", + default=None, + metavar="ENCODING", + help="Encoding to use for the output", + ) + return p + + def handleLine(self, aLine): + """ + Handle a single line of input (internal). + """ + if self.actionLevel == 0 and self.comment.match(aLine): + self.actionLevel = 1 + m = self.instruction.match(aLine) + if m: + args = None + cmd = m.group("cmd") + try: + args = m.group("args") + except IndexError: + pass + if cmd not in self.cmds: + raise Preprocessor.Error(self, "INVALID_CMD", aLine) + level, cmd = self.cmds[cmd] + if level >= self.disableLevel: + cmd(args) + if cmd != "literal": + self.actionLevel = 2 + elif self.disableLevel == 0: + if self.comment.match(aLine): + # make sure the comment is not ambiguous with a command + m = self.ambiguous_comment.match(aLine) + if m: + cmd = m.group("cmd") + if cmd in self.cmds: + raise Preprocessor.Error(self, "AMBIGUOUS_COMMENT", aLine) + else: + self.write(aLine) + + # Instruction handlers + # These are named do_'instruction name' and take one argument + + # Variables + def do_define(self, args): + m = re.match(r"(?P<name>\w+)(?:\s(?P<value>.*))?", args, re.U) + if not m: + raise Preprocessor.Error(self, "SYNTAX_DEF", args) + val = "" + if m.group("value"): + val = self.applyFilters(m.group("value")) + try: + val = int(val) + except Exception: + pass + self.context[m.group("name")] = val + + def do_undef(self, args): + m = re.match(r"(?P<name>\w+)$", args, re.U) + if not m: + raise Preprocessor.Error(self, "SYNTAX_DEF", args) + if args in self.context: + del self.context[args] + + # Logic + def ensure_not_else(self): + if len(self.ifStates) == 0 or self.ifStates[-1] == 2: + sys.stderr.write( + "WARNING: bad nesting of #else in %s\n" % self.context["FILE"] + ) + + def do_if(self, args, replace=False): + if self.disableLevel and not replace: + self.disableLevel += 1 + return + val = None + try: + e = Expression(args) + val = e.evaluate(self.context) + except Exception: + # XXX do real error reporting + raise Preprocessor.Error(self, "SYNTAX_ERR", args) + if isinstance(val, six.text_type) or isinstance(val, six.binary_type): + # we're looking for a number value, strings are false + val = False + if not val: + self.disableLevel = 1 + if replace: + if val: + self.disableLevel = 0 + self.ifStates[-1] = self.disableLevel + else: + self.ifStates.append(self.disableLevel) + + def do_ifdef(self, args, replace=False): + if self.disableLevel and not replace: + self.disableLevel += 1 + return + if re.search(r"\W", args, re.U): + raise Preprocessor.Error(self, "INVALID_VAR", args) + if args not in self.context: + self.disableLevel = 1 + if replace: + if args in self.context: + self.disableLevel = 0 + self.ifStates[-1] = self.disableLevel + else: + self.ifStates.append(self.disableLevel) + + def do_ifndef(self, args, replace=False): + if self.disableLevel and not replace: + self.disableLevel += 1 + return + if re.search(r"\W", args, re.U): + raise Preprocessor.Error(self, "INVALID_VAR", args) + if args in self.context: + self.disableLevel = 1 + if replace: + if args not in self.context: + self.disableLevel = 0 + self.ifStates[-1] = self.disableLevel + else: + self.ifStates.append(self.disableLevel) + + def do_else(self, args, ifState=2): + self.ensure_not_else() + hadTrue = self.ifStates[-1] == 0 + self.ifStates[-1] = ifState # in-else + if hadTrue: + self.disableLevel = 1 + return + self.disableLevel = 0 + + def do_elif(self, args): + if self.disableLevel == 1: + if self.ifStates[-1] == 1: + self.do_if(args, replace=True) + else: + self.do_else(None, self.ifStates[-1]) + + def do_elifdef(self, args): + if self.disableLevel == 1: + if self.ifStates[-1] == 1: + self.do_ifdef(args, replace=True) + else: + self.do_else(None, self.ifStates[-1]) + + def do_elifndef(self, args): + if self.disableLevel == 1: + if self.ifStates[-1] == 1: + self.do_ifndef(args, replace=True) + else: + self.do_else(None, self.ifStates[-1]) + + def do_endif(self, args): + if self.disableLevel > 0: + self.disableLevel -= 1 + if self.disableLevel == 0: + self.ifStates.pop() + + # output processing + def do_expand(self, args): + lst = re.split(r"__(\w+)__", args, re.U) + + def vsubst(v): + if v in self.context: + return _to_text(self.context[v]) + return "" + + for i in range(1, len(lst), 2): + lst[i] = vsubst(lst[i]) + lst.append("\n") # add back the newline + self.write(six.moves.reduce(lambda x, y: x + y, lst, "")) + + def do_literal(self, args): + self.write(args + "\n") + + def do_filter(self, args): + filters = [f for f in args.split(" ") if hasattr(self, "filter_" + f)] + if len(filters) == 0: + return + current = dict(self.filters) + for f in filters: + current[f] = getattr(self, "filter_" + f) + self.filters = [(fn, current[fn]) for fn in sorted(current.keys())] + return + + def do_unfilter(self, args): + filters = args.split(" ") + current = dict(self.filters) + for f in filters: + if f in current: + del current[f] + self.filters = [(fn, current[fn]) for fn in sorted(current.keys())] + return + + # Filters + # + # emptyLines: Strips blank lines from the output. + def filter_emptyLines(self, aLine): + if aLine == "\n": + return "" + return aLine + + # dumbComments: Empties out lines that consists of optional whitespace + # followed by a `//`. + def filter_dumbComments(self, aLine): + return re.sub(r"^\s*//.*", "", aLine) + + # substitution: variables wrapped in @ are replaced with their value. + def filter_substitution(self, aLine, fatal=True): + def repl(matchobj): + varname = matchobj.group("VAR") + if varname in self.context: + return _to_text(self.context[varname]) + if fatal: + raise Preprocessor.Error(self, "UNDEFINED_VAR", varname) + return matchobj.group(0) + + return self.varsubst.sub(repl, aLine) + + # attemptSubstitution: variables wrapped in @ are replaced with their + # value, or an empty string if the variable is not defined. + def filter_attemptSubstitution(self, aLine): + return self.filter_substitution(aLine, fatal=False) + + # File ops + def do_include(self, args, filters=True): + """ + Preprocess a given file. + args can either be a file name, or a file-like object. + Files should be opened, and will be closed after processing. + """ + isName = isinstance(args, six.string_types) + oldCheckLineNumbers = self.checkLineNumbers + self.checkLineNumbers = False + if isName: + try: + args = _to_text(args) + if filters: + args = self.applyFilters(args) + if not os.path.isabs(args): + args = os.path.join(self.curdir, args) + args = io.open(args, "r", encoding="utf-8") + except Preprocessor.Error: + raise + except Exception: + raise Preprocessor.Error(self, "FILE_NOT_FOUND", _to_text(args)) + self.checkLineNumbers = bool( + re.search(r"\.(js|jsm|java|webidl)(?:\.in)?$", args.name) + ) + oldFile = self.context["FILE"] + oldLine = self.context["LINE"] + oldDir = self.context["DIRECTORY"] + oldCurdir = self.curdir + self.noteLineInfo() + + if args.isatty(): + # we're stdin, use '-' and '' for file and dir + self.context["FILE"] = "-" + self.context["DIRECTORY"] = "" + self.curdir = "." + else: + abspath = os.path.abspath(args.name) + self.curdir = os.path.dirname(abspath) + self.includes.add(six.ensure_text(abspath)) + if self.topobjdir and path_starts_with(abspath, self.topobjdir): + abspath = "$OBJDIR" + normsep(abspath[len(self.topobjdir) :]) + elif self.topsrcdir and path_starts_with(abspath, self.topsrcdir): + abspath = "$SRCDIR" + normsep(abspath[len(self.topsrcdir) :]) + self.context["FILE"] = abspath + self.context["DIRECTORY"] = os.path.dirname(abspath) + self.context["LINE"] = 0 + + for l in args: + self.context["LINE"] += 1 + self.handleLine(l) + if isName: + args.close() + + self.context["FILE"] = oldFile + self.checkLineNumbers = oldCheckLineNumbers + self.context["LINE"] = oldLine + self.context["DIRECTORY"] = oldDir + self.curdir = oldCurdir + + def do_includesubst(self, args): + args = self.filter_substitution(args) + self.do_include(args) + + def do_error(self, args): + raise Preprocessor.Error(self, "Error: ", _to_text(args)) + + +def preprocess(includes=[sys.stdin], defines={}, output=sys.stdout, marker="#"): + pp = Preprocessor(defines=defines, marker=marker) + for f in includes: + with io.open(f, "r", encoding="utf-8") as input: + pp.processFile(input=input, output=output) + return pp.includes + + +# Keep this module independently executable. +if __name__ == "__main__": + pp = Preprocessor() + pp.handleCommandLine(None, True) |