"""
HTML_SUFFIX = """
"""
def warning(*args, prefix: str | None = None):
if prefix is None:
prefix = PROGRAM_NAME
message = ": ".join(itertools.chain([prefix, "warning"], args))
print(message)
def error(*args, prefix=None):
if prefix is None:
prefix = PROGRAM_NAME
message = ": ".join(itertools.chain([prefix, "error"], args))
print(message)
exit(1)
@dataclasses.dataclass
class Builtin:
implementation: str
definition_location: tuple[str, int] # Filename and line number
names: list[str]
documentation: str
def builtins_from_file(source_path: str) -> typing.Generator[Builtin, None, None]:
with open(source_path) as f:
source = f.readlines()
builtins: dict[str, Builtin] = {}
definition = re.compile(
r"""force_define.*\("([^"]+)"\s*,\s*(builtin_[a-zA-Z0-9_]+)\)"""
)
current_documentation = []
for lineno, line in enumerate(source):
line = line.strip()
# Check if line contains force_define with static string and builtin_*
# thats beeing defined. It's a one of many names that given builtin
# has in Musique
if result := definition.search(line):
musique_name = result.group(1)
builtin_name = result.group(2)
if builtin_name in builtins:
builtins[builtin_name].names.append(musique_name)
else:
error(
f"tried adding Musique name '{musique_name}' to builtin '{builtin_name}' that has not been defined yet",
prefix=f"{source_path}:{lineno}",
)
continue
# Check if line contains special documentation comment.
# We assume that only documentation comments are in given line (modulo whitespace)
if line.startswith("//:"):
line = line.removeprefix("//:").strip()
current_documentation.append(line)
continue
# Check if line contains builtin_* identifier.
# If contains then this must be first definition of this function
# and therefore all documentation comments before it describe it
if (index := line.find("builtin_")) >= 0 and line[
index - 1
] not in CPP_FUNC_IDENT_ALLOWLIST:
identifier = line[index:]
for i, char in enumerate(identifier):
if char not in CPP_FUNC_IDENT_ALLOWLIST:
identifier = identifier[:i]
break
if identifier not in builtins:
builtin = Builtin(
implementation=identifier,
# TODO Allow redefinition of source path with some prefix
# to allow website links
definition_location=(source_path, lineno),
names=[],
documentation="\n".join(current_documentation),
)
builtins[identifier] = builtin
current_documentation = []
continue
for builtin in builtins.values():
builtin.names.sort()
yield builtin
def filter_builtins(builtins: list[Builtin]) -> typing.Generator[Builtin, None, None]:
for builtin in builtins:
if not builtin.documentation:
warning(f"builtin '{builtin.implementation}' doesn't have documentation")
continue
# Testt if builtin is unused
if not builtin.names:
continue
yield builtin
def each_musique_name_occurs_once(
builtins: typing.Iterable[Builtin],
) -> typing.Generator[Builtin, None, None]:
names = {}
for builtin in builtins:
for name in builtin.names:
if name in names:
error(
f"'{name}' has been registered as both '{builtin.implementation}' and '{names[name]}'"
)
names[name] = builtin.implementation
yield builtin
def generate_html_document(builtins: list[Builtin], output_path: str):
with open(output_path, "w") as out:
out.write(HTML_PREFIX)
out.write("")
out.write("")
for builtin in builtins:
out.write("