• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# ====- Generate documentation 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# ==-------------------------------------------------------------------------==#
10from argparse import ArgumentParser, Namespace
11from pathlib import Path
12from typing import Dict
13import sys
14import json
15
16from header import Header
17
18
19class DocgenAPIFormatError(Exception):
20    """Raised on fatal formatting errors with a description of a formatting error"""
21
22
23def check_api(header: Header, api: Dict):
24    """
25    Checks that docgen json files are properly formatted. If there are any
26    fatal formatting errors, raises exceptions with error messages useful for
27    fixing formatting. Warnings are printed to stderr on non-fatal formatting
28    errors. The code that runs after ``check_api(api)`` is called expects that
29    ``check_api`` executed without raising formatting exceptions so the json
30    matches the formatting specified here.
31
32    The json file may contain:
33    * an optional macros object
34    * an optional functions object
35
36    Formatting of ``macros`` and ``functions`` objects
37    ==================================================
38
39    If a macros or functions object is present, then it may contain nested
40    objects. Each of these nested objects should have a name matching a macro
41    or function's name, and each nested object must have the property:
42    ``"c-definition"`` or ``"posix-definition"``.
43
44    Description of properties
45    =========================
46    The defined property is intended to be a reference to a part of the
47    standard that defines the function or macro. For the ``"c-definition"`` property,
48    this should be a C standard section number. For the ``"posix-definition"`` property,
49    this should be a link to the definition.
50
51    :param api: docgen json file contents parsed into a dict
52    """
53    errors = []
54    cdef = "c-definition"
55    pdef = "posix-definition"
56
57    # Validate macros
58    if "macros" in api:
59        if not header.macro_file_exists():
60            print(
61                f"warning: Macro definitions are listed for {header.name}, but no macro file can be found in the directory tree rooted at {header.macros_dir}. All macros will be listed as not implemented.",
62                file=sys.stderr,
63            )
64
65        macros = api["macros"]
66
67        for name, obj in macros.items():
68            if not (cdef in obj or pdef in obj):
69                err = f'error: Macro {name} does not contain at least one required property: "{cdef}" or "{pdef}"'
70                errors.append(err)
71
72    # Validate functions
73    if "functions" in api:
74        if not header.fns_dir_exists():
75            print(
76                f"warning: Function definitions are listed for {header.name}, but no function implementation directory exists at {header.fns_dir}. All functions will be listed as not implemented.",
77                file=sys.stderr,
78            )
79
80        fns = api["functions"]
81        for name, obj in fns.items():
82            if not (cdef in obj or pdef in obj):
83                err = f'error: function {name} does not contain at least one required property: "{cdef}" or "{pdef}"'
84                errors.append(err)
85
86    if errors:
87        raise DocgenAPIFormatError("\n".join(errors))
88
89
90def load_api(header: Header) -> Dict:
91    api = header.docgen_json.read_text(encoding="utf-8")
92    return json.loads(api)
93
94
95def print_tbl_dir():
96    print(
97        f"""
98.. list-table::
99  :widths: auto
100  :align: center
101  :header-rows: 1
102
103  * - Function
104    - Implemented
105    - C23 Standard Section
106    - POSIX.1-2017 Standard Section"""
107    )
108
109
110def print_functions_rst(header: Header, functions: Dict):
111    tbl_hdr = "Functions"
112    print(tbl_hdr)
113    print("=" * len(tbl_hdr))
114
115    print_tbl_dir()
116
117    for name in sorted(functions.keys()):
118        print(f"  * - {name}")
119
120        if header.fns_dir_exists() and header.implements_fn(name):
121            print("    - |check|")
122        else:
123            print("    -")
124
125        if "c-definition" in functions[name]:
126            print(f'    - {functions[name]["c-definition"]}')
127        else:
128            print("    -")
129
130        if "posix-definition" in functions[name]:
131            print(f'    - {functions[name]["posix-definition"]}')
132        else:
133            print("    -")
134
135
136def print_macros_rst(header: Header, macros: Dict):
137    tbl_hdr = "Macros"
138    print(tbl_hdr)
139    print("=" * len(tbl_hdr))
140
141    print_tbl_dir()
142
143    for name in sorted(macros.keys()):
144        print(f"  * - {name}")
145
146        if header.macro_file_exists() and header.implements_macro(name):
147            print("    - |check|")
148        else:
149            print("    -")
150
151        if "c-definition" in macros[name]:
152            print(f'    - {macros[name]["c-definition"]}')
153        else:
154            print("    -")
155
156        if "posix-definition" in macros[name]:
157            print(f'    - {macros[name]["posix-definition"]}')
158        else:
159            print("    -")
160    print()
161
162
163def print_impl_status_rst(header: Header, api: Dict):
164    print(".. include:: check.rst\n")
165
166    print("=" * len(header.name))
167    print(header.name)
168    print("=" * len(header.name))
169    print()
170
171    # the macro and function sections are both optional
172    if "macros" in api:
173        print_macros_rst(header, api["macros"])
174
175    if "functions" in api:
176        print_functions_rst(header, api["functions"])
177
178
179def parse_args() -> Namespace:
180    parser = ArgumentParser()
181    choices = [p.with_suffix(".h").name for p in Path(__file__).parent.glob("*.json")]
182    parser.add_argument("header_name", choices=choices)
183    return parser.parse_args()
184
185
186if __name__ == "__main__":
187    args = parse_args()
188    header = Header(args.header_name)
189    api = load_api(header)
190    check_api(header, api)
191
192    print_impl_status_rst(header, api)
193