Width and height argtypes, kdesc --argtypes from ref/, txt @define/@left_right/@columns, kdiag --check value validation (from dev d6993110e9b3)

Also: the klammertext argtype (descriptions kept as source), txt table
wrapping and :hpos/:offset, @c between ‹ ›, txt section headings, the
ktext newline-only trim, capitalize(), and the argtype test suite.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ackandClaude Fable 5.1 committed 2026-09-16 16:59:49 +02:00
1 parent 9b0d0d7897
commit 0ead40f272
39 files changed
+734 -158

No files matched your search

+6 -1
View File
@@ -13,7 +13,10 @@
Parameter_set& Argtype_registry::parameters()
{
static Parameter_set instance("name | desc :pattern .* :python_cast str :default :alone");
// desc.klammertext: the description is Klammertext, kept exactly as
// written (Argtype::m_desc) for a Machine to render -- kdesc's listing,
// the Handbook. Plain-text displays (an argument error) unquote it.
static Parameter_set instance("name | desc.klammertext :pattern .* :python_cast str :default :alone");
return instance;
}
@@ -205,6 +208,8 @@ std::string Argtype_registry::describe(bool long_form, int indent_width) const
int pat_width = name_width + 2 + indent_width + label.size();
result << indent << std::setw(name_width) << name << sp_arrow;
if (long_form) {
// The stored text, as written: this is the programmer's dump
// (kdiag --machine), and a quoted "^@" in it is the fact.
result << m_types.at(name).m_desc << "\n";
result << std::setw(pat_width) << "regex: " << m_types.at(name).m_symbolic_pattern;
if (m_types.at(name).m_symbolic_pattern != m_types.at(name).m_pattern)
+13 -2
View File
@@ -27,6 +27,7 @@ public:
Argtype get(const std::string& name, const Locator& loc) const;
std::string eval(const std::string& value, const std::string& type_name, const Locator& loc);
std::string describe(bool long_form=false, int indent_width=4) const;
inline bool is_defined(const std::string& name) const {
@@ -51,7 +52,7 @@ std::vector<std::tuple<std::string, std::string, std::string, std::string, modif
R"([^\s]+)", "str",
pyformat_string },
{ "bool", "a Boolean value of 'false', 'False', '0', 'true', 'True', or '1'",
{ "bool", "a Boolean value of @c false c@, @c False c@, @c 0 c@, @c true c@, @c True c@, or @c 1 c@",
R"(0|1|true|false|True|False)",
"(lambda b : True if b not in {'0','false','False'} else False)",
pyformat_bool },
@@ -82,11 +83,21 @@ std::vector<std::tuple<std::string, std::string, std::string, std::string, modif
{ "rest", "the remaining arguments as a list nested to N dimensions "
"(rest(N)); the delimiter for dimension n is a run of n bar "
"characters, so | separates elements and || lists of elements",
"characters, so ^| separates elements and ^|^| lists of elements",
R"((?:.|\n)*)", R"((lambda s : __import__("kutil").rest_split(s, N)))",
nullptr },
{ "literal", "literal text passed without interpretation",
R"((?:.|\n)*)", "str",
pyformat_string },
// Klammertext SOURCE, exactly as written -- quoting carets included --
// for a parameter whose value will be handed to a Machine to render
// later (an @@@argtype's description). Unlike "literal", whose content
// has its "^" forms processed as the value is built, this keeps them:
// "^@date" stays "^@date", so it re-reads as the text "@date" rather
// than as a klammer application. (2026-09-13)
{ "klammertext", "Klammertext source, kept exactly as written",
R"((?:.|\n)*)", "str",
pyformat_string },
};
+11 -2
View File
@@ -414,7 +414,16 @@ Parameter_set::value_map(
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);
// A "klammertext" parameter holds Klammertext SOURCE -- the text as
// written, quoting carets included -- so that whoever displays it can
// hand it to a Machine (an @@@argtype's description, 2026-09-13).
// The katomizer strips the caret of a quoted special into m_text, so
// the plain join would turn "^@date" into "@date", which re-reads as
// a klammer application. A "literal" parameter keeps the plain join:
// its "^" forms ARE processed (a "^@" in @c prints "@").
std::string arg = param.m_argtype.m_name == "klammertext"
? as_source(positional[i].begin(), positional[i].end(), true)
: as_string(positional[i].begin(), positional[i].end(), true);
values[param.m_name] = arg;
}
auto optional_values = check_optional(optional, loc);
@@ -483,7 +492,7 @@ void Parameter_set::validate(
// A .k description is prose: collapse its source layout (line
// breaks, continuation indentation) so it re-flows with the
// sentence -- indented .k lines would otherwise read as verbatim.
<< collapse_whitespace(argtype.m_desc) << "\n";
<< collapse_whitespace(unquote_punctuation(argtype.m_desc)) << "\n";
throw Argument_error(ss.str(), loc);
}
}
+66
View File
@@ -4,6 +4,7 @@
#include <sstream>
#include "check.h"
#include "error.h"
#include "machine.h"
#include "katom.h"
#include "katom_process.h"
@@ -23,12 +24,32 @@ namespace {
// check time nothing has been reduced, so the nesting is still present and
// must be tracked. (The depth-0 rule is the same one cond_separator_bars()
// applies for @cond; see doc/cond_evaluation_order.md.)
// A value as written, and whether it is LITERAL: made of nothing but text
// and whitespace at depth 0, so that what the writer typed is what the
// argument type will see. A value holding a nested klammer, a *variable*,
// a quoted special or a caret form is computed at run time and cannot be
// judged here -- the static-structure rule: check what is fixed, skip what
// is computed.
struct Argument_value
{
std::string m_text {};
bool m_literal { true };
};
struct Application_shape
{
int m_positional { 0 };
std::vector<std::string> m_options {};
std::vector<Argument_value> m_positional_values {};
std::vector<std::pair<std::string, Argument_value>> m_option_values {};
};
bool is_literal_katom(const Katom& k)
{
return k.m_type == katom_t::text || k.m_type == katom_t::word
|| k.m_type == katom_t::space || k.m_type == katom_t::newline;
}
bool is_boundary_katom(const Katom& k)
{
return k.m_type == katom_t::bar || k.m_type == katom_t::option_name;
@@ -49,14 +70,36 @@ Application_shape application_shape(katom_list::const_iterator begin, katom_list
bool in_positional = first->m_type != katom_t::option_name;
if (in_positional) shape.m_positional = 1;
// The value being accumulated: a positional part, or the option named
// last. Closed at each depth-0 boundary and at the end.
Argument_value current {};
bool current_is_option = !in_positional;
std::string current_option {};
auto close = [&]() {
current.m_text = trim(current.m_text);
if (current_is_option) {
shape.m_option_values.emplace_back(current_option, current);
} else {
shape.m_positional_values.push_back(current);
}
current = Argument_value {};
};
int depth = 0;
for (auto k = first; k != end; ++k) {
if (depth == 0 && is_boundary_katom(*k)) {
close();
if (k->m_type == katom_t::bar) {
++shape.m_positional;
current_is_option = false;
} else {
shape.m_options.push_back(k->m_text.substr(1));
current_is_option = true;
current_option = k->m_text.substr(1);
}
} else {
if (depth > 0 || !is_literal_katom(*k)) current.m_literal = false;
current.m_text += k->m_text;
}
if (level_increase(*k)) {
++depth;
@@ -64,6 +107,7 @@ Application_shape application_shape(katom_list::const_iterator begin, katom_list
--depth;
}
}
close();
return shape;
}
@@ -159,6 +203,28 @@ void Checker::check_application(
error(ss.str(), context, begin->m_loc);
}
// Argument VALUES against their argument types -- the check that finds
// a height unit on a width, a word where a number is wanted -- for every
// value that is literal (see Argument_value). The same test the engine
// applies when the klammer is applied, Parameter_set::validate(), so the
// two can never disagree; its exception becomes a diagnostic here.
auto validate = [&](const Parameter& parameter, const Argument_value& value) {
if (!value.m_literal || value.m_text.empty()) return;
try {
Parameter_set::validate(parameter, value.m_text, begin->m_loc);
} catch (const Argument_error& e) {
error("@" + name + ": " + collapse_whitespace(e.m_desc), context, begin->m_loc);
}
};
for (size_t i = 0; i < shape.m_positional_values.size() && i < parameters.m_positional.size(); i++) {
validate(parameters.m_positional[i], shape.m_positional_values[i]);
}
for (const auto& [option, value] : shape.m_option_values) {
if (const Parameter* parameter = parameters.find(option)) {
validate(*parameter, value);
}
}
std::vector<std::string> seen {};
for (const auto& option : shape.m_options) {
if (std::ranges::count(parameters.m_optional_names, option) == 0) {
+7
View File
@@ -454,6 +454,13 @@ strings_t get_sks_directories(const std::string& s, bool include_argument)
return result;
}
std::string symbolic_klammertext_filename(std::string filename)
{
return string_replace(
filename, get_env_var("KLAMMERTEXT_HOME"), "$KLAMMERTEXT_HOME");
}
std::string cache_directory(const std::string& subdirectory, std::string parent_directory)
{
if (parent_directory.empty()) {
+1
View File
@@ -55,6 +55,7 @@ fs::path klammertext_filename(
const std::string& basename, bool error_if_missing=true, bool make_directory_if_missing=false);
std::vector<std::string> sks_dirs();
std::vector<std::string> get_sks_directories(const std::string& s, bool include_argument=true);
std::string symbolic_klammertext_filename(std::string filename);
std::string cache_directory(const std::string& subdirectory, std::string parent_directory="");
std::time_t to_time_t(const fs::file_time_type& ftime);
bool in_modification_order(std::string filename1, std::string filename2);
+12
View File
@@ -140,6 +140,18 @@ std::string as_string(const katom_list& ks, bool strip_whitespace)
return as_string(ks.begin(), ks.end(), strip_whitespace);
}
std::string as_source(std::vector<Katom>::const_iterator begin, std::vector<Katom>::const_iterator end, bool strip_whitespace)
{
std::string result {};
for (auto ki = begin; ki < end; ki++) {
if (ki->m_type != katom_t::replaced && ki->m_type != katom_t::ignored)
result += ki->m_src;
}
if (strip_whitespace)
result = trim(result);
return result;
}
katom_list trim(katom_list& katoms, std::set<katom_t> trim_types)
{
katom_iter begin =
+4
View File
@@ -97,6 +97,10 @@ std::vector<Katom> text_katoms(
std::string as_string(std::vector<Katom>::const_iterator begin, std::vector<Katom>::const_iterator end, bool strip_whitespace);
std::string as_string(const std::vector<Katom>& katoms, bool strip_whitespace);
// The text of a katom range EXACTLY AS WRITTEN (Katom::m_src): a quoted
// special keeps its caret, so the result is Klammertext source that can be
// re-read. What a literal parameter of a definition holds.
std::string as_source(std::vector<Katom>::const_iterator begin, std::vector<Katom>::const_iterator end, bool strip_whitespace);
std::vector<Katom> trim(std::vector<Katom>& katoms, std::set<katom_t> trim_types = {katom_t::space, katom_t::newline});
+23
View File
@@ -134,6 +134,29 @@ strings_t word_split(const std::string& s)
return regex_split(s, std::regex("\\s+"));
}
std::string capitalize(std::string s)
{
// The cast through unsigned char is what makes std::toupper safe on a
// byte above 127 (negative as a char, and undefined behaviour to pass).
if (!s.empty())
s[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(s[0])));
return s;
}
std::string unquote_punctuation(const std::string& s)
{
std::string result {};
result.reserve(s.size());
for (size_t i = 0; i < s.size(); i++) {
if (s[i] == '^' && i + 1 < s.size() && std::ispunct(static_cast<unsigned char>(s[i + 1]))) {
result += s[++i];
} else {
result += s[i];
}
}
return result;
}
std::string collapse_whitespace(const std::string& s)
{
std::string result {};
+11
View File
@@ -46,6 +46,17 @@ std::vector<std::string> word_split(const std::string& s);
// whose description happens to fit one line and miss the identically worded
// one that does not.
std::string collapse_whitespace(const std::string& s);
// Klammertext source shown as PLAIN text: "^" before an ASCII punctuation
// character is removed ("^@date" -> "@date"), nothing else changes. For an
// error message or a log line that quotes a literal description; a display
// that can render should hand the source to a Machine instead.
std::string unquote_punctuation(const std::string& s);
// The string with its first character in upper case ("table" -> "Table").
// ASCII only: a non-ASCII first byte is left as it is, since a std::string
// is bytes and the standard library has no Unicode case mapping. Unlike
// Python's str.capitalize(), the rest of the string is untouched.
std::string capitalize(std::string s);
// Case-insensitive substring test, ASCII folding only. The project takes no
// ICU or locale dependency (the same rule as the language tables and