Files
klammertext/mac/katom_rewrite_rules.h
T

144 lines
6.7 KiB
C++
Raw Normal View History

#pragma once
#include <regex>
#include <string>
#include <vector>
#include "ktype.h"
// A regex kept beside the pattern it was built from, so a rule can be
// DISPLAYED as written (kdesc --rewrite) as well as applied. It lived in
// ktype.h until the rule table moved here, and nothing else ever used it.
class Rgx {
public:
explicit Rgx(std::string pattern)
: m_pattern(pattern)
, m_regex(std::regex(pattern)) {}
std::string m_pattern;
std::regex m_regex;
};
// THE KATOM REWRITE RULES: what divides text that is not yet a katom.
// Live since 2026-09-02. The table moved here from ktype.h, where it had
// been a flat list of tuples, and gained a category per rule; its companion
// is katomize.{h,cpp}, which does the dividing.
//
// WHY CATEGORIES. The rule list is a PRIORITY list: the first rule whose
// rewrite changes the word fires, its parts are re-katomized, and the loop
// RETURNS. Every later rule that would also have matched is preempted --
// and that preemption is the interaction the flat list states nowhere.
// Measured over every tracked .k and .kt (dbg/rule_conflicts.cpp): 1,971
// words reach the rules and there are 806 preemptions.
//
// Two facts from that measurement make the categories worth having:
//
// * TWO THIRDS OF THE INTERACTION IS LOCAL. 532 of the 806 preemptions
// are between rules of the SAME category. Grouping puts those pairs
// next to each other, where the reader can see them.
//
// * THE CATEGORY RELATION IS A STRICT ORDER. Cross-category preemption
// has no cycles, and -- this is the surprise -- the current declaration
// order already violates it nowhere. The order in ktype.h was arrived
// at one precedent at a time and is nevertheless a valid topological
// sort. Categories therefore DOCUMENT the existing order rather than
// changing it, which is why this file is expected to be behaviorally
// identical to the current katomizer. Any divergence the comparison
// reports is a transcription error here, not a design difference.
//
// The order between categories, and how much of it is forced:
//
// caret FORCED first -- it preempts every other category
// bar free (only "after caret" is forced)
// removal_span before removal_line
// karg before klammer
// punctuation before klammer
// desugar free (only "after caret" is forced)
// klammer before removal_line
// spacing before removal_line
// removal_line FORCED last
//
// Six constraints in place of thirty positions. Note that removal_span
// (#[, ]#, #-) and removal_line (a bare #) are one CHARACTER and two
// categories: the specific forms are decided before the general one, which
// is longest match stated as an ordering.
enum class rule_category {
caret, // ^| ^' '^ ^X ^...^ ^Xy -- the quoting and character forms
bar, // || | -- argument separators
removal_span, // #[ ]# #- -- delimited removal and whitespace removal
karg, // *name* -- argument substitution in a body
punctuation, // @ before . , ; : ? ! ( ) '
desugar, // @name-arg -- the abbreviated application
klammer, // @name@ and klammers embedded in a word
spacing, // #+N -- added whitespace
removal_line, // # -- remove to end of line
};
std::string category_name(rule_category category);
// The category sequence the dispatch follows. This is the whole of the
// cross-category ordering decision, in one place.
const std::vector<rule_category>& category_order();
struct Rewrite_rule {
rule_category category;
std::string description;
Rgx pattern;
std::string replacement;
// Non-null for a rule whose rewrite is not a plain regex_replace. One
// rule needs it: the abbreviated application @name-a-b, which today is
// a hand-cut special case at the TOP of expand_compound_katom() rather
// than an entry in the table at all. Making it a rule in the desugar
// category is safe -- no rule of an earlier category can match a word
// made only of "@", word characters and hyphens -- and it removes the
// oddity that the shortcut is currently reported by "kdiag --rewrite"
// as the doing of whichever rule happened to be first in the list.
std::string (*special)(const std::string& word) = nullptr;
};
// In DECLARATION order, matching ktype.h's katom_rewrite_rules index for
// index, so the two can be diffed. Dispatch order is a separate question:
// see rule_order() below.
extern const std::vector<Rewrite_rule> rewrite_rules;
enum class rewrite_order {
// The order written in ktype.h. Categories are annotation only, and
// the behavior is by construction identical to the current katomizer.
as_declared,
// Rules grouped so each category is contiguous, in category_order().
// Differs from as_declared by the position of ONE rule -- "single
// argument for special character", which moves from 15 to just after
// the punctuation rules -- and that rule is one of the eight that never
// fire. So this too is expected to be identical, and the comparison is
// what turns "expected" into "measured".
by_category,
};
// Indices into rewrite_rules, in dispatch order. Computed once.
const std::vector<size_t>& rule_order(rewrite_order order);
struct Rewrite_result {
bool matched = false;
size_t rule = 0; // index into rewrite_rules
std::string text {}; // the rewritten word
};
// The first rule that USEFULLY rewrites word: it changes the string, and no
// part of the result is the whole input. That second condition is the
// termination guard -- a rule that only pads a word with whitespace hands
// back the word it was given, and recursing on it exhausts the stack.
Rewrite_result find_rewrite(const std::string& word, rewrite_order order = rewrite_order::by_category);
// Every rule that would usefully rewrite word, in dispatch order. The
// first is what find_rewrite() returns; the rest are preempted by it.
std::vector<size_t> matching_rules(const std::string& word, rewrite_order order = rewrite_order::by_category);
// The executable form of the claim this file rests on: whatever fires, no
// rule of an EARLIER category also matched. When that holds for every
// word, the category order is a complete account of cross-category
// interaction and a reader need only understand one category at a time.
// Returns true when it holds; otherwise fills why with a description.
// This is what a flat priority list cannot offer -- it has no boundary to
// state an invariant across.
bool category_order_holds(const std::string& word, std::string& why,
rewrite_order order = rewrite_order::by_category);