Initial commit: Klammertext source distribution
Curated source subset assembled by klammertext-dev's doc/make_dist.sh: the Klammermachine (mac), the Standard Klammer Set (sks), the commands (com), editor plugins and install guides (doc), a test subset (tst), and lib/bin placeholders. Builds with 'make -C com'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
420
mac/argument_set.cpp
Normal file
420
mac/argument_set.cpp
Normal file
@@ -0,0 +1,420 @@
|
||||
#include <ranges>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "show.h"
|
||||
#include "log.h"
|
||||
#include "katom.h"
|
||||
#include "argtype_set.h"
|
||||
#include "argument_set.h"
|
||||
#include "util.h"
|
||||
|
||||
bool operator==(Parameter_set lhs, Parameter_set rhs)
|
||||
{
|
||||
return as_string(lhs.m_katoms.begin(), lhs.m_katoms.end(), true) ==
|
||||
as_string(rhs.m_katoms.begin(), rhs.m_katoms.end(), true);
|
||||
}
|
||||
|
||||
std::regex parameter_regex(bool optional=false)
|
||||
{
|
||||
//std::string pattern = R"(([A-Za-z]\w*)(?:(\.\w*)(?:(\.\w*)))?)";
|
||||
std::string pattern = R"((?:([A-Za-z]\w*))|(?:([A-Za-z]\w*)\.(\w+))|(?:([A-Za-z]\w*)\.(\w+)\.(\w+)))";
|
||||
if (optional) {
|
||||
pattern = R"((?::([A-Za-z]\w*))|(?::([A-Za-z]\w*)\.(\w+))|(?::([A-Za-z]\w*)\.(\w*)\.(\w+)))";
|
||||
// x x
|
||||
}
|
||||
// (void)K::log(3, pattern);
|
||||
return std::regex(pattern);
|
||||
}
|
||||
|
||||
Parameter_set::Parameter_set(const std::string parameter_string)
|
||||
{
|
||||
(void)K::log(3);
|
||||
Argtype_set argtypes {};
|
||||
parse_parameters(
|
||||
katomize(line_split(parameter_string), Locator().str()),
|
||||
argtypes);
|
||||
}
|
||||
|
||||
|
||||
Parameter_set::Parameter_set(const std::vector<Katom>& katoms)
|
||||
: m_katoms(katoms)
|
||||
{
|
||||
(void)K::log(3);
|
||||
Argtype_set argtypes {};
|
||||
parse_parameters(m_katoms, argtypes);
|
||||
}
|
||||
|
||||
Parameter_set::Parameter_set(const std::vector<Katom>& katoms, const Argtype_set& argtypes)
|
||||
: m_katoms(katoms)
|
||||
{
|
||||
(void)K::log(3);
|
||||
parse_parameters(m_katoms, argtypes);
|
||||
}
|
||||
|
||||
// Parameter parsing
|
||||
|
||||
Parameter parse_positional_parameter(const katom_list& katoms, const Argtype_set& argtypes)
|
||||
{
|
||||
// (void)K::log(3, katoms);
|
||||
if (katoms.size() > 1) {
|
||||
throw Argument_error("Multiple katoms for positional argument: " +
|
||||
as_string(katoms.begin(), katoms.end(), true) +
|
||||
"\nPositional arguments are separated by the bar (|) character.",
|
||||
katoms[0].m_loc, false);
|
||||
}
|
||||
Katom k = katoms[0];
|
||||
std::string name = k.m_text;
|
||||
std::smatch match {};
|
||||
if (!std::regex_match(name, match, parameter_regex())) {
|
||||
throw Argument_error(
|
||||
"The structure of the word \"" + name + "\" is not correct for a positional parameter",
|
||||
k.m_loc);
|
||||
} else {
|
||||
std::string match_name = std::string(match[1]) + std::string(match[2]) + std::string(match[4]);
|
||||
std::string match_type = std::string(match[3]) + std::string(match[5]);
|
||||
std::string match_target = match[6];
|
||||
if (match_type.empty()) {
|
||||
match_type = "string";
|
||||
}
|
||||
return Parameter(match_name, argtypes.get(match_type, k.m_loc), k.m_loc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Parameter parse_optional_parameter(const katom_list& katoms, const Argtype_set& argtypes)
|
||||
{
|
||||
//(void)K::log(3);
|
||||
Katom k = katoms[0];
|
||||
std::string default_value {};
|
||||
if (katoms.size() > 1) {
|
||||
default_value = to_string(katoms.cbegin() + 1, katoms.cend(), true);
|
||||
}
|
||||
|
||||
std::string name = k.m_text;
|
||||
std::smatch match {};
|
||||
if (!std::regex_match(name, match, parameter_regex(true))) {
|
||||
throw Argument_error(
|
||||
"The structure of the word \"" + name +
|
||||
"\" is not correct for an optional parameter",
|
||||
k.m_loc);
|
||||
} else {
|
||||
std::string match_name = std::string(match[1]) + std::string(match[2]) + std::string(match[4]);
|
||||
std::string match_type = std::string(match[3]) + std::string(match[5]);
|
||||
std::string match_target = match[6];
|
||||
if (match_type.empty()) {
|
||||
match_type = "string";
|
||||
}
|
||||
return Parameter(match_name, argtypes.get(match_type, k.m_loc),
|
||||
k.m_loc, true, default_value);
|
||||
}
|
||||
}
|
||||
|
||||
void check_for_missing_parameter(const katom_list& katoms)
|
||||
{
|
||||
(void)K::log(3, katoms.size());
|
||||
// Yeah, yeah, "algorithms."
|
||||
auto ki = katoms.begin();
|
||||
while (ki < katoms.end() - 1) {
|
||||
ki = std::find_if(ki, katoms.end(), [](const Katom& k) {
|
||||
return k.m_type == katom_t::bar; });
|
||||
if (ki == katoms.end()) {
|
||||
break;
|
||||
}
|
||||
auto kstart = ki;
|
||||
ki = std::find_if(ki + 1, katoms.end(), [](const Katom& k) {
|
||||
return !k.is_whitespace(); });
|
||||
if (ki == katoms.end()) {
|
||||
throw Argument_error(
|
||||
"A parameter list ends with a bar character", kstart->m_loc);
|
||||
}
|
||||
auto type_after_bar = ki->m_type;
|
||||
if (type_after_bar == katom_t::bar) {
|
||||
throw Argument_error(
|
||||
"A parameter name was missing between two bar characters", kstart->m_loc);
|
||||
} else if (type_after_bar == katom_t::option_name) {
|
||||
throw Argument_error(
|
||||
"A parameter name was missing between a bar character and an option name",
|
||||
kstart->m_loc);
|
||||
}
|
||||
++ki;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_boundary(katom_list::const_iterator ki)
|
||||
{
|
||||
return ki->m_type == katom_t::option_name || ki->m_type == katom_t::bar;
|
||||
}
|
||||
|
||||
std::vector<std::vector<Katom>>
|
||||
function_symbol_parts(katom_list::const_iterator kbegin, katom_list::const_iterator kend)
|
||||
{
|
||||
std::vector<std::vector<Katom>> parts;
|
||||
katom_list part {};
|
||||
auto ki = kbegin;
|
||||
while (ki != kend && ki->is_whitespace()) {
|
||||
ki++;
|
||||
}
|
||||
if (ki == kend) {
|
||||
return {};
|
||||
}
|
||||
if (ki->m_type != katom_t::option_name) {
|
||||
part.push_back(Katom("|", katom_t::bar, kbegin->m_loc));
|
||||
}
|
||||
while (ki < kend) {
|
||||
if (!part.empty() && is_boundary(ki)) {
|
||||
parts.push_back(trim(part));
|
||||
part = {};
|
||||
}
|
||||
part.push_back(*ki);
|
||||
ki++;
|
||||
}
|
||||
if (!part.empty()) {
|
||||
parts.push_back(trim(part));
|
||||
}
|
||||
// std::cout << "PARTS:\n";
|
||||
// for (size_t i = 0; i < parts.size(); i++) {
|
||||
// std::cout << i << sp_arrow << parts[i] << "\n";
|
||||
// }
|
||||
return parts;
|
||||
}
|
||||
|
||||
katom_list trim_part(katom_list part)
|
||||
{
|
||||
return trim(part, {katom_t::space, katom_t::newline, katom_t::bar});
|
||||
}
|
||||
|
||||
std::tuple<katom_lists,katom_lists>
|
||||
parameter_split(katom_list::const_iterator kbegin, katom_list::const_iterator kend)
|
||||
{
|
||||
(void)K::log(3); //, "begin:", *kbegin, "end:", *(kend - 1));
|
||||
// "distance:", std::distance(kbegin, kend));
|
||||
auto parts = function_symbol_parts(kbegin, kend);
|
||||
// std::cout << "parts: " << parts << "\n";
|
||||
katom_lists positional {};
|
||||
katom_lists optional {};
|
||||
for (auto p : parts) {
|
||||
if (p[0].m_type == katom_t::option_name) {
|
||||
optional.push_back(p);
|
||||
} else {
|
||||
positional.push_back(trim_part(p));
|
||||
}
|
||||
}
|
||||
return {positional, optional};
|
||||
}
|
||||
|
||||
void Parameter_set::parse_parameters(const katom_list& katoms, const Argtype_set& argtypes)
|
||||
{
|
||||
(void)K::log(3, trim(katoms));
|
||||
if (katoms.empty()) {
|
||||
return;
|
||||
}
|
||||
check_for_missing_parameter(katoms);
|
||||
auto [positional, optional] = parameter_split(katoms.cbegin(), katoms.cend());
|
||||
for (auto req : positional) {
|
||||
auto pos = parse_positional_parameter(req, argtypes);
|
||||
if (pos.m_argtype.m_name == "rest") {
|
||||
m_rest.push_back(pos);
|
||||
} else {
|
||||
m_positional.push_back(pos);
|
||||
}
|
||||
}
|
||||
for (auto opt : optional) {
|
||||
auto param = parse_optional_parameter(opt, argtypes);
|
||||
if (std::ranges::count(m_optional_names, param.m_name) > 0) {
|
||||
throw Argument_error(
|
||||
"Optional parameter \":" + param.m_name + "\" already defined",
|
||||
katoms[0].m_loc);
|
||||
}
|
||||
m_optional.push_back(param);
|
||||
m_optional_names.push_back(param.m_name);
|
||||
}
|
||||
if (!m_rest.empty()) {
|
||||
m_positional_count = m_positional.size();
|
||||
}
|
||||
}
|
||||
|
||||
void describe_arguments(
|
||||
std::string label,
|
||||
std::vector<std::vector<Katom>> positional,
|
||||
std::vector<std::vector<Katom>> optional,
|
||||
std::vector<Katom> rest)
|
||||
{
|
||||
std::cout << label << ":\n"
|
||||
<< " positional: " << positional << "\n"
|
||||
<< " optional: " << optional << "\n"
|
||||
<< " rest: " << rest << "\n";
|
||||
}
|
||||
|
||||
|
||||
void Parameter_set::describe_parameters()
|
||||
{
|
||||
(void)K::log(3);
|
||||
std::cout << " positional: ";
|
||||
if (!m_positional.empty()) {
|
||||
for (auto p : m_positional) {
|
||||
std::cout << p << " ";
|
||||
}
|
||||
} else {
|
||||
std::cout << "[none]";
|
||||
}
|
||||
std::cout << "\n optional: ";
|
||||
if (!m_optional.empty()) {
|
||||
for (auto p : m_optional) {
|
||||
std::cout << p << " ";
|
||||
}
|
||||
} else {
|
||||
std::cout << "[none]";
|
||||
}
|
||||
std::cout << "\n rest: ";
|
||||
if (!m_rest.empty()) {
|
||||
std::cout << kall << m_rest << kreset << "\n";
|
||||
} else {
|
||||
std::cout << "[none]";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
std::tuple<katom_lists,katom_lists,katom_list>
|
||||
argument_split(katom_list::const_iterator kbegin, katom_list::const_iterator kend,
|
||||
long unsigned int positional_limit)
|
||||
{
|
||||
// (void)K::log(3, "begin:", *(kbegin+1), "end:", *(kend - 1),
|
||||
// "distance:", std::distance(kbegin, kend), "limit:", positional_limit);
|
||||
(void)K::log(3);
|
||||
// msg() << std::pair(kbegin, kend) << "\n";
|
||||
auto parts = function_symbol_parts(kbegin, kend);
|
||||
|
||||
katom_lists positional {};
|
||||
katom_lists optional {};
|
||||
katom_list rest {};
|
||||
|
||||
for (auto p : parts) {
|
||||
if (p[0].m_type == katom_t::option_name) {
|
||||
optional.push_back(p);
|
||||
} else if (positional.size() < positional_limit) {
|
||||
positional.push_back(trim_part(p));
|
||||
} else {
|
||||
rest.insert(rest.end(), p.begin(), p.end());
|
||||
}
|
||||
}
|
||||
return {positional, optional, trim_part(rest)};
|
||||
}
|
||||
|
||||
// Parameter/argument mapping
|
||||
|
||||
void Parameter_set::check_positional(const katom_lists& positional_arguments, const Locator& loc)
|
||||
{
|
||||
(void)K::log(3, "required:", m_positional.size(), positional_arguments.size()); //, positional_arguments);
|
||||
auto positional_count = m_positional.size();
|
||||
auto given_count = positional_arguments.size();
|
||||
if (positional_count > given_count) {
|
||||
// std::cout << "Given less than required\n";
|
||||
std::vector<Parameter> missing(m_positional.begin() + given_count, m_positional.end());
|
||||
//std::cout << "missing: " << missing << "\n";
|
||||
auto missing_count = missing.size();
|
||||
std::stringstream ss {};
|
||||
ss << "Positional " << plural("argument", missing_count) << " " << to_be(missing_count)
|
||||
<< " missing:\n";
|
||||
std::cout << ss.str();
|
||||
for (auto arg : missing) {
|
||||
ss << " " << arg.m_name << "\n";
|
||||
}
|
||||
//std::cout << ss.str();
|
||||
throw Argument_error(ss.str(), loc, false);
|
||||
} else if (positional_count < given_count) {
|
||||
//std::cout << "DESCRIBE\n";
|
||||
//describe_parameters();
|
||||
std::stringstream ss {};
|
||||
ss << "Too many positional arguments were given; "
|
||||
<< positional_count << " needed but " << given_count << " given";
|
||||
throw Argument_error(ss.str(), loc);
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, std::string>
|
||||
Parameter_set::check_optional(const katom_lists& optional_arguments, const Locator& loc)
|
||||
{
|
||||
(void)K::log(3, optional_arguments.size());
|
||||
std::vector<std::string> optional_names_used {};
|
||||
std::map<std::string, std::string> values {};
|
||||
for (const auto& opt : optional_arguments) {
|
||||
std::string name(opt[0].m_text, 1);
|
||||
if (std::ranges::count(m_optional_names, name) == 0) {
|
||||
throw Argument_error("Optional argument \":" + name + "\" not defined", loc);
|
||||
}
|
||||
if (std::ranges::count(optional_names_used, name) > 0) {
|
||||
throw Argument_error("Optional argument \":" + name + "\" already provided "
|
||||
+ "with a value of:\n" + values[name], loc, false);
|
||||
}
|
||||
katom_list value_katoms(opt.begin()+1, opt.end());
|
||||
std::string value = trim(to_string(value_katoms));
|
||||
values[name] = value;
|
||||
optional_names_used.push_back(name);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
const std::map<std::string, std::string>
|
||||
Parameter_set::value_map(
|
||||
const katom_lists& positional, const katom_lists& optional, const katom_list& rest,
|
||||
const Locator& loc)
|
||||
{
|
||||
(void)K::log(3, "positional:", positional.size(), "optional:", optional.size(), "rest:", rest.size());
|
||||
std::map<std::string, std::string> values {};
|
||||
check_positional(positional, loc);
|
||||
for (size_t i = 0; i < m_positional.size(); ++i) {
|
||||
auto param = m_positional[i];
|
||||
std::string arg = as_string(positional[i].begin(), positional[i].end(), true);
|
||||
values[param.m_name] = arg;
|
||||
}
|
||||
auto optional_values = check_optional(optional, loc);
|
||||
for (auto [key, value] : optional_values) {
|
||||
values[key] = value;
|
||||
}
|
||||
for (auto opt : m_optional) {
|
||||
values.try_emplace(opt.m_name, opt.m_default);
|
||||
}
|
||||
if (active(rest)) {
|
||||
if (!m_rest.empty()) {
|
||||
values[m_rest[0].m_name] = as_string(rest.begin(), rest.end(), true);
|
||||
} else {
|
||||
std::stringstream ss {};
|
||||
ss << "More positional arguments were given (" << positional.size() + rest.size()
|
||||
<< ") than defined (" << m_positional.size() << ")";
|
||||
throw Argument_error(ss.str(), loc);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
// Parameter/argument substitution
|
||||
|
||||
std::string replace_arguments(
|
||||
const std::map<std::string, std::string>& values,
|
||||
const std::string& parameterized_text, const Locator& loc)
|
||||
{
|
||||
(void)K::log(3);
|
||||
std::string result = parameterized_text;
|
||||
for (auto [name, value] : values) {
|
||||
result = string_replace(result, '*' + name + '*', value);
|
||||
}
|
||||
auto matches = find_all(result, std::regex(R"((\*.*?\*))"));
|
||||
std::vector<std::string> unmatched;
|
||||
unmatched.reserve(matches.size());
|
||||
std::copy(matches.begin(), matches.end(), std::back_inserter(unmatched));
|
||||
|
||||
auto unmatched_count = unmatched.size();
|
||||
if (unmatched_count > 0) {
|
||||
std::stringstream ss {};
|
||||
ss << "Undefined " << plural("argument", unmatched_count) << " in klammer:\n";
|
||||
for (const auto& arg : unmatched) {
|
||||
ss << " " << arg << "\n";
|
||||
}
|
||||
ss << "To prevent the \"*\" character from specifying an argument, "
|
||||
<< "precede it with the \"^\" character.";
|
||||
throw Argument_error(ss.str(), loc, false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user