Verbatim-safe typography, ^-punctuation quoting, full-range ^UUUU^, polyglot html
Typographic transforms (---, quote pairs, ~) no longer touch verbatim text: @c/@code/@source_listing content and ^'...'^ spans show exactly the characters written. "^" before any punctuation character quotes it in every target (the apostrophe excepted: ^' opens a literal span), with the new :resolve option on @@@target declaring per-target renderings. The ^UUUU^ code-point form accepts 4-6 hex digits, the full Unicode range. The html output and transform spellings are polyglot (XML-valid), in preparation for an EPUB target. New suites: transform_test, character_test (engine), typography_test (SKS). (from dev 07ce5ea86a0a)
This commit is contained in:
@@ -48,7 +48,7 @@ std::string Argtype_registry::replace_symbols(const std::string& pattern, const
|
||||
ss << "Argtype symbol " << symbol << " not defined.\n\n"
|
||||
<< "Defined argtypes:\n";
|
||||
ss << describe();
|
||||
throw Definition_error(ss.str(), loc, false);
|
||||
throw Definition_error(ss.str(), loc);
|
||||
}
|
||||
}
|
||||
return expanded;
|
||||
@@ -88,7 +88,7 @@ void Argtype_registry::add(const std::string& name, const std::string& desc,
|
||||
if (pattern != expanded_pattern) {
|
||||
ss << "expanded to:\n " << expanded_pattern << "\n";
|
||||
}
|
||||
throw Definition_error(ss.str(), loc, false);
|
||||
throw Definition_error(ss.str(), loc);
|
||||
}
|
||||
if (!default_value.empty() &&
|
||||
!std::regex_match(default_value, m_types[name].m_regex)) {
|
||||
@@ -96,7 +96,7 @@ void Argtype_registry::add(const std::string& name, const std::string& desc,
|
||||
ss << "The default value \"" << default_value << "\" for argument type \""
|
||||
<< name << "\" does not match its own pattern:\n"
|
||||
<< " " << pattern << "\n";
|
||||
throw Definition_error(ss.str(), loc, false);
|
||||
throw Definition_error(ss.str(), loc);
|
||||
}
|
||||
if (!alone_value.empty()) {
|
||||
// A pattern that matches running text cannot delimit a bare option
|
||||
@@ -110,21 +110,21 @@ void Argtype_registry::add(const std::string& name, const std::string& desc,
|
||||
// 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"
|
||||
ss << "The argument type \"" << name << "\" cannot declare an :alone value"
|
||||
<< "because its pattern matches running text:"
|
||||
<< " " << 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);
|
||||
<< "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.";
|
||||
throw Definition_error(ss.str(), loc);
|
||||
}
|
||||
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);
|
||||
throw Definition_error(ss.str(), loc);
|
||||
}
|
||||
}
|
||||
m_names.push_back(name);
|
||||
|
||||
@@ -86,10 +86,10 @@ Parameter parse_positional_parameter(const katom_list& katoms, const Argtype_reg
|
||||
{
|
||||
// (void)K::log(3, katoms);
|
||||
if (katoms.size() > 1) {
|
||||
throw Argument_error("Multiple katoms for positional argument: " +
|
||||
throw Argument_error("Multiple katoms for positional argument:\n " +
|
||||
as_string(katoms.begin(), katoms.end(), true) +
|
||||
"\nPositional arguments are separated by the bar (|) character.",
|
||||
katoms[0].m_loc, false);
|
||||
katoms[0].m_loc);
|
||||
}
|
||||
Katom k = katoms[0];
|
||||
std::string name = k.m_text;
|
||||
@@ -367,7 +367,7 @@ void Parameter_set::check_positional(const katom_lists& positional_arguments, co
|
||||
ss << " " << arg.m_name << "\n";
|
||||
}
|
||||
//std::cout << ss.str();
|
||||
throw Argument_error(ss.str(), loc, false);
|
||||
throw Argument_error(ss.str(), loc);
|
||||
} else if (positional_count < given_count) {
|
||||
//std::cout << "DESCRIBE\n";
|
||||
//describe_parameters();
|
||||
@@ -391,7 +391,7 @@ Parameter_set::check_optional(const katom_lists& optional_arguments, const Locat
|
||||
}
|
||||
if (std::ranges::count(optional_names_used, name) > 0) {
|
||||
throw Argument_error("Optional argument \":" + name + "\" already provided "
|
||||
+ "with a value of:\n" + values[name], loc, false);
|
||||
+ "with a value of:\n" + values[name], loc);
|
||||
}
|
||||
katom_list value_katoms(opt.begin()+1, opt.end());
|
||||
std::string value = trim(to_string(value_katoms));
|
||||
@@ -477,9 +477,12 @@ void Parameter_set::validate(
|
||||
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);
|
||||
<< "\" argument type.\n\nThe \"" << argtype.m_name << "\" argument type describes "
|
||||
// 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";
|
||||
throw Argument_error(ss.str(), loc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,7 +523,7 @@ std::string replace_arguments(
|
||||
}
|
||||
ss << "To prevent the \"*\" character from specifying an argument, "
|
||||
<< "precede it with the \"^\" character.";
|
||||
throw Argument_error(ss.str(), loc, false);
|
||||
throw Argument_error(ss.str(), loc);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
36
mac/argv.cpp
36
mac/argv.cpp
@@ -257,7 +257,7 @@ void Argv::check_flags_and_options(const std::string& command, strings_t& words)
|
||||
for (auto w : not_defined) {
|
||||
ss << " " << w << "\n";
|
||||
}
|
||||
throw Argument_error(ss.str(), Locator(), false);
|
||||
throw Argument_error(ss.str(), Locator::none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ void Argv::parse_flags(strings_t& words, string_map& named_args)
|
||||
for (auto w : not_defined) {
|
||||
ss << " " << w << "\n";
|
||||
}
|
||||
throw Argument_error(ss.str(), Locator(), false);
|
||||
throw Argument_error(ss.str(), Locator::none());
|
||||
}
|
||||
*/
|
||||
// std::cout << "Flags found: " << flag_args << "\n";
|
||||
@@ -320,16 +320,29 @@ void Argv::parse_optional(strings_t& words, string_map& named_args)
|
||||
"The option " + flag_name(opt) + " needs a value: " +
|
||||
m_args[opt].m_syntax + ". Enter \"" +
|
||||
file_basename(command_name) + "\" for the list of arguments.",
|
||||
Locator(), false);
|
||||
Locator::none());
|
||||
}
|
||||
std::string opt_arg = words[index] + " ";
|
||||
index++;
|
||||
std::regex opt_regex = m_args[opt].m_rgx;
|
||||
|
||||
// std::cout << "Regex match? " << std::regex_match(trim(opt_arg), opt_regex) << "\n";
|
||||
|
||||
while (index < words.size() && words[index][0] != '-'
|
||||
// && std::regex_match(trim(opt_arg), opt_regex)
|
||||
// Only a LIST-valued option may span several argv words
|
||||
// (--klammersets a b c; --katoms type index). A single-valued
|
||||
// option takes exactly ONE argv element -- argv boundaries are
|
||||
// authoritative -- so a positional written after it is not
|
||||
// swallowed into the value. The former unconditional run
|
||||
// consumed everything up to the next dash-word: "-s 'text'
|
||||
// doc.kt" folded doc.kt into the -s TEXT (and rendered its
|
||||
// pathname), and "--klammersets none doc.kt" consumed the input
|
||||
// filename, so ktext reported that none was given. -s must be
|
||||
// usable in any argv position: its role with a file argument is
|
||||
// to modify the interpretation of that file, and it is
|
||||
// evaluated first regardless of where it is written. A list
|
||||
// value stops at the "--" delimiter (it begins with '-'), which
|
||||
// remains the escape for filenames after a list option.
|
||||
bool list_valued = m_args[opt].m_rgx_symbol == "'list'"
|
||||
|| m_args[opt].m_rgx_symbol == "'katom_display'";
|
||||
while (list_valued && index < words.size() && words[index][0] != '-'
|
||||
&& std::regex_match(trim(opt_arg + words[index]), opt_regex)
|
||||
|
||||
) {
|
||||
@@ -370,7 +383,7 @@ void Argv::parse_positional(const std::string& command, //strings_t words,
|
||||
}
|
||||
std::stringstream ss {};
|
||||
ss << "The argument " << q_(req) << " was not found in:\n " << command;
|
||||
throw Argument_error(ss.str(), Locator(), false);
|
||||
throw Argument_error(ss.str(), Locator::none());
|
||||
}
|
||||
named_args[req] = substring;
|
||||
// Drop the whitespace that separated this positional from the next.
|
||||
@@ -467,7 +480,7 @@ void Argv::check_flags(const string_map& arg_map, const std::string& command)
|
||||
for (const std::string& undef : undefined) {
|
||||
ss << " " << flag_name(undef);
|
||||
}
|
||||
throw Argument_error(ss.str(), Locator());
|
||||
throw Argument_error(ss.str(), Locator::none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,7 +521,7 @@ void Argv::parse(int argc, char* argv[], bool full_parse)
|
||||
usage(file_basename(argv[0]));
|
||||
throw Argument_error(
|
||||
"Incorrect value for option \"" + name + "\":\n" + m_args[name].m_desc + "\n",
|
||||
Locator(), false);
|
||||
Locator());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,7 +607,8 @@ int Argv::as_verbosity(const std::string& name)
|
||||
(void)K::log(2, name);
|
||||
auto value = get(name);
|
||||
if (!std::regex_match(value, std::regex(regex_symbols["'verbosity'"]))) {
|
||||
throw Argument_error("Argument \"" + value + "\" is not a verbosity level");
|
||||
throw Argument_error("Argument \"" + value + "\" is not a verbosity level",
|
||||
Locator::none());
|
||||
}
|
||||
return std::stoi(value);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ std::string utf8char(int cp)
|
||||
c[1] = ((cp>>12)&63)+128;
|
||||
c[2] = ((cp>>6)&63)+128;
|
||||
c[3]=(cp&63)+128;
|
||||
} else {
|
||||
// Reachable since ^UUUU^ accepts 6 hex digits: FFFFFF > 10FFFF.
|
||||
return "Invalid Unicode: " + std::to_string(cp);
|
||||
}
|
||||
return std::string(c);
|
||||
}
|
||||
@@ -65,7 +68,13 @@ std::string unicode_hex_to_char(std::string s, int width=4) //, std::string mark
|
||||
std::string process_diacritics(std::string s)
|
||||
{
|
||||
(void)K::log(4);
|
||||
std::regex diacritic_re("\\^([^\\s`'~@|^:*#])([" + diacritic_symbols + "])");
|
||||
// The base may not be whitespace, a digit, or ASCII punctuation (the
|
||||
// four ranges !-/ :-@ [-` {-~): a diacritic sits on a letter. Without
|
||||
// the exclusion, ^ before a quoted punctuation character followed by a
|
||||
// mark character composed nonsense -- ^-- became a hyphen with a macron
|
||||
// instead of a literal hyphen before a hyphen. A multi-byte (non-ASCII)
|
||||
// base is unaffected: its bytes are outside every excluded range.
|
||||
std::regex diacritic_re("\\^([^\\s0-9!-/:-@\\[-`{-~])([" + diacritic_symbols + "])");
|
||||
|
||||
std::string result {s};
|
||||
std::sregex_iterator end {};
|
||||
@@ -126,9 +135,14 @@ std::string process_unicode_codepoint(std::string s)
|
||||
//return std::regex_replace(s, unicode_re, hidehat + "$1" + hidehat);
|
||||
std::string result {s};
|
||||
std::sregex_iterator end {};
|
||||
for (std::sregex_iterator p { s.begin(), s.end(), unicode_re }; p!= end; ++p) {
|
||||
for (std::sregex_iterator p { s.begin(), s.end(), unicode_re }; p!= end; ++p) {
|
||||
std::regex hit_re { regex_escape((*p)[0].str()) };
|
||||
result = std::regex_replace(result, hit_re, unicode_hex_to_char((*p)[1].str()));
|
||||
// Convert the captured hex DIRECTLY: unicode_hex_to_char() re-scans
|
||||
// its argument at a fixed width, and its 4-digit default truncated a
|
||||
// 5-digit code point to its first four digits (^13000^ rendered as
|
||||
// U+1300 followed by a literal "0").
|
||||
result = std::regex_replace(result, hit_re,
|
||||
utf8char(std::stoi((*p)[1].str(), nullptr, 16)));
|
||||
}
|
||||
return result;
|
||||
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
#include <regex>
|
||||
#include <unistd.h>
|
||||
|
||||
const std::regex unicode_re(R"(\^(([0-9A-Fa-f]{5})|([0-9A-Fa-f]{4})|([0-9A-Fa-f]))\^)");
|
||||
// ^UUUU^ — a Unicode code point in hex, 4 to 6 digits (^263A^ is the BMP,
|
||||
// ^13000^ EGYPTIAN HIEROGLYPH A001, ^10FFFD^ the top of the range), or a
|
||||
// single digit. Lengths 2-3 are NOT accepted: two- and three-letter
|
||||
// sequences of a-f collide with the ^s^-style mnemonic names.
|
||||
const std::regex unicode_re(R"(\^(([0-9A-Fa-f]{4,6})|([0-9A-Fa-f]))\^)");
|
||||
const std::regex unicode_hide_re(R"(=([0-9A-Fa-f]{2})=)");
|
||||
const std::regex hex2_re(R"(([0-9A-Fa-f]{2}))");
|
||||
const std::regex hex4_re(R"(([0-9A-Fa-f]{4}))");
|
||||
|
||||
@@ -9,7 +9,16 @@ using namespace std::string_literals;
|
||||
|
||||
fs::path construct_command_pathname(char* command)
|
||||
{
|
||||
return fs::path(fs::current_path().string() + "/" + std::string(command));
|
||||
// The pseudo source file for command-line string input (-s, kdiag's
|
||||
// input): a file IN THE CWD named after the command, so that a relative
|
||||
// @read in string input resolves against the directory the user ran the
|
||||
// command from -- the string-input analog of "a document's relative
|
||||
// names resolve against the document". The basename matters: the
|
||||
// former "<cwd>/<argv[0]>" string concatenation anchored resolution at
|
||||
// the BINARY's directory whenever argv[0] was absolute
|
||||
// (/usr/local/bin/ktext, a wrapper script), so every relative @read in
|
||||
// -s input searched bin/ instead of the cwd.
|
||||
return fs::current_path() / fs::path(command).filename();
|
||||
}
|
||||
|
||||
void set_verbose_level(int argc, char* argv[])
|
||||
@@ -124,7 +133,7 @@ void load_klammersets(Machine& machine, const strings_t& symbols)
|
||||
"The klammerset \"none\" cannot be combined with other klammersets: "
|
||||
"it means that none is loaded. Give \"none\" alone, or name only the "
|
||||
"klammersets to load.",
|
||||
Locator());
|
||||
Locator::none());
|
||||
}
|
||||
if (symbols.empty()) {
|
||||
// Which klammerset was loaded, and from where, is a DERIVED value: the
|
||||
@@ -146,7 +155,7 @@ void load_klammersets(Machine& machine, const strings_t& symbols)
|
||||
continue;
|
||||
}
|
||||
std::string klammerset_filename = resolve_klammerset_symbol(
|
||||
symbol, machine.m_state.value("K_input_dir"), Locator()).string();
|
||||
symbol, machine.m_state.value("K_input_dir"), Locator::none()).string();
|
||||
// Symbol -> file is the search path's answer, and the search
|
||||
// path has three stages with shadowing: the file it landed on is
|
||||
// exactly what a user cannot read off "--klammersets x".
|
||||
|
||||
@@ -23,8 +23,7 @@ void Error::print_message(const std::string& epilog)
|
||||
if (epilog != "") {
|
||||
m_desc += "\n\n" + epilog + "\n";
|
||||
}
|
||||
if (m_just)
|
||||
m_desc = justify(m_desc, 80, 0);
|
||||
m_desc = justify(m_desc, 80, 0);
|
||||
std::cerr << "\n" << red << command_name << " (" << m_type << " error)";
|
||||
if (display_source(m_loc.m_filename)) {
|
||||
std::cerr << ":\n " << m_loc.m_filename;
|
||||
|
||||
40
mac/error.h
40
mac/error.h
@@ -11,11 +11,10 @@ inline std::string command_pathname { "Pathname of command executed on the comma
|
||||
class Error : std::exception {
|
||||
public:
|
||||
Error(const std::string& error_type, const std::string& description,
|
||||
const Locator& locator = Locator(), bool do_justify = true)
|
||||
const Locator& locator = Locator())
|
||||
: m_type(error_type)
|
||||
, m_desc(description)
|
||||
, m_loc(locator)
|
||||
, m_just(do_justify)
|
||||
{}
|
||||
|
||||
void print_message(const std::string& epilog="");
|
||||
@@ -23,57 +22,56 @@ public:
|
||||
std::string m_type {};
|
||||
std::string m_desc {};
|
||||
Locator m_loc; // {};
|
||||
bool m_just { true };
|
||||
};
|
||||
|
||||
|
||||
class Parsing_error : public Error {
|
||||
public:
|
||||
explicit Parsing_error(
|
||||
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
|
||||
: Error("parsing", description, locator, do_justify) {};
|
||||
const std::string& description, const Locator& locator=Locator())
|
||||
: Error("parsing", description, locator) {};
|
||||
};
|
||||
|
||||
class File_error : public Error {
|
||||
public:
|
||||
explicit File_error(
|
||||
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
|
||||
: Error("file", description, locator, do_justify) {};
|
||||
const std::string& description, const Locator& locator=Locator())
|
||||
: Error("file", description, locator) {};
|
||||
};
|
||||
|
||||
class Target_error : public Error {
|
||||
public:
|
||||
explicit Target_error(
|
||||
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
|
||||
: Error("target", description, locator, do_justify) {};
|
||||
const std::string& description, const Locator& locator=Locator())
|
||||
: Error("target", description, locator) {};
|
||||
};
|
||||
|
||||
class Klammerset_error : public Error {
|
||||
public:
|
||||
explicit Klammerset_error(
|
||||
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
|
||||
: Error("klammerset", description, locator, do_justify) {};
|
||||
const std::string& description, const Locator& locator=Locator())
|
||||
: Error("klammerset", description, locator) {};
|
||||
};
|
||||
|
||||
class Definition_error : public Error {
|
||||
public:
|
||||
explicit Definition_error(
|
||||
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
|
||||
: Error("definition", description, locator, do_justify) {};
|
||||
const std::string& description, const Locator& locator=Locator())
|
||||
: Error("definition", description, locator) {};
|
||||
};
|
||||
|
||||
class Argument_error : public Error {
|
||||
public:
|
||||
explicit Argument_error(
|
||||
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
|
||||
: Error("argument", description, locator, do_justify) {};
|
||||
const std::string& description, const Locator& locator=Locator())
|
||||
: Error("argument", description, locator) {};
|
||||
};
|
||||
|
||||
class Environment_error : public Error {
|
||||
public:
|
||||
explicit Environment_error(
|
||||
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
|
||||
: Error("environment", description, locator, do_justify) {};
|
||||
const std::string& description, const Locator& locator=Locator())
|
||||
: Error("environment", description, locator) {};
|
||||
};
|
||||
|
||||
// Klammer application nested deeper than the engine's limit. Raised by the
|
||||
@@ -83,13 +81,13 @@ public:
|
||||
class Recursion_error : public Error {
|
||||
public:
|
||||
explicit Recursion_error(
|
||||
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
|
||||
: Error("recursion", description, locator, do_justify) {};
|
||||
const std::string& description, const Locator& locator=Locator())
|
||||
: Error("recursion", description, locator) {};
|
||||
};
|
||||
|
||||
class Internal_error : public Error {
|
||||
public:
|
||||
explicit Internal_error(
|
||||
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
|
||||
: Error("internal", description, locator, do_justify) {};
|
||||
const std::string& description, const Locator& locator=Locator())
|
||||
: Error("internal", description, locator) {};
|
||||
};
|
||||
|
||||
31
mac/eval.cpp
31
mac/eval.cpp
@@ -42,8 +42,8 @@ std::string shell(State state, std::string command, Locator loc)
|
||||
err_template.push_back('\0');
|
||||
int err_fd = mkstemp(err_template.data());
|
||||
if (err_fd == -1) {
|
||||
throw Environment_error("Could not create a temporary file for the "
|
||||
"command's error output", loc, false);
|
||||
throw Environment_error(
|
||||
"Could not create a temporary file for the command's error output", loc);
|
||||
}
|
||||
close(err_fd);
|
||||
err_path = err_template.data();
|
||||
@@ -55,7 +55,7 @@ std::string shell(State state, std::string command, Locator loc)
|
||||
if (!pipe) {
|
||||
fs::remove(err_path);
|
||||
throw Environment_error(
|
||||
"Could not run command:\n" + command, loc, false);
|
||||
"Could not run command:\n " + command, loc);
|
||||
}
|
||||
char buffer[128];
|
||||
std::string result = "";
|
||||
@@ -87,7 +87,7 @@ std::string shell(State state, std::string command, Locator loc)
|
||||
// "environment", not "parsing": the failure is OUTSIDE Klammertext,
|
||||
// in the command the document invoked -- the same class as a missing
|
||||
// xelatex or an unset environment variable.
|
||||
throw Environment_error(ss.str(), loc, false);
|
||||
throw Environment_error(ss.str(), loc);
|
||||
}
|
||||
if (!error_output.empty()) {
|
||||
(void)K::log(1, "shell command wrote to stderr:", command,
|
||||
@@ -122,8 +122,8 @@ std::string run_haskell(const std::string& hsfile, Locator loc)
|
||||
err_template.push_back('\0');
|
||||
int err_fd = mkstemp(err_template.data());
|
||||
if (err_fd == -1) {
|
||||
throw Environment_error("Could not create a temporary file for runghc's "
|
||||
"error output", loc, false);
|
||||
throw Environment_error(
|
||||
"Could not create a temporary file for runghc's error output", loc);
|
||||
}
|
||||
close(err_fd);
|
||||
err_path = err_template.data();
|
||||
@@ -132,7 +132,7 @@ std::string run_haskell(const std::string& hsfile, Locator loc)
|
||||
FILE* pipe = popen(command.c_str(), "r");
|
||||
if (!pipe) {
|
||||
fs::remove(err_path);
|
||||
throw Environment_error("Could not run runghc.", loc, false);
|
||||
throw Environment_error("Could not run runghc.", loc);
|
||||
}
|
||||
char buffer[128];
|
||||
std::string result = "";
|
||||
@@ -154,12 +154,12 @@ std::string run_haskell(const std::string& hsfile, Locator loc)
|
||||
}
|
||||
ss << ".";
|
||||
if (!error_output.empty()) {
|
||||
ss << "\nrunghc reported:\n" << error_output;
|
||||
ss << "\n\nrunghc reported:\n " << error_output;
|
||||
}
|
||||
// A compile error arrives here, and so does a program that ran and then
|
||||
// exited nonzero; the message does not guess which, it shows what
|
||||
// runghc said.
|
||||
throw Environment_error(ss.str(), loc, false);
|
||||
throw Environment_error(ss.str(), loc);
|
||||
}
|
||||
if (!error_output.empty()) {
|
||||
// GHC's warnings, and anything the program itself wrote to stderr.
|
||||
@@ -175,9 +175,9 @@ std::string haskell(State state, std::string code, Locator loc)
|
||||
// "environment", not "parsing": a missing external command is the same
|
||||
// class as a missing xelatex, and "parsing error" misdescribes it.
|
||||
throw Environment_error(
|
||||
"@eval with the :haskell argument requires runghc, which was not found in PATH.\n"
|
||||
"@eval with the :haskell argument requires runghc, which was not found in PATH.\n\n"
|
||||
"Install it using GHCup; see https://www.haskell.org/ghcup/install/.",
|
||||
loc, false);
|
||||
loc);
|
||||
}
|
||||
code = state.subst(code);
|
||||
|
||||
@@ -193,7 +193,7 @@ std::string haskell(State state, std::string code, Locator loc)
|
||||
int fd = mkstemp(tmppath.data());
|
||||
if (fd < 0) {
|
||||
throw Environment_error(
|
||||
"Could not create temporary file for Haskell evaluation.", loc, false);
|
||||
"Could not create temporary file for Haskell evaluation.", loc);
|
||||
}
|
||||
std::string hsfile = std::string(tmppath.data()) + ".hs";
|
||||
close(fd);
|
||||
@@ -202,8 +202,7 @@ std::string haskell(State state, std::string code, Locator loc)
|
||||
FILE* f = fopen(hsfile.c_str(), "w");
|
||||
if (!f) {
|
||||
unlink(hsfile.c_str());
|
||||
throw Parsing_error(
|
||||
"Could not write temporary Haskell file.", loc, false);
|
||||
throw Parsing_error("Could not write temporary Haskell file.", loc);
|
||||
}
|
||||
fprintf(f, "%s\n", code.c_str());
|
||||
fclose(f);
|
||||
@@ -222,7 +221,7 @@ void check_cpp_arguments(katom_list args, Locator loc)
|
||||
<< "or\n"
|
||||
<< " @eval :cpp <library-basename> <function-name> @\n"
|
||||
<< "In the first case, the library basename is used for the function name.";
|
||||
throw Argument_error(ss.str(), loc, false);
|
||||
throw Argument_error(ss.str(), loc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +278,7 @@ std::string Eval::eval_command(katom_iter begin, katom_iter end)
|
||||
if (dir.empty() || !fs::is_directory(dir)) {
|
||||
throw Argument_error(
|
||||
"The :cwd directory does not exist: \"" + dir + "\"",
|
||||
begin->m_loc, false);
|
||||
begin->m_loc);
|
||||
}
|
||||
cwd_guard.emplace(dir);
|
||||
first = resume;
|
||||
|
||||
@@ -34,7 +34,7 @@ std::string Eval_cpp::eval(const fs::path& library_path, const std::string& func
|
||||
std::stringstream ss {};
|
||||
ss << "Cannot load symbol " << function_name
|
||||
<< " from library " << library_path << ":\n " << error_desc;
|
||||
throw File_error(ss.str(), m_loc, false);
|
||||
throw File_error(ss.str(), m_loc);
|
||||
}
|
||||
std::string result = func(m_machine);
|
||||
dlclose(handle);
|
||||
|
||||
@@ -40,6 +40,21 @@ Eval_python::Eval_python(Machine& machine, const Locator& loc)
|
||||
Py_DECREF(result_text);
|
||||
}
|
||||
import_module("inspect", false);
|
||||
// The :after_apply phase dispatcher: a phase that declares a K
|
||||
// parameter receives the state's class K; one that does not (a stdlib
|
||||
// function like string.capwords, whose second parameter is a separator)
|
||||
// is called with the text alone. Discrimination is by parameter NAME,
|
||||
// not count -- capwords has two parameters and K is not one of them.
|
||||
// Builtins whose signature inspect cannot read are treated as not
|
||||
// wanting K.
|
||||
PyRun_String(
|
||||
"def K_phase_call(f, text, K):\n"
|
||||
" try:\n"
|
||||
" wants = 'K' in inspect.signature(f).parameters\n"
|
||||
" except (ValueError, TypeError):\n"
|
||||
" wants = False\n"
|
||||
" return f(text, K=K) if wants else f(text)\n",
|
||||
Py_file_input, m_globals, m_globals);
|
||||
if (!m_machine.m_state.m_frames.empty()) {
|
||||
PyRun_String(m_machine.m_state.python_code().c_str(), Py_file_input, m_globals, m_locals);
|
||||
}
|
||||
@@ -154,7 +169,7 @@ void Eval_python::import_module(const std::string& module_name, bool verify)
|
||||
if (!detail.empty()) {
|
||||
message += ":\n\n" + detail;
|
||||
}
|
||||
throw Argument_error(message, m_loc, false);
|
||||
throw Argument_error(message, m_loc);
|
||||
}
|
||||
// PyDict_SetItemString steals a reference, so we don't need to DECREF module
|
||||
// The dictionary will own the reference
|
||||
|
||||
32
mac/file.cpp
32
mac/file.cpp
@@ -160,7 +160,6 @@ strings_t group_filename_tokens(const strings_t& tokens, const std::string& base
|
||||
// name exists. A name that never resolves is kept as given, so the
|
||||
// missing-file error downstream reports what the user wrote.
|
||||
size_t i = 0;
|
||||
bool reported = false; // at most one "tried" report per list
|
||||
while (i < tokens.size()) {
|
||||
if (filename_exists(tokens[i], base_dir)) {
|
||||
result.push_back(tokens[i]);
|
||||
@@ -188,27 +187,14 @@ strings_t group_filename_tokens(const strings_t& tokens, const std::string& base
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// Nothing resolved. The groupings that were TRIED are what
|
||||
// the user needs at verbosity 0, because the downstream error
|
||||
// names only the first token and cannot say why the others
|
||||
// were not joined to it. Recorded on the name so the caller
|
||||
// can report them with the missing-file error.
|
||||
strings_t tried {};
|
||||
std::string acc2 = tokens[i];
|
||||
tried.push_back(q_(acc2));
|
||||
for (size_t k = i + 1; k < tokens.size(); ++k) {
|
||||
acc2 += " " + tokens[k];
|
||||
tried.push_back(q_(acc2));
|
||||
}
|
||||
// Once: the first unresolved token's list already shows
|
||||
// every joining from that point on, and one report per
|
||||
// leftover word buries it.
|
||||
if (tried.size() > 1 && !reported) {
|
||||
std::cerr << command_name << ": no file matches "
|
||||
<< q_(tokens[i]) << "; tried "
|
||||
<< join(tried, ", ") << "\n";
|
||||
reported = true;
|
||||
}
|
||||
// Nothing resolved: the token passes through as written and
|
||||
// its DOWNSTREAM OWNER decides -- a bare :files word is the
|
||||
// kt/ shortcut (which resolves or errors there), a missing
|
||||
// file is a located error. A failed rescue itself says
|
||||
// NOTHING at any verbosity (2026-08-22): every outcome is
|
||||
// already either the -v 1 resolution line or an error that
|
||||
// stops the run, so a tried-list adds noise to the first and
|
||||
// nothing to the second.
|
||||
result.push_back(tokens[i]);
|
||||
++i;
|
||||
}
|
||||
@@ -358,7 +344,7 @@ std::string find_file(const std::string& basename, strings_t search_path, bool e
|
||||
std::sort(search_path.begin(), search_path.end());
|
||||
ss << "File with basename \"" << basename << "\" not found in search path:\n "
|
||||
<< join(search_path, "\n ");
|
||||
throw File_error(ss.str(), Locator(), false);
|
||||
throw File_error(ss.str(), Locator());
|
||||
}
|
||||
return pathname;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ void warn_unparsed_katoms(katom_list& katoms, bool warn)
|
||||
// commands issue has one format: it said "[warning]" where
|
||||
// everything else says "(warning)".
|
||||
warning("Word not parsed:\n " + k.m_text
|
||||
+ "\nTo include a special character (@, |, #, and ^), put "
|
||||
+ "\nTo include a punctuation character literally, put "
|
||||
"\"^\" before it.",
|
||||
k.m_loc);
|
||||
k.m_unparsed = false;
|
||||
|
||||
@@ -114,7 +114,7 @@ void missing_open(const Katom& k, bool error_exit)
|
||||
std::stringstream ss {};
|
||||
ss << "A klammer ends without a beginning: " << k;
|
||||
if (error_exit) {
|
||||
throw Parsing_error(ss.str(), k.m_loc, false);
|
||||
throw Parsing_error(ss.str(), k.m_loc);
|
||||
} else {
|
||||
std::cout << " " << ss.str() << "\n";
|
||||
}
|
||||
@@ -134,7 +134,7 @@ void missing_close(const katom_list& bounds, bool error_exit)
|
||||
ss << " " << k.m_loc << " " << k.m_src << "\n";
|
||||
}
|
||||
if (error_exit) {
|
||||
throw Parsing_error(ss.str(), bounds[0].m_loc, false);
|
||||
throw Parsing_error(ss.str(), bounds[0].m_loc);
|
||||
} else {
|
||||
std::cout << ss.str() << "\n";
|
||||
}
|
||||
@@ -148,7 +148,7 @@ void bad_close(const Katom& open, const Katom& close, bool error_exit)
|
||||
<< " " << open.m_loc << " " << open << "\n"
|
||||
<< " " << close.m_loc << " " << close;
|
||||
if (error_exit) {
|
||||
throw Parsing_error(ss.str(), open.m_loc, false);
|
||||
throw Parsing_error(ss.str(), open.m_loc);
|
||||
} else {
|
||||
std::cout << ss.str() << "\n";
|
||||
}
|
||||
@@ -174,7 +174,7 @@ void check_named_katom_span(const Katom& begin, const Katom& end)
|
||||
ss << " A named end katom does not match:\n"
|
||||
<< " " << begin.m_loc << " " << begin << "\n"
|
||||
<< " " << end.m_loc << " " << end;
|
||||
throw Parsing_error(ss.str(), end.m_loc, false);
|
||||
throw Parsing_error(ss.str(), end.m_loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,6 +370,24 @@ void hide_special_katoms(katom_list& katoms)
|
||||
definition_depth == 0 && code_depth == 0) {
|
||||
k.m_text = hide_structural_characters(k.m_text);
|
||||
}
|
||||
// "^" before ANY punctuation character quotes it, not only the six
|
||||
// Klammertext specials the katomizer knows: ^- is a literal hyphen
|
||||
// (no dash transform), ^~ a literal tilde. The pair becomes the
|
||||
// KTESC marker of the character here -- text-level, on writer-text
|
||||
// katoms only, under the same span skips as the quoted specials
|
||||
// above -- so code inside an @eval keeps its carets (grep '^-'
|
||||
// reaches the shell intact) and ^'...'^ content (katom_t::literal,
|
||||
// marked before this pass runs) stays exactly as typed. How the
|
||||
// quoted character renders is the target's decision: the :escape
|
||||
// and :resolve tables map its marker, and an unmapped marker
|
||||
// decodes to the character itself. A katom this converts is no
|
||||
// longer unparsed (the ^- warning would otherwise misfire).
|
||||
if ((k.m_type == katom_t::word || k.m_type == katom_t::text) &&
|
||||
definition_depth == 0 && code_depth == 0) {
|
||||
if (hide_quoted_punctuation(k.m_text)) {
|
||||
k.m_unparsed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,7 +474,7 @@ void check_bar_count(katom_iter begin, katom_iter end)
|
||||
ss << "Incorrectly formatted @cond klammer. There should only be one or two bar characters:\n"
|
||||
<< " @cond <predicate> | <result-if-true @\nor:\n"
|
||||
<< " @cond <predicate> | <result-if-true> | <result-if-false> @";
|
||||
throw Argument_error(ss.str(), begin->m_loc, false);
|
||||
throw Argument_error(ss.str(), begin->m_loc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ parse_name(const Target_registry& targets, const Katom& name_katom)
|
||||
|
||||
if (!std::regex_match(name_with_target, match, Klammer::name_re)) {
|
||||
throw Parsing_error(
|
||||
"The klammer name \"" + name_with_target + "\" is not correctly defined. "
|
||||
"The klammer name \"" + name_with_target + "\" is not correctly defined.\n\n"
|
||||
"The form is \"<klammer-name>\" for general klammers or \"<klammer-name>.<target-name>\" "
|
||||
"for a specialized target. Several targets that share one body are written as a "
|
||||
"comma-separated list: \"<klammer-name>.<target-name>,<target-name>\". The klammer "
|
||||
@@ -44,7 +44,7 @@ parse_name(const Target_registry& targets, const Katom& name_katom)
|
||||
if (!targets.has(target_name)) {
|
||||
throw Target_error(
|
||||
"The target \"" + target_name + "\" in klammer definition \"" + name_with_target + "\" "
|
||||
"is not defined. Enter \"kdesc -t\" to see the targets defined by the Standard Klammer Set.",
|
||||
"is not defined.\n\nEnter \"kdesc -t\" to see the targets defined by the Standard Klammer Set.",
|
||||
name_katom.m_loc);
|
||||
}
|
||||
if (is_in(target_name, seen)) {
|
||||
@@ -105,7 +105,7 @@ parse_definition_katoms(
|
||||
" @@<name>[.<target>] :: <body> @@ instance (uses .k parameters)\n"
|
||||
" @@<name>[.<target>] ::: <body> @@ override existing definition\n"
|
||||
" @@<name>[.<target>] <parameters> :::: <body> @@ default (can be overridden)",
|
||||
begin->m_loc, false);
|
||||
begin->m_loc);
|
||||
|
||||
}
|
||||
katom_list parameter_katoms(begin, deftype);
|
||||
@@ -274,7 +274,7 @@ void Klammer::disallow_instances() //Klammer::components declaration)
|
||||
<< plural("instance", icount) << " (defined by \"::\"), but "
|
||||
<< "no declarations (defined by a \".k\" target)";
|
||||
throw Definition_error(
|
||||
error_list(ss.str(), instances), instances[0].loc, false);
|
||||
error_list(ss.str(), instances), instances[0].loc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ bool Klammer::copy_to_instances(const Target_registry& targets)
|
||||
<< plural("instance", icount) << " (defined by \"::\"), but "
|
||||
<< dcount << " "<< plural("definition", dcount) << " (defined by \":\")";
|
||||
throw Definition_error(
|
||||
error_list(ss.str(), definitions), definitions[0].loc, false);
|
||||
error_list(ss.str(), definitions), definitions[0].loc);
|
||||
} else {
|
||||
copy_components(definitions[0].parameters, m_defs, targets);
|
||||
return true;
|
||||
@@ -336,7 +336,7 @@ void Klammer::check_for_multiple_general_klammers()
|
||||
error_list(
|
||||
"There is more than one general klammer (a klammer in which no target is defined)",
|
||||
general_klammers),
|
||||
general_klammers[0].loc, false);
|
||||
general_klammers[0].loc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ void Klammer::check_for_declaration_and_definitions()
|
||||
".\nWrite \"::\" instead of \":\" so the definition takes its parameters "
|
||||
"from the declaration",
|
||||
definitions),
|
||||
declares[0].loc, false);
|
||||
declares[0].loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,7 +384,7 @@ void Klammer::copy_general_klammer_to_undefined(const Target_registry& targets)
|
||||
throw Definition_error(
|
||||
error_list("The general parameters are different than the defined parameters",
|
||||
general_klammers),
|
||||
m_parameters.m_katoms[0].m_loc, false);
|
||||
m_parameters.m_katoms[0].m_loc);
|
||||
}
|
||||
}
|
||||
const auto& [target, deftype, parameters, body, varmap, loc] = general_klammers[0];
|
||||
@@ -470,19 +470,19 @@ void Klammer::no_declarations(const Target_registry& targets)
|
||||
// definitions by hand to find which one drifted.
|
||||
std::stringstream ss {};
|
||||
ss << "The parameters of klammer \"" << m_name
|
||||
<< "\" are not the same for every target,\n"
|
||||
<< "\" are not the same for every target, "
|
||||
"and there is no declaration (.k) target to define them once:\n";
|
||||
for (const auto& def : m_defs) {
|
||||
if (std::ranges::find(target_names, def.target) == target_names.end())
|
||||
continue;
|
||||
std::string target = def.target;
|
||||
target.resize(std::max(target.size(), size_t(6)), ' ');
|
||||
ss << " " << target << " " << parameter_signature(def.parameters)
|
||||
<< "\n " << def.loc.desc() << "\n";
|
||||
target.resize(std::max(target.size(), size_t(4)), ' ');
|
||||
ss << " Target: " << target << " Parameters: " << parameter_signature(def.parameters)
|
||||
<< "\n " << def.loc.desc() << "\n";
|
||||
}
|
||||
ss << "Use a .k target to declare the parameters and describe the klammer,\n"
|
||||
"and \"::\" with no parameters for each target's definition.";
|
||||
throw Definition_error(ss.str(), m_defs[0].loc, false);
|
||||
throw Definition_error(ss.str(), m_defs[0].loc);
|
||||
} else {
|
||||
// std::cout << " All equal\n";
|
||||
copy_components(m_defs[0].parameters, m_defs, targets);
|
||||
@@ -506,7 +506,7 @@ void Klammer::many_declarations(const std::vector<Klammer::components>& declares
|
||||
// std::cout << boldblack << "Many declarations\n" << black;
|
||||
throw Definition_error(
|
||||
error_list("More than one declaration (.k) klammer", declares),
|
||||
declares[0].loc, false);
|
||||
declares[0].loc);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -206,9 +206,9 @@ fs::path resolve_klammerset_symbol(
|
||||
}
|
||||
throw Klammerset_error(
|
||||
"The klammerset \"" + symbol + "\" was not found. A symbol x names the "
|
||||
"declaration file x/x.k in one of the search directories: "
|
||||
+ join(dirs, ", ")
|
||||
+ ". Enter \"kdesc --klammersets\" to list the available klammersets.",
|
||||
"declaration file x/x.k in one of the search directories:\n "
|
||||
+ join(dirs, "\n ")
|
||||
+ "\nEnter \"kdesc --klammersets\" to list the available klammersets.",
|
||||
loc);
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ std::vector<Ktype> katom_types {
|
||||
Ktype(katom_t::ws_newline, "ws-newline", ws_newline_s, "Remove all whitespace, leaving <number> newlines (default: 1)"),
|
||||
Ktype(katom_t::special, "special-char", R"(\^[@|#^:*])", "Klammertext special character treated as regular text"),
|
||||
//Ktype(katom_t::nonascii, "non-ascii-char", R"(\^\w(?:.|\^))", "Non-ASCII character"),
|
||||
Ktype(katom_t::nonascii, "non-ascii-char", R"((\^(\w(?:.|\^)))|(\^[A-Fa-f0-9]{1,5}\^))", "Non-ASCII character"),
|
||||
Ktype(katom_t::nonascii, "non-ascii-char", R"((\^(\w(?:.|\^)))|(\^[A-Fa-f0-9]{1,6}\^))", "Non-ASCII character"),
|
||||
|
||||
// Ktype(katom_t::word, "word", R"([a-z][a-z0-9_]*)", "Lower-case letters, numbers, or underscore"),
|
||||
// Ktype(katom_t::text, "text",
|
||||
|
||||
@@ -24,6 +24,14 @@ public:
|
||||
, m_chr(int(location.column()))
|
||||
{};
|
||||
Locator(const fs::path& filename, int line, int chr);
|
||||
// "No location": for errors with no document position -- command-line
|
||||
// argument errors, lookups made on the command's behalf. NOT the same
|
||||
// as Locator(): the default constructor captures the C++ CALL SITE via
|
||||
// std::source_location (and a defaulted Locator parameter captures the
|
||||
// CALLER), which is what leaked "argv.cpp, line 597" into user-facing
|
||||
// errors (found by the error gallery, 2026-08-22). Locator() is for
|
||||
// logging; errors either carry a real document location or this.
|
||||
static Locator none() { return Locator(fs::path{}, -1, -1); }
|
||||
std::string str(bool relative = false) const;
|
||||
std::string desc(bool relative = false) const;
|
||||
std::string abbrev(bool include_chr=true) const;
|
||||
|
||||
@@ -61,10 +61,10 @@ public:
|
||||
if (apply_depth >= max_apply_depth) {
|
||||
std::stringstream ss {};
|
||||
ss << "Klammer application nested more than " << max_apply_depth
|
||||
<< " levels deep while applying " << q_(name) << ".\n"
|
||||
<< " levels deep while applying " << q_(name) << "."
|
||||
<< "A klammer that applies itself, directly or through a cycle "
|
||||
<< "of klammers, does not terminate.";
|
||||
throw Recursion_error(ss.str(), loc, false);
|
||||
throw Recursion_error(ss.str(), loc);
|
||||
}
|
||||
++apply_depth;
|
||||
}
|
||||
@@ -132,7 +132,7 @@ void check_bar_count(katom_iter begin, std::size_t count)
|
||||
ss << "Incorrectly formatted @cond klammer. There should only be one or two bar characters:\n"
|
||||
<< " @cond <predicate> | <result-if-true @\nor:\n"
|
||||
<< " @cond <predicate> | <result-if-true> | <result-if-false> @";
|
||||
throw Argument_error(ss.str(), begin->m_loc, false);
|
||||
throw Argument_error(ss.str(), begin->m_loc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,24 +749,23 @@ katom_list Machine::apply_klammer(
|
||||
// remain in the klammer body substitution for final target-specific output.
|
||||
katom_list result(klammer.m_body[target].begin(), klammer.m_body[target].end());
|
||||
auto varmap = klammer.m_varmap[target];
|
||||
m_state.open_frame("Arguments for klammer " + q_(klammer.m_name));
|
||||
m_state.set(values, klammer.m_parameters);
|
||||
for (const auto& [name, indices] : varmap) {
|
||||
std::regex arg("\\*" + name + "\\*");
|
||||
for (auto i : indices) {
|
||||
result[i].m_text = std::regex_replace(result[i].m_text, arg, m_state.value(name));
|
||||
result[i].m_type = katom_t::text;
|
||||
}
|
||||
}
|
||||
// Escape target-specific characters (e.g. tex "&" -> "\&") in the writer
|
||||
// text of a GENERAL klammer's body. Runs BEFORE process_katoms/apply()
|
||||
// below expand the body, so that target-native markup pulled in by nested
|
||||
// klammers (e.g. nl.tex -> "\newline") is left untouched -- only this
|
||||
// klammer's own literal writer text is escaped here; nested klammers escape
|
||||
// theirs when they are applied in turn. Bodies from target-specific
|
||||
// definitions (m_body_generic[target] == false) are already in target form
|
||||
// and skipped. KTESC markers are idempotent, so text already escaped at the
|
||||
// top level passes through unchanged. Two kinds of body content are NOT
|
||||
// text of a GENERAL klammer's body. Runs BEFORE the *arg* substitution
|
||||
// below: an argument value is writer text already escaped at the top
|
||||
// level (KTESC markers, idempotent) plus final target markup from
|
||||
// klammers the writer nested in the argument, and neither may be escaped
|
||||
// here -- escaping after substitution swept both, so a general klammer
|
||||
// with a klammer-bearing argument emitted \textbackslash{}emph{...}
|
||||
// (fixed 2026-08-19; tst/escape_test.sh cases 26-29). A karg katom is
|
||||
// skipped by the type filter; a variable inside a mixed text katom
|
||||
// survives because no target declares '*' or identifier characters as
|
||||
// escapes. Also runs BEFORE process_katoms/apply() expand the body, so
|
||||
// that target-native markup pulled in by nested klammers (e.g. nl.tex ->
|
||||
// "\newline") is left untouched -- only this klammer's own literal writer
|
||||
// text is escaped here; nested klammers escape theirs when they are
|
||||
// applied in turn. Bodies from target-specific definitions
|
||||
// (m_body_generic[target] == false) are already in target form and
|
||||
// skipped. Two kinds of body content are NOT
|
||||
// writer text and must be skipped:
|
||||
// * ^'...'^ literal spans -- raw target markup the writer typed directly.
|
||||
// At this point they are typed literal_begin/literal_end with plain-text
|
||||
@@ -818,6 +817,16 @@ katom_list Machine::apply_klammer(
|
||||
}
|
||||
}
|
||||
}
|
||||
m_state.open_frame("Arguments for klammer " + q_(klammer.m_name));
|
||||
m_state.set(values, klammer.m_parameters);
|
||||
for (const auto& [name, indices] : varmap) {
|
||||
std::regex arg("\\*" + name + "\\*");
|
||||
for (auto i : indices) {
|
||||
result[i].m_text = std::regex_replace(
|
||||
result[i].m_text, arg, m_state.value(name, true, result[i].m_loc));
|
||||
result[i].m_type = katom_t::text;
|
||||
}
|
||||
}
|
||||
process_katoms(result, klammer.m_name);
|
||||
apply(m_klammers, result, target);
|
||||
m_state.close_frame();
|
||||
@@ -876,7 +885,18 @@ std::string Machine::run_phase_functions()
|
||||
// phase sees its predecessor's result in K_result.
|
||||
Eval E(*this, Locator());
|
||||
if (!f.empty() && f[0] != ':') {
|
||||
f += "(K_result)";
|
||||
// K reaches a phase only as a parameter: the state's
|
||||
// "class K" exists in the EVAL's globals (python_code()
|
||||
// defines it), but a phase function's body resolves names in
|
||||
// its own MODULE's globals. Every phase signature has
|
||||
// carried "K=None" for this since the calling convention was
|
||||
// created; the call never passed it, which surfaced when
|
||||
// txt_justify_blocks needed Target_txt_width (2026-08-22).
|
||||
// K_phase_call (defined in Eval_python's globals) passes K
|
||||
// only to a function that DECLARES a K parameter, so a
|
||||
// stdlib phase (string.capwords, whose second parameter is a
|
||||
// separator) keeps working.
|
||||
f = "K_phase_call(" + f + ", K_result, K)";
|
||||
}
|
||||
f = "@eval " + f + " @";
|
||||
auto katoms = katomize(line_split(f), "phase");
|
||||
@@ -916,7 +936,7 @@ std::string Machine::apply(const std::string& target_name, bool final_processing
|
||||
// Characters produced later by klammer bodies will not be escaped.
|
||||
// Skipped for sub-Machine apply() calls (e.g., from @eval), where the
|
||||
// text is already in target-specific form.
|
||||
auto target = m_targets.get(target_name, Locator());
|
||||
auto target = m_targets.get(target_name, Locator::none());
|
||||
if (escape_characters)
|
||||
escape_target_characters(target, m_katoms);
|
||||
|
||||
@@ -935,18 +955,24 @@ std::string Machine::apply(const std::string& target_name, bool final_processing
|
||||
if (++apply_count > apply_round_limit) {
|
||||
std::stringstream ss {};
|
||||
ss << "Klammer application did not reach a fixed point after "
|
||||
<< apply_round_limit << " rounds.\n"
|
||||
<< apply_round_limit << " rounds.\n\n"
|
||||
<< "Each round applies every klammer present; a klammer whose "
|
||||
<< "result contains further klammers starts another round.";
|
||||
throw Recursion_error(ss.str(), Locator(), false);
|
||||
throw Recursion_error(ss.str(), Locator());
|
||||
}
|
||||
}
|
||||
// Typographic transforms run on the katoms, not the joined string, so
|
||||
// that ^'...'^ literal content (katom_t::literal) stays verbatim --
|
||||
// Target::transform() skips literal katoms. Verbatim text produced by
|
||||
// an @eval renderer (@code, @c) is plain text by the time it is spliced
|
||||
// back; the SKS protects it with KTESC markers (hide_typographic() in
|
||||
// klammer_base.py), which are inert here and decode in resolve_escapes.
|
||||
if (final_processing) {
|
||||
target.transform(m_katoms);
|
||||
}
|
||||
m_result = to_string(m_katoms.begin(), m_katoms.end());
|
||||
|
||||
if (final_processing) {
|
||||
for (const auto& [old_str, new_str] : target.m_transforms) {
|
||||
m_result = string_replace(m_result, old_str, new_str);
|
||||
}
|
||||
m_result = target.resolve_escapes(m_result);
|
||||
m_result = run_phase_functions();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ void Option_set_registry::add(
|
||||
if (deftype.m_initial_type == katom_t::klammer_instance) {
|
||||
throw Definition_error(
|
||||
"The option set " + q_(name) + " is declared with \"::\", which takes its "
|
||||
"parameters from a \".k\" declaration. An option set IS a declaration: "
|
||||
"parameters from a \".k\" declaration. An option set is a declaration; "
|
||||
"it declares its parameters itself, after \":\".",
|
||||
begin->m_loc);
|
||||
}
|
||||
@@ -68,16 +68,16 @@ void Option_set_registry::add(
|
||||
throw Definition_error(
|
||||
"The option set " + q_(name) + " declares the positional "
|
||||
+ plural("parameter", static_cast<int>(names.size())) + " "
|
||||
+ join(names, ", ") + ".\n"
|
||||
+ join(names, ", ") + ".\n\n"
|
||||
"An option set declares only optional parameters -- names written with "
|
||||
"a leading \":\".",
|
||||
begin->m_loc, false);
|
||||
begin->m_loc);
|
||||
}
|
||||
if (parameters.m_optional.empty()) {
|
||||
throw Definition_error(
|
||||
"The option set " + q_(name) + " declares no parameters.\n"
|
||||
"The option set " + q_(name) + " declares no parameters.\n\n"
|
||||
"The form is: @@" + name + ".o :name.argtype default ... : <description> @@",
|
||||
begin->m_loc, false);
|
||||
begin->m_loc);
|
||||
}
|
||||
|
||||
// The members as written: these katoms are what is spliced into the
|
||||
@@ -201,14 +201,14 @@ std::string definition_name(const std::string& klammer_name, const std::string&
|
||||
std::stringstream ss {};
|
||||
if (option_sets.has(name) && target_name == Target_registry::optionset_name) {
|
||||
ss << "The option set " << q_(name) << " is used in the declaration of the option "
|
||||
<< "set " << q_(klammer_name) << ".\n"
|
||||
<< "set " << q_(klammer_name) << ".\n\n"
|
||||
<< "An option set is used only in the parameter list of a \".k\" declaration, "
|
||||
<< "so a set does not include another set: a klammer that needs two "
|
||||
<< "vocabularies names two sets, and each set stays a vocabulary that can "
|
||||
<< "be learned whole.";
|
||||
} else if (option_sets.has(name)) {
|
||||
ss << "The option set " << q_(name) << " is used in the parameter list of "
|
||||
<< definition_name(klammer_name, target_name) << ".\n"
|
||||
<< definition_name(klammer_name, target_name) << ".\n\n"
|
||||
<< "An option set may be used only in the parameter list of a \".k\" "
|
||||
<< "declaration, which is where a klammer's interface is declared once "
|
||||
<< "for all of its targets. Declare "
|
||||
@@ -216,15 +216,15 @@ std::string definition_name(const std::string& klammer_name, const std::string&
|
||||
<< "an instance (\"::\"), which inherits the declared parameters.";
|
||||
} else {
|
||||
ss << "The klammer " << q_(name) << " is applied in the parameter list of "
|
||||
<< definition_name(klammer_name, target_name) << ".\n"
|
||||
<< definition_name(klammer_name, target_name) << ".\n\n"
|
||||
<< "A klammer application in a parameter list is not allowed: it is "
|
||||
<< "resolved after the parameters are parsed, so the parameter list it "
|
||||
<< "was meant to contribute is not there when the list is read. An "
|
||||
<< "option set, declared with a \".o\" target, is how parameters are "
|
||||
<< "shared between klammers. Declared option sets: "
|
||||
<< "shared between klammers.\n\nDeclared option sets: "
|
||||
<< option_sets.available() << ".";
|
||||
}
|
||||
throw Definition_error(ss.str(), loc, false);
|
||||
throw Definition_error(ss.str(), loc);
|
||||
}
|
||||
|
||||
// The default written for each member at the use site:
|
||||
@@ -237,10 +237,10 @@ std::map<std::string, std::string> use_site_defaults(
|
||||
if (!positional.empty() || active(rest)) {
|
||||
throw Definition_error(
|
||||
"The use of the option set " + q_(option_set.m_name) +
|
||||
" gives a value that is not an option.\n"
|
||||
" gives a value that is not an option.\n\n"
|
||||
"A set's names and types are fixed where the set is declared; only a "
|
||||
"default may be given where it is used, written as \":name value\".",
|
||||
begin->m_loc, false);
|
||||
begin->m_loc);
|
||||
}
|
||||
std::map<std::string, std::string> defaults {};
|
||||
for (const auto& option : optional) {
|
||||
@@ -248,17 +248,17 @@ std::map<std::string, std::string> use_site_defaults(
|
||||
if (name.size() + 1 != option[0].m_text.size()) {
|
||||
throw Definition_error(
|
||||
"The use of the option set " + q_(option_set.m_name) + " gives a type for \":"
|
||||
+ name + "\".\n"
|
||||
+ name + "\".\n\n"
|
||||
"A set's names and types are declared where the set is; only a default "
|
||||
"may be given where it is used.",
|
||||
option[0].m_loc, false);
|
||||
option[0].m_loc);
|
||||
}
|
||||
const Parameter* member = option_set.find(name);
|
||||
if (member == nullptr) {
|
||||
throw Definition_error(
|
||||
"The option set " + q_(option_set.m_name) + " has no parameter \":"
|
||||
+ name + "\".\n It declares: " + option_set.member_names(),
|
||||
option[0].m_loc, false);
|
||||
+ name + "\". It declares:\n " + option_set.member_names(),
|
||||
option[0].m_loc);
|
||||
}
|
||||
if (defaults.count(name) > 0) {
|
||||
throw Definition_error(
|
||||
@@ -297,7 +297,7 @@ option_set_uses_t expand_option_sets(
|
||||
throw Definition_error(
|
||||
"The parameter \":" + name + "\" of " + q_(klammer_name) +
|
||||
" is declared twice:\n " + previous->second + "\n " + from,
|
||||
loc, false);
|
||||
loc);
|
||||
}
|
||||
origin[name] = from;
|
||||
};
|
||||
|
||||
@@ -24,16 +24,21 @@ void Target::add_transforms(const string_pairs& transforms)
|
||||
}
|
||||
}
|
||||
|
||||
// The typographic transform pass, run by Machine::apply() at final
|
||||
// processing. Per katom rather than over the joined result string, so
|
||||
// that ^'...'^ literal content (katom_t::literal) is never transformed --
|
||||
// verbatim text must show the characters the writer typed. A transform
|
||||
// source therefore cannot match across a katom boundary, which is the
|
||||
// correct reading: two hyphens separated by a klammer application were
|
||||
// separated by the writer and are not a dash.
|
||||
void Target::transform(katom_list& katoms) const
|
||||
{
|
||||
(void)K::log(3);
|
||||
std::for_each(
|
||||
katoms.begin(), katoms.end(),
|
||||
[this] (Katom& k) {
|
||||
// std::cout << "transform: " << k << "\n";
|
||||
if (k.m_type != katom_t::literal) {
|
||||
for (const auto& [a, b] : this->m_transforms) {
|
||||
// std::cout << " " << a << right_arrow << b << "\n";
|
||||
k.m_text = string_replace(k.m_text, a, b);
|
||||
}
|
||||
}
|
||||
@@ -67,6 +72,14 @@ void Target::add_escapes(const std::string& escape_spec)
|
||||
}
|
||||
}
|
||||
|
||||
void Target::add_resolves(const std::string& resolve_spec)
|
||||
{
|
||||
auto words = word_split(resolve_spec);
|
||||
for (size_t i = 0; i + 1 < words.size(); i += 2) {
|
||||
m_resolves.push_back({words[i], words[i+1]});
|
||||
}
|
||||
}
|
||||
|
||||
std::string Target::escape_marker(const std::string& ch)
|
||||
{
|
||||
std::stringstream ss {};
|
||||
@@ -94,11 +107,16 @@ std::string Target::unescape_text(std::string text) const
|
||||
std::string Target::resolve_escapes(std::string text) const
|
||||
{
|
||||
// Target-declared escapes first (marker -> declared replacement), then
|
||||
// the generic decode for the remaining markers (marker -> the character
|
||||
// itself: quoted Klammertext specials and literal-span content).
|
||||
// the resolution-only entries (:resolve -- how a QUOTED character
|
||||
// renders here), then the generic decode for the remaining markers
|
||||
// (marker -> the character itself: quoted punctuation and literal-span
|
||||
// content).
|
||||
for (const auto& [ch, repl] : m_escapes) {
|
||||
text = string_replace(text, escape_marker(ch), repl);
|
||||
}
|
||||
for (const auto& [ch, repl] : m_resolves) {
|
||||
text = string_replace(text, escape_marker(ch), repl);
|
||||
}
|
||||
return ktesc_resolve(text);
|
||||
}
|
||||
|
||||
@@ -130,6 +148,29 @@ std::string ktesc_resolve(std::string text)
|
||||
return text;
|
||||
}
|
||||
|
||||
bool hide_quoted_punctuation(std::string& s)
|
||||
{
|
||||
// ASCII punctuation, EXCEPT "'" -- ^' opens a ^'...'^ literal span, the
|
||||
// one documented exception to the rule (a literal apostrophe is ^0027^).
|
||||
// Hand-rolled scan: no std::regex, this can run over large text.
|
||||
static const std::string punct = R"pct(!"#$%&()*+,-./:;<=>?@[\]^_`{|}~)pct";
|
||||
if (s.find('^') == std::string::npos) return false;
|
||||
std::string result {};
|
||||
bool changed = false;
|
||||
for (size_t i = 0; i < s.size(); ++i) {
|
||||
if (s[i] == '^' && i + 1 < s.size()
|
||||
&& punct.find(s[i + 1]) != std::string::npos) {
|
||||
result += Target::escape_marker(std::string(1, s[i + 1]));
|
||||
++i;
|
||||
changed = true;
|
||||
} else {
|
||||
result += s[i];
|
||||
}
|
||||
}
|
||||
if (changed) s = result;
|
||||
return changed;
|
||||
}
|
||||
|
||||
std::string hide_structural_characters(const std::string& s)
|
||||
{
|
||||
std::string result {};
|
||||
@@ -145,7 +186,26 @@ std::string hide_structural_characters(const std::string& s)
|
||||
void Target::add_after_apply(const std::string& function_specs)
|
||||
{
|
||||
for (const auto& f : regex_split(function_specs, std::regex(R"(\s+;\s+)"), true)) {
|
||||
// msg() << "Add " << m_name << " after-apply: " << f << "\n";
|
||||
// A bare spec is ONE Python name (module.function): whitespace
|
||||
// inside it means two specs were written without the " ; "
|
||||
// separator, and the glued call would otherwise fail only at render
|
||||
// time, as a Python SyntaxError located at "phase" rather than at
|
||||
// this declaration (found 2026-08-22, the first time a target
|
||||
// declared two phases). Mode-tagged specs (":cpp <library>
|
||||
// <function>") are exempt: the library is a filename, and filenames
|
||||
// may contain spaces -- which is exactly why the list separator is
|
||||
// ";" rather than whitespace.
|
||||
if (!f.empty() && f[0] != ':'
|
||||
&& f.find_first_of(" \t\n") != std::string::npos) {
|
||||
throw Definition_error(
|
||||
"The :after_apply phase \"" + f + "\" contains whitespace. "
|
||||
"A bare phase is a single Python name (module.function), and "
|
||||
"several phases are separated by \" ; \":\n"
|
||||
" :after_apply first.phase ; second.phase\n"
|
||||
"(A mode-tagged phase -- \":cpp <library> <function>\" -- may "
|
||||
"contain spaces; its library is a filename.)",
|
||||
m_loc);
|
||||
}
|
||||
m_after_apply.push_back(f);
|
||||
}
|
||||
}
|
||||
|
||||
19
mac/target.h
19
mac/target.h
@@ -24,6 +24,7 @@ public:
|
||||
void transform(std::vector<Katom>& katoms) const;
|
||||
|
||||
void add_escapes(const std::string& escape_spec);
|
||||
void add_resolves(const std::string& resolve_spec);
|
||||
std::string escape_text(std::string text) const;
|
||||
std::string unescape_text(std::string text) const;
|
||||
std::string resolve_escapes(std::string text) const;
|
||||
@@ -40,6 +41,13 @@ public:
|
||||
Locator m_loc {};
|
||||
std::vector<std::pair<std::string, std::string>> m_transforms {};
|
||||
std::vector<std::pair<std::string, std::string>> m_escapes {};
|
||||
// Resolution-only entries (:resolve): how a QUOTED character renders in
|
||||
// this target. The resolve half of :escape without the escape half --
|
||||
// needed where the raw character must stay untouched in writer text
|
||||
// (tex cannot escape "-" without destroying the --- convention) but the
|
||||
// quoted character must not decode to its raw self (a decoded -- would
|
||||
// re-form TeX's dash ligature).
|
||||
std::vector<std::pair<std::string, std::string>> m_resolves {};
|
||||
// Argtype_registry m_argtypes {};
|
||||
|
||||
|
||||
@@ -60,3 +68,14 @@ std::string ktesc_resolve(std::string text);
|
||||
// sub-Machines and the @eval result read-back. Resolved by ktesc_resolve()
|
||||
// at final processing.
|
||||
std::string hide_structural_characters(const std::string& s);
|
||||
|
||||
// Replace each ^P pair in s -- "^" before any ASCII punctuation character
|
||||
// except "'" (which opens a ^'...'^ literal span) -- with the KTESC marker
|
||||
// of P: "^" before a punctuation character quotes it, uniformly, not only
|
||||
// the six Klammertext specials. A letter or digit is never special, so
|
||||
// ^<letter-or-digit> keeps its existing meanings (diacritics, mnemonics,
|
||||
// ^UUUU^ code points). Returns whether anything was replaced. Called from
|
||||
// hide_special_katoms() on writer-text katoms OUTSIDE definition and
|
||||
// @eval/@read/@cond spans -- code keeps its carets (grep '^-' must reach
|
||||
// the shell intact), the same skip set as the quoted-special hiding.
|
||||
bool hide_quoted_punctuation(std::string& s);
|
||||
|
||||
@@ -14,7 +14,7 @@ std::string Target_registry::general_name = "*";
|
||||
std::string Target_registry::optionset_name = "o";
|
||||
|
||||
Target_registry::Target_registry()
|
||||
: m_parameters(Parameter_set("name | desc :after_apply :after_write :includes :escape | transforms.rest"))
|
||||
: m_parameters(Parameter_set("name | desc :after_apply :after_write :includes :escape :resolve | transforms.rest"))
|
||||
{
|
||||
// Registration order is display order. The two that declare an interface
|
||||
// come first -- "k" a klammer's, "o" an option set's -- and then "*", the
|
||||
@@ -47,14 +47,25 @@ void Target_registry::add(std::vector<Katom>::iterator begin, std::vector<Katom>
|
||||
Target target(values["name"], values["desc"], begin->m_loc);
|
||||
target.add_transforms(values["transforms"]);
|
||||
target.add_escapes(values["escape"]);
|
||||
target.add_resolves(values["resolve"]);
|
||||
target.add_after_apply(values["after_apply"]);
|
||||
target.m_includes = word_split(values["includes"]);
|
||||
// Inherit escapes from included targets
|
||||
// Inherit escapes AND typographic transforms from included targets: an
|
||||
// including target renders through the included one's syntax (pdf
|
||||
// through tex), so both tables apply there too. Inherited entries are
|
||||
// appended after the including target's own, so a declaration can
|
||||
// override an inherited pair by declaring its source first.
|
||||
for (const auto& included : target.m_includes) {
|
||||
if (m_targets.count(included)) {
|
||||
for (const auto& esc : m_targets[included].m_escapes) {
|
||||
target.m_escapes.push_back(esc);
|
||||
}
|
||||
for (const auto& tr : m_targets[included].m_transforms) {
|
||||
target.add_transform(tr.first, tr.second);
|
||||
}
|
||||
for (const auto& res : m_targets[included].m_resolves) {
|
||||
target.m_resolves.push_back(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Registration (and the previous-definition check) goes through
|
||||
@@ -79,7 +90,7 @@ void Target_registry::check_for_previous_definition(const std::string& name, con
|
||||
if (has(name)) {
|
||||
const Target& current = m_targets.at(name);
|
||||
throw Target_error("Target \"" + name + "\" is already defined:\n " + current.m_loc.desc(),
|
||||
loc, false);
|
||||
loc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
|
||||
|
||||
/*
|
||||
std::string m_parameters_spec {"name | desc :after_apply :after_write :includes | transforms.rest"};
|
||||
std::string m_parameters_spec {"name | desc :after_apply :after_write :includes :escape :resolve | transforms.rest"};
|
||||
Parameter_set m_parameters =
|
||||
katomize(line_split(m_parameters_spec), Locator().str());
|
||||
*/
|
||||
|
||||
62
mac/util.cpp
62
mac/util.cpp
@@ -226,14 +226,66 @@ std::string justify_string(const std::string& s, unsigned int width=80, bool fre
|
||||
return result;
|
||||
}
|
||||
|
||||
// The error-message formatting contract (2026-08-21): a message carries no
|
||||
// decisions about line breaks EXCEPT by indentation.
|
||||
// * a line beginning with whitespace is VERBATIM -- an example, a pattern,
|
||||
// a signature, a list entry -- emitted untouched: no folding, no
|
||||
// wrapping, no "~" substitution (a pattern may contain a literal ~);
|
||||
// * blank lines separate blocks (runs collapse to one);
|
||||
// * everything else is prose: consecutive lines fold into one paragraph
|
||||
// and are wrapped to the width.
|
||||
// So prose stays machine-wrapped however the source hand-wraps it, and the
|
||||
// one legitimate exception is marked in the one place it cannot be missed.
|
||||
// This replaced a per-call do_justify flag on the Error constructors, whose
|
||||
// decision lived apart from the text it governed. Line-based by hand: no
|
||||
// std::regex over the whole message (the ambiguous-alternation hazard).
|
||||
std::string justify(
|
||||
const std::string& input_text, unsigned int text_width, unsigned int margin_width)
|
||||
{
|
||||
std::string result {};
|
||||
text_width -= margin_width;
|
||||
for (const std::string& par : split_into_paragraphs(trim(input_text))) {
|
||||
result += justify_string(par, text_width) + "\n\n";
|
||||
strings_t lines {};
|
||||
{
|
||||
const std::string text = trim(input_text);
|
||||
std::string::size_type from = 0;
|
||||
while (from <= text.size()) {
|
||||
auto nl = text.find('\n', from);
|
||||
if (nl == std::string::npos) {
|
||||
lines.push_back(text.substr(from));
|
||||
break;
|
||||
}
|
||||
lines.push_back(text.substr(from, nl - from));
|
||||
from = nl + 1;
|
||||
}
|
||||
}
|
||||
std::string result {};
|
||||
strings_t prose {};
|
||||
bool pending_blank = false;
|
||||
auto emit = [&](const std::string& block) {
|
||||
if (!result.empty()) {
|
||||
result += pending_blank ? "\n\n" : "\n";
|
||||
}
|
||||
result += block;
|
||||
pending_blank = false;
|
||||
};
|
||||
auto flush_prose = [&]() {
|
||||
if (!prose.empty()) {
|
||||
emit(justify_string(join(prose, " "), text_width));
|
||||
prose.clear();
|
||||
}
|
||||
};
|
||||
for (const std::string& line : lines) {
|
||||
if (trim(line).empty()) { // block separator
|
||||
flush_prose();
|
||||
pending_blank = !result.empty();
|
||||
} else if (line[0] == ' ' || line[0] == '\t') { // verbatim line
|
||||
flush_prose();
|
||||
emit(trim_right(line));
|
||||
} else { // prose
|
||||
prose.push_back(trim(line));
|
||||
}
|
||||
}
|
||||
flush_prose();
|
||||
result += "\n";
|
||||
if (margin_width > 0) {
|
||||
result = add_margin(result, margin_width);
|
||||
}
|
||||
@@ -367,8 +419,8 @@ std::vector<std::pair<std::string, std::string>> environment_variables(bool allo
|
||||
auto parts = regex_split(environ[i], std::regex("="), true);
|
||||
if (!allow_empty_definitions && parts.size() < 2) {
|
||||
throw Internal_error(
|
||||
"Incorrect environment variable format:\n" + std::string(environ[i]),
|
||||
Locator(), false);
|
||||
"Incorrect environment variable format:\n " + std::string(environ[i]),
|
||||
Locator());
|
||||
}
|
||||
std::string name = parts[0];
|
||||
parts.erase(parts.begin());
|
||||
|
||||
Reference in New Issue
Block a user