Merge branch 'integrated-documentation' into staged-0.5
This commit is contained in:
commit
943b065626
10
CHANGELOG.md
10
CHANGELOG.md
@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Builtin documentation for builtin functions display from repl and command line (`musique doc <builtin>`)
|
||||||
|
- Man documentation for commandline interface builtin (`musique man`)
|
||||||
|
- Suggestions which command line parameters user may wanted to use
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- New parameter passing convention for command line invocation. `musique help` to learn how it changed
|
||||||
|
|
||||||
## [0.4.0]
|
## [0.4.0]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -13,7 +13,7 @@ VERSION := $(MAJOR).$(MINOR).$(PATCH)-dev+$(COMMIT)
|
|||||||
|
|
||||||
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result
|
CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-type -Werror=unused-result
|
||||||
CPPFLAGS:=$(CPPFLAGS) -DMusique_Version='"$(VERSION)"' \
|
CPPFLAGS:=$(CPPFLAGS) -DMusique_Version='"$(VERSION)"' \
|
||||||
-Ilib/expected/ -I. -Ilib/bestline/ -Ilib/rtmidi/ -Ilib/link/include -Ilib/asio/include/
|
-Ilib/expected/ -I. -Ilib/bestline/ -Ilib/rtmidi/ -Ilib/link/include -Ilib/asio/include/ -Ilib/edit_distance.cc/
|
||||||
LDFLAGS=-flto
|
LDFLAGS=-flto
|
||||||
LDLIBS= -lpthread
|
LDLIBS= -lpthread
|
||||||
|
|
||||||
|
5
lib/edit_distance.cc/.gitignore
vendored
Normal file
5
lib/edit_distance.cc/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
test
|
||||||
|
test.cc
|
||||||
|
Makefile
|
||||||
|
.cache
|
||||||
|
compile_commands.json
|
19
lib/edit_distance.cc/LICENSE
Normal file
19
lib/edit_distance.cc/LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2023 Robert Bendun
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
76
lib/edit_distance.cc/edit_distance.hh
Normal file
76
lib/edit_distance.cc/edit_distance.hh
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2023 Robert Bendun <robert@bendun.cc>
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
// a copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
// the following conditions:
|
||||||
|
|
||||||
|
// The above copyright notice and this permission notice shall be
|
||||||
|
// included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <concepts>
|
||||||
|
#include <iterator>
|
||||||
|
#include <numeric>
|
||||||
|
#include <ranges>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
template<std::random_access_iterator S, std::random_access_iterator T>
|
||||||
|
requires std::equality_comparable_with<
|
||||||
|
std::iter_value_t<S>,
|
||||||
|
std::iter_value_t<T>
|
||||||
|
>
|
||||||
|
constexpr int edit_distance(S s, unsigned m, T t, unsigned n)
|
||||||
|
{
|
||||||
|
std::array<std::vector<int>, 2> memo;
|
||||||
|
auto *v0 = &memo[0];
|
||||||
|
auto *v1 = &memo[1];
|
||||||
|
|
||||||
|
for (auto& v : memo) {
|
||||||
|
v.resize(n+1);
|
||||||
|
}
|
||||||
|
std::iota(v0->begin(), v0->end(), 0);
|
||||||
|
|
||||||
|
for (auto i = 0u; i < m; ++i) {
|
||||||
|
(*v1)[0] = i+1;
|
||||||
|
for (auto j = 0u; j < n; ++j) {
|
||||||
|
auto const deletion_cost = (*v0)[j+1] + 1;
|
||||||
|
auto const insertion_cost = (*v1)[j] + 1;
|
||||||
|
auto const substitution_cost = (*v0)[j] + (s[i] != t[j]);
|
||||||
|
|
||||||
|
(*v1)[j+1] = std::min({ deletion_cost, insertion_cost, substitution_cost });
|
||||||
|
}
|
||||||
|
std::swap(v0, v1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*v0)[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
std::ranges::random_access_range Range1,
|
||||||
|
std::ranges::random_access_range Range2
|
||||||
|
>
|
||||||
|
requires std::equality_comparable_with<
|
||||||
|
std::ranges::range_value_t<Range1>,
|
||||||
|
std::ranges::range_value_t<Range2>
|
||||||
|
>
|
||||||
|
constexpr int edit_distance(Range1 const& range1, Range2 const& range2)
|
||||||
|
{
|
||||||
|
return edit_distance(
|
||||||
|
std::begin(range1), std::ranges::size(range1),
|
||||||
|
std::begin(range2), std::ranges::size(range2)
|
||||||
|
);
|
||||||
|
}
|
474
musique/cmd.cc
Normal file
474
musique/cmd.cc
Normal file
@ -0,0 +1,474 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <edit_distance.hh>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <musique/cmd.hh>
|
||||||
|
#include <musique/common.hh>
|
||||||
|
#include <musique/errors.hh>
|
||||||
|
#include <musique/interpreter/builtin_function_documentation.hh>
|
||||||
|
#include <musique/pretty.hh>
|
||||||
|
#include <set>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
// TODO: Command line parameters full documentation in other then man pages format. Maybe HTML generation?
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
extern "C" {
|
||||||
|
#include <io.h>
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using Empty_Argument = void(*)();
|
||||||
|
using Requires_Argument = void(*)(std::string_view);
|
||||||
|
using Defines_Code = cmd::Run(*)(std::string_view);
|
||||||
|
using Parameter = std::variant<Empty_Argument, Requires_Argument, Defines_Code>;
|
||||||
|
|
||||||
|
using namespace cmd;
|
||||||
|
|
||||||
|
// from musique/main.cc:
|
||||||
|
extern bool enable_repl;
|
||||||
|
extern bool ast_only_mode;
|
||||||
|
|
||||||
|
static Defines_Code provide_function = [](std::string_view fname) -> cmd::Run {
|
||||||
|
return { .type = Run::Deffered_File, .argument = fname };
|
||||||
|
};
|
||||||
|
|
||||||
|
static Defines_Code provide_inline_code = [](std::string_view code) -> cmd::Run {
|
||||||
|
return { .type = Run::Argument, .argument = code };
|
||||||
|
};
|
||||||
|
|
||||||
|
static Defines_Code provide_file = [](std::string_view fname) -> cmd::Run {
|
||||||
|
return { .type = Run::File, .argument = fname };
|
||||||
|
};
|
||||||
|
|
||||||
|
static Requires_Argument show_docs = [](std::string_view builtin) {
|
||||||
|
if (auto maybe_docs = find_documentation_for_builtin(builtin); maybe_docs) {
|
||||||
|
std::cout << *maybe_docs << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
|
||||||
|
std::cerr << " cannot find documentation for given builtin" << std::endl;
|
||||||
|
|
||||||
|
std::cerr << "Similar ones are:" << std::endl;
|
||||||
|
for (auto similar : similar_names_to_builtin(builtin)) {
|
||||||
|
std::cerr << " " << similar << '\n';
|
||||||
|
}
|
||||||
|
std::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
static Empty_Argument set_interactive_mode = [] { enable_repl = true; };
|
||||||
|
static Empty_Argument set_ast_only_mode = [] { ast_only_mode = true; };
|
||||||
|
|
||||||
|
static Empty_Argument print_version = [] { std::cout << Musique_Version << std::endl; };
|
||||||
|
static Empty_Argument print_help = usage;
|
||||||
|
|
||||||
|
[[noreturn]]
|
||||||
|
static void print_manpage();
|
||||||
|
|
||||||
|
struct Entry
|
||||||
|
{
|
||||||
|
std::string_view name;
|
||||||
|
Parameter handler;
|
||||||
|
bool internal = false;
|
||||||
|
|
||||||
|
void* handler_ptr() const
|
||||||
|
{
|
||||||
|
return std::visit([](auto p) { return reinterpret_cast<void*>(p); }, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t arguments() const
|
||||||
|
{
|
||||||
|
return std::visit([]<typename R, typename ...A>(R(*)(A...)) { return sizeof...(A); }, handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// First entry for given action type should always be it's cannonical name
|
||||||
|
static auto all_parameters = std::array {
|
||||||
|
Entry { "run", provide_file },
|
||||||
|
Entry { "r", provide_file },
|
||||||
|
Entry { "exec", provide_file },
|
||||||
|
Entry { "load", provide_file },
|
||||||
|
|
||||||
|
Entry { "fun", provide_function },
|
||||||
|
Entry { "def", provide_function },
|
||||||
|
Entry { "f", provide_function },
|
||||||
|
Entry { "func", provide_function },
|
||||||
|
Entry { "function", provide_function },
|
||||||
|
|
||||||
|
Entry { "repl", set_interactive_mode },
|
||||||
|
Entry { "i", set_interactive_mode },
|
||||||
|
Entry { "interactive", set_interactive_mode },
|
||||||
|
|
||||||
|
Entry { "doc", show_docs },
|
||||||
|
Entry { "d", show_docs },
|
||||||
|
Entry { "docs", show_docs },
|
||||||
|
|
||||||
|
Entry { "man", print_manpage },
|
||||||
|
|
||||||
|
Entry { "inline", provide_inline_code },
|
||||||
|
Entry { "c", provide_inline_code },
|
||||||
|
Entry { "code", provide_inline_code },
|
||||||
|
|
||||||
|
Entry { "help", print_help },
|
||||||
|
Entry { "?", print_help },
|
||||||
|
Entry { "/?", print_help },
|
||||||
|
Entry { "h", print_help },
|
||||||
|
|
||||||
|
Entry { "version", print_version },
|
||||||
|
Entry { "v", print_version },
|
||||||
|
|
||||||
|
Entry {
|
||||||
|
.name = "ast",
|
||||||
|
.handler = set_ast_only_mode,
|
||||||
|
.internal = true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Documentation_For_Handler_Entry
|
||||||
|
{
|
||||||
|
void *handler;
|
||||||
|
std::string_view short_documentation{};
|
||||||
|
std::string_view long_documentation{};
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto documentation_for_handler = std::array {
|
||||||
|
Documentation_For_Handler_Entry {
|
||||||
|
.handler = reinterpret_cast<void*>(provide_function),
|
||||||
|
.short_documentation = "load file as function",
|
||||||
|
.long_documentation =
|
||||||
|
"Loads given file, placing it inside a function. The main use for this mechanism is\n"
|
||||||
|
"to delay execution of a file e.g. to play it using synchronization infrastructure.\n"
|
||||||
|
"Name of function is derived from file name, replacing special characters with underscores.\n"
|
||||||
|
"New name is reported when entering interactive mode."
|
||||||
|
},
|
||||||
|
Documentation_For_Handler_Entry {
|
||||||
|
.handler = reinterpret_cast<void*>(provide_file),
|
||||||
|
.short_documentation = "execute given file",
|
||||||
|
.long_documentation =
|
||||||
|
"Run provided Musique source file."
|
||||||
|
},
|
||||||
|
Documentation_For_Handler_Entry {
|
||||||
|
.handler = reinterpret_cast<void*>(set_interactive_mode),
|
||||||
|
.short_documentation = "enable interactive mode",
|
||||||
|
.long_documentation =
|
||||||
|
"Enables interactive mode. It's enabled by default when provided without arguments or\n"
|
||||||
|
"when all arguments are files loaded as functions."
|
||||||
|
},
|
||||||
|
Documentation_For_Handler_Entry {
|
||||||
|
.handler = reinterpret_cast<void*>(print_help),
|
||||||
|
.short_documentation = "print help",
|
||||||
|
.long_documentation =
|
||||||
|
"Prints short version of help, to provide version easy for quick lookup by the user."
|
||||||
|
},
|
||||||
|
Documentation_For_Handler_Entry {
|
||||||
|
.handler = reinterpret_cast<void*>(print_version),
|
||||||
|
.short_documentation = "print version information",
|
||||||
|
.long_documentation =
|
||||||
|
"Prints version of Musique, following Semantic Versioning.\n"
|
||||||
|
"It's either '<major>.<minor>.<patch>' for official releases or\n"
|
||||||
|
"'<major>.<minor>.<patch>-dev+gc<commit hash>' for self-build releases."
|
||||||
|
},
|
||||||
|
Documentation_For_Handler_Entry {
|
||||||
|
.handler = reinterpret_cast<void*>(show_docs),
|
||||||
|
.short_documentation = "print documentation for given builtin",
|
||||||
|
.long_documentation =
|
||||||
|
"Prints documentation for given builtin function (function predefined by language).\n"
|
||||||
|
"Documentation is in Markdown format and can be passed to render."
|
||||||
|
},
|
||||||
|
Documentation_For_Handler_Entry {
|
||||||
|
.handler = reinterpret_cast<void*>(provide_inline_code),
|
||||||
|
.short_documentation = "run code from an argument",
|
||||||
|
.long_documentation =
|
||||||
|
"Runs code passed as next argument. Same rules apply as for code inside a file."
|
||||||
|
},
|
||||||
|
Documentation_For_Handler_Entry {
|
||||||
|
.handler = reinterpret_cast<void*>(set_ast_only_mode),
|
||||||
|
.short_documentation = "don't run code, print AST of it",
|
||||||
|
.long_documentation =
|
||||||
|
"Parameter made for internal usage. Instead of executing provided code,\n"
|
||||||
|
"prints program syntax tree."
|
||||||
|
},
|
||||||
|
Documentation_For_Handler_Entry {
|
||||||
|
.handler = reinterpret_cast<void*>(print_manpage),
|
||||||
|
.short_documentation = "print man page source code to standard output",
|
||||||
|
.long_documentation =
|
||||||
|
"Prints Man page document to standard output of Musique full command line interface.\n"
|
||||||
|
"One can view it with 'musique man > /tmp/musique.1; man /tmp/musique.1'"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Supported types of argument input:
|
||||||
|
// With arity = 0
|
||||||
|
// -i -j -k ≡ --i --j --k ≡ i j k
|
||||||
|
// With arity = 1
|
||||||
|
// -i 1 -j 2 ≡ --i 1 --j 2 ≡ i 1 j 2 ≡ --i=1 --j=2
|
||||||
|
// Arity ≥ 2 is not supported
|
||||||
|
std::optional<std::string_view> cmd::accept_commandline_argument(std::vector<cmd::Run> &runnables, std::span<char const*> &args)
|
||||||
|
{
|
||||||
|
if (args.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This struct when function returns automatically ajust number of arguments used
|
||||||
|
struct Fix_Args_Array
|
||||||
|
{
|
||||||
|
std::string_view name() const { return m_name; }
|
||||||
|
|
||||||
|
std::optional<std::string_view> value() const
|
||||||
|
{
|
||||||
|
if (m_has_value) { m_success = m_value_consumed = true; return m_value; }
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mark_success() { m_success = true; }
|
||||||
|
|
||||||
|
~Fix_Args_Array() { args = args.subspan(int(m_success) + (!m_packed * int(m_value_consumed))); }
|
||||||
|
|
||||||
|
std::span<char const*> &args;
|
||||||
|
std::string_view m_name = {};
|
||||||
|
std::string_view m_value = {};
|
||||||
|
bool m_has_value = false;
|
||||||
|
bool m_packed = false;
|
||||||
|
mutable bool m_success = false;
|
||||||
|
mutable bool m_value_consumed = false;
|
||||||
|
} state { .args = args };
|
||||||
|
|
||||||
|
std::string_view s = args.front();
|
||||||
|
if (s.starts_with("--")) s.remove_prefix(2);
|
||||||
|
if (s.starts_with('-')) s.remove_prefix(1);
|
||||||
|
|
||||||
|
state.m_name = s;
|
||||||
|
|
||||||
|
if (auto p = s.find('='); p != std::string_view::npos) {
|
||||||
|
state.m_name = s.substr(0, p);
|
||||||
|
state.m_value = s.substr(p+1);
|
||||||
|
state.m_has_value = true;
|
||||||
|
state.m_packed = true;
|
||||||
|
} else if (args.size() >= 2) {
|
||||||
|
state.m_has_value = true;
|
||||||
|
state.m_value = args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const& p : all_parameters) {
|
||||||
|
if (p.name != state.name()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::visit(Overloaded {
|
||||||
|
[&state](Empty_Argument const& h) {
|
||||||
|
state.mark_success();
|
||||||
|
h();
|
||||||
|
},
|
||||||
|
[&state, p](Requires_Argument const& h) {
|
||||||
|
auto arg = state.value();
|
||||||
|
if (!arg) {
|
||||||
|
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
|
||||||
|
std::cerr << " option " << std::quoted(p.name) << " requires an argument" << std::endl;
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
h(*arg);
|
||||||
|
},
|
||||||
|
[&state, &runnables, p](Defines_Code const& h) {
|
||||||
|
auto arg = state.value();
|
||||||
|
if (!arg) {
|
||||||
|
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
|
||||||
|
std::cerr << " option " << std::quoted(p.name) << " requires an argument" << std::endl;
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
runnables.push_back(h(*arg));
|
||||||
|
}
|
||||||
|
}, p.handler);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
Documentation_For_Handler_Entry find_documentation_for_handler(void *handler)
|
||||||
|
{
|
||||||
|
auto it = std::find_if(documentation_for_handler.begin(), documentation_for_handler.end(),
|
||||||
|
[=](Documentation_For_Handler_Entry const& e) { return e.handler == handler; });
|
||||||
|
|
||||||
|
ensure(it != documentation_for_handler.end(), "Parameter handler doesn't have matching documentation");
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
|
|
||||||
|
Documentation_For_Handler_Entry find_documentation_for_parameter(std::string_view param)
|
||||||
|
{
|
||||||
|
auto entry = std::find_if(all_parameters.begin(), all_parameters.end(),
|
||||||
|
[=](auto const& e) { return e.name == param; });
|
||||||
|
|
||||||
|
ensure(entry != all_parameters.end(), "Cannot find parameter that maches given name");
|
||||||
|
return find_documentation_for_handler(entry->handler_ptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmd::print_close_matches(std::string_view arg)
|
||||||
|
{
|
||||||
|
auto minimum_distance = std::numeric_limits<int>::max();
|
||||||
|
|
||||||
|
std::array<typename decltype(all_parameters)::value_type, 3> closest;
|
||||||
|
|
||||||
|
std::partial_sort_copy(
|
||||||
|
all_parameters.begin(), all_parameters.end(),
|
||||||
|
closest.begin(), closest.end(),
|
||||||
|
[&minimum_distance, arg](auto const& lhs, auto const& rhs) {
|
||||||
|
auto const lhs_score = edit_distance(arg, lhs.name);
|
||||||
|
auto const rhs_score = edit_distance(arg, rhs.name);
|
||||||
|
minimum_distance = std::min({ minimum_distance, lhs_score, rhs_score });
|
||||||
|
return lhs_score < rhs_score;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
std::vector<std::string> shown;
|
||||||
|
if (minimum_distance <= 3) {
|
||||||
|
for (auto const& p : closest) {
|
||||||
|
if (std::find(shown.begin(), shown.end(), std::string(p.name)) == shown.end()) {
|
||||||
|
shown.push_back(std::string(p.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shown.empty()) {
|
||||||
|
void *previous = nullptr;
|
||||||
|
std::cout << "Available subcommands are:\n";
|
||||||
|
for (auto const& p : all_parameters) {
|
||||||
|
auto handler_p = p.handler_ptr();
|
||||||
|
if (std::exchange(previous, handler_p) == handler_p || p.internal) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::cout << " " << p.name << " - " << find_documentation_for_handler(handler_p).short_documentation << '\n';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cout << "The most similar commands are:\n";
|
||||||
|
for (auto const& name : shown) {
|
||||||
|
std::cout << " " << name << " - " << find_documentation_for_parameter(name).short_documentation << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\nInvoke 'musique help' to read more about available commands\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void iterate_over_documentation(
|
||||||
|
std::ostream& out,
|
||||||
|
std::string_view Documentation_For_Handler_Entry::* handler,
|
||||||
|
std::string_view prefix,
|
||||||
|
std::ostream&(*first)(std::ostream&, std::string_view name))
|
||||||
|
{
|
||||||
|
decltype(std::optional(all_parameters.begin())) previous = std::nullopt;
|
||||||
|
|
||||||
|
for (auto it = all_parameters.begin();; ++it) {
|
||||||
|
if (it != all_parameters.end() && it->internal)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (it == all_parameters.end() || (previous && it->handler_ptr() != (*previous)->handler_ptr())) {
|
||||||
|
auto &e = **previous;
|
||||||
|
switch (e.arguments()) {
|
||||||
|
break; case 0: out << '\n';
|
||||||
|
break; case 1: out << " ARG\n";
|
||||||
|
break; default: unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
out << prefix << find_documentation_for_handler(e.handler_ptr()).*handler << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it == all_parameters.end()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous && (**previous).handler_ptr() == it->handler_ptr()) {
|
||||||
|
out << ", " << it->name;
|
||||||
|
} else {
|
||||||
|
first(out, it->name);
|
||||||
|
}
|
||||||
|
previous = it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmd::usage()
|
||||||
|
{
|
||||||
|
std::cerr << "usage: " << pretty::begin_bold << "musique" << pretty::end << " [subcommand]...\n";
|
||||||
|
std::cerr << " where available subcommands are:\n";
|
||||||
|
|
||||||
|
iterate_over_documentation(std::cerr, &Documentation_For_Handler_Entry::short_documentation, " ",
|
||||||
|
[](std::ostream& out, std::string_view name) -> std::ostream&
|
||||||
|
{
|
||||||
|
return out << " " << pretty::begin_bold << name << pretty::end;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_manpage()
|
||||||
|
{
|
||||||
|
auto const ymd = std::chrono::year_month_day(
|
||||||
|
std::chrono::floor<std::chrono::days>(
|
||||||
|
std::chrono::system_clock::now()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
std::cout << ".TH MUSIQUE 1 "
|
||||||
|
<< int(ymd.year()) << '-'
|
||||||
|
<< std::setfill('0') << std::setw(2) << unsigned(ymd.month()) << '-'
|
||||||
|
<< std::setfill('0') << std::setw(2) << unsigned(ymd.day())
|
||||||
|
<< " Linux Linux\n";
|
||||||
|
|
||||||
|
std::cout << R"troff(.SH NAME
|
||||||
|
musique \- interactive, musical programming language
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B musique
|
||||||
|
[
|
||||||
|
SUBCOMMANDS
|
||||||
|
]
|
||||||
|
.SH DESCRIPTION
|
||||||
|
Musique is an interpreted, interactive, musical domain specific programming language
|
||||||
|
that allows for algorythmic music composition, live-coding and orchestra performing.
|
||||||
|
.SH SUBCOMMANDS
|
||||||
|
All subcommands can be expressed in three styles: -i arg -j -k
|
||||||
|
.I or
|
||||||
|
--i=arg --j --k
|
||||||
|
.I or
|
||||||
|
i arg j k
|
||||||
|
)troff";
|
||||||
|
|
||||||
|
iterate_over_documentation(std::cout, &Documentation_For_Handler_Entry::long_documentation, {},
|
||||||
|
[](std::ostream& out, std::string_view name) -> std::ostream&
|
||||||
|
{
|
||||||
|
return out << ".TP\n" << name;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::cout << R"troff(.SH ENVIROMENT
|
||||||
|
.TP
|
||||||
|
NO_COLOR
|
||||||
|
This enviroment variable overrides standard Musique color behaviour.
|
||||||
|
When it's defined, it disables colors and ensures they are not enabled.
|
||||||
|
.SH FILES
|
||||||
|
.TP
|
||||||
|
History file
|
||||||
|
History file for interactive mode is kept in XDG_DATA_HOME (or similar on other operating systems).
|
||||||
|
.SH EXAMPLES
|
||||||
|
.TP
|
||||||
|
musique \-c "play (c5 + up 12)"
|
||||||
|
Plays all semitones in 5th octave
|
||||||
|
.TP
|
||||||
|
musique run examples/ode-to-joy.mq
|
||||||
|
Play Ode to Joy written as Musique source code in examples/ode-to-joy.mq
|
||||||
|
)troff";
|
||||||
|
|
||||||
|
std::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cmd::is_tty()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
return _isatty(STDOUT_FILENO);
|
||||||
|
#else
|
||||||
|
return isatty(fileno(stdout));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
39
musique/cmd.hh
Normal file
39
musique/cmd.hh
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#ifndef MUSIQUE_CMD_HH
|
||||||
|
#define MUSIQUE_CMD_HH
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
#include <string_view>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace cmd
|
||||||
|
{
|
||||||
|
/// Describes all arguments that will be run
|
||||||
|
struct Run
|
||||||
|
{
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
File,
|
||||||
|
Argument,
|
||||||
|
Deffered_File
|
||||||
|
} type;
|
||||||
|
|
||||||
|
std::string_view argument;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Accept and execute next command line argument with its parameters if it has any
|
||||||
|
std::optional<std::string_view> accept_commandline_argument(std::vector<cmd::Run> &runnables, std::span<char const*> &args);
|
||||||
|
|
||||||
|
/// Print all arguments that are similar to one provided
|
||||||
|
void print_close_matches(std::string_view arg);
|
||||||
|
|
||||||
|
/// Recognize if stdout is connected to terminal
|
||||||
|
bool is_tty();
|
||||||
|
|
||||||
|
[[noreturn]]
|
||||||
|
void usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MUSIQUE_CMD_HH
|
||||||
|
|
14
musique/interpreter/builtin_function_documentation.hh
Normal file
14
musique/interpreter/builtin_function_documentation.hh
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef MUSIQUE_BUILTIN_FUNCTION_DOCUMENTATION_HH
|
||||||
|
#define MUSIQUE_BUILTIN_FUNCTION_DOCUMENTATION_HH
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
std::optional<std::string_view> find_documentation_for_builtin(std::string_view builtin_name);
|
||||||
|
|
||||||
|
|
||||||
|
/// Returns top 4 similar names to required
|
||||||
|
std::vector<std::string_view> similar_names_to_builtin(std::string_view builtin_name);
|
||||||
|
|
||||||
|
#endif
|
178
musique/main.cc
178
musique/main.cc
@ -1,13 +1,13 @@
|
|||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <edit_distance.hh>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <span>
|
#include <musique/cmd.hh>
|
||||||
#include <thread>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#include <musique/format.hh>
|
#include <musique/format.hh>
|
||||||
|
#include <musique/interpreter/builtin_function_documentation.hh>
|
||||||
#include <musique/interpreter/env.hh>
|
#include <musique/interpreter/env.hh>
|
||||||
#include <musique/interpreter/interpreter.hh>
|
#include <musique/interpreter/interpreter.hh>
|
||||||
#include <musique/lexer/lines.hh>
|
#include <musique/lexer/lines.hh>
|
||||||
@ -17,13 +17,11 @@
|
|||||||
#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 <span>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifndef _WIN32
|
||||||
extern "C" {
|
|
||||||
#include <io.h>
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#include <unistd.h>
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <bestline.h>
|
#include <bestline.h>
|
||||||
}
|
}
|
||||||
@ -31,66 +29,12 @@ extern "C" {
|
|||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
static bool quiet_mode = false;
|
bool ast_only_mode = false;
|
||||||
static bool ast_only_mode = false;
|
bool enable_repl = false;
|
||||||
static bool enable_repl = false;
|
|
||||||
static unsigned repl_line_number = 1;
|
static unsigned repl_line_number = 1;
|
||||||
|
|
||||||
#define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0)
|
#define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0)
|
||||||
|
|
||||||
/// Pop string from front of an array
|
|
||||||
template<typename T = std::string_view>
|
|
||||||
static T pop(std::span<char const*> &span)
|
|
||||||
{
|
|
||||||
auto element = span.front();
|
|
||||||
span = span.subspan(1);
|
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, std::string_view>) {
|
|
||||||
return element;
|
|
||||||
} else if constexpr (std::is_arithmetic_v<T>) {
|
|
||||||
T result;
|
|
||||||
auto end = element + std::strlen(element);
|
|
||||||
auto [ptr, ec] = std::from_chars(element, end, result);
|
|
||||||
if (ec != decltype(ec){}) {
|
|
||||||
std::cout << "Expected natural number as argument" << std::endl;
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
static_assert(always_false<T>, "Unsupported type for pop operation");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print usage and exit
|
|
||||||
[[noreturn]] void usage()
|
|
||||||
{
|
|
||||||
std::cerr <<
|
|
||||||
"usage: musique <options> [filename]\n"
|
|
||||||
" where filename is path to file with Musique code that will be executed\n"
|
|
||||||
" where options are:\n"
|
|
||||||
" -c,--run CODE\n"
|
|
||||||
" executes given code\n"
|
|
||||||
" -I,--interactive,--repl\n"
|
|
||||||
" enables interactive mode even when another code was passed\n"
|
|
||||||
"\n"
|
|
||||||
" -f,--as-function FILENAME\n"
|
|
||||||
" deffer file execution and turn it into a file\n"
|
|
||||||
"\n"
|
|
||||||
" --ast\n"
|
|
||||||
" prints ast for given code\n"
|
|
||||||
"\n"
|
|
||||||
" -v,--version\n"
|
|
||||||
" prints Musique interpreter version\n"
|
|
||||||
"\n"
|
|
||||||
"Thanks to:\n"
|
|
||||||
" Sy Brand, https://sybrand.ink/, creator of tl::expected https://github.com/TartanLlama/expected\n"
|
|
||||||
" Justine Tunney, https://justinetunney.com, creator of bestline readline library https://github.com/jart/bestline\n"
|
|
||||||
" Gary P. Scavone, http://www.music.mcgill.ca/~gary/, creator of rtmidi https://github.com/thestk/rtmidi\n"
|
|
||||||
" Creators of ableton/link, https://github.com/Ableton/link\n"
|
|
||||||
;
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_repl_help()
|
void print_repl_help()
|
||||||
{
|
{
|
||||||
std::cout <<
|
std::cout <<
|
||||||
@ -245,15 +189,6 @@ void completion(char const* buf, bestlineCompletions *lc)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool is_tty()
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
return _isatty(STDOUT_FILENO);
|
|
||||||
#else
|
|
||||||
return isatty(fileno(stdout));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles commands inside REPL session (those starting with ':')
|
/// Handles commands inside REPL session (those starting with ':')
|
||||||
///
|
///
|
||||||
/// Returns if one of command matched
|
/// Returns if one of command matched
|
||||||
@ -352,84 +287,30 @@ static Result<bool> handle_repl_session_commands(std::string_view input, Runner
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Fancy main that supports Result forwarding on error (Try macro)
|
/// Fancy main that supports Result forwarding on error (Try macro)
|
||||||
static std::optional<Error> Main(std::span<char const*> args)
|
static std::optional<Error> Main(std::span<char const*> args)
|
||||||
{
|
{
|
||||||
if (is_tty() && getenv("NO_COLOR") == nullptr) {
|
enable_repl = args.empty();
|
||||||
|
|
||||||
|
if (cmd::is_tty() && getenv("NO_COLOR") == nullptr) {
|
||||||
pretty::terminal_mode();
|
pretty::terminal_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes all arguments that will be run
|
std::vector<cmd::Run> runnables;
|
||||||
struct Run
|
|
||||||
{
|
|
||||||
enum Type
|
|
||||||
{
|
|
||||||
File,
|
|
||||||
Argument,
|
|
||||||
Deffered_File
|
|
||||||
} type;
|
|
||||||
std::string_view argument;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Run> runnables;
|
|
||||||
|
|
||||||
while (not args.empty()) {
|
while (args.size()) if (auto failed = cmd::accept_commandline_argument(runnables, args)) {
|
||||||
std::string_view arg = pop(args);
|
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
|
||||||
|
std::cerr << " Failed to recognize parameter " << std::quoted(*failed) << std::endl;
|
||||||
if (arg == "-" || !arg.starts_with('-')) {
|
cmd::print_close_matches(args.front());
|
||||||
runnables.push_back({ .type = Run::File, .argument = std::move(arg) });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "-c" || arg == "--run") {
|
|
||||||
if (args.empty()) {
|
|
||||||
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
runnables.push_back({ .type = Run::Argument, .argument = pop(args) });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "-f" || arg == "--as-function") {
|
|
||||||
if (args.empty()) {
|
|
||||||
std::cerr << "musique: error: option " << arg << " requires an argument" << std::endl;
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
runnables.push_back({ .type = Run::Deffered_File, .argument = pop(args) });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--quiet" || arg == "-q") {
|
|
||||||
quiet_mode = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg == "--ast") {
|
|
||||||
ast_only_mode = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--repl" || arg == "-I" || arg == "--interactive") {
|
|
||||||
enable_repl = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--version" || arg == "-v") {
|
|
||||||
std::cout << Musique_Version << std::endl;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "-h" || arg == "--help") {
|
|
||||||
usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cerr << "musique: error: unrecognized command line option: " << arg << std::endl;
|
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Runner runner;
|
Runner runner;
|
||||||
|
|
||||||
for (auto const& [type, argument] : runnables) {
|
for (auto const& [type, argument] : runnables) {
|
||||||
if (type == Run::Argument) {
|
if (type == cmd::Run::Argument) {
|
||||||
Lines::the.add_line("<arguments>", argument, repl_line_number);
|
Lines::the.add_line("<arguments>", argument, repl_line_number);
|
||||||
Try(runner.run(argument, "<arguments>"));
|
Try(runner.run(argument, "<arguments>"));
|
||||||
repl_line_number++;
|
repl_line_number++;
|
||||||
@ -440,7 +321,8 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
eternal_sources.emplace_back(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
|
eternal_sources.emplace_back(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
|
||||||
} else {
|
} else {
|
||||||
if (not fs::exists(path)) {
|
if (not fs::exists(path)) {
|
||||||
std::cerr << "musique: error: couldn't open file: " << path << std::endl;
|
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
|
||||||
|
std::cerr << " couldn't open file: " << path << std::endl;
|
||||||
std::exit(1);
|
std::exit(1);
|
||||||
}
|
}
|
||||||
std::ifstream source_file{fs::path(path)};
|
std::ifstream source_file{fs::path(path)};
|
||||||
@ -448,21 +330,18 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Lines::the.add_file(std::string(path), eternal_sources.back());
|
Lines::the.add_file(std::string(path), eternal_sources.back());
|
||||||
if (type == Run::File) {
|
if (type == cmd::Run::File) {
|
||||||
Try(runner.run(eternal_sources.back(), path));
|
Try(runner.run(eternal_sources.back(), path));
|
||||||
} else {
|
} else {
|
||||||
Try(runner.deffered_file(eternal_sources.back(), argument));
|
Try(runner.deffered_file(eternal_sources.back(), argument));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enable_repl = enable_repl || std::all_of(runnables.begin(), runnables.end(),
|
enable_repl = enable_repl || (!runnables.empty() && std::all_of(runnables.begin(), runnables.end(),
|
||||||
[](Run const& run) {
|
[](cmd::Run const& run) { return run.type == cmd::Run::Deffered_File; }));
|
||||||
return run.type == Run::Deffered_File;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (runnables.empty() || enable_repl) {
|
if (enable_repl) {
|
||||||
repl_line_number = 1;
|
repl_line_number = 1;
|
||||||
enable_repl = true;
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
bestlineSetCompletionCallback(completion);
|
bestlineSetCompletionCallback(completion);
|
||||||
#else
|
#else
|
||||||
@ -504,7 +383,8 @@ static std::optional<Error> Main(std::span<char const*> args)
|
|||||||
if (command.starts_with(':')) {
|
if (command.starts_with(':')) {
|
||||||
command.remove_prefix(1);
|
command.remove_prefix(1);
|
||||||
if (!Try(handle_repl_session_commands(command, runner))) {
|
if (!Try(handle_repl_session_commands(command, runner))) {
|
||||||
std::cerr << "musique: error: unrecognized REPL command '" << command << '\'' << std::endl;
|
std::cerr << pretty::begin_error << "musique: error:" << pretty::end;
|
||||||
|
std::cerr << " unrecognized REPL command '" << command << '\'' << std::endl;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,11 @@ extern "C" {
|
|||||||
|
|
||||||
namespace starters
|
namespace starters
|
||||||
{
|
{
|
||||||
static std::string_view Error;
|
static std::string_view Bold;
|
||||||
static std::string_view Path;
|
|
||||||
static std::string_view Comment;
|
static std::string_view Comment;
|
||||||
static std::string_view End;
|
static std::string_view End;
|
||||||
|
static std::string_view Error;
|
||||||
|
static std::string_view Path;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& pretty::begin_error(std::ostream& os)
|
std::ostream& pretty::begin_error(std::ostream& os)
|
||||||
@ -29,6 +30,11 @@ std::ostream& pretty::begin_comment(std::ostream& os)
|
|||||||
return os << starters::Comment;
|
return os << starters::Comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::ostream& pretty::begin_bold(std::ostream& os)
|
||||||
|
{
|
||||||
|
return os << starters::Bold;
|
||||||
|
}
|
||||||
|
|
||||||
std::ostream& pretty::end(std::ostream& os)
|
std::ostream& pretty::end(std::ostream& os)
|
||||||
{
|
{
|
||||||
return os << starters::End;
|
return os << starters::End;
|
||||||
@ -56,16 +62,18 @@ void pretty::terminal_mode()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
starters::Error = "\x1b[31;1m";
|
starters::Bold = "\x1b[1m";
|
||||||
starters::Path = "\x1b[34;1m";
|
|
||||||
starters::Comment = "\x1b[30;1m";
|
starters::Comment = "\x1b[30;1m";
|
||||||
starters::End = "\x1b[0m";
|
starters::End = "\x1b[0m";
|
||||||
|
starters::Error = "\x1b[31;1m";
|
||||||
|
starters::Path = "\x1b[34;1m";
|
||||||
}
|
}
|
||||||
|
|
||||||
void pretty::no_color_mode()
|
void pretty::no_color_mode()
|
||||||
{
|
{
|
||||||
starters::Error = {};
|
starters::Bold = {};
|
||||||
starters::Path = {};
|
|
||||||
starters::Comment = {};
|
starters::Comment = {};
|
||||||
starters::End = {};
|
starters::End = {};
|
||||||
|
starters::Error = {};
|
||||||
|
starters::Path = {};
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,9 @@ namespace pretty
|
|||||||
/// Mark start of printing a comment
|
/// Mark start of printing a comment
|
||||||
std::ostream& begin_comment(std::ostream&);
|
std::ostream& begin_comment(std::ostream&);
|
||||||
|
|
||||||
|
/// Mark start of printing with bold face
|
||||||
|
std::ostream& begin_bold(std::ostream&);
|
||||||
|
|
||||||
/// Mark end of any above
|
/// Mark end of any above
|
||||||
std::ostream& end(std::ostream&);
|
std::ostream& end(std::ostream&);
|
||||||
|
|
||||||
|
@ -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,38 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string_view> similar_names_to_builtin(std::string_view builtin_name)
|
||||||
|
{
|
||||||
|
auto minimum_distance = std::numeric_limits<int>::max();
|
||||||
|
|
||||||
|
std::array<std::pair<std::string_view, std::string_view>, 4> closest;
|
||||||
|
std::partial_sort_copy(
|
||||||
|
names_to_documentation.begin(), names_to_documentation.end(),
|
||||||
|
closest.begin(), closest.end(),
|
||||||
|
[&minimum_distance, builtin_name](auto const& lhs, auto const& rhs) {
|
||||||
|
auto const lhs_score = edit_distance(builtin_name, lhs.first);
|
||||||
|
auto const rhs_score = edit_distance(builtin_name, rhs.first);
|
||||||
|
minimum_distance = std::min({ minimum_distance, lhs_score, rhs_score });
|
||||||
|
return lhs_score < rhs_score;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
std::vector<std::string_view> result;
|
||||||
|
result.resize(4);
|
||||||
|
std::transform(closest.begin(), closest.end(), result.begin(), [](auto const& p) { return p.first; });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
def warning(*args, prefix: str | None = None):
|
def warning(*args, prefix: str | None = None):
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
@ -151,7 +184,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 +202,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 +213,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 +253,47 @@ 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:
|
||||||
|
includes = [
|
||||||
|
"algorithm",
|
||||||
|
"array",
|
||||||
|
"edit_distance.hh",
|
||||||
|
"musique/interpreter/builtin_function_documentation.hh",
|
||||||
|
"vector",
|
||||||
|
]
|
||||||
|
for include in includes:
|
||||||
|
print(f"#include <{include}>", 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 +301,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 +325,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