Files
klammertext/mac/target_registry.cpp
Andy Kopra 240cff4278 An output policy for the three commands, and @cond as a true special form
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.
2026-08-16 01:37:59 +02:00

181 lines
6.6 KiB
C++

#include <sstream>
#include <iterator>
#include "target_registry.h"
#include "error.h"
#include "log.h"
#include "util.h"
#include "show.h"
#include "log.h"
#include "katom.h"
std::string Target_registry::declare_name = "k";
std::string Target_registry::general_name = "*";
std::string Target_registry::optionset_name = "o";
Target_registry::Target_registry()
: m_parameters(Parameter_set("name | desc :after_apply :after_write :includes :escape | transforms.rest"))
{
// Registration order is display order. The two that declare an interface
// come first -- "k" a klammer's, "o" an option set's -- and then "*", the
// targets themselves beginning after it.
Target k(declare_name, "Description of parameters and klammer result", Locator());
Target option_set(optionset_name, "Declaration of an option set: parameters shared by klammers", Locator());
Target general(general_name, "All targets", Locator());
add(k);
add(option_set);
add(general);
}
void Target_registry::add(Target target)
{
(void)K::log(3, target);
check_for_previous_definition(target.m_name, target.m_loc);
m_targets[target.m_name] = target;
m_names.push_back(target.m_name);
}
void Target_registry::add(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, std::vector<Katom>& katoms)
{
(void)K::log(3, *begin, *(end - 1));
auto [positional, optional, rest] =
argument_split(begin + 1, end - 1, m_parameters.m_positional.size());
auto values = m_parameters.value_map(positional, optional, rest, begin->m_loc);
//std::cout << ktype << "Transformed: " << kreplaced << std::pair(begin, end) << "\n";
//std::cout << values << "\n";
Target target(values["name"], values["desc"], begin->m_loc);
target.add_transforms(values["transforms"]);
target.add_escapes(values["escape"]);
target.add_after_apply(values["after_apply"]);
target.m_includes = word_split(values["includes"]);
// Inherit escapes from included targets
for (const auto& included : target.m_includes) {
if (m_targets.count(included)) {
for (const auto& esc : m_targets[included].m_escapes) {
target.m_escapes.push_back(esc);
}
}
}
// Registration (and the previous-definition check) goes through
// add(Target) -- the single registration path.
add(target);
for (auto& [name, defined_target] : m_targets) {
if (is_in(name, target.m_includes)) {
defined_target.m_provides.push_back(target.m_name);
}
}
// std::for_each(begin, end, [](Katom& k) { k.m_type = katom_t::replaced; });
modify_type(katom_t::replaced, begin, end);
auto next_iter = end;
ignore_whitespace(next_iter, katoms);
}
void Target_registry::check_for_previous_definition(const std::string& name, const Locator& loc) const
{
if (has(name)) {
const Target& current = m_targets.at(name);
throw Target_error("Target \"" + name + "\" is already defined:\n " + current.m_loc.desc(),
loc, false);
}
}
bool Target_registry::has(const std::string& target_name) const
{
// Membership comes from the map; m_names exists only to preserve
// definition order for describe().
return m_targets.count(target_name) > 0;
}
Target Target_registry::get(const std::string& target_name, const Locator& loc) const
{
if (has(target_name) || target_name == Target_registry::general_name) {
return m_targets.at(target_name);
} else {
throw Target_error("Target " + target_name + " does not exist", loc);
}
}
void Target_registry::transform(const std::string& target_name, katom_list& katoms) const
{
(void)K::log(3);
m_targets.at(target_name).transform(katoms);
}
std::vector<std::string> Target_registry::user_defined() const
{
return collect_if(
m_names, [](const auto& name) {
return name != Target_registry::declare_name
&& name != Target_registry::general_name
&& name != Target_registry::optionset_name; });
}
// The targets a klammer body can be applied under. Neither "k" nor "o"
// produces output: both declare an interface and describe it.
std::vector<std::string> Target_registry::applicable() const
{
return collect_if(
m_names, [](const auto& name) {
return name != Target_registry::declare_name
&& name != Target_registry::optionset_name; });
}
std::string Target_registry::describe(int margin, bool long_format,
const strings_t& defined_outside) const
{
// The built-in pseudo-targets belong to the language rather than to any
// klammerset, so a filtered listing keeps them.
auto shown = [&](const std::string& name) {
if (defined_outside.empty()) return true;
if (name == declare_name || name == general_name || name == optionset_name) {
return true;
}
return !is_in(m_targets.at(name).m_loc.m_filename, defined_outside);
};
std::string tab(margin, ' ');
std::stringstream ss {};
std::vector<std::string> descs {};
for (const std::string& name : m_names) {
descs.push_back(m_targets.at(name).m_desc);
}
auto name_width = max_length(m_names);
auto desc_width = max_length(descs);
for (const std::string& name : m_names) {
if (!shown(name)) continue;
const Target& t = m_targets.at(name);
if (long_format) {
ss << tab << std::setfill(' ') << std::setw(name_width) << std::right << name << " "
<< std::setw(desc_width) << std::left << t.m_desc << " "
<< t.m_loc.str() << "\n";
} else {
ss << tab << std::setfill(' ') << std::setw(name_width) << name << sp_arrow << t << "\n";
}
}
return ss.str();
}
/*
void Targets::describe()
{
std::string intro =
"A \"target\" specifies the output format of Klammertext processing. "
"Targets are identified by the typical filename extension of the format. "
"A klammer defines how it converts its arguments to the appropriate structure for one or more targets. "
"The special target \"k\" is used for a klammer definition that describes that klammer's "
"arguments and purpose in the various targets for which it is defined. "
"If the klammer definition does not specify a target, the klammer can be used for any target.";
std::cout << "Klammertext targets\n\n" << justify(intro) << "\n\n";
for (std::string name : names) {
if (name == Target::any_target_name)
continue;
targets[name]->describe();
std::cout << "\n";
}
}
*/