Typed arguments, calculated tables, spans, closed-world fonts, top-level fnt/ and env/

Sync with klammertext-dev through b90b0e09:

- Argument types end to end: :python_cast values are applied (Python
  @eval receives real bools/numbers/lists), argument values are
  validated against their argtype patterns with the argtype's
  description as the error message, argtypes can declare :default
  (overridable per declaration), and parameterized type families are
  supported: rest(N) casts a rest argument to an N-dimensional list
  (bar-count = dimension).
- Unified indexed_range syntax (selector with parenthesized subsets,
  composable mnemonic names) for table lines and spans.
- Table klammer: caption fonts fixed in both targets, :column_width /
  :leading / :colsep wired, :colspan and :rowspan render (HTML
  attributes; \multicolumn / \multirow), calculated cell values (:calc)
  with prefix operators, display-precision semantics, :calc_format and
  :decimal period|comma.
- Fonts: closed-world resolution on the Klammertext font store
  (infrastructure in mac/font_store; no Google Fonts links or fetch).
  Default fonts live in the top-level fnt/; additional fonts install
  into KLAMMERTEXT_FONTS directories via kdesc --font (list, samples,
  preview, install — classification by font metadata).  CSS font family
  names are quoted (digit-initial families were silently lost).
- Environment files moved from mac/env/ to the top-level env/; shell
  profiles source env/runtime.env.  Dead per-host variants removed.
- Container: fnt/ ships in the image; curl removed (no network use).
This commit is contained in:
2026-07-22 18:17:43 +02:00
parent 6b75aa0c54
commit 8a2699a253
100 changed files with 1726 additions and 1152 deletions

View File

