| #! /usr/bin/python |
| #===---------------- Script to generate header files ----------------------===# |
| # |
| # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| # See https:#llvm.org/LICENSE.txt for license information. |
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| # |
| #===-----------------------------------------------------------------------===# |
| # |
| # This script takes a .h.def file and generates a .h header file. |
| # See docs/header_generation.md for more information. |
| # |
| #===-----------------------------------------------------------------------===# |
| |
| import argparse |
| import contextlib |
| import os |
| import sys |
| |
| COMMAND_PREFIX = "%%" |
| COMMENT_PREFIX = "<!>" |
| |
| BEGIN_COMMAND = "begin" |
| COMMENT_COMMAND = "comment" |
| INCLUDE_FILE_COMMAND = "include_file" |
| |
| |
| class _Location(object): |
| def __init__(self, filename, line_number): |
| self.filename = filename |
| self.line_number = line_number |
| |
| def __str__(self): |
| return "%s:%s" % (self.filename, self.line_number) |
| |
| |
| @contextlib.contextmanager |
| def output_stream_manager(filename): |
| if filename is None: |
| try: |
| yield sys.stdout |
| finally: |
| pass |
| else: |
| output_stream = open(filename, "w") |
| try: |
| yield output_stream |
| finally: |
| output_stream.close() |
| |
| |
| def _parse_command(loc, line): |
| open_paren = line.find("(") |
| if open_paren < 0 or line[-1] != ")": |
| return _fatal_error(loc, "Incorrect header generation command syntax.") |
| command_name = line[len(COMMAND_PREFIX):open_paren] |
| args = line[open_paren + 1:-1].split(",") |
| args = [a.strip() for a in args] |
| if len(args) == 1 and not args[0]: |
| # There are no args, so we will make the args list an empty list. |
| args = [] |
| return command_name.strip(), args |
| |
| |
| def _is_named_arg(token): |
| if token.startswith("${") and token.endswith("}"): |
| return True |
| else: |
| return False |
| |
| |
| def _get_arg_name(token): |
| return token[2:-1] |
| |
| |
| def _fatal_error(loc, msg): |
| sys.exit("ERROR:%s: %s" % (loc, msg)) |
| |
| |
| def _is_begin_command(line): |
| if line.startswith(COMMAND_PREFIX + BEGIN_COMMAND): |
| return True |
| |
| |
| def include_file_command(out_stream, loc, args, values): |
| if len(args) != 1: |
| _fatal_error(loc, "`%%include_file` command takes exactly one " |
| "argument. %d given." % len(args)) |
| include_file_path = args[0] |
| if _is_named_arg(include_file_path): |
| arg_name = _get_arg_name(include_file_path) |
| include_file_path = values.get(arg_name) |
| if not include_file_path: |
| _fatal_error( |
| loc, |
| "No value specified for argument '%s'." % arg_name) |
| if not os.path.exists(include_file_path): |
| _fatal_error( |
| loc, |
| "Include file %s not found." % include_file_path) |
| with open(include_file_path, "r") as include_file: |
| begin = False |
| for line in include_file.readlines(): |
| line = line.strip() |
| if _is_begin_command(line): |
| # Parse the command to make sure there are no errors. |
| command_name, args = _parse_command(loc, line) |
| if args: |
| _fatal_error(loc, "Begin command does not take any args.") |
| begin = True |
| # Skip the line on which %%begin() is listed. |
| continue |
| if begin: |
| out_stream.write(line + "\n") |
| |
| |
| def begin_command(out_stream, loc, args, values): |
| # "begin" command can only occur in a file included with %%include_file |
| # command. It is not a replacement command. Hence, we just fail with |
| # a fatal error. |
| _fatal_error(loc, "Begin command cannot be listed in an input file.") |
| |
| |
| # Mapping from a command name to its implementation function. |
| REPLACEMENT_COMMANDS = { |
| INCLUDE_FILE_COMMAND: include_file_command, |
| BEGIN_COMMAND: begin_command, |
| } |
| |
| |
| def apply_replacement_command(out_stream, loc, line, values): |
| if not line.startswith(COMMAND_PREFIX): |
| # This line is not a replacement command. |
| return line |
| command_name, args = _parse_command(loc, line) |
| command = REPLACEMENT_COMMANDS.get(command_name.strip()) |
| if not command: |
| _fatal_error(loc, "Unknown replacement command `%`", command_name) |
| command(out_stream, loc, args, values) |
| |
| |
| def parse_options(): |
| parser = argparse.ArgumentParser( |
| description="Script to generate header files from .def files.") |
| parser.add_argument("def_file", metavar="DEF_FILE", |
| help="Path to the .def file.") |
| parser.add_argument("--args", "-P", nargs= "*", default=[], |
| help="NAME=VALUE pairs for command arguments in the " |
| "input .def file.") |
| # The output file argument is optional. If not specified, the generated |
| # header file content will be written to stdout. |
| parser.add_argument("--out-file", "-o", |
| help="Path to the generated header file. Defaults to " |
| "stdout") |
| opts = parser.parse_args() |
| if not all(["=" in arg for arg in opts.args]): |
| # We want all args to be specified in the form "name=value". |
| _fatal_error( |
| __file__ + ":" + "[command line]", |
| "Command arguments should be listed in the form NAME=VALUE") |
| return opts |
| |
| |
| def main(): |
| opts = parse_options() |
| arg_values = {} |
| for name_value_pair in opts.args: |
| name, value = name_value_pair.split("=") |
| arg_values[name] = value |
| with open(opts.def_file, "r") as def_file: |
| loc = _Location(opts.def_file, 0) |
| with output_stream_manager(opts.out_file) as out_stream: |
| for line in def_file: |
| loc.line_number += 1 |
| line = line.strip() |
| if line.startswith(COMMAND_PREFIX): |
| replacement_text = apply_replacement_command( |
| out_stream, loc, line, arg_values) |
| out_stream.write("\n") |
| elif line.startswith(COMMENT_PREFIX): |
| # Ignore comment line |
| continue |
| else: |
| out_stream.write(line + "\n") |
| |
| |
| if __name__ == "__main__": |
| main() |