Files
klammertext/mac/katomize.cpp
T

162 lines
6.4 KiB
C++
Raw Normal View History

// katomize.cpp -- the katomization pipeline over the categorized rules.
// See katomize.h for what this experiment is, and katom_rewrite_rules.h
// for what
// was measured to justify the categories.
//
// A faithful re-expression of the former make_katoms_from_word() and its
// two callers, not a redesign -- proved identical over every tracked .k and
// .kt before it replaced them. What changed is where the decisions live:
//
// * The termination guard is inside find_rewrite(), as part of what it
// means for a rule to match, instead of a condition the loop has to
// remember. A caller cannot forget it.
// * The abbreviated application is a RULE in the desugar category rather
// than a special case above the loop, so it can be named when it fires
// and counted when it does not.
// * The rule loop is gone from here entirely: this file asks
// find_rewrite() a question and acts on the answer. Which rule, and
// why that one, is katom_rewrite_rules.cpp's business.
//
// What deliberately did NOT change: the katom types are consulted first and
// unchanged, the recursion structure is the same, the character position
// handed to each katom is that of the text it came from (as in the
// original), and text no rule can divide is still an unparsed katom rather
// than an error.
#include <algorithm>
#include <iostream>
#include "katomize.h"
#include "katom_matching.h"
#include "log.h"
#include "show.h"
#include "util.h"
namespace {
// The katom type this text IS, or nullptr. Whole-text match, exactly as
// the current katomizer does it -- the first type in declaration order
// whose pattern matches the entire text.
const Ktype* whole_text_type(const std::string& text)
{
auto found = std::find_if(katom_types.begin(), katom_types.end(),
[&](const Ktype& k) { return k.match(text); });
return found == katom_types.end() ? nullptr : &*found;
}
} // namespace
Katom source_katom(const std::string& text, katom_t type, const Locator& loc)
{
Katom k(text, type, loc);
if (type == katom_t::special && !k.m_text.empty() && k.m_text[0] == '^')
k.m_text.erase(0, 1);
return k;
}
katom_list katomize_by_rewriting(const std::string& text, const std::string& source, int line, int chr,
rewrite_order order)
{
Locator loc(source, line, chr);
if (const Ktype* type = whole_text_type(text))
return { source_katom(text, type->m_type, loc) };
Rewrite_result rewrite = find_rewrite(text, order);
if (rewrite.matched) {
const Rewrite_rule& rule = rewrite_rules[rewrite.rule];
if (show_rewrite_rules) {
std::cout << loc << " Rewrite [" << category_name(rule.category) << "] ("
<< rule.description << "): " << rule.pattern.m_pattern
<< " " << right_arrow << " " << rewrite.text << "\n";
}
katom_list result {};
for (const std::string& piece : word_split(rewrite.text)) {
if (piece.empty())
continue;
katom_list ks = katomize_by_rewriting(piece, source, line, chr, order);
result.insert(result.end(), ks.begin(), ks.end());
}
return result;
}
// No katom type, no katom rewrite rule that makes progress. The
// warning is deferred to warn_unparsed_katoms(), after removal and
// literal marking, so text inside a comment never warns.
Katom unparsed(text, katom_t::word, loc);
unparsed.m_unparsed = true;
return { unparsed };
}
katom_list katomize_text(const std::string& text, const std::string& source, int line,
rewrite_order order)
{
std::string s = string_replace(text, "\r", "");
s = string_replace(s, "\t", " ");
// Whitespace is preserved, so the split keeps each single space and
// newline as a piece in its own right -- they become katoms like any
// other text. Same regex the current katomizer splits with.
static const std::regex split_rgx(R"([ ]|[\n]|[^\s]+|.+)");
katom_list result {};
int position = 0;
for (auto it = std::sregex_iterator(s.begin(), s.end(), split_rgx);
it != std::sregex_iterator(); ++it) {
for (Katom& k : katomize_by_rewriting(it->str(), source, line, position, order)) {
k.m_loc = Locator(source, line, position);
result.push_back(k);
position += static_cast<int>(k.m_src.size());
}
}
return result;
}
katom_list katomize_text_by_matching(const std::string& text, const std::string& source, int line)
{
return expand_abbreviations(katomize_by_matching(text, source, line));
}
katom_list katomize_lines(const strings_t& lines, const std::string& source, rewrite_order order)
{
katom_list result {};
int number = 0;
for (const std::string& line : lines) {
katom_list ks = katomize_text(line + "\n", source, ++number, order);
result.insert(result.end(), ks.begin(), ks.end());
}
if (!result.empty())
result.pop_back(); // the newline added to the last line
return result;
}
void warn_unparsed_katoms(katom_list& katoms, bool warn)
{
for (Katom& k : katoms) {
if (!k.m_unparsed)
continue;
if (!warn) {
k.m_unparsed = false;
continue;
}
if (k.m_type != katom_t::ignored
&& k.m_type != katom_t::replaced
&& k.m_type != katom_t::literal) {
// One of the two surviving WARNINGS (the commands otherwise treat
// an anomaly as an error and stop -- see the output policy in
// CLAUDE.md). It stays a warning because the judgment is a
// HEURISTIC, not exact: the @eval read-back legitimately fails to
// katomize raw target markup, which is why m_warn_unparsed exists.
// A heuristic that halts turns every false positive into a blocker
// on a document that would otherwise render. Promote it to an
// error when the judgment becomes exact.
//
// Through warning(), not a raw std::cerr, so every warning the
// commands issue has one format.
warning("Word not parsed:\n " + k.m_text
+ "\nTo include a punctuation character literally, put "
"\"^\" before it.",
k.m_loc);
k.m_unparsed = false;
}
}
}