Private
Public Access
170 lines
6.1 KiB
Python
Executable File
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())
|