Introduced integrated documentation for builtin functions

This commit is contained in:
Robert Bendun 2023-01-30 14:59:14 +01:00
parent 25b446a5cc
commit deabd1865a
4 changed files with 105 additions and 11 deletions

View File

@ -0,0 +1,9 @@
#ifndef MUSIQUE_BUILTIN_FUNCTION_DOCUMENTATION_HH
#define MUSIQUE_BUILTIN_FUNCTION_DOCUMENTATION_HH
#include <optional>
#include <string_view>
std::optional<std::string_view> find_documentation_for_builtin(std::string_view builtin_name);
#endif

View File

@ -17,6 +17,7 @@
#include <musique/try.hh> #include <musique/try.hh>
#include <musique/unicode.hh> #include <musique/unicode.hh>
#include <musique/value/block.hh> #include <musique/value/block.hh>
#include <musique/interpreter/builtin_function_documentation.hh>
#ifdef _WIN32 #ifdef _WIN32
extern "C" { extern "C" {
@ -418,6 +419,20 @@ static std::optional<Error> Main(std::span<char const*> args)
return {}; return {};
} }
if (arg == "--doc" || arg == "-d") {
if (args.empty()) {
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
std::exit(1);
}
if (auto maybe_docs = find_documentation_for_builtin(pop(args)); maybe_docs) {
std::cout << *maybe_docs << std::endl;
return {};
} else {
std::cerr << "musique: error: cannot find documentation for given builtin" << std::endl;
std::exit(1);
}
}
if (arg == "-h" || arg == "--help") { if (arg == "-h" || arg == "--help") {
usage(); usage();
} }

View File

