diff --git a/lix/code-generation/__init__.py b/lix/code-generation/__init__.py new file mode 100644 index 000000000..9a071b07b --- /dev/null +++ b/lix/code-generation/__init__.py @@ -0,0 +1 @@ +# noqa: N999 # consistency with rest of the codebase diff --git a/lix/code-generation/build_builtin_constants.py b/lix/code-generation/build_builtin_constants.py index beb7b6bfa..bdba163c5 100644 --- a/lix/code-generation/build_builtin_constants.py +++ b/lix/code-generation/build_builtin_constants.py @@ -1,15 +1,12 @@ -from typing import List, NamedTuple +from typing import NamedTuple + +from frontmatter import Post from common import cxx_literal, generate_file, load_data +import argparse + +KNOWN_KEYS = {"name", "type", "constructorArgs", "implementation", "impure", "renameInGlobalScope"} -KNOWN_KEYS = set([ - 'name', - 'type', - 'constructorArgs', - 'implementation', - 'impure', - 'renameInGlobalScope', -]) class BuiltinConstant(NamedTuple): name: str @@ -19,75 +16,97 @@ class BuiltinConstant(NamedTuple): rename_in_global_scope: bool documentation: str - def parse(datum): + @classmethod + def parse(cls, datum: Post) -> "BuiltinConstant": unknown_keys = set(datum.keys()) - KNOWN_KEYS if unknown_keys: - raise Exception('unknown keys', unknown_keys) + msg = f"unknown keys: {unknown_keys!r}" + raise ValueError(msg) + if (constructor_args := datum.get("constructorArgs")) is not None: + args = [f"NewValueAs::{datum['type']}"] + constructor_args # type: ignore + impl = f"{{{','.join(args)}}}" + else: + impl = datum["implementation"] + return BuiltinConstant( - name = datum['name'], - type = datum['type'], - implementation = ('{' + ', '.join([f'NewValueAs::{datum["type"]}', *datum['constructorArgs']]) + '}') if 'constructorArgs' in datum else datum['implementation'], - impure = datum.get('impure', False), - rename_in_global_scope = datum.get('renameInGlobalScope', True), - documentation = datum.content, + name=datum["name"], # type: ignore + type=datum["type"], # type: ignore + implementation=impl, + impure=datum.get("impure", False), # type: ignore + rename_in_global_scope=datum.get("renameInGlobalScope", True), # type: ignore + documentation=datum.content, ) + VALUE_TYPES = { - 'attrs': 'nAttrs', - 'boolean': 'nBool', - 'integer': 'nInt', - 'list': 'nList', - 'null': 'nNull', - 'string': 'nString', + "attrs": "nAttrs", + "boolean": "nBool", + "integer": "nInt", + "list": "nList", + "null": "nNull", + "string": "nString", } HUMAN_TYPES = { - 'attrs': 'set', - 'boolean': 'Boolean', - 'integer': 'integer', - 'list': 'list', - 'null': 'null', - 'string': 'string', + "attrs": "set", + "boolean": "Boolean", + "integer": "integer", + "list": "list", + "null": "null", + "string": "string", } -def main(): - import argparse +def main(): ap = argparse.ArgumentParser() - ap.add_argument('--header', help='Path of the header to generate') - ap.add_argument('--docs', help='Path of the documentation file to generate') - ap.add_argument('defs', help='Builtin definition files', nargs='+') + ap.add_argument("--header", help="Path of the header to generate") + ap.add_argument("--docs", help="Path of the documentation file to generate") + ap.add_argument("defs", help="Builtin definition files", nargs="+") args = ap.parse_args() builtin_constants = load_data(args.defs, BuiltinConstant.parse) - generate_file(args.header, builtin_constants, lambda constant: + generate_file( + args.header, + builtin_constants, + lambda constant: # `builtins` is magic and must come first - '' if constant.name == 'builtins' else constant.name, - lambda constant: - f'''{'if (!evalSettings.pureEval) ' if constant.impure else ''}{{ - addConstant({cxx_literal(('__' if constant.rename_in_global_scope else '') + constant.name)}, {constant.implementation}, {{ + "" if constant.name == "builtins" else constant.name, + lambda constant: f"""{"if (!evalSettings.pureEval) " if constant.impure else ""}{{ + addConstant({cxx_literal(("__" if constant.rename_in_global_scope else "") + constant.name)}, {constant.implementation}, {{ .type = {VALUE_TYPES[constant.type]}, .doc = {cxx_literal(constant.documentation)}, .impureOnly = {cxx_literal(constant.impure)}, }}); }} -''') - generate_file(args.docs, builtin_constants, lambda constant: constant.name, lambda constant: - f'''
+""", + ) + generate_file( + args.docs, + builtin_constants, + lambda constant: constant.name, + lambda constant: f"""
{constant.name} ({HUMAN_TYPES[constant.type]})
{constant.documentation} -''' + ('''> **Note** +""" + + ( + """> **Note** > > Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval). -''' if constant.impure else '') + '''
+""" + if constant.impure + else "" + ) + + """ -''') +""", + ) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/lix/code-generation/build_builtins.py b/lix/code-generation/build_builtins.py index 64021b4d3..ee9c9f3a2 100644 --- a/lix/code-generation/build_builtins.py +++ b/lix/code-generation/build_builtins.py @@ -1,61 +1,71 @@ -from typing import List, NamedTuple, Optional +from typing import NamedTuple + +from frontmatter import Post from build_experimental_features import ExperimentalFeature from common import cxx_literal, generate_file, load_data +import argparse + +KNOWN_KEYS = {"name", "implementation", "renameInGlobalScope", "args", "experimentalFeature"} -KNOWN_KEYS = set([ - 'name', - 'implementation', - 'renameInGlobalScope', - 'args', - 'experimentalFeature', -]) class Builtin(NamedTuple): name: str implementation: str rename_in_global_scope: bool - args: List[str] - experimental_feature: Optional[str] + args: list[str] + experimental_feature: str | None documentation: str - def parse(datum): + @classmethod + def parse(cls, datum: Post) -> "Builtin": unknown_keys = set(datum.keys()) - KNOWN_KEYS if unknown_keys: - raise Exception('unknown keys', unknown_keys) + msg = f"unknown keys: {unknown_keys!r}" + raise ValueError(msg) return Builtin( - name = datum['name'], - implementation = datum['implementation'] if 'implementation' in datum else f'prim_{datum["name"]}', - rename_in_global_scope = datum.get('renameInGlobalScope', True), - args = datum['args'], - experimental_feature = datum.get('experimentalFeature', None), - documentation = datum.content, + name=datum["name"], # type: ignore + implementation=datum["implementation"] + if "implementation" in datum + else f"prim_{datum['name']}", + rename_in_global_scope=datum.get("renameInGlobalScope", True), # type: ignore + args=datum["args"], # type: ignore + experimental_feature=datum.get("experimentalFeature", None), # type: ignore + documentation=datum.content, ) -def main(): - import argparse +def main(): ap = argparse.ArgumentParser() - ap.add_argument('--header', help='Path of the header to generate') - ap.add_argument('--docs', help='Path of the documentation file to generate') - ap.add_argument('--experimental-features', help='Directory containing the experimental feature definitions') - ap.add_argument('defs', help='Builtin definition files', nargs='+') + ap.add_argument("--header", help="Path of the header to generate") + ap.add_argument("--docs", help="Path of the documentation file to generate") + ap.add_argument( + "--experimental-features", help="Directory containing the experimental feature definitions" + ) + ap.add_argument("defs", help="Builtin definition files", nargs="+") args = ap.parse_args() builtins = load_data(args.defs, Builtin.parse) - experimental_feature_names = set([builtin.experimental_feature for (_, builtin) in builtins]) + experimental_feature_names = {builtin.experimental_feature for (_, builtin) in builtins} experimental_feature_names.discard(None) - experimental_feature_files = [f'{args.experimental_features}/{name}.md' for name in experimental_feature_names] + experimental_feature_files = [ + f"{args.experimental_features}/{name}.md" for name in experimental_feature_names + ] experimental_features = load_data(experimental_feature_files, ExperimentalFeature.parse) - experimental_features = dict(map(lambda path_and_feature: - (path_and_feature[1].name, f'Xp::{path_and_feature[1].internal_name}'), experimental_features)) - experimental_features[None] = 'std::nullopt' + experimental_features = { + path_and_feature[1].name: f"Xp::{path_and_feature[1].internal_name}" + for path_and_feature in experimental_features + } + experimental_features[None] = "std::nullopt" - generate_file(args.header, builtins, lambda builtin: builtin.name, lambda builtin: - f'''{'' if builtin.experimental_feature is None else f'if (experimentalFeatureSettings.isEnabled({experimental_features[builtin.experimental_feature]})) '}{{ + generate_file( + args.header, + builtins, + lambda builtin: builtin.name, + lambda builtin: f"""{"" if builtin.experimental_feature is None else f"if (experimentalFeatureSettings.isEnabled({experimental_features[builtin.experimental_feature]})) "}{{ addPrimOp({{ - .name = {cxx_literal(('__' if builtin.rename_in_global_scope else '') + builtin.name)}, + .name = {cxx_literal(("__" if builtin.rename_in_global_scope else "") + builtin.name)}, .args = {cxx_literal(builtin.args)}, .arity = {len(builtin.args)}, .doc = {cxx_literal(builtin.documentation)}, @@ -63,20 +73,32 @@ def main(): .experimentalFeature = {experimental_features[builtin.experimental_feature]}, }}); }} -''') - generate_file(args.docs, builtins, lambda builtin: builtin.name, lambda builtin: - f'''
- {builtin.name} {' '.join([f'{arg}' for arg in builtin.args])} +""", + ) + generate_file( + args.docs, + builtins, + lambda builtin: builtin.name, + lambda builtin: f"""
+ {builtin.name} {" ".join([f"{arg}" for arg in builtin.args])}
{builtin.documentation} -''' + (f'''This function is only available if the [{builtin.experimental_feature}](@docroot@/contributing/experimental-features.md#xp-feature-{builtin.experimental_feature}) experimental feature is enabled. +""" + + ( + f"""This function is only available if the [{builtin.experimental_feature}](@docroot@/contributing/experimental-features.md#xp-feature-{builtin.experimental_feature}) experimental feature is enabled. -''' if builtin.experimental_feature is not None else '') + '''
+""" + if builtin.experimental_feature is not None + else "" + ) + + """ -''') +""", + ) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/lix/code-generation/build_experimental_features.py b/lix/code-generation/build_experimental_features.py index 8768d826e..73dedb2fd 100644 --- a/lix/code-generation/build_experimental_features.py +++ b/lix/code-generation/build_experimental_features.py @@ -1,58 +1,77 @@ from typing import NamedTuple +from frontmatter import Post + from common import cxx_literal, generate_file, load_data +import argparse + +KNOWN_KEYS = {"name", "internalName"} -KNOWN_KEYS = set([ - 'name', - 'internalName', -]) class ExperimentalFeature(NamedTuple): name: str internal_name: str description: str - def parse(datum): + @classmethod + def parse(cls, datum: Post) -> "ExperimentalFeature": unknown_keys = set(datum.keys()) - KNOWN_KEYS if unknown_keys: - raise ValueError('unknown keys', unknown_keys) + msg = f"unknown keys: {unknown_keys!r}" + raise ValueError(msg) return ExperimentalFeature( - name = datum['name'], - internal_name = datum['internalName'], - description = datum.content, + name=datum["name"], # type: ignore + internal_name=datum["internalName"], # type: ignore + description=datum.content, ) -def main(): - import argparse +def main(): ap = argparse.ArgumentParser() - ap.add_argument('--deprecated', action='store_true', help='Generate deprecated features') - ap.add_argument('--header', help='Path of the declaration header to generate') - ap.add_argument('--impl-header', help='Path of the implementation header to generate') - ap.add_argument('--descriptions', help='Path of the description file to generate') - ap.add_argument('--shortlist', help='Path of the shortlist file to generate') - ap.add_argument('defs', help='Experimental feature definition files', nargs='+') + ap.add_argument("--deprecated", action="store_true", help="Generate deprecated features") + ap.add_argument("--header", help="Path of the declaration header to generate") + ap.add_argument("--impl-header", help="Path of the implementation header to generate") + ap.add_argument("--descriptions", help="Path of the description file to generate") + ap.add_argument("--shortlist", help="Path of the shortlist file to generate") + ap.add_argument("defs", help="Experimental feature definition files", nargs="+") args = ap.parse_args() features = load_data(args.defs, ExperimentalFeature.parse) - generate_file(args.header, features, lambda feature: feature.name, lambda feature: - f' {feature.internal_name},\n') - generate_file(args.impl_header, features, lambda feature: feature.name, lambda feature: - f''' {{ + generate_file( + args.header, + features, + lambda feature: feature.name, + lambda feature: f" {feature.internal_name},\n", + ) + generate_file( + args.impl_header, + features, + lambda feature: feature.name, + lambda feature: f""" {{ .tag = {"Dep" if args.deprecated else "Xp"}::{feature.internal_name}, .name = {cxx_literal(feature.name)}, .description = {cxx_literal(feature.description)}, }}, -''') - generate_file(args.descriptions, features, lambda feature: feature.name, lambda feature: - f'''## [`{feature.name}`]{{#{"dp" if args.deprecated else "xp"}-feature-{feature.name}}} +""", + ) + generate_file( + args.descriptions, + features, + lambda feature: feature.name, + lambda feature: f"""## [`{feature.name}`]{{#{"dp" if args.deprecated else "xp"}-feature-{feature.name}}} {feature.description} -''') - generate_file(args.shortlist, features, lambda feature: feature.name, lambda feature: - f' - [`{feature.name}`](@docroot@/contributing/{"deprecated" if args.deprecated else "experimental"}-features.md#{"dp" if args.deprecated else "xp"}-feature-{feature.name})\n') +""", + ) + generate_file( + args.shortlist, + features, + lambda feature: feature.name, + lambda feature: f" - [`{feature.name}`](@docroot@/contributing/{'deprecated' if args.deprecated else 'experimental'}-features.md#{'dp' if args.deprecated else 'xp'}-feature-{feature.name})\n", + ) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/lix/code-generation/build_settings.py b/lix/code-generation/build_settings.py index f9e993795..35e746f3b 100644 --- a/lix/code-generation/build_settings.py +++ b/lix/code-generation/build_settings.py @@ -1,101 +1,121 @@ -from typing import List, NamedTuple, Optional +from typing import NamedTuple, Any + +from frontmatter import Post from build_experimental_features import ExperimentalFeature from common import cxx_literal, generate_file, load_data +import argparse + +KNOWN_KEYS = { + "name", + "internalName", + "platforms", + "type", + "settingType", + "default", + "defaultExpr", + "defaultText", + "aliases", + "experimentalFeature", + "deprecated", +} -KNOWN_KEYS = set([ - 'name', - 'internalName', - 'platforms', - 'type', - 'settingType', - 'default', - 'defaultExpr', - 'defaultText', - 'aliases', - 'experimentalFeature', - 'deprecated', -]) class Setting(NamedTuple): name: str internal_name: str description: str - platforms: Optional[List[str]] + platforms: list[str] | None setting_type: str default_expr: str default_text: str - aliases: List[str] - experimental_feature: Optional[str] + aliases: list[str] + experimental_feature: str | None deprecated: bool - def parse(datum): + @classmethod + def parse(cls, datum: Post) -> "Setting": unknown_keys = set(datum.keys()) - KNOWN_KEYS if unknown_keys: - raise ValueError('unknown keys', unknown_keys) - default_text = f'`{nix_conf_literal(datum["default"])}`' if 'default' in datum else datum['defaultText'] - if default_text == '``': - default_text = '*empty*' + msg = f"unknown keys: {unknown_keys!r}" + raise ValueError(msg) + default_text = ( + f"`{nix_conf_literal(datum['default'])}`" + if "default" in datum + else datum["defaultText"] + ) + if default_text == "``": + default_text = "*empty*" return Setting( - name = datum['name'], - internal_name = datum['internalName'], - description = datum.content, - platforms = datum.get('platforms', None), - setting_type = f'Setting<{datum["type"]}>' if 'type' in datum else datum['settingType'], - default_expr = cxx_literal(datum['default']) if 'default' in datum else datum['defaultExpr'], - default_text = default_text, - aliases = datum.get('aliases', []), - experimental_feature = datum.get('experimentalFeature', None), - deprecated = datum.get('deprecated', False), + name=datum["name"], # type: ignore + internal_name=datum["internalName"], # type: ignore + description=datum.content, + platforms=datum.get("platforms", None), # type: ignore + setting_type=f"Setting<{datum['type']}>" if "type" in datum else datum["settingType"], + default_expr=cxx_literal(datum["default"]) + if "default" in datum + else datum["defaultExpr"], + default_text=default_text, + aliases=datum.get("aliases", []), # type: ignore + experimental_feature=datum.get("experimentalFeature", None), # type: ignore + deprecated=datum.get("deprecated", False), # type: ignore ) -platform_names = { - 'darwin': 'Darwin', - 'linux': 'Linux', -} -def nix_conf_literal(v): +platform_names = {"darwin": "Darwin", "linux": "Linux"} + + +def nix_conf_literal(v: Any) -> str: if v is None: - return '' - elif isinstance(v, bool) and v == False: # 0 == False - return 'false' - elif isinstance(v, bool) and v == True: # 1 == True - return 'true' - elif isinstance(v, int): + return "" + if v is False: + return "false" + if v is True: + return "true" + if isinstance(v, int): return str(v) - elif isinstance(v, str): + if isinstance(v, str): return v - elif isinstance(v, list): - return ' '.join([nix_conf_literal(item) for item in v]) - else: - raise NotImplementedError(f'Cannot represent {repr(v)} in nix.conf') + if isinstance(v, list): + return " ".join([nix_conf_literal(item) for item in v]) + msg = f"Cannot represent {v!r} in nix.conf" + raise NotImplementedError(msg) + + +def indent(prefix: str, body: str) -> str: + return "".join(["\n" if not line else f"{prefix}{line}\n" for line in body.split("\n")]) -def indent(prefix, body): - return ''.join(['\n' if line == '' else f'{prefix}{line}\n' for line in body.split('\n')]) def main(): - import argparse - ap = argparse.ArgumentParser() - ap.add_argument('--kernel', help='Name of the kernel Lix will run on') - ap.add_argument('--header', help='Path of the header to generate') - ap.add_argument('--docs', help='Path of the documentation file to generate') - ap.add_argument('--experimental-features', help='Directory containing the experimental feature definitions') - ap.add_argument('defs', help='Setting definition files', nargs='+') + ap.add_argument("--kernel", help="Name of the kernel Lix will run on") + ap.add_argument("--header", help="Path of the header to generate") + ap.add_argument("--docs", help="Path of the documentation file to generate") + ap.add_argument( + "--experimental-features", help="Directory containing the experimental feature definitions" + ) + ap.add_argument("defs", help="Setting definition files", nargs="+") args = ap.parse_args() settings = load_data(args.defs, Setting.parse) - experimental_feature_names = set([setting.experimental_feature for (_, setting) in settings]) + experimental_feature_names = {setting.experimental_feature for (_, setting) in settings} experimental_feature_names.discard(None) - experimental_feature_files = [f'{args.experimental_features}/{name}.md' for name in experimental_feature_names] + experimental_feature_files = [ + f"{args.experimental_features}/{name}.md" for name in experimental_feature_names + ] experimental_features = load_data(experimental_feature_files, ExperimentalFeature.parse) - experimental_features = dict(map(lambda path_and_feature: - (path_and_feature[1].name, f'Xp::{path_and_feature[1].internal_name}'), experimental_features)) - experimental_features[None] = 'std::nullopt' + experimental_features = { + path_and_feature[1].name: f"Xp::{path_and_feature[1].internal_name}" + for path_and_feature in experimental_features + } + experimental_features[None] = "std::nullopt" - generate_file(args.header, settings, lambda setting: setting.name, lambda setting: - f'''{setting.setting_type} {setting.internal_name} {{ + generate_file( + args.header, + settings, + lambda setting: setting.name, + lambda setting: f"""{setting.setting_type} {setting.internal_name} {{ this, {setting.default_expr}, {cxx_literal(setting.name)}, @@ -106,15 +126,28 @@ def main(): {cxx_literal(setting.deprecated)} }}; -''' if setting.platforms is None or args.kernel in setting.platforms else '') - generate_file(args.docs, settings, lambda setting: setting.name, lambda setting: - f'''- [`{setting.name}`](#conf-{setting.name}) +""" + if setting.platforms is None or args.kernel in setting.platforms + else "", + ) + generate_file( + args.docs, + settings, + lambda setting: setting.name, + lambda setting: f"""- [`{setting.name}`](#conf-{setting.name}) {indent(" ", setting.description)} -''' + (f''' > **Note** - > This setting is only available on {', '.join([platform_names[platform] for platform in setting.platforms])} systems. +""" + + ( + f""" > **Note** + > This setting is only available on {", ".join([platform_names[platform] for platform in setting.platforms])} systems. -''' if setting.platforms is not None else '') + (f''' > **Warning** +""" + if setting.platforms is not None + else "" + ) + + ( + f""" > **Warning** > This setting is part of an > [experimental feature](@docroot@/contributing/experimental-features.md). @@ -128,14 +161,30 @@ def main(): {setting.name} = ... ``` -''' if setting.experimental_feature is not None else '') + (''' > **Warning** +""" + if setting.experimental_feature is not None + else "" + ) + + ( + """ > **Warning** > This setting is deprecated and will be removed in a future version of Lix. -''' if setting.deprecated else '') + f''' **Default:** {setting.default_text} +""" + if setting.deprecated + else "" + ) + + f""" **Default:** {setting.default_text} -''' + (f''' **Deprecated alias:** {', '.join([f'`{item}`' for item in setting.aliases])} +""" + + ( + f""" **Deprecated alias:** {", ".join([f"`{item}`" for item in setting.aliases])} -''' if setting.aliases != [] else '')) +""" + if setting.aliases != [] + else "" + ), + ) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/lix/code-generation/capnpc_wrapper.py b/lix/code-generation/capnpc_wrapper.py index 8bcd1b36b..5ecaeb79f 100755 --- a/lix/code-generation/capnpc_wrapper.py +++ b/lix/code-generation/capnpc_wrapper.py @@ -1,4 +1,5 @@ #!@python@ +# ruff: noqa: SIM112 # ignore lowercase env variable names for capnpc as we have them in lower case as arguments import argparse import capnp @@ -7,58 +8,61 @@ import os import subprocess import sys -if lang := os.environ.get('lix_capnp_lang'): - outputs = os.environ['lix_capnp_outputs'].split() - old_cwd = os.environ['lix_capnp_old_cwd'] - schema = capnp.load('@capnp_include@/capnp/schema.capnp', imports=['@capnp_include@']) +if lang := os.environ.get("lix_capnp_lang"): + outputs = os.environ["lix_capnp_outputs"].split() + old_cwd = os.environ["lix_capnp_old_cwd"] + schema = capnp.load("@capnp_include@/capnp/schema.capnp", imports=["@capnp_include@"]) request = schema.CodeGeneratorRequest.read(sys.stdin) subprocess.run([lang], input=request.as_builder().to_bytes()).check_returncode() - base_dir = os.getcwd() + base_dir = Path.cwd() os.chdir(old_cwd) - include = [ str(Path(p).resolve()) for p in os.environ['lix_capnp_include'].split(':') ] + include = [str(Path(p).resolve()) for p in os.environ["lix_capnp_include"].split(":")] - if depfile := os.environ['lix_capnp_depfile']: + if depfile := os.environ["lix_capnp_depfile"]: deps = "" - for input in request.requestedFiles: - deps += " ".join(f"{input.filename}.{o}" for o in outputs) + for input_file in request.requestedFiles: + deps += " ".join(f"{input_file.filename}.{o}" for o in outputs) deps += ":" - for dep in input.imports: + for dep in input_file.imports: if dep.name.startswith("/"): for candidate in (Path(i + dep.name) for i in include): if candidate.exists(): deps += " " + str(candidate) break else: - raise RuntimeError("not handling relative includes") + msg = "not handling relative includes" + raise RuntimeError(msg) deps += "\n\n" Path(depfile).write_text(deps) else: parser = argparse.ArgumentParser() - parser.add_argument('--language') - parser.add_argument('--outdir') - parser.add_argument('--src-prefix') - parser.add_argument('--depfile', default="") - parser.add_argument('-I', '--include', action='append', default=['@capnp_include@']) - parser.add_argument('inputs', nargs='+') + parser.add_argument("--language") + parser.add_argument("--outdir") + parser.add_argument("--src-prefix") + parser.add_argument("--depfile", default="") + parser.add_argument("-I", "--include", action="append", default=["@capnp_include@"]) + parser.add_argument("inputs", nargs="+") args = parser.parse_args() for infile in args.inputs: - os.environ['lix_capnp_lang'] = f"capnpc-{args.language}" - os.environ['lix_capnp_include'] = ':'.join(args.include) - os.environ['lix_capnp_depfile'] = args.depfile - os.environ['lix_capnp_old_cwd'] = os.getcwd() + os.environ["lix_capnp_lang"] = f"capnpc-{args.language}" + os.environ["lix_capnp_include"] = ":".join(args.include) + os.environ["lix_capnp_depfile"] = args.depfile + os.environ["lix_capnp_old_cwd"] = str(Path.cwd()) if args.language == "c++": - os.environ['lix_capnp_outputs'] = "c++ h" + os.environ["lix_capnp_outputs"] = "c++ h" else: raise RuntimeError("unknown language " + args.language) - subprocess.run([ - '@capnp@', - 'compile', - f'-o{sys.argv[0]}:{args.outdir}', - f'--src-prefix={args.src_prefix}', - *(f"-I{i}" for i in args.include), - infile - ]).check_returncode() + subprocess.run( + [ + "@capnp@", + "compile", + f"-o{sys.argv[0]}:{args.outdir}", + f"--src-prefix={args.src_prefix}", + *(f"-I{i}" for i in args.include), + infile, + ] + ).check_returncode() diff --git a/lix/code-generation/common.py b/lix/code-generation/common.py index 38e8122a0..3e4d8124a 100644 --- a/lix/code-generation/common.py +++ b/lix/code-generation/common.py @@ -1,60 +1,72 @@ +from collections.abc import Callable +from pathlib import Path +from typing import Any + import frontmatter -import pathlib -from collections import defaultdict -def cxx_escape_character(c): - if ord(c) >= 0x20 and ord(c) < 0x7f and c != '"' and c != '?' and c != '\\': + +def cxx_escape_character(c: str) -> str: + if 0x20 <= ord(c) < 0x7F and c != '"' and c != "?" and c != "\\": return c - elif c == '\t': - return r'\t' - elif c == '\n': - return r'\n' - elif c == '\r': - return r'\r' - elif c == '"': - return r'\"' - elif c == '?': - return r'\?' - elif c == '\\': - return r'\\' - elif ord(c) <= 0xffff: - return str.format(r'\u{:04x}', ord(c)) - else: - return str.format(r'\U{:08x}', ord(c)) + if c == "\t": + return r"\t" + if c == "\n": + return r"\n" + if c == "\r": + return r"\r" + if c == '"': + return r"\"" + if c == "?": + return r"\?" + if c == "\\": + return r"\\" + if ord(c) <= 0xFFFF: + return str.format(r"\u{:04x}", ord(c)) + return str.format(r"\U{:08x}", ord(c)) -def cxx_literal(v): + +def cxx_literal(v: Any) -> str: if v is None: - return 'std::nullopt' - elif isinstance(v, bool) and v == False: # 0 == False - return 'false' - elif isinstance(v, bool) and v == True: # 1 == True - return 'true' - elif isinstance(v, int): + return "std::nullopt" + if v is False: + return "false" + if v is True: + return "true" + if isinstance(v, int): return str(v) - elif isinstance(v, str): - return ''.join(['"', *(cxx_escape_character(c) for c in v), '"']) - elif isinstance(v, list): - return f'{{{", ".join([cxx_literal(item) for item in v])}}}' - else: - raise NotImplementedError(f'cannot represent {repr(v)} in C++') + if isinstance(v, str): + return "".join(['"', *(cxx_escape_character(c) for c in v), '"']) + if isinstance(v, list): + return f"{{{', '.join([cxx_literal(item) for item in v])}}}" + msg = f"cannot represent {v!r} in C++" + raise NotImplementedError(msg) -def load_data(defs, parse_function): + +def load_data[T](defs: list[str], parse_function: Callable[[frontmatter.Post], T]) -> list[T]: data = [] for path in defs: try: datum = frontmatter.load(path) data.append((path, parse_function(datum))) except Exception as e: - e.add_note(f'in {path}') + e.add_note(f"in {path}") raise return data -def generate_file(path, data, sort_key_function, generate_function): + +def generate_file( + path: str | None, + data: list[Any], + sort_key_function: Callable[[Any], str], + generate_function: Callable[[Any], str], +): if path is not None: - with open(path, 'w') as out: - for path, datum in sorted(data, key=lambda pathAndDatum: sort_key_function(pathAndDatum[1])): + with Path(path).open("w") as out: + for path, datum in sorted( + data, key=lambda path_and_datum: sort_key_function(path_and_datum[1]) + ): try: out.write(generate_function(datum)) except Exception as e: - e.add_note(f'in {path}') + e.add_note(f"in {path}") raise diff --git a/treefmt.toml b/treefmt.toml index aa39993f2..508801778 100644 --- a/treefmt.toml +++ b/treefmt.toml @@ -12,5 +12,5 @@ priority = 0 [formatter.ruff] command = "ruff" options = ["check"] -includes = ["tests/functional2/**/*.py", "meson/clang-tidy/*.py", "maintainers/*.py"] +includes = ["tests/functional2/**/*.py", "meson/clang-tidy/*.py", "maintainers/*.py", "lix/code-generation/*.py"] priority = 1