Files
capy-bearer/scripts/generate_capy_doc_signatures.py
T

170 lines
6.1 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import re
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
PAGE_SYMBOLS = {
"clear": ["clear"],
"delete": ["delete"],
"get": ["get"],
"get_by_path": ["get_by_path"],
"get_or_create": ["get_or_create"],
"get_type_name": ["get_type_name"],
"is_array": ["is_array"],
"is_list": ["is_list"],
"keys": ["keys"],
"pop": ["pop"],
"push": ["push"],
"set": ["set"],
"values": ["values"],
"session_set": ["session_set"],
"session_remove": ["session_remove"],
"call": ["call"],
"type/bool": ["bool"],
"type/s8": ["s8"],
"type/s16": ["s16"],
"type/s32": ["s32"],
"type/s64": ["s64"],
"type/u8": ["u8"],
"type/u16": ["u16"],
"type/u32": ["u32"],
"type/u64": ["u64"],
"type/f32": ["f32"],
"type/f64": ["f64"],
"string": ["string"],
"flush_output": ["flush_output"],
}
PAGE_SIGNATURES = {
"type/struct": ['struct Name { field : type, ... }'],
"type/array": ['[element_type]'],
"type/function": ['function(parameter_type, ...) result_type'],
"type/module": ['module'],
"has": ["has(value : dval, key : string) bool"],
"length": [
"function length(value : string) s64",
"function length(value : dval) s64",
"function length(value : [T]) s64",
],
"trap": ["function trap()"],
"mysql_info": ["function mysql_info(handle : u64, key : string) dval"],
"sqlite_info": ["function sqlite_info(handle : u64, key : string) dval"],
"posix_info": ["function posix_info(key : string, value : dval) dval"],
"clone": ["function clone(value : string) string"],
"dval": [
"function dval(value : string) dval",
"function dval(value : s32) dval",
"function dval(value : s64) dval",
"function dval(value : u64) dval",
"function dval(value : f64) dval",
"function dval(value : bool) dval",
"function dval(value : dval) dval",
],
}
def quote(text: str) -> str:
return '"' + text.replace('\\', '\\\\').replace('"', '\\"') + '"'
def compiler_signatures(capyc: Path) -> tuple[dict[str, list[tuple[str, str]]], dict[str, list[tuple[str, str]]]]:
output = subprocess.check_output(
[str(capyc), "--stdlib-signatures", "src/capy/stdlib.capy"], cwd=ROOT, text=True
)
signatures: dict[str, list[tuple[str, str]]] = {}
pages: dict[str, list[tuple[str, str]]] = {}
for line in output.splitlines():
fields = line.split("\t")
if len(fields) == 2:
symbol, declaration = fields
if not symbol.startswith("page:"):
raise RuntimeError(f"missing call boundary in stdlib signature line: {line!r}")
boundary = ""
elif len(fields) == 3:
symbol, declaration, boundary = fields
else:
raise RuntimeError(f"invalid stdlib signature line: {line!r}")
if symbol.startswith("page:"):
pages.setdefault(symbol[5:], []).append((declaration, boundary))
else:
signatures.setdefault(symbol, []).append((declaration, boundary))
return signatures, pages
def supported_pages() -> list[tuple[str, str]]:
source = (ROOT / "src/capy/parity_manifest.h").read_text()
body = source.split("supported{{", 1)[1].split("}};", 1)[0]
return [(name, marker) for name, _path, marker in re.findall(r'\{"([^"]+)","([^"]*)","([^"]*)"\}', body)]
def generate(capyc: Path) -> str:
signatures, compiler_pages = compiler_signatures(capyc)
pages: dict[str, list[str]] = {}
for page, marker in supported_pages():
if page in compiler_pages:
pages[page] = compiler_pages[page]
continue
if page in PAGE_SYMBOLS:
pages[page] = [entry for symbol in PAGE_SYMBOLS[page] for entry in signatures[symbol]]
continue
if page in PAGE_SIGNATURES:
match = re.match(r"function ([A-Za-z_][A-Za-z0-9_]*)", PAGE_SIGNATURES[page][0])
boundary = signatures.get(match.group(1), [("", "lib")])[0][1] if match else "lib"
pages[page] = [(declaration, boundary) for declaration in PAGE_SIGNATURES[page]]
continue
match = re.match(r"([A-Za-z_][A-Za-z0-9_]*)\(", marker)
if not match or match.group(1) not in signatures:
raise RuntimeError(f"{page}: no Capy signature mapping for evidence marker {marker!r}")
pages[page] = signatures[match.group(1)]
lines = [
"// Generated by scripts/generate_capy_doc_signatures.py. Do not edit.",
"#pragma once",
"#include <string_view>",
"",
"struct DocCapySignature { std::string_view page, declaration, boundary; };",
"inline constexpr DocCapySignature doc_capy_signatures[] = {",
]
for page in sorted(pages, key=str.lower):
for declaration, boundary in pages[page]:
lines.append(f"\t{{{quote(page)}, {quote(declaration)}, {quote(boundary)}}},")
lines.extend([
"};",
"",
"DValue doc_capy_signature_lines(String page)",
"{",
"\tDValue lines;",
"\tfor(const auto& entry : doc_capy_signatures)",
"\t{",
"\t\tif(entry.page == std::string_view(page))",
"\t\t\tlines.push_back(String(entry.declaration));",
"\t\telse if(lines.size() > 0)",
"\t\t\tbreak;",
"\t}",
"\treturn(lines);",
"}",
"",
])
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--capyc", type=Path, default=ROOT / "bin/capyc")
parser.add_argument("--output", type=Path, default=ROOT / "site/doc/lib/capy_signatures.generated.h")
parser.add_argument("--check", action="store_true")
args = parser.parse_args()
content = generate(args.capyc.resolve())
if args.check:
if not args.output.is_file() or args.output.read_text() != content:
raise SystemExit("generated Capy documentation signatures are stale")
elif not args.output.is_file() or args.output.read_text() != content:
args.output.write_text(content)
return 0
if __name__ == "__main__":
raise SystemExit(main())