#pragma once #include #include #include #include #include #include inline const std::string at_s {"@"}; inline const std::string at2_s { "@@" }; inline const std::string at3_s { "@@@" }; inline const std::string bar_s { "|" }; inline const std::string open_s { R"(\()" }; inline const std::string close_s { R"(\))" }; inline const std::string karg_s { "*" }; inline const std::string read_s { "read" }; inline const std::string eval_s { "eval" }; inline const std::string cond_s { "cond" }; inline const std::string lit_s { "lit" }; inline const std::string hat_s { "^" }; inline const std::string ignore_line_s { "#" }; inline const std::string ignore_begin_s { R"(#\[)" }; inline const std::string ignore_end_s { R"(\]#)" }; inline const std::string ignore_rest_s { "##" }; inline const std::string ws_remove_s { "#-" }; inline const std::string ws_space_s { R"(#\+\d*)" }; inline const std::string ws_newline_s { R"(#/\d*)" }; // const std::string kname = "[a-zA-Z]+[a-zA-Z0-9_.]*"; // const std::string dname = "[a-zA-Z]+[a-zA-Z0-9_]*"; // const std::string k_name = R"([^^@#|\:]+)"; inline const std::string definition_name = R"([a-zA-Z][a-zA-Z0-9_.]*)"; // The name at the START of a definition may carry a comma-separated list of // targets ("@@table.html,tex"), so its katom runs through commas. This is // deliberately looser than the grammar: katomization's job is to delimit // the katom, and parse_name() in klammer.cpp validates the list, where a // malformed one can be reported with its location. The comma stays out of // definition_name itself -- that pattern also delimits klammer APPLICATIONS, // option names and "*arg*" variables, where a comma is ordinary writer text. // "*" is here for the same reason: "@@date.*" declares a klammer for every // target explicitly, and "*" is a Klammertext special everywhere else. inline const std::string definition_begin_name = R"([a-zA-Z][a-zA-Z0-9_.,*]*)"; enum class katom_t { space, word, newline, text, apply_begin, apply_end, bar, double_bar, option_name, read_begin, eval_begin, cond_begin, define_begin, define_end, klammer_definition, klammer_instance, klammer_override, klammer_default, karg, machine_begin, machine_end, ignore_line, ignore_begin, ignore_end, ignore_rest, ws_remove, ws_space, ws_newline, special, nonascii, literal_begin, literal_end, ws_added, literal, eval_result, replaced, ignored, }; class Ktype; inline std::map katom_type_names {}; inline std::vector katom_type_list {}; class Ktype { public: // Ktype() = default; Ktype(katom_t type, std::string name, std::string pattern, bool use_equal = false) : m_type(type), m_name(name), m_pattern(pattern), m_use_equal(use_equal), m_rgx(std::regex(pattern)) { katom_type_list.push_back(type); katom_type_names.insert({m_type, m_name}); } // A description used to be the fourth argument. A string literal there // would now convert to bool and silently set use_equal, so it is refused. Ktype(katom_t, std::string, std::string, const char*) = delete; bool match(const std::string& s) const { return m_use_equal ? s == m_pattern : std::regex_match(s, m_rgx); } katom_t m_type; std::string m_name; std::string m_pattern; bool m_use_equal; std::regex m_rgx; }; inline const std::vector katom_types { Ktype(katom_t::space, "space", " ", false), Ktype(katom_t::word, "word", "[a-zA-Z]+", false), Ktype(katom_t::newline, "newline", "\n", false), Ktype(katom_t::bar, "bar", "\\|", false), Ktype(katom_t::double_bar, "double-bar", "\\|\\|", false), Ktype(katom_t::read_begin, "read-begin", at_s+read_s, false), Ktype(katom_t::eval_begin, "eval-begin", at_s+eval_s, false), Ktype(katom_t::cond_begin, "cond-begin", at_s+cond_s, false), Ktype(katom_t::apply_begin, "apply-begin", at_s + definition_name), Ktype(katom_t::apply_end, "apply-end", "(" + definition_name + ")?" + at_s), Ktype(katom_t::option_name, "option-name", ":" + definition_name), Ktype(katom_t::define_begin, "define-begin", at2_s + definition_begin_name), Ktype(katom_t::define_end, "define-end", "(" + definition_name + ")?" + at2_s), Ktype(katom_t::klammer_default, "klammer-default", "::::"), Ktype(katom_t::klammer_override, "klammer-override", ":::"), Ktype(katom_t::klammer_definition, "klammer-definition", ":"), Ktype(katom_t::klammer_instance, "klammer-instance", "::"), Ktype(katom_t::karg, "klammer-arg", "\\*" + definition_name +"\\*"), Ktype(katom_t::machine_begin, "machine-begin", at3_s + definition_name), Ktype(katom_t::machine_end, "machine-end", "(" + definition_name + ")?" + at3_s), Ktype(katom_t::ignore_begin, "ignore-begin", ignore_begin_s), Ktype(katom_t::ignore_end, "ignore-end", ignore_end_s), Ktype(katom_t::ignore_rest, "ignore-rest", ignore_rest_s), Ktype(katom_t::ignore_line, "ignore-line", ignore_line_s), Ktype(katom_t::ws_remove, "ws-remove", ws_remove_s), Ktype(katom_t::ws_space, "ws-space", ws_space_s), Ktype(katom_t::ws_newline, "ws-newline", ws_newline_s), Ktype(katom_t::special, "special-char", R"(\^[@|#^:*])"), //Ktype(katom_t::nonascii, "non-ascii-char", R"(\^\w(?:.|\^))", "Non-ASCII character"), // The second alternative is the "^...^" delimited form: a code point // (^263A^, 4-6 hex digits) OR a mnemonic (^s^, ^oe^). Its character // class is [A-Za-z0-9], not [A-Fa-f0-9], because a mnemonic is not // hex: "^oe^" matched NEITHER alternative and so was not a katom at // all, fell through to the "non-diacritic non-ascii" rewrite rule, // which pads it with whitespace and hands back the same single word -- // and make_katoms_from_word() then recursed on the identical string // until the stack died (SIGSEGV, no message). The two entries that // appeared to work, ^ae^ and ^AE^, did so only because "ae" happens to // be hex digits; they were being katomized as CODE POINTS and rendered // correctly only because encode() runs process_extended_latin() before // process_unicode_codepoint(). Which of the two a form means is // decided there, by the tables, not here -- katomization's job is only to // recognize the shape. A shape that resolves to no character at all is // a located error, raised at the end of encode(). Ktype(katom_t::nonascii, "non-ascii-char", R"((\^(\w(?:.|\^)))|(\^[A-Za-z0-9]{1,6}\^))"), // Ktype(katom_t::word, "word", R"([a-z][a-z0-9_]*)", "Lower-case letters, numbers, or underscore"), // Ktype(katom_t::text, "text", Ktype(katom_t::literal_begin, "literal-begin", R"(\^')"), Ktype(katom_t::literal_end, "literal-end", R"('\^)"), Ktype(katom_t::text, "text", R"([^^@#|]+)"), // spaces, @, #, |, or ^, Ktype(katom_t::ws_added, "ws-added", " or \\n"), Ktype(katom_t::literal, "literal", ""), Ktype(katom_t::eval_result, "eval-result", ""), Ktype(katom_t::replaced, "replaced", ""), Ktype(katom_t::ignored, "ignored", ""), // Ktype(katom_t::undefined, "undefined", "", "Undefined pattern") }; // The human-facing description of the katom types, and the ONLY one: class // Ktype above holds what the program needs (type, name, regex), this holds // what a reader is told, and katom_t joins them. The pattern and the // description are Klammertext, rendered by katom_descriptions() in // ref/reflect.cpp (kdesc --katoms; a Handbook chapter). A title is // Klammertext too, without styling -- the generator styles every title alike. // // The ORDER is the display order, from what every author meets to what only // a programmer does, and it is also the order of the katom_t enumerators: // the table's Index column is the enum value, which is the subscript kdiag // prints. tst/ktype_test.cpp asserts both halves of the tie -- every type // in katom_types described exactly once, and the indices increasing. struct Katom_type_description { katom_t m_type; std::string m_pattern; std::string m_description; }; struct Katom_type_group { std::string m_title; std::vector m_types; }; inline const std::vector katom_type_groups { {"Plain text", { {katom_t::space, "@symb-space", "One space character"}, {katom_t::word, "@symb only-letters @", "Text only containing letters"}, {katom_t::newline, "\\n", "One newline character"}, {katom_t::text, "@symb no-special-chars @", "No special characters, whitespace, or an initial colon"}}}, {"Klammer application", { {katom_t::apply_begin, "^@ #- @symb-name", "Beginning of a klammer call"}, {katom_t::apply_end, "^@ @ri-or @symb-name #- ^@", "End of a klammer call"}, {katom_t::bar, "^|", "Bar character used as positional argument separator"}, {katom_t::double_bar, "^|^|", "Separator for compound positional arguments"}, {katom_t::option_name, ": #- @symb-name", "Name of an optional argument"}, {katom_t::read_begin, "@t ^@read @", "Beginning of the file input klammer"}, {katom_t::eval_begin, "@t ^@eval @", "Beginning of the evaluation klammer"}, {katom_t::cond_begin, "@t ^@cond @", "Beginning of the conditional (if/then/else) klammer"}}}, {"Klammer definition", { {katom_t::define_begin, "^@^@ #- @symb-name", "Beginning of a klammer definition"}, {katom_t::define_end, "^@^@ @ri-or @symb-name #- ^@^@", "End of a klammer definition"}, {katom_t::klammer_definition, ":", "Klammer definition, including parameters"}, {katom_t::klammer_instance, "::", "Klammer definition using previously defined parameters"}, {katom_t::klammer_override, ":::", "Override existing klammer definition"}, {katom_t::klammer_default, "::::", "Define klammer default value for possible override"}, {katom_t::karg, "* #- @symb-name #- *", "Klammer argument in klammer body definition"}}}, {"System commands", { {katom_t::machine_begin, "^@^@^@ #- @symb-name", "Beginning of a system command"}, {katom_t::machine_end, "^@^@^@ @ri-or @symb-name #- ^@^@^@", "End of a system command"}}}, {"Text removal and whitespace", { {katom_t::ignore_line, "^#", "Remove all text to the first newline, inclusive"}, {katom_t::ignore_begin, "^#[", "Beginning of text to remove"}, {katom_t::ignore_end, "]^#", "End of text to remove"}, {katom_t::ignore_rest, "^#^#", "Remove all text to the end of file or string"}, {katom_t::ws_remove, "^#-", "Remove all whitespace at this point"}, {katom_t::ws_space, "^#+ @ri-or ^#+ #- @symb-n", "Remove all whitespace, leaving @symb-n spaces (default: 1)"}, {katom_t::ws_newline, "^#/ @ri-or ^#/ #- @symb-n", "Remove all whitespace, leaving @symb-n newlines (default: 1)"}}}, {"Special characters and literal text", { {katom_t::special, "^@, ^|, ^#, ^:, @ri-or ^^", "Klammertext special character treated as regular text"}, {katom_t::nonascii, "^^ #- @symb-code @ri-or ^^ #- @symb-code #- ^^", "Non-ASCII character"}, {katom_t::literal_begin, "^^ #- '", "Begin unprocessed text"}, {katom_t::literal_end, "'#- ^^", "End unprocessed text"}}}, {"Types assigned during processing", { {katom_t::ws_added, "@symb-space @ri-or \\n", "Added whitespace characters from @t ^#+ @ and @t ^#/ @"}, {katom_t::literal, "@symb any-text @", "Literal katom (type changed by @t ^^ #- '...'#- ^^ @ #- )"}, {katom_t::eval_result, "@symb any-text @", "Katom produced by @t ^@eval @ klammer"}, {katom_t::replaced, "@symb any-text @", "A katom replaced by definitions or klammers"}, {katom_t::ignored, "@symb any-text @", "A katom ignored by the action of a text-removal katom"}}} }; inline const std::set printable_katom_types { katom_t::word, katom_t::space, katom_t::newline, katom_t::literal, katom_t::ws_added }; inline const std::set open_level { katom_t::literal_begin, katom_t::read_begin, katom_t::eval_begin, katom_t::cond_begin, katom_t::apply_begin, katom_t::define_begin, katom_t::machine_begin, katom_t::ignore_begin }; inline const std::set close_level { katom_t::literal_end, katom_t::apply_end, katom_t::apply_end, katom_t::apply_end, katom_t::apply_end, katom_t::define_end, katom_t::machine_end, katom_t::ignore_end }; inline std::map katom_spans { { katom_t::literal_begin, katom_t::literal_end }, { katom_t::read_begin, katom_t::apply_end }, { katom_t::eval_begin, katom_t::apply_end }, { katom_t::cond_begin, katom_t::apply_end }, { katom_t::apply_begin, katom_t::apply_end }, { katom_t::define_begin, katom_t::define_end }, { katom_t::machine_begin, katom_t::machine_end }, { katom_t::ignore_begin, katom_t::ignore_end } }; bool is_active(katom_t t); bool is_printable(katom_t type); bool is_deftype(katom_t type); const Ktype& type_to_katom_instance(katom_t type); std::string type_to_name(katom_t type);