A snapshot of the development tree. The substantial changes since the last one:
COMMAND OUTPUT POLICY. The three commands display text in exactly three cases,
and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before
termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext
that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging
used to share the stream and land inside the document. A bare command prints
its usage and succeeds rather than failing. Colour is emitted only to a
terminal, per stream, and NO_COLOR is honoured.
"-v 1" reports every decision whose outcome you could not have read off your own
input: the klammerset that was loaded and from which file, a font's directory, a
":files" name's file, how "-o" was expanded. Higher levels are the trace.
The commands no longer warn and continue: an anomaly is an error, described with
its location. Two exceptions remain, each for a stated reason -- a condition
that is expected and temporary by design, and a judgment that is a heuristic
rather than exact.
@cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when
the file is read. Two consequences for a writer:
* a state variable reaches the predicate. "@@@state Flag :value true @@@
@cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and
silently take the false branch. The document now behaves like a klammer
body, whose arguments are bound before its conditionals are decided.
* nothing in a discarded branch happens -- it is not read, not evaluated, not
expanded. An @eval in the branch not taken used to run anyway.
Its predicate relation is total and strict: true, True, 1; false, False, 0, and
empty; anything else is an error at the @cond rather than silently false.
@eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard
error out of the document (it appears under "-v 1") and treat a nonzero exit as
an error naming what the command reported. A command that exits nonzero on
purpose -- "grep" finding no match -- says so with "|| true".
KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the
order given, sharing one namespace, with the definition modes deciding
collisions. "none" means none and may not be combined with other symbols. A
klammerset with symbol X is declared in a file X/X.k, which is what lets two
sets require the same third set without loading it twice.
TESTS. Four new suites: the kdiag command's interface, the @eval primitive's
contract with the outside world, and verbosity at both tiers. Three suites
that could not run on macOS at all now do.
Assembled from dev commit 6c8ee6c22fca.
693 lines
26 KiB
C++
693 lines
26 KiB
C++
#include <algorithm>
|
|
|
|
#include "klammer.h"
|
|
#include "show.h"
|
|
#include "log.h"
|
|
#include "argument_set.h"
|
|
#include "eval.h"
|
|
#include "util.h"
|
|
#include "character.h"
|
|
|
|
using namespace std::literals;
|
|
|
|
std::regex Klammer::name_re = std::regex(R"((\w+)(?:\.((?:\w+|\*)(?:,(?:\w+|\*))*))?)");
|
|
|
|
std::tuple<std::string, strings_t, bool>
|
|
parse_name(const Target_registry& targets, const Katom& name_katom)
|
|
{
|
|
std::string name_with_target = trim_char(name_katom.m_text, '@');
|
|
std::smatch match {};
|
|
|
|
if (!std::regex_match(name_with_target, match, Klammer::name_re)) {
|
|
throw Parsing_error(
|
|
"The klammer name \"" + name_with_target + "\" is not correctly defined. "
|
|
"The form is \"<klammer-name>\" for general klammers or \"<klammer-name>.<target-name>\" "
|
|
"for a specialized target. Several targets that share one body are written as a "
|
|
"comma-separated list: \"<klammer-name>.<target-name>,<target-name>\". The klammer "
|
|
"defined as \"<klammer-name>.k\" specifies the "
|
|
"arguments and contains a description of the klammer in the definition body.",
|
|
name_katom.m_loc);
|
|
}
|
|
std::string klammer_name = match[1];
|
|
std::string target_part = match[2];
|
|
if (target_part.empty()) {
|
|
// No suffix at all: the general target, but not a STATEMENT about
|
|
// coverage. This is the writer's macro form -- someone defining a
|
|
// repeated phrase is not building a klammer set -- so the third
|
|
// result is false and the coverage analysis treats the body on its
|
|
// merits rather than as an assertion.
|
|
return { klammer_name, { Target_registry::general_name }, false };
|
|
}
|
|
strings_t target_names = regex_split(target_part, std::regex(","));
|
|
strings_t seen {};
|
|
for (const auto& target_name : target_names) {
|
|
if (!targets.has(target_name)) {
|
|
throw Target_error(
|
|
"The target \"" + target_name + "\" in klammer definition \"" + name_with_target + "\" "
|
|
"is not defined. Enter \"kdesc -t\" to see the targets defined by the Standard Klammer Set.",
|
|
name_katom.m_loc);
|
|
}
|
|
if (is_in(target_name, seen)) {
|
|
throw Target_error(
|
|
"The target \"" + target_name + "\" is named more than once in klammer definition \"" +
|
|
name_with_target + "\".",
|
|
name_katom.m_loc);
|
|
}
|
|
seen.push_back(target_name);
|
|
}
|
|
// A ".k" declaration states ONE interface for every target, and a ".o"
|
|
// declares an option set; neither produces output, so neither has any
|
|
// meaning as a member of a list of output targets.
|
|
if (target_names.size() > 1) {
|
|
for (const auto& reserved :
|
|
{ Target_registry::declare_name, Target_registry::optionset_name }) {
|
|
if (is_in(reserved, target_names)) {
|
|
throw Target_error(
|
|
"The klammer definition \"" + name_with_target + "\" names \"" + reserved +
|
|
"\" in a list of targets. A \"." + reserved + "\" definition declares an "
|
|
"interface for all targets rather than producing output for one, so it must "
|
|
"be written on its own.",
|
|
name_katom.m_loc);
|
|
}
|
|
}
|
|
}
|
|
// "*" written out is an ASSERTION: this klammer works for every target,
|
|
// including targets that do not exist yet. A list of "all the targets
|
|
// defined today" cannot say that, and the difference matters the moment a
|
|
// new target is added. It may not appear IN a list -- "all targets and
|
|
// also html" is either redundant or a misunderstanding.
|
|
bool general_declared = is_in(Target_registry::general_name, target_names);
|
|
if (general_declared && target_names.size() > 1) {
|
|
throw Target_error(
|
|
"The klammer definition \"" + name_with_target + "\" names \"" +
|
|
Target_registry::general_name + "\" in a list of targets. \"" +
|
|
Target_registry::general_name + "\" already means every target, so it "
|
|
"must be written on its own.",
|
|
name_katom.m_loc);
|
|
}
|
|
return { klammer_name, target_names, general_declared };
|
|
}
|
|
|
|
std::tuple<Katom, Parameter_set, katom_list, Locator>
|
|
parse_definition_katoms(
|
|
const std::string& klammer_name, const std::string& target_name,
|
|
const Argtype_registry& argtypes, Option_set_registry& option_sets,
|
|
katom_iter& begin, katom_iter& end)
|
|
{
|
|
(void)K::log(3, *begin, *(end - 1));
|
|
katom_iter deftype = std::find_if(
|
|
begin, end, [](const Katom& k) { return is_deftype(k.m_type); });
|
|
if (deftype == end) {
|
|
throw Argument_error(
|
|
"The klammer definition does not contain a definition separator that specifies\n"
|
|
"how the klammer should be defined. The klammer syntax is:\n\n"
|
|
" @@<name>[.<target>] <parameters> : <body> @@ definition\n"
|
|
" @@<name>[.<target>] :: <body> @@ instance (uses .k parameters)\n"
|
|
" @@<name>[.<target>] ::: <body> @@ override existing definition\n"
|
|
" @@<name>[.<target>] <parameters> :::: <body> @@ default (can be overridden)",
|
|
begin->m_loc, false);
|
|
|
|
}
|
|
katom_list parameter_katoms(begin, deftype);
|
|
// Resolve text-removal markers (#, #[...]#, ##) in the parameter list.
|
|
// The interior of a @@...@@ definition is held verbatim until now (so a
|
|
// literal klammer body receives its content untouched), which means the
|
|
// parameter declarations still contain any removal markers the writer
|
|
// used. Apply removal here, to the parameters only -- the body is left
|
|
// verbatim by add_target_definition.
|
|
mark_ignored_katoms(parameter_katoms);
|
|
std::erase_if(parameter_katoms,
|
|
[](const Katom& k) { return k.m_type == katom_t::ignored; });
|
|
parameter_katoms = trim_whitespace(parameter_katoms);
|
|
// "::" and ":::" take their parameters from the ".k" declaration, so a
|
|
// parameter list written with them is a mistake -- EXCEPT for an option
|
|
// set, which has no separate declaration to inherit from: a set is its
|
|
// own declaration, so an override restates what it declares.
|
|
bool inherits_parameters = target_name != Target_registry::optionset_name;
|
|
if (inherits_parameters &&
|
|
(deftype->m_type == katom_t::klammer_instance ||
|
|
deftype->m_type == katom_t::klammer_override) &&
|
|
!parameter_katoms.empty()) {
|
|
std::string sym = deftype->m_type == katom_t::klammer_instance ? "::" : ":::";
|
|
throw Definition_error(
|
|
"The \"" + klammer_name + "\" klammer uses the \"" + sym + "\" symbol but defines parameters.",
|
|
begin->m_loc);
|
|
}
|
|
// Replace the option sets used in the parameter list with the parameters
|
|
// they declare. This is where the difference between an option set and a
|
|
// klammer lies: a set is resolved HERE, as the parameter list is read,
|
|
// rather than in the fixed-point apply loop, so what it contributes is
|
|
// present when the list is parsed. Any other klammer application in a
|
|
// parameter list is rejected by the same pass.
|
|
option_set_uses_t option_set_uses =
|
|
expand_option_sets(parameter_katoms, klammer_name, target_name, option_sets);
|
|
Parameter_set parameters(parameter_katoms, argtypes);
|
|
stamp_option_set_uses(parameters, option_set_uses);
|
|
|
|
katom_list body_katoms(deftype + 1, end - 1);
|
|
body_katoms = trim_whitespace(body_katoms);
|
|
return { *deftype, parameters, body_katoms, begin->m_loc };
|
|
}
|
|
|
|
void Klammer::add_target_definition(
|
|
const std::string& target_name, const Argtype_registry& argtypes,
|
|
Option_set_registry& option_sets, katom_iter begin, katom_iter end)
|
|
{
|
|
(void)K::log(3, *begin, *(end-1));
|
|
auto [deftype, parameters, body, loc] =
|
|
parse_definition_katoms(m_name, target_name, argtypes, option_sets, begin, end);
|
|
// msg() << "Klammer " << m_name << " add: " << target_name << "\n";
|
|
// parameters.describe_parameters();
|
|
|
|
std::regex variable_re(R"(\*(\w+)\*)");
|
|
int i = 0;
|
|
variable_map_t varmap {};
|
|
for (const auto& k : body) {
|
|
std::smatch match {};
|
|
std::string txt = k.m_text;
|
|
while (std::regex_search(txt, match, variable_re) &&
|
|
(k.m_type == katom_t::karg || k.m_type == katom_t::text)) {
|
|
//std::cout << " txt: " << txt << "\n";
|
|
std::string varname = match[1];
|
|
//m_varmap[target_name][varname].push_back(i);
|
|
varmap[varname].push_back(i);
|
|
txt = std::regex_replace(txt, std::regex(R"(\*)" + varname + R"(\*)"), "");
|
|
}
|
|
i++;
|
|
}
|
|
// m_body[target_name] = body;
|
|
m_defs.push_back({target_name, deftype.m_initial_type, parameters, body, varmap, begin->m_loc});
|
|
m_defloc[target_name] = begin->m_loc;
|
|
m_defmode[target_name] = defmode_from_katom(deftype.m_initial_type);
|
|
// std::cout << "Klammer " << m_name << "." << target_name << ": " << m_defloc[target_name] << "\n";
|
|
}
|
|
|
|
void Klammer::remove_target_definition(const std::string& target_name)
|
|
{
|
|
std::erase_if(m_defs,
|
|
[&target_name](const auto& def) { return def.target == target_name; });
|
|
m_defloc.erase(target_name);
|
|
m_defmode.erase(target_name);
|
|
m_body.erase(target_name);
|
|
m_varmap.erase(target_name);
|
|
}
|
|
|
|
// Rationalize multiple definitions
|
|
|
|
// ONE definition in the source can register more than once. A target that
|
|
// "provides" another registers both (the SKS's pdf includes tex, so
|
|
// "@@fraktur.tex : ..." becomes a tex definition and a pdf one), and so does
|
|
// every member of a comma-separated target list. Counting or listing those
|
|
// registrations reports work the author did not do: "2 definitions" for a
|
|
// single line, followed by that same line printed twice -- which sends the
|
|
// reader hunting for a second definition that does not exist.
|
|
//
|
|
// These two report what was WRITTEN. Registrations are grouped by source
|
|
// location, and a location that produced several targets names them, so the
|
|
// count and the listing agree with the file.
|
|
using location_group = std::pair<std::string, strings_t>;
|
|
|
|
std::vector<location_group> group_by_location(const auto& components)
|
|
{
|
|
std::vector<location_group> groups {};
|
|
for (const auto& c : components) {
|
|
std::string loc = c.loc.desc();
|
|
auto it = std::find_if(groups.begin(), groups.end(),
|
|
[&loc](const location_group& g) { return g.first == loc; });
|
|
if (it == groups.end()) {
|
|
groups.push_back({loc, {c.target}});
|
|
} else {
|
|
it->second.push_back(c.target);
|
|
}
|
|
}
|
|
return groups;
|
|
}
|
|
|
|
int written_count(const auto& components)
|
|
{
|
|
return static_cast<int>(group_by_location(components).size());
|
|
}
|
|
|
|
std::string error_list(const std::string& label, const auto& components, const std::string& after="")
|
|
{
|
|
std::stringstream ss {};
|
|
ss << label << ":\n";
|
|
for (const auto& [loc, targets] : group_by_location(components)) {
|
|
ss << " " << loc;
|
|
if (targets.size() > 1) {
|
|
ss << " (targets " << join(targets, ", ") << ")";
|
|
}
|
|
ss << "\n";
|
|
}
|
|
ss << after;
|
|
return ss.str();
|
|
}
|
|
|
|
auto Klammer::target_defs(const std::vector<std::string>& target_names)
|
|
{
|
|
std::vector<Klammer::components> defs {};
|
|
for (const auto& target : target_names) {
|
|
auto target_defs = collect_if(
|
|
m_defs, [&target](const auto& def) { return def.target == target; });
|
|
defs.insert(defs.end(), target_defs.begin(), target_defs.end());
|
|
}
|
|
return defs;
|
|
}
|
|
|
|
auto Klammer::instance_defs()
|
|
{
|
|
return collect_if(
|
|
m_defs,
|
|
[] (const auto& def) {
|
|
return def.deftype == katom_t::klammer_instance
|
|
|| def.deftype == katom_t::klammer_override; });
|
|
}
|
|
|
|
|
|
void Klammer::disallow_instances() //Klammer::components declaration)
|
|
{
|
|
auto instances = instance_defs();
|
|
if (!instances.empty()) {
|
|
int icount = written_count(instances);
|
|
std::stringstream ss {};
|
|
ss << "There " << to_be(icount) << " " << icount << " "
|
|
<< plural("instance", icount) << " (defined by \"::\"), but "
|
|
<< "no declarations (defined by a \".k\" target)";
|
|
throw Definition_error(
|
|
error_list(ss.str(), instances), instances[0].loc, false);
|
|
}
|
|
}
|
|
|
|
bool Klammer::copy_to_instances(const Target_registry& targets)
|
|
{
|
|
auto instances = instance_defs();
|
|
if (!instances.empty()) {
|
|
auto definitions = collect_if(
|
|
m_defs,
|
|
[] (const auto& def) {
|
|
return def.deftype != katom_t::klammer_instance
|
|
&& def.deftype != katom_t::klammer_override; });
|
|
int dcount = written_count(definitions);
|
|
if (dcount != 1) {
|
|
int icount = written_count(instances);
|
|
std::stringstream ss {};
|
|
ss << "There " << to_be(icount) << " " << icount << " "
|
|
<< plural("instance", icount) << " (defined by \"::\"), but "
|
|
<< dcount << " "<< plural("definition", dcount) << " (defined by \":\")";
|
|
throw Definition_error(
|
|
error_list(ss.str(), definitions), definitions[0].loc, false);
|
|
} else {
|
|
copy_components(definitions[0].parameters, m_defs, targets);
|
|
return true;
|
|
}
|
|
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Klammer::copy_components(
|
|
const Parameter_set& parameters, const std::vector<Klammer::components>& cs,
|
|
const Target_registry& targets)
|
|
{
|
|
(void)K::log(4);
|
|
for (const auto& target_name : targets.m_names) {
|
|
if (target_name == Target_registry::declare_name ||
|
|
target_name == Target_registry::general_name ||
|
|
target_name == Target_registry::optionset_name) {
|
|
continue;
|
|
}
|
|
m_parameters = parameters;
|
|
}
|
|
for (const auto& c : cs) {
|
|
m_body[c.target] = c.body;
|
|
m_body_generic[c.target] = (c.target == Target_registry::general_name);
|
|
m_varmap[c.target] = c.varmap;
|
|
}
|
|
}
|
|
|
|
// Verify that there is no more than one general klammer definition
|
|
|
|
void Klammer::check_for_multiple_general_klammers()
|
|
{
|
|
auto general_klammers = target_defs({Target_registry::general_name});
|
|
if (general_klammers.size() > 1) {
|
|
throw Definition_error(
|
|
error_list(
|
|
"There is more than one general klammer (a klammer in which no target is defined)",
|
|
general_klammers),
|
|
general_klammers[0].loc, false);
|
|
}
|
|
}
|
|
|
|
void Klammer::check_for_declaration_and_definitions()
|
|
{
|
|
auto declares = target_defs({Target_registry::declare_name});
|
|
if (!declares.empty()) {
|
|
std::vector<Klammer::components> definitions {};
|
|
for (const auto& def : m_defs) {
|
|
if (def.target != Target_registry::declare_name) {
|
|
if (def.deftype == katom_t::klammer_definition ||
|
|
def.deftype == katom_t::klammer_default) {
|
|
definitions.push_back(def);
|
|
}
|
|
}
|
|
}
|
|
if (!definitions.empty()) {
|
|
int defsize = written_count(definitions);
|
|
std::string desc = defsize == 1 ? "a definition that declares its own parameters"
|
|
: std::to_string(defsize) + " definitions that declare their own parameters";
|
|
throw Definition_error(
|
|
error_list("A klammer has both a \".k\" declaration and " + desc +
|
|
".\nWrite \"::\" instead of \":\" so the definition takes its parameters "
|
|
"from the declaration",
|
|
definitions),
|
|
declares[0].loc, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If a general definition exists, use it for targets not defined, but check signatures
|
|
|
|
void Klammer::copy_general_klammer_to_undefined(const Target_registry& targets)
|
|
{
|
|
(void)K::log(4);
|
|
auto general_klammers = target_defs({Target_registry::general_name});
|
|
auto declares = target_defs({Target_registry::declare_name});
|
|
|
|
// Check matching signatures (though this case already handled)
|
|
if (general_klammers.size() == 1) {
|
|
if (declares.empty() && !m_parameters.m_katoms.empty()) {
|
|
auto general_parameters = general_klammers[0].parameters;
|
|
if (general_parameters != m_parameters) {
|
|
// m_parameters.describe_parameters();
|
|
throw Definition_error(
|
|
error_list("The general parameters are different than the defined parameters",
|
|
general_klammers),
|
|
m_parameters.m_katoms[0].m_loc, false);
|
|
}
|
|
}
|
|
const auto& [target, deftype, parameters, body, varmap, loc] = general_klammers[0];
|
|
if (m_parameters.m_katoms.empty()) {
|
|
m_parameters = parameters;
|
|
}
|
|
for (const auto& target_name : targets.m_names) {
|
|
// std::cout << "General copy, considering " << target_name << "\n";
|
|
// "k" and "o" declare interfaces rather than produce output, so a
|
|
// general body is never copied to them.
|
|
if (m_body.count(target_name) == 0 &&
|
|
target_name != Target_registry::declare_name &&
|
|
target_name != Target_registry::optionset_name) {
|
|
// std::cout << " Copying to " << target_name << "\n";
|
|
m_body[target_name] = body;
|
|
m_body_generic[target_name] = true; // general body -> writer content
|
|
m_defloc[target_name] = loc;
|
|
m_varmap[target_name] = varmap;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Three declaration cases: none, one, many
|
|
|
|
namespace {
|
|
|
|
// A compact, plain-text rendering of ONE definition's parameter list, for
|
|
// diagnostics that must show how two definitions differ.
|
|
// Klammer::signature_text() cannot serve here: it renders the rationalized
|
|
// m_parameters, which is exactly what does not exist yet when the per-target
|
|
// lists disagree.
|
|
std::string parameter_signature(const Parameter_set& parameters)
|
|
{
|
|
std::string result {};
|
|
auto add = [&result](const std::string& s) {
|
|
if (!result.empty()) result += " ";
|
|
result += s;
|
|
};
|
|
auto typed = [](const Parameter& p) {
|
|
return p.m_argtype.m_name == default_argtype
|
|
? p.m_name : p.m_name + "." + p.m_argtype.m_name;
|
|
};
|
|
bool first = true;
|
|
for (const auto& pos : parameters.m_positional) {
|
|
// std::string(...) on the leading literal: see the note in
|
|
// argtype.cpp::pylist -- append rather than insert-at-front, and it
|
|
// avoids a gcc-12 -Wrestrict false positive.
|
|
add(first ? typed(pos) : std::string("| ") + typed(pos));
|
|
first = false;
|
|
}
|
|
for (const auto& rest : parameters.m_rest)
|
|
add(typed(rest));
|
|
for (const auto& opt : parameters.m_optional)
|
|
add(std::string(":") + typed(opt)
|
|
+ (opt.m_default.empty() ? "" : " " + opt.m_default));
|
|
return result.empty() ? "(no parameters)" : result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void Klammer::no_declarations(const Target_registry& targets)
|
|
{
|
|
(void)K::log(4);
|
|
// std::cout << boldblack << "No declarations\n" << black;
|
|
check_for_multiple_general_klammers();
|
|
|
|
std::vector<Klammer::components> defs {};
|
|
std::vector<std::string> target_names = targets.applicable();
|
|
std::vector<Parameter_set> all_parameter_sets {};
|
|
// Are all parameters the same?
|
|
for (const auto& def : m_defs) {
|
|
if (std::ranges::find(target_names, def.target) != target_names.end()) {
|
|
// std::cout << " Found: " << def.target << "\n";
|
|
all_parameter_sets.push_back(def.parameters);
|
|
} else {
|
|
// std::cout << " Not found: " << def.target << "\n";
|
|
}
|
|
}
|
|
if (!all_equal<Parameter_set>(all_parameter_sets)) {
|
|
// Show each target's own parameter list, not just its location: with
|
|
// more than two targets the designer would otherwise have to diff the
|
|
// definitions by hand to find which one drifted.
|
|
std::stringstream ss {};
|
|
ss << "The parameters of klammer \"" << m_name
|
|
<< "\" are not the same for every target,\n"
|
|
"and there is no declaration (.k) target to define them once:\n";
|
|
for (const auto& def : m_defs) {
|
|
if (std::ranges::find(target_names, def.target) == target_names.end())
|
|
continue;
|
|
std::string target = def.target;
|
|
target.resize(std::max(target.size(), size_t(6)), ' ');
|
|
ss << " " << target << " " << parameter_signature(def.parameters)
|
|
<< "\n " << def.loc.desc() << "\n";
|
|
}
|
|
ss << "Use a .k target to declare the parameters and describe the klammer,\n"
|
|
"and \"::\" with no parameters for each target's definition.";
|
|
throw Definition_error(ss.str(), m_defs[0].loc, false);
|
|
} else {
|
|
// std::cout << " All equal\n";
|
|
copy_components(m_defs[0].parameters, m_defs, targets);
|
|
}
|
|
copy_general_klammer_to_undefined(targets);
|
|
}
|
|
|
|
void Klammer::one_declaration(const Target_registry& targets, const Klammer::components& declare)
|
|
{
|
|
(void)K::log(4);
|
|
// std::cout << boldblack << "One declaration\n" << black;
|
|
check_for_multiple_general_klammers();
|
|
check_for_declaration_and_definitions();
|
|
copy_components(declare.parameters, m_defs, targets);
|
|
copy_general_klammer_to_undefined(targets);
|
|
}
|
|
|
|
void Klammer::many_declarations(const std::vector<Klammer::components>& declares)
|
|
{
|
|
(void)K::log(4);
|
|
// std::cout << boldblack << "Many declarations\n" << black;
|
|
throw Definition_error(
|
|
error_list("More than one declaration (.k) klammer", declares),
|
|
declares[0].loc, false);
|
|
}
|
|
|
|
|
|
|
|
void Klammer::rationalize(const Target_registry& targets)
|
|
{
|
|
(void)K::log(3, m_name);
|
|
auto declares = target_defs({Target_registry::declare_name});
|
|
auto declare_count = declares.size();
|
|
if (declare_count == 0) {
|
|
disallow_instances();
|
|
if (!copy_to_instances(targets)) {
|
|
no_declarations(targets);
|
|
}
|
|
} else if (declare_count == 1) {
|
|
one_declaration(targets, declares[0]);
|
|
} else {
|
|
many_declarations(declares);
|
|
}
|
|
}
|
|
|
|
|
|
std::string klammer_name_from_katom(const std::string& s, const Locator& loc)
|
|
{
|
|
std::regex rgx(R"(@(\w+).*)");
|
|
std::smatch match {};
|
|
if (std::regex_match(s, match, rgx)) {
|
|
return match[1];
|
|
} else {
|
|
throw Definition_error("The form of the klammer name " + q_(s) + " is not correct", loc);
|
|
}
|
|
}
|
|
|
|
|
|
void label(const std::string& s)
|
|
{
|
|
int w = 13;
|
|
std::cout << std::right << std::setw(w) << std::setfill(' ') << s << ": ";
|
|
}
|
|
|
|
void show_args(const std::string& label_text, const std::vector<Argument>& arguments)
|
|
{
|
|
if (!arguments.empty()) {
|
|
label(label_text);
|
|
for (const auto& a : arguments) {
|
|
std::cout << a << " ";
|
|
}
|
|
std::cout << '\n';
|
|
}
|
|
}
|
|
|
|
bool Klammer::defined_outside(const strings_t& files) const
|
|
{
|
|
for (const auto& def : m_defs) {
|
|
if (!is_in(def.loc.m_filename, files)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
strings_t Klammer::get_target_names() const
|
|
{
|
|
strings_t names {};
|
|
for (const auto& [target, body] : m_body) {
|
|
std::stringstream ss {};
|
|
// ss << name << target.m_loc.m_line;
|
|
ss << target;
|
|
names.push_back(ss.str());
|
|
}
|
|
return names; // return join(names, ","s);
|
|
}
|
|
|
|
strings_t Klammer::get_locations()
|
|
{
|
|
strings_t locs {};
|
|
for (auto [target, locator] : m_defloc) {
|
|
std::cout << target << right_arrow << locator << "\n";
|
|
|
|
}
|
|
return {};
|
|
}
|
|
|
|
std::string Klammer::signature_text() const
|
|
{
|
|
std::string result {};
|
|
bool has_pos = false;
|
|
bool has_opt = false;
|
|
for (const auto& pos : m_parameters.m_positional) {
|
|
result += pos.m_name;
|
|
std::string type = pos.m_argtype.m_name;
|
|
if (type != default_argtype) {
|
|
result += "." + type;
|
|
}
|
|
result += " | ";
|
|
has_pos = true;
|
|
}
|
|
if (result.size() >= 2)
|
|
result.resize(result.size() - 2);
|
|
auto opt_count = m_parameters.m_optional.size();
|
|
if (opt_count <= 3 && !has_pos) {
|
|
result += " ";
|
|
}
|
|
for (const auto& opt : m_parameters.m_optional) {
|
|
if (opt_count > 3) {
|
|
result += "\n :" + opt.m_name;
|
|
} else {
|
|
result += ":" + opt.m_name;
|
|
}
|
|
std::string type = opt.m_argtype.m_name;
|
|
if (type != default_argtype) {
|
|
result += "." + type;
|
|
}
|
|
// result += ":" + opt.m_name + "." + opt.m_argtype.m_name;
|
|
if (!opt.m_default.empty()) {
|
|
result += " " + italic_on() + opt.m_default + italic_off();
|
|
}
|
|
result += " ";
|
|
has_opt = true;
|
|
}
|
|
result = trim_right(result);
|
|
if (has_pos) {
|
|
result = " " + result;
|
|
}
|
|
if (opt_count > 3) {
|
|
result += "\n";
|
|
} else if (has_opt or has_pos) {
|
|
result += " ";
|
|
}
|
|
result += "@\n";
|
|
return result;
|
|
}
|
|
|
|
|
|
std::string Klammer::description_text() const
|
|
{
|
|
std::string result = " [" + m_name + ": no description]";
|
|
if (m_body.contains("k")) {
|
|
result = to_string(m_body.at("k"), true);
|
|
result = justify(result, 80, 1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
// Which option sets this klammer's parameters came from, and which of their
|
|
// defaults it overrode. A reader of the signature sees the effective
|
|
// interface; this says how much of it the klammer shares with other klammers,
|
|
// which is the reason for declaring a set in the first place.
|
|
std::string Klammer::option_set_text() const
|
|
{
|
|
std::vector<std::string> sets {};
|
|
std::map<std::string, std::vector<std::string>> overridden {};
|
|
for (const auto& opt : m_parameters.m_optional) {
|
|
if (opt.m_option_set.empty()) continue;
|
|
if (!is_in(opt.m_option_set, sets)) {
|
|
sets.push_back(opt.m_option_set);
|
|
}
|
|
if (opt.m_default_overridden) {
|
|
overridden[opt.m_option_set].push_back(":" + opt.m_name);
|
|
}
|
|
}
|
|
if (sets.empty()) return "";
|
|
std::vector<std::string> descriptions {};
|
|
for (const auto& set : sets) {
|
|
std::string desc = set;
|
|
if (overridden.count(set) > 0) {
|
|
desc += " (" + join(overridden[set], ", ") + " defaulted here)";
|
|
}
|
|
descriptions.push_back(desc);
|
|
}
|
|
return "\n Option sets: " + join(descriptions, ", ");
|
|
}
|
|
|
|
std::string Klammer::describe(int margin) const
|
|
{
|
|
std::string result {};
|
|
result += "@" + m_name + signature_text() + description_text() + option_set_text();
|
|
result = add_margin(result, margin) + "\n";
|
|
return result;
|
|
}
|
|
|
|
|