Introduced integrated documentation for builtin functions
This commit is contained in:
parent
25b446a5cc
commit
deabd1865a
9
musique/interpreter/builtin_function_documentation.hh
Normal file
9
musique/interpreter/builtin_function_documentation.hh
Normal 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
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user