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:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user