xref: /llvm-project/libc/utils/hdrgen/yaml_to_classes.py (revision 6ad0dcf67f5dccdf8506ce5f51d793062a1c6879)
1#!/usr/bin/env python3
2#
3# ===- Generate headers for libc functions  -------------------*- python -*--==#
4#
5# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6# See https://llvm.org/LICENSE.txt for license information.
7# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8#
9# ==-------------------------------------------------------------------------==#
10
11import yaml
12import argparse
13from pathlib import Path
14
15from enumeration import Enumeration
16from function import Function
17from gpu_headers import GpuHeaderFile as GpuHeader
18from header import HeaderFile
19from macro import Macro
20from object import Object
21from type import Type
22
23
24def yaml_to_classes(yaml_data, header_class, entry_points=None):
25    """
26    Convert YAML data to header classes.
27
28    Args:
29        yaml_data: The YAML data containing header specifications.
30        header_class: The class to use for creating the header.
31        entry_points: A list of specific function names to include in the header.
32
33    Returns:
34        HeaderFile: An instance of HeaderFile populated with the data.
35    """
36    header_name = yaml_data.get("header")
37    header = header_class(header_name)
38    header.template_file = yaml_data.get("header_template")
39
40    for macro_data in yaml_data.get("macros", []):
41        header.add_macro(Macro(macro_data["macro_name"], macro_data["macro_value"]))
42
43    types = yaml_data.get("types", [])
44    sorted_types = sorted(types, key=lambda x: x["type_name"])
45    for type_data in sorted_types:
46        header.add_type(Type(type_data["type_name"]))
47
48    for enum_data in yaml_data.get("enums", []):
49        header.add_enumeration(
50            Enumeration(enum_data["name"], enum_data.get("value", None))
51        )
52
53    functions = yaml_data.get("functions", [])
54    if entry_points:
55        entry_points_set = set(entry_points)
56        functions = [f for f in functions if f["name"] in entry_points_set]
57    sorted_functions = sorted(functions, key=lambda x: x["name"])
58    guards = []
59    guarded_function_dict = {}
60    for function_data in sorted_functions:
61        guard = function_data.get("guard", None)
62        if guard is None:
63            arguments = [arg["type"] for arg in function_data["arguments"]]
64            attributes = function_data.get("attributes", None)
65            standards = function_data.get("standards", None)
66            header.add_function(
67                Function(
68                    function_data["return_type"],
69                    function_data["name"],
70                    arguments,
71                    standards,
72                    guard,
73                    attributes,
74                )
75            )
76        else:
77            if guard not in guards:
78                guards.append(guard)
79                guarded_function_dict[guard] = []
80                guarded_function_dict[guard].append(function_data)
81            else:
82                guarded_function_dict[guard].append(function_data)
83    sorted_guards = sorted(guards)
84    for guard in sorted_guards:
85        for function_data in guarded_function_dict[guard]:
86            arguments = [arg["type"] for arg in function_data["arguments"]]
87            attributes = function_data.get("attributes", None)
88            standards = function_data.get("standards", None)
89            header.add_function(
90                Function(
91                    function_data["return_type"],
92                    function_data["name"],
93                    arguments,
94                    standards,
95                    guard,
96                    attributes,
97                )
98            )
99
100    objects = yaml_data.get("objects", [])
101    sorted_objects = sorted(objects, key=lambda x: x["object_name"])
102    for object_data in sorted_objects:
103        header.add_object(
104            Object(object_data["object_name"], object_data["object_type"])
105        )
106
107    return header
108
109
110def load_yaml_file(yaml_file, header_class, entry_points):
111    """
112    Load YAML file and convert it to header classes.
113
114    Args:
115        yaml_file: Path to the YAML file.
116        header_class: The class to use for creating the header (HeaderFile or GpuHeader).
117        entry_points: A list of specific function names to include in the header.
118
119    Returns:
120        HeaderFile: An instance of HeaderFile populated with the data.
121    """
122    with open(yaml_file, "r") as f:
123        yaml_data = yaml.safe_load(f)
124    return yaml_to_classes(yaml_data, header_class, entry_points)
125
126
127def fill_public_api(header_str, h_def_content):
128    """
129    Replace the %%public_api() placeholder in the .h.def content with the generated header content.
130
131    Args:
132        header_str: The generated header string.
133        h_def_content: The content of the .h.def file.
134
135    Returns:
136        The final header content with the public API filled in.
137    """
138    header_str = header_str.strip()
139    return h_def_content.replace("%%public_api()", header_str, 1)
140
141
142def parse_function_details(details):
143    """
144    Parse function details from a list of strings and return a Function object.
145
146    Args:
147        details: A list containing function details
148
149    Returns:
150        Function: An instance of Function initialized with the details.
151    """
152    return_type, name, arguments, standards, guard, attributes = details
153    standards = standards.split(",") if standards != "null" else []
154    arguments = [arg.strip() for arg in arguments.split(",")]
155    attributes = attributes.split(",") if attributes != "null" else []
156
157    return Function(
158        return_type=return_type,
159        name=name,
160        arguments=arguments,
161        standards=standards,
162        guard=guard if guard != "null" else None,
163        attributes=attributes if attributes else [],
164    )
165
166
167def add_function_to_yaml(yaml_file, function_details):
168    """
169    Add a function to the YAML file.
170
171    Args:
172        yaml_file: The path to the YAML file.
173        function_details: A list containing function details (return_type, name, arguments, standards, guard, attributes).
174    """
175    new_function = parse_function_details(function_details)
176
177    with open(yaml_file, "r") as f:
178        yaml_data = yaml.safe_load(f)
179    if "functions" not in yaml_data:
180        yaml_data["functions"] = []
181
182    function_dict = {
183        "name": new_function.name,
184        "standards": new_function.standards,
185        "return_type": new_function.return_type,
186        "arguments": [{"type": arg} for arg in new_function.arguments],
187    }
188
189    if new_function.guard:
190        function_dict["guard"] = new_function.guard
191
192    if new_function.attributes:
193        function_dict["attributes"] = new_function.attributes
194
195    insert_index = 0
196    for i, func in enumerate(yaml_data["functions"]):
197        if func["name"] > new_function.name:
198            insert_index = i
199            break
200    else:
201        insert_index = len(yaml_data["functions"])
202
203    yaml_data["functions"].insert(insert_index, function_dict)
204
205    class IndentYamlListDumper(yaml.Dumper):
206        def increase_indent(self, flow=False, indentless=False):
207            return super(IndentYamlListDumper, self).increase_indent(flow, False)
208
209    with open(yaml_file, "w") as f:
210        yaml.dump(
211            yaml_data,
212            f,
213            Dumper=IndentYamlListDumper,
214            default_flow_style=False,
215            sort_keys=False,
216        )
217
218    print(f"Added function {new_function.name} to {yaml_file}")
219
220
221def main():
222    parser = argparse.ArgumentParser(description="Generate header files from YAML")
223    parser.add_argument(
224        "yaml_file", help="Path to the YAML file containing header specification"
225    )
226    parser.add_argument(
227        "--output_dir",
228        help="Directory to output the generated header file",
229    )
230    parser.add_argument(
231        "--add_function",
232        nargs=6,
233        metavar=(
234            "RETURN_TYPE",
235            "NAME",
236            "ARGUMENTS",
237            "STANDARDS",
238            "GUARD",
239            "ATTRIBUTES",
240        ),
241        help="Add a function to the YAML file",
242    )
243    parser.add_argument(
244        "--entry-point",
245        action="append",
246        help="Entry point to include",
247        dest="entry_points",
248    )
249    parser.add_argument(
250        "--export-decls",
251        action="store_true",
252        help="Flag to use GpuHeader for exporting declarations",
253    )
254    args = parser.parse_args()
255
256    if args.add_function:
257        add_function_to_yaml(args.yaml_file, args.add_function)
258
259    header_class = GpuHeader if args.export_decls else HeaderFile
260    header = load_yaml_file(args.yaml_file, header_class, args.entry_points)
261
262    header_str = str(header)
263
264    if args.output_dir:
265        output_file_path = Path(args.output_dir)
266        if output_file_path.is_dir():
267            output_file_path /= f"{Path(args.yaml_file).stem}.h"
268    else:
269        output_file_path = Path(f"{Path(args.yaml_file).stem}.h")
270
271    if args.export_decls:
272        with open(output_file_path, "w") as f:
273            f.write(header_str)
274
275
276if __name__ == "__main__":
277    main()
278