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.
212 lines
8.0 KiB
C++
212 lines
8.0 KiB
C++
#include "klammer.h"
|
|
#include "klammer_registry.h"
|
|
#include "show.h"
|
|
#include "util.h"
|
|
#include "log.h"
|
|
#include "error.h"
|
|
|
|
/*
|
|
bool Klammer_registry::has(std::string name, std::string target)
|
|
{
|
|
return m_klammers.count(name) > 0;
|
|
}
|
|
*/
|
|
|
|
void Klammer_registry::add(
|
|
const Argtype_registry& argtypes, const Target_registry& targets,
|
|
Option_set_registry& option_sets, katom_iter begin, katom_iter end, katom_list& katoms)
|
|
{
|
|
(void)K::log(3, *begin, *(end - 1));
|
|
restore_initial_type(begin, end);
|
|
auto [klammer_name, target_names, general_declared] = parse_name(targets, *begin);
|
|
if (is_in(Target_registry::optionset_name, target_names)) {
|
|
// The Machine routes an ".o" definition to the option set registry;
|
|
// reaching here means it did not. parse_name has already rejected
|
|
// ".o" as a member of a list, so this is the bare ".o" form.
|
|
throw Internal_error(
|
|
"The option set declaration \"" + klammer_name + ".o\" reached the klammer registry",
|
|
begin->m_loc);
|
|
}
|
|
// Find the incoming definition mode
|
|
katom_t incoming_deftype = katom_t::klammer_definition;
|
|
for (auto it = begin + 1; it != end - 1; ++it) {
|
|
if (is_deftype(it->m_type)) {
|
|
incoming_deftype = it->m_type;
|
|
break;
|
|
}
|
|
}
|
|
defmode_t incoming_mode = defmode_from_katom(incoming_deftype);
|
|
|
|
if (m_klammers.count(klammer_name) == 0) {
|
|
m_klammers[klammer_name] = Klammer(klammer_name);
|
|
}
|
|
// Sticky: once a definition has written ".*", the klammer carries the
|
|
// claim. A klammer with both a ".*" body and a target-specific one still
|
|
// asserts that the general body serves everything else.
|
|
if (general_declared) {
|
|
m_klammers[klammer_name].m_general_declared = true;
|
|
}
|
|
// One definition per target named. A comma-separated list is surface
|
|
// syntax: each target goes through the same registration, including the
|
|
// redefinition transition table, so a list that collides with an existing
|
|
// definition is decided per target -- one member may be silently ignored
|
|
// or rejected while the others are created.
|
|
for (const auto& target_name : target_names) {
|
|
if (m_klammers[klammer_name].m_defloc.count(target_name) > 0) {
|
|
defmode_t existing_mode = m_klammers[klammer_name].m_defmode[target_name];
|
|
const auto& result = defmode_transition(existing_mode, incoming_mode);
|
|
std::string name_target = klammer_name + "." + target_name;
|
|
std::string at_desc = m_klammers[klammer_name].m_defloc[target_name].desc();
|
|
if (!result.replace) {
|
|
if (result.message.empty()) {
|
|
// Silent ignore (e.g., create + default)
|
|
continue;
|
|
}
|
|
std::string msg = result.message;
|
|
msg = string_replace(msg, "NAME", q_(name_target));
|
|
msg = string_replace(msg, "AT", at_desc);
|
|
throw Definition_error(msg, begin->m_loc);
|
|
}
|
|
if (result.warn) {
|
|
std::string msg = result.message;
|
|
msg = string_replace(msg, "NAME", q_(name_target));
|
|
msg = string_replace(msg, "AT", at_desc);
|
|
// An override is not a warning: ":::" exists to override, and
|
|
// a klammer set a user did not write may be overridden by
|
|
// design (TODO #33). A warning nobody can act on is not a
|
|
// warning. It IS worth reporting at "-v 1", because the
|
|
// definition being replaced usually lives in another file, in
|
|
// another klammerset, which the user cannot see from what they
|
|
// wrote (Andy, 2026-08-15).
|
|
(void)K::log(1, msg + " at " + begin->m_loc.desc());
|
|
}
|
|
m_klammers[klammer_name].remove_target_definition(target_name);
|
|
}
|
|
m_klammers[klammer_name].add_target_definition(
|
|
target_name, argtypes, option_sets, begin + 1, end - 1);
|
|
|
|
// This add's target:
|
|
Target target = targets.get(target_name, begin->m_loc);
|
|
for (const auto& provide_name : target.m_provides) {
|
|
if (m_klammers[klammer_name].m_defloc.count(provide_name) > 0) {
|
|
m_klammers[klammer_name].remove_target_definition(provide_name);
|
|
}
|
|
m_klammers[klammer_name].add_target_definition(
|
|
provide_name, argtypes, option_sets, begin + 1, end - 1);
|
|
}
|
|
}
|
|
|
|
modify_type(katom_t::replaced, begin, end);
|
|
auto next_iter = end;
|
|
if (next_iter < katoms.end()) {
|
|
// std::cout << "next_iter: " << kindex << *next_iter << "\n";
|
|
} else {
|
|
// std::cout << "next_iter past end. katoms length: " << katoms.size() << "\n";
|
|
}
|
|
ignore_whitespace(next_iter, katoms);
|
|
}
|
|
|
|
void Klammer_registry::rationalize(const Target_registry& targets)
|
|
{
|
|
(void)K::log(3);
|
|
for (auto& [name, klammer] : m_klammers) {
|
|
klammer.rationalize(targets);
|
|
}
|
|
}
|
|
|
|
/*
|
|
bool Klammer_registry::has(std::string name, std::string target)
|
|
{
|
|
return m_klammers.count(name) > 0;
|
|
}
|
|
*/
|
|
|
|
void Klammer_registry::check_klammer(const std::string& name, const std::string& target, const Locator& loc) const
|
|
{
|
|
if (m_klammers.count(name) == 0) {
|
|
throw Definition_error("The klammer " + q_(name) + " is not defined for an unspecified target", loc);
|
|
}
|
|
const Klammer& k = m_klammers.at(name);
|
|
if (k.m_defloc.count(target) == 0) {
|
|
std::string desc;
|
|
if (target == Target_registry::general_name) {
|
|
desc = "an unspecified target";
|
|
} else {
|
|
desc = "target " + q_(target);
|
|
}
|
|
throw Definition_error("The " + q_(name) + " klammer is not defined for " + desc, loc);
|
|
}
|
|
}
|
|
|
|
|
|
const std::vector<Katom>* Klammer_registry::constant_body(const std::string& name) const
|
|
{
|
|
auto it = m_klammers.find(name);
|
|
if (it == m_klammers.end()) return nullptr;
|
|
const Klammer& k = it->second;
|
|
// A constant klammer has no parameters at all — neither in the general
|
|
// definition nor inherited from a .k declaration. A "::" instance has
|
|
// empty d.parameters (it inherits), so we must also check that no other
|
|
// definition (especially .k) declares parameters for this klammer.
|
|
for (const auto& d : k.m_defs) {
|
|
if (!d.parameters.empty()) return nullptr;
|
|
}
|
|
for (const auto& d : k.m_defs) {
|
|
if (d.target == Target_registry::general_name && d.parameters.empty()) {
|
|
return &d.body;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int max_length(const std::map<std::string, Klammer>& ss)
|
|
{
|
|
size_t result = 0;
|
|
for_each(ss.begin(), ss.end(),
|
|
[&result](const auto& s) { result = std::max(result, s.first.size()); });
|
|
return result;
|
|
}
|
|
|
|
std::string Klammer_registry::instance_list(int margin) const
|
|
{
|
|
std::stringstream ss {};
|
|
std::string tab(margin, ' ');
|
|
auto name_width = max_length(m_klammers);
|
|
for (const auto& [name, k] : m_klammers) {
|
|
ss << tab << std::setfill(' ') << std::setw(name_width) << name
|
|
<< sp_arrow << k << "\n";
|
|
}
|
|
return ss.str();
|
|
}
|
|
|
|
std::string Klammer_registry::describe(int margin, const std::string& search,
|
|
const strings_t& defined_outside) const
|
|
{
|
|
/*
|
|
strings_t names {};
|
|
std::vector<strings_t> targets {};
|
|
strings_t locations {};
|
|
*/
|
|
std::string result;
|
|
std::string query = collapse_whitespace(search);
|
|
for (const auto& [name, k] : m_klammers) {
|
|
if (!defined_outside.empty() && !k.defined_outside(defined_outside)) {
|
|
continue;
|
|
}
|
|
if (!query.empty() &&
|
|
!contains_fold(name, query) &&
|
|
!contains_fold(collapse_whitespace(k.description_text()), query)) {
|
|
continue;
|
|
}
|
|
result += k.describe(margin) + "\n";
|
|
/*
|
|
names.push_back(name);
|
|
targets.push_back(k.get_target_names());
|
|
std::cout << name << " " << k.get_target_names() << "\n";
|
|
locator_summary(k.get_locations());
|
|
//locations.push_back(
|
|
*/
|
|
}
|
|
return result;
|
|
}
|