llvm-project/libc/utils/build_scripts/gen_hdr.py

189 lines
6.2 KiB
Python
Executable File

#! /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()