feat(argtype): :alone - the value of an option written without one
An optional argument has three values: the default (the name is absent), the argument type's :alone value (the name is written alone), and a written value. :alone is declared by the argument type only, never by a klammer's parameter declaration -- a default is what one klammer means by silence, but a bare option name must read the same way in every klammer. The bool type declares :alone true, which is the whole of the convention that a bare boolean option means true; there is no boolean special case in the engine. A type whose pattern matches running text cannot declare :alone, since an option's value runs to the next bar or option name and would swallow the following text. kdesc and `ktext -m` now show [default: X] and [alone: Y] per argument type. In the Standard Klammer Set: @code :number becomes a bool (it was an untyped string tested only for truthiness, so a bare :number was a no-op); decimal_mark declares :alone comma; table_hline and table_vline declare :alone all. The 35 bools that default false gained the bare form for free. Tests: tst/alone_test.sh (21 cases) joins the shipped suite. (from dev 6024f49c2859)
This commit is contained in:
@@ -7,13 +7,15 @@
|
||||
#include "util.h"
|
||||
|
||||
Argtype::Argtype(std::string name, std::string desc, std::string symbolic_pattern, std::string pattern,
|
||||
std::string default_value, std::string python_cast, modify_string_f python_format,
|
||||
std::string default_value, std::string alone_value,
|
||||
std::string python_cast, modify_string_f python_format,
|
||||
const Locator& loc)
|
||||
: m_name(name)
|
||||
, m_desc(desc)
|
||||
, m_symbolic_pattern(symbolic_pattern)
|
||||
, m_pattern(pattern)
|
||||
, m_default(default_value)
|
||||
, m_alone(alone_value)
|
||||
, m_python_cast(python_cast)
|
||||
, m_python_format(python_format)
|
||||
, m_regex(std::regex(pattern))
|
||||
|
||||
@@ -26,7 +26,8 @@ public:
|
||||
{};
|
||||
|
||||
Argtype(std::string name, std::string desc, std::string symbolic_pattern, std::string pattern,
|
||||
std::string default_value, std::string python_cast, modify_string_f python_format,
|
||||
std::string default_value, std::string alone_value,
|
||||
std::string python_cast, modify_string_f python_format,
|
||||
const Locator& loc);
|
||||
|
||||
std::string python_value(const std::string& var_name, std::vector<std::string> value, size_t name_size);
|
||||
@@ -48,6 +49,18 @@ public:
|
||||
// (cell_hpos, column_width); general types (bool, float) have no
|
||||
// sensible universal default and leave it empty.
|
||||
std::string m_default {};
|
||||
// Value for an optional argument whose name is written alone, with no
|
||||
// value after it (:number rather than :number 10). Declared by the
|
||||
// argument type, never by a klammer's parameter declaration: the
|
||||
// default is what a klammer means by silence and is properly
|
||||
// per-klammer, but a bare option name must read the same way in every
|
||||
// klammer or the writer cannot know what it means without consulting
|
||||
// each signature. bool declares "true", which is where the
|
||||
// presence-means-true convention comes from; a type whose pattern
|
||||
// matches everything cannot declare it (the value would swallow the
|
||||
// following text instead). Empty means the type has no alone value,
|
||||
// and a bare option name yields the empty string as before.
|
||||
std::string m_alone {};
|
||||
// Type parameter for parameterized types like rest(2): the value N
|
||||
// is bound around the python cast as (lambda N: <cast>)(2)(...).
|
||||
// Empty means unparameterized; a type with a default parameter
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
Parameter_set& Argtype_set::parameters()
|
||||
{
|
||||
static Parameter_set instance("name | desc :pattern .* :python_cast str :default");
|
||||
static Parameter_set instance("name | desc :pattern .* :python_cast str :default :alone");
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -22,11 +22,15 @@ Argtype_set::Argtype_set()
|
||||
(void)(void)K::log(2);
|
||||
Locator loc = current_locator();
|
||||
for (auto [name, desc, pattern, python_cast, python_format] : base_argtypes) {
|
||||
add(name, desc, pattern, "", python_cast, python_format, loc);
|
||||
add(name, desc, pattern, "", "", python_cast, python_format, loc);
|
||||
}
|
||||
// rest is a parameterized type (rest(N)); an unparameterized use is
|
||||
// one-dimensional.
|
||||
m_types["rest"].m_parameter = "1";
|
||||
// A bool option written alone is true: :number means :number true.
|
||||
// This is a declaration like any other type's :alone, not an engine
|
||||
// special case for booleans.
|
||||
m_types["bool"].m_alone = "true";
|
||||
}
|
||||
|
||||
std::string Argtype_set::replace_symbols(const std::string& pattern, const Locator& loc)
|
||||
@@ -63,6 +67,7 @@ void Argtype_set::check_for_existing_definition(
|
||||
|
||||
void Argtype_set::add(const std::string& name, const std::string& desc,
|
||||
const std::string& pattern, const std::string& default_value,
|
||||
const std::string& alone_value,
|
||||
const std::string& python_cast, modify_string_f python_format,
|
||||
const Locator& loc)
|
||||
{
|
||||
@@ -73,7 +78,8 @@ void Argtype_set::add(const std::string& name, const std::string& desc,
|
||||
m_pattern_size = std::max(m_pattern_size, expanded_pattern.size());
|
||||
try {
|
||||
m_types[name] = Argtype(name, desc, pattern, expanded_pattern,
|
||||
default_value, python_cast, python_format, loc);
|
||||
default_value, alone_value,
|
||||
python_cast, python_format, loc);
|
||||
} catch (const std::regex_error& e) {
|
||||
std::stringstream ss {};
|
||||
ss << "The pattern for argument type \"" << name
|
||||
@@ -92,6 +98,35 @@ void Argtype_set::add(const std::string& name, const std::string& desc,
|
||||
<< " " << pattern << "\n";
|
||||
throw Definition_error(ss.str(), loc, false);
|
||||
}
|
||||
if (!alone_value.empty()) {
|
||||
// A pattern that matches running text cannot delimit a bare option
|
||||
// name from the text after it: an option's value runs to the next
|
||||
// bar or option name, so ":opt some words" would silently take
|
||||
// "some words" as the value and the alone value would never be
|
||||
// reached. A pattern that rejects multi-word text raises a clean
|
||||
// argument error there instead. The test is behavioral rather
|
||||
// than a comparison against matches_all()'s one literal pattern,
|
||||
// because an @@@argtype written without a :pattern gets ".*",
|
||||
// which is equally unable to delimit.
|
||||
if (std::regex_match(std::string("one two"), m_types[name].m_regex)) {
|
||||
std::stringstream ss {};
|
||||
ss << "The argument type \"" << name << "\" cannot declare an :alone value "
|
||||
<< "because its pattern matches running text:\n"
|
||||
<< " " << pattern << "\n\n"
|
||||
<< "An :alone value is used when an option name is written without a "
|
||||
<< "value. A type that matches running text cannot tell a bare option "
|
||||
<< "name from one whose value follows it, so the text after the name "
|
||||
<< "would be taken as the value instead.\n";
|
||||
throw Definition_error(ss.str(), loc, false);
|
||||
}
|
||||
if (!std::regex_match(alone_value, m_types[name].m_regex)) {
|
||||
std::stringstream ss {};
|
||||
ss << "The alone value \"" << alone_value << "\" for argument type \""
|
||||
<< name << "\" does not match its own pattern:\n"
|
||||
<< " " << pattern << "\n";
|
||||
throw Definition_error(ss.str(), loc, false);
|
||||
}
|
||||
}
|
||||
m_names.push_back(name);
|
||||
}
|
||||
|
||||
@@ -108,7 +143,7 @@ void Argtype_set::add(std::vector<Katom>::iterator begin, std::vector<Katom>::it
|
||||
// std::for_each(begin, end+1, [](Katom& k) { k.m_type = katom_t::replaced; });
|
||||
|
||||
add(values["name"], values["desc"], values["pattern"], values["default"],
|
||||
values["python_cast"], modify_string_f{},
|
||||
values["alone"], values["python_cast"], modify_string_f{},
|
||||
begin->m_loc);
|
||||
|
||||
modify_type(katom_t::replaced, begin, end);
|
||||
@@ -143,6 +178,21 @@ std::string Argtype_set::eval(
|
||||
}
|
||||
}
|
||||
|
||||
// The values a type supplies when an argument does not give one: the
|
||||
// default (the option was not written at all) and the alone value (the
|
||||
// option name was written without a value). Shown only when declared.
|
||||
static std::string values_note(const Argtype& type)
|
||||
{
|
||||
std::stringstream note {};
|
||||
if (!type.m_default.empty()) {
|
||||
note << " [default: " << type.m_default << "]";
|
||||
}
|
||||
if (!type.m_alone.empty()) {
|
||||
note << " [alone: " << type.m_alone << "]";
|
||||
}
|
||||
return note.str();
|
||||
}
|
||||
|
||||
std::string Argtype_set::describe(bool long_form, int indent_width) const
|
||||
{
|
||||
std::string indent(' ', indent_width);
|
||||
@@ -161,8 +211,9 @@ std::string Argtype_set::describe(bool long_form, int indent_width) const
|
||||
result << sp_arrow << m_types.at(name).m_pattern;
|
||||
result << "\n";
|
||||
} else {
|
||||
// result << abbrev(m_types.at(name).m_desc) << "\n";
|
||||
result << regex_split(m_types.at(name).m_desc, std::regex("\\n"), true)[0] << "\n";
|
||||
// result << abbrev(m_types.at(name).m_desc) << "\n";
|
||||
result << regex_split(m_types.at(name).m_desc, std::regex("\\n"), true)[0]
|
||||
<< values_note(m_types.at(name)) << "\n";
|
||||
}
|
||||
}
|
||||
if (long_form) {
|
||||
|
||||
@@ -19,6 +19,7 @@ public:
|
||||
void check_for_existing_definition(const std::string& name, const Locator& loc);
|
||||
void add(const std::string& name, const std::string& desc,
|
||||
const std::string& pattern, const std::string& default_value,
|
||||
const std::string& alone_value,
|
||||
const std::string& python_cast, modify_string_f python_format,
|
||||
const Locator& loc);
|
||||
void add(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, std::vector<Katom>& katoms);
|
||||
|
||||
@@ -417,6 +417,16 @@ Parameter_set::value_map(
|
||||
}
|
||||
auto optional_values = check_optional(optional, loc);
|
||||
for (auto [key, value] : optional_values) {
|
||||
// check_optional returns only the options that were actually
|
||||
// written, so an empty value here means the name was written alone
|
||||
// (":number" rather than ":number 10") — distinct from the option
|
||||
// being absent, which is filled from the default below. The
|
||||
// argument type supplies the alone value; bool declares "true",
|
||||
// which is what makes a bare boolean option mean true.
|
||||
const Parameter* parameter = find(key);
|
||||
if (value.empty() && parameter && !parameter->m_argtype.m_alone.empty()) {
|
||||
value = parameter->m_argtype.m_alone;
|
||||
}
|
||||
values[key] = value;
|
||||
}
|
||||
for (auto opt : m_optional) {
|
||||
|
||||
Reference in New Issue
Block a user