@@ -17,16 +17,44 @@ bool operator==(Parameter_set lhs, Parameter_set rhs)
std::regex parameter_regex(bool optional=false)
{
//std::string pattern = R"(([A-Za-z]\w*)(?:(\.\w*)(?:(\.\w*)))?)";
std::string pattern = R"((?:([A-Za-z]\w*))|(?:([A-Za-z]\w*)\.(\w+))|(?:([A-Za-z]\w*)\.(\w+)\.(\w+)))";
// The type component may carry a numeric type parameter, e.g.
// rows.rest(2) (see resolve_argtype below). In the optional
// three-part form the type may be empty (:paper_size..tex).
std::string type = R"(\w+(?:\(\d+\))?)";
std::string opt_type = R"(\w*(?:\(\d+\))?)";
std::string pattern = R"((?:([A-Za-z]\w*))|(?:([A-Za-z]\w*)\.()" + type +
R"())|(?:([A-Za-z]\w*)\.()" + type + R"()\.(\w+)))";
if (optional) {
pattern = R"((?::([A-Za-z]\w*))|(?::([A-Za-z]\w*)\.(\w+))|(?::([A-Za-z]\w*)\.(\w*)\.(\w+)))";
// x x
pattern = R"((?::([A-Za-z]\w*))|(?::([A-Za-z]\w*)\.()" + type +
R"())|(?::([A-Za-z]\w*)\.()" + opt_type + R"()\.(\w+)))";
}
// (void)K::log(3, pattern);
return std::regex(pattern);
}
// Look up an argument type, specializing a parameterized use such as
// rest(2): the base type is copied, its type parameter set, and its
// display name extended, so kdesc signatures show rows.rest(2).
static Argtype resolve_argtype(
const std::string& type_text, const Argtype_set& argtypes, const Locator& loc)
{
static const std::regex parameterized(R"((\w+)\((\d+)\))");
std::smatch match {};
if (std::regex_match(type_text, match, parameterized)) {
Argtype argtype = argtypes.get(match[1], loc);
argtype.m_parameter = match[2];
argtype.m_name += "(" + std::string(match[2]) + ")";
return argtype;
}
return argtypes.get(type_text, loc);
}
// True for the rest type in any parameterization (rest, rest(2), ...).
static bool is_rest(const Argtype& argtype)
{
return argtype.m_name == "rest" || argtype.m_name.rfind("rest(", 0) == 0;
}
Parameter_set::Parameter_set(const std::string parameter_string)
{
(void)K::log(3);
@@ -77,7 +105,7 @@ Parameter parse_positional_parameter(const katom_list& katoms, const Argtype_set
if (match_type.empty()) {
match_type = "string";
}
return Parameter(match_name, argtypes.get(match_type, k.m_loc), k.m_loc);
return Parameter(match_name, resolve_argtype(match_type, argtypes, k.m_loc), k.m_loc);
}
}
@@ -105,8 +133,19 @@ Parameter parse_optional_parameter(const katom_list& katoms, const Argtype_set&
if (match_type.empty()) {
match_type = "string";
}
return Parameter(match_name, argtypes.get(match_type, k.m_loc),
k.m_loc, true, default_value);
Parameter parameter(match_name, resolve_argtype(match_type, argtypes, k.m_loc),
k.m_loc, true, default_value);
// Two-level default resolution: a default declared in the
// parameter list wins; otherwise the argument type's :default
// fills in. Both are validated here, at definition time, so an
// invalid default cannot reach an application.
if (default_value.empty() && !parameter.m_argtype.m_default.empty()) {
parameter.m_default = parameter.m_argtype.m_default;
parameter.m_default_from_type = true;
} else if (!default_value.empty()) {
Parameter_set::validate(parameter, default_value, k.m_loc);
}
return parameter;
}
}
@@ -213,7 +252,7 @@ void Parameter_set::parse_parameters(const katom_list& katoms, const Argtype_set
auto [positional, optional] = parameter_split(katoms.cbegin(), katoms.cend());
for (auto req : positional) {
auto pos = parse_positional_parameter(req, argtypes);
if (pos.m_argtype.m_name == "rest") {
if (is_rest(pos.m_argtype)) {
m_rest.push_back(pos);
} else {
m_positional.push_back(pos);
@@ -295,6 +334,13 @@ argument_split(katom_list::const_iterator kbegin, katom_list::const_iterator ken
} else if (positional.size() < positional_limit) {
positional.push_back(trim_part(p));
} else {
// Parts were trimmed, so adjacent parts would abut their bar
// katoms (a row separator "||" next to an empty cell's "|"
// would serialize as "|||"). A space keeps the writer's
// bar/double-bar distinction parseable.
if (!rest.empty()) {
rest.push_back(Katom(" ", katom_t::space, p[0].m_loc));
}
rest.insert(rest.end(), p.begin(), p.end());
}
}
@@ -386,9 +432,59 @@ Parameter_set::value_map(
throw Argument_error(ss.str(), loc);
}
}
for (const auto& [name, value] : values) {
const Parameter* parameter = find(name);
if (parameter) {
validate(*parameter, value, loc);
}
}
return values;
}
// Check an argument value against its argument type's pattern. An empty
// value (an unsupplied optional argument without a default) is not checked.
// The error message includes the argument type's description from its
// @@@argtype definition, so the .k description text is what the writer
// sees when a complicated value (e.g. a table line specification) is wrong.
// The value-size limit guards against std::regex stack overflow: the
// libstdc++ executor recurses per character, so a pattern applied to a
// very large value crashes. Typed argument values are short; large
// values are content (rest, :text) whose types match everything and are
// excluded by matches_all() anyway.
const size_t validation_size_limit = 4096;
void Parameter_set::validate(
const Parameter& parameter, const std::string& value, const Locator& loc)
{
if (value.empty() || value.size() > validation_size_limit) {
return;
}
const Argtype& argtype = parameter.m_argtype;
if (argtype.matches_all()) {
return;
}
if (!std::regex_match(value, argtype.m_regex)) {
std::stringstream ss {};
ss << "The value \"" << value << "\" given for the argument \""
<< parameter.m_name << "\" does not match the \"" << argtype.m_name
<< "\" argument type:\n\n"
<< trim(argtype.m_desc) << "\n";
throw Argument_error(ss.str(), loc, false);
}
}
const Parameter* Parameter_set::find(const std::string& name) const
{
for (const auto& params : {&m_positional, &m_optional, &m_rest}) {
for (const Parameter& p : *params) {
if (p.m_name == name) {
return &p;
}
}
}
return nullptr;
}
// Parameter/argument substitution
std::string replace_arguments(