@ -1,4 +1,4 @@
Release_Obj=$(addprefix bin/$(os)/,$(Obj)) Release_Obj=$(addprefix bin/$(os)/,$(Obj)) bin/$(os)/builtin_function_documentation.o
bin/$(os)/bestline.o: lib/bestline/bestline.c lib/bestline/bestline.h bin/$(os)/bestline.o: lib/bestline/bestline.c lib/bestline/bestline.h
@echo "CC $@" @echo "CC $@"
@ -12,7 +12,14 @@ bin/$(os)/$(Target): $(Release_Obj) bin/$(os)/main.o bin/$(os)/rtmidi.o $(Bestli
@echo "CXX $@" @echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/$(os)/rtmidi.o $(Bestline) $(LDFLAGS) $(LDLIBS) @$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $(Release_Obj) bin/$(os)/rtmidi.o $(Bestline) $(LDFLAGS) $(LDLIBS)
Debug_Obj=$(addprefix bin/$(os)/debug/,$(Obj)) bin/$(os)/builtin_function_documentation.o: bin/$(os)/builtin_function_documentation.cc
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(RELEASE_FLAGS) $(CPPFLAGS) -o $@ $< -c
bin/$(os)/builtin_function_documentation.cc: musique/interpreter/builtin_functions.cc scripts/document-builtin.py
scripts/document-builtin.py -f cpp -o $@ musique/interpreter/builtin_functions.cc
Debug_Obj=$(addprefix bin/$(os)/debug/,$(Obj)) bin/$(os)/debug/builtin_function_documentation.o
bin/$(os)/debug/$(Target): $(Debug_Obj) bin/$(os)/debug/main.o bin/$(os)/rtmidi.o $(Bestline) bin/$(os)/debug/$(Target): $(Debug_Obj) bin/$(os)/debug/main.o bin/$(os)/rtmidi.o $(Bestline)
@echo "CXX $@" @echo "CXX $@"
@ -22,3 +29,6 @@ bin/$(os)/debug/%.o: musique/%.cc
@echo "CXX $@" @echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c @$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c
bin/$(os)/debug/builtin_function_documentation.o: bin/$(os)/builtin_function_documentation.cc
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(DEBUG_FLAGS) $(CPPFLAGS) -o $@ $< -c

View File

@ -1,11 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import dataclasses import dataclasses
import string
import re
import itertools import itertools
import typing import json
import re
import string
import subprocess import subprocess
import typing
MARKDOWN_CONVERTER = "lowdown -m 'shiftheadinglevelby=3'" MARKDOWN_CONVERTER = "lowdown -m 'shiftheadinglevelby=3'"
CPP_FUNC_IDENT_ALLOWLIST = string.ascii_letters + string.digits + "_" CPP_FUNC_IDENT_ALLOWLIST = string.ascii_letters + string.digits + "_"
@ -60,6 +61,16 @@ HTML_SUFFIX = """
</html> </html>
""" """
FIND_DOCUMENTATION_FOR_BUILTIN = """
std::optional<std::string_view> find_documentation_for_builtin(std::string_view builtin_name)
{
for (auto [name, doc] : names_to_documentation) {
if (builtin_name == name)
return doc;
}
return std::nullopt;
}
"""
def warning(*args, prefix: str | None = None): def warning(*args, prefix: str | None = None):
if prefix is None: if prefix is None:
@ -151,7 +162,9 @@ def builtins_from_file(source_path: str) -> typing.Generator[Builtin, None, None
yield builtin yield builtin
def filter_builtins(builtins: list[Builtin]) -> typing.Generator[Builtin, None, None]: def filter_builtins(
builtins: typing.Iterable[Builtin],
) -> typing.Generator[Builtin, None, None]:
for builtin in builtins: for builtin in builtins:
if not builtin.documentation: if not builtin.documentation:
warning(f"builtin '{builtin.implementation}' doesn't have documentation") warning(f"builtin '{builtin.implementation}' doesn't have documentation")
@ -167,7 +180,7 @@ def filter_builtins(builtins: list[Builtin]) -> typing.Generator[Builtin, None,
def each_musique_name_occurs_once( def each_musique_name_occurs_once(
builtins: typing.Iterable[Builtin], builtins: typing.Iterable[Builtin],
) -> typing.Generator[Builtin, None, None]: ) -> typing.Generator[Builtin, None, None]:
names = {} names: dict[str, str] = {}
for builtin in builtins: for builtin in builtins:
for name in builtin.names: for name in builtin.names:
if name in names: if name in names:
@ -178,7 +191,7 @@ def each_musique_name_occurs_once(
yield builtin yield builtin
def generate_html_document(builtins: list[Builtin], output_path: str): def generate_html_document(builtins: typing.Iterable[Builtin], output_path: str):
with open(output_path, "w") as out: with open(output_path, "w") as out:
out.write(HTML_PREFIX) out.write(HTML_PREFIX)
@ -218,7 +231,40 @@ def generate_html_document(builtins: list[Builtin], output_path: str):
out.write(HTML_SUFFIX) out.write(HTML_SUFFIX)
def main(source_path: str, output_path: str): def generate_cpp_documentation(builtins: typing.Iterable[Builtin], output_path: str):
# TODO Support markdown rendering and colors using `pretty` sublibrary
def documentation_str_var(builtin: Builtin):
return f"{builtin.implementation}_doc"
with open(output_path, "w") as out:
print("#include <array>", file=out)
print("#include <musique/interpreter/builtin_function_documentation.hh>\n", file=out)
# 1. Generate strings with documentation
for builtin in builtins:
# FIXME json.dumps will probably produce valid C++ strings for most cases,
# but can we ensure that will output valid strings for all cases?
print(
"static constexpr std::string_view %s = %s;"
% (documentation_str_var(builtin), json.dumps(builtin.documentation)),
file=out,
)
print("", file=out)
# 2. Generate array mapping from name to documentation variable
names_to_documentation = list(sorted((name, documentation_str_var(builtin)) for builtin in builtins for name in builtin.names))
print("static constexpr std::array<std::pair<std::string_view, std::string_view>, %d> names_to_documentation = {" % (len(names_to_documentation), ), file=out)
for name, doc in names_to_documentation:
print(" std::pair { std::string_view(%s), %s }," % (json.dumps(name), doc), file=out)
print("};", file=out);
# 3. Generate function that given builtin name results in documentation string
print(FIND_DOCUMENTATION_FOR_BUILTIN, file=out)
def main(source_path: str, output_path: str, format: typing.Literal["html", "cpp"]):
"Generates documentaiton from file source_path and saves in output_path" "Generates documentaiton from file source_path and saves in output_path"
builtins = builtins_from_file(source_path) builtins = builtins_from_file(source_path)
@ -226,7 +272,10 @@ def main(source_path: str, output_path: str):
builtins = each_musique_name_occurs_once(builtins) builtins = each_musique_name_occurs_once(builtins)
builtins = sorted(list(builtins), key=lambda builtin: builtin.names[0]) builtins = sorted(list(builtins), key=lambda builtin: builtin.names[0])
if format == "md":
generate_html_document(builtins, output_path) generate_html_document(builtins, output_path)
else:
generate_cpp_documentation(builtins, output_path)
if __name__ == "__main__": if __name__ == "__main__":
@ -247,10 +296,21 @@ if __name__ == "__main__":
required=True, required=True,
help="path for standalone HTML file containing generated documentation", help="path for standalone HTML file containing generated documentation",
) )
parser.add_argument(
"-f",
"--format",
type=str,
default="md",
help="output format. One of {html, cpp} are allowed, where HTML yields standalone docs, and C++ mode yields integrated docs",
)
PROGRAM_NAME = parser.prog PROGRAM_NAME = parser.prog
args = parser.parse_args() args = parser.parse_args()
assert len(args.source) == 1 assert len(args.source) == 1
assert len(args.output) == 1 assert len(args.output) == 1
assert args.format in (
"html",
"cpp",
), "Only C++ and HTML output formats are supported"
main(source_path=args.source[0], output_path=args.output[0]) main(source_path=args.source[0], output_path=args.output[0], format=args.format)