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:
1 parent
9b0d0d7897
commit
0ead40f272
39 files changed
+734
-158
No files matched your search
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user