The @@@klammerset system command formally declares a klammerset — a named, logically related group of klammer definitions — with an operative, idempotent declaration (:requires and :files load in order at the declaration point, relative to the declaring file). A bare symbol given to ktext -k, kdesc --input, or :requires resolves to x/x.k on the search path: the document's directory, then KLAMMERTEXT_KLAMMERSETS, then KLAMMERTEXT_HOME; kdesc --klammerset lists the available sets. sks/sks.k is the first declared klammerset, so `-k sks` loads the SKS by name. The engine's lookup classes were renamed *_set → *_registry to keep the two concepts apart, and the whole C++ tree now follows standard const-correctness conventions. tst/ gains klammerset_test.sh (18 cases). (from dev 64b1abf23e56)
421 lines
13 KiB
C++
421 lines
13 KiB
C++
#include <iostream>
|
|
#include <fstream>
|
|
#include <ranges>
|
|
#include <algorithm>
|
|
|
|
#include "katom.h"
|
|
#include "ktype.h"
|
|
#include "error.h"
|
|
#include "log.h"
|
|
#include "show.h"
|
|
#include "util.h"
|
|
#include "file.h"
|
|
|
|
bool dbg = false;
|
|
|
|
size_t Katom::index = 0;
|
|
|
|
Katom::Katom(const std::string& src, katom_t type, Locator loc)
|
|
: m_index(Katom::index++)
|
|
, m_text(src)
|
|
, m_src(src)
|
|
, m_loc(loc)
|
|
, m_type(type)
|
|
, m_initial_type(type)
|
|
{
|
|
if (m_type == katom_t::special && m_text[0] == '^') {
|
|
m_text.erase(0, 1);
|
|
}
|
|
}
|
|
|
|
int active_count(const katom_list& katoms)
|
|
{
|
|
return std::ranges::count_if(
|
|
katoms,
|
|
[] (const Katom& k) {
|
|
return k.m_type != katom_t::replaced
|
|
&& k.m_type != katom_t::ignored
|
|
&& k.m_type != katom_t::space
|
|
&& k.m_type != katom_t::newline;
|
|
});
|
|
}
|
|
|
|
bool active(const katom_list& katoms)
|
|
{
|
|
return active_count(katoms) > 0;
|
|
}
|
|
|
|
std::string expand_compound_katom(const std::string& s, std::regex rgx, const std::string& expanded)
|
|
{
|
|
if (s.find('-') != std::string::npos) { // Hyphen shortcut for arguments that allow it
|
|
const std::regex shortcut_rgx(R"((@\w+)(-\w+)+)");
|
|
std::smatch match {};
|
|
if (std::regex_match(s, match, shortcut_rgx)) {
|
|
std::string modified = string_replace(s, "-", " | ") + " @";
|
|
return std::regex_replace(modified, std::regex(R"((@\w+) \|)"), "$1");
|
|
}
|
|
}
|
|
return std::regex_replace(s, rgx, expanded);
|
|
}
|
|
|
|
|
|
katom_list make_katoms_from_word(std::string s, const std::string& source_desc, int line, int chr)
|
|
{
|
|
if (dbg) msg() << " make word: " << broken_bar << s << broken_bar << " [" << chr << "]\n";
|
|
auto ktyp = std::find_if(katom_types.begin(), katom_types.end(), [&](const auto& ktype) { return ktype.match(s); });
|
|
if (ktyp != katom_types.end()) {
|
|
return { Katom(s, ktyp->m_type, Locator(source_desc, line, chr)) };
|
|
} else {
|
|
if (dbg) {
|
|
std::cout << "\nBREAK: |" << s << "|\n";
|
|
}
|
|
for (const auto& [desc, rgx, expanded] : katom_rewrite_rules) {
|
|
std::string modified = expand_compound_katom(s, rgx.m_regex, expanded);
|
|
if (dbg) {
|
|
msg() << "Rewrite: " << desc << " " << rgx.m_pattern << " " << expanded << "\n";
|
|
}
|
|
if (modified != s) {
|
|
auto parts = word_split(modified);
|
|
if (dbg) std::cout << " Parts: " << parts << "\n";
|
|
if (show_rewrite_rules) {
|
|
Locator loc(source_desc, line, chr);
|
|
std::cout << loc << " Rewrite (" << desc << "): " << rgx.m_pattern
|
|
<< " " << right_arrow << " " << expanded << "\n";
|
|
}
|
|
katom_list klist {};
|
|
for (const std::string& p : parts) {
|
|
if (!p.empty()) {
|
|
auto ks = make_katoms_from_word(p, source_desc, line, chr);
|
|
std::copy(ks.begin(), ks.end(), std::back_inserter(klist));
|
|
}
|
|
}
|
|
return klist;
|
|
}
|
|
}
|
|
Katom k(s, katom_t::word, Locator(source_desc, line, chr));
|
|
k.m_unparsed = true; // Warning deferred to warn_unparsed_katoms()
|
|
return std::vector{ k };
|
|
}
|
|
}
|
|
|
|
// Warn about words that matched no katom type -- but only those that
|
|
// survive processing: text removed by #, ##, or #[...]#, replaced spans,
|
|
// and literal content (definition interiors, @code bodies) never warn.
|
|
// Called at the end of Machine::process_katoms(), after those passes have
|
|
// marked the katoms. Clears the flag after warning so repeated processing
|
|
// of the same katom list does not warn twice. With warn=false (the @eval
|
|
// read-back sub-Machine, whose katoms hold machine-generated result text)
|
|
// no warning is printed and every flag is cleared, so the katoms stay
|
|
// silent after they are spliced into the calling Machine's list.
|
|
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) {
|
|
std::cerr << command_name
|
|
<< " [warning]: Word not parsed in "
|
|
<< k.m_loc.m_filename << ", line " << k.m_loc.m_line << ":\n"
|
|
<< " " << k.m_text << "\n"
|
|
<< "To include a special character (@, |, #, and ^), put \"^\" before it.\n";
|
|
k.m_unparsed = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
katom_list split_into_katoms(std::string s, const std::string& source, int source_line)
|
|
{
|
|
if (dbg) {
|
|
msg() << "Make katoms: " << s << "<\n";
|
|
}
|
|
// const std::string middle_dot { "\u00B7" };
|
|
s = string_replace(s, "\r", "");
|
|
s = string_replace(s, "\t", " ");
|
|
|
|
//std::regex words_regex("^\||[ ]|[\\n]|[^\\s]+|.+");
|
|
//std::regex words_regex(R"((?:[^][|])|[ ]|[\n]|[^\s]+|.+)");
|
|
std::regex words_regex(R"([ ]|[\n]|[^\s]+|.+)");
|
|
auto words_begin = std::sregex_iterator(s.begin(), s.end(), words_regex);
|
|
auto words_end = std::sregex_iterator();
|
|
if (dbg) {
|
|
std::cout << "Found " << std::distance(words_begin, words_end) << " words:\n";
|
|
}
|
|
strings_t atoms {};
|
|
for (std::sregex_iterator iter = words_begin; iter != words_end; ++iter) {
|
|
if (dbg) {
|
|
std::cout << middle_dot << iter->str();
|
|
}
|
|
atoms.push_back(iter->str());
|
|
}
|
|
if (dbg) std::cout << middle_dot << "\n";
|
|
//std::cout << kall << ktype;
|
|
|
|
katom_list result {};
|
|
|
|
int cpos = 0;
|
|
for (const auto& a : atoms) {
|
|
auto k = make_katoms_from_word(a, source, source_line, cpos);
|
|
for (auto& kk : k) {
|
|
Locator loc(source, source_line, cpos);
|
|
//kk->m_loc = loc;
|
|
kk.m_loc = loc;
|
|
//m_katoms.push_back(kk);
|
|
result.push_back(kk);
|
|
if (dbg) {
|
|
//std::cout << "LOOP: " << kk.m_text << " - " << cpos << "\n";
|
|
}
|
|
cpos += kk.m_src.size();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void restore_initial_type(katom_iter begin, katom_iter end)
|
|
{
|
|
std::for_each(
|
|
begin, end, [](Katom& k) { k.m_type = k.m_initial_type; });
|
|
}
|
|
|
|
void modify_type(katom_t new_type, katom_iter begin, katom_iter end)
|
|
{
|
|
std::for_each(
|
|
begin, end, [&new_type](Katom& k) { k.m_type = new_type; });
|
|
}
|
|
|
|
void modify_type(katom_t old_type, katom_t new_type, katom_iter begin, katom_iter end)
|
|
{
|
|
std::for_each(
|
|
begin, end, [&](Katom& k) { if (k.m_type == old_type) k.m_type = new_type; });
|
|
}
|
|
|
|
void ignore_whitespace(katom_iter& begin, katom_list& katoms)
|
|
{
|
|
constexpr bool _dbg = false;
|
|
if (begin < katoms.end()) {
|
|
katom_iter ki = begin;
|
|
if (_dbg) std::cout << "IGNORE_WHITESPACE: ";
|
|
auto end = katoms.end();
|
|
while (ki < end && ki->is_whitespace()) {
|
|
if (_dbg) std::cout << kall << ktype << *ki << sp_arrow;
|
|
ki->m_type = katom_t::ignored;
|
|
if (_dbg) std::cout << kignored << kall << ktype << *ki << " " << "\n";
|
|
++ki;
|
|
}
|
|
if (_dbg) std::cout << black << kreset;
|
|
}
|
|
}
|
|
|
|
katom_iter after_whitespace(katom_iter begin)
|
|
{
|
|
katom_iter result = begin;
|
|
while (result->is_whitespace()) {
|
|
result++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<Katom> text_katoms(katom_iter& begin, katom_iter& end)
|
|
{
|
|
katom_list result {};
|
|
for (auto ki = begin; ki < end; ki++) {
|
|
if (!ki->is_whitespace()) {
|
|
auto k = *ki;
|
|
//msg() << " push: " << kindex << ktype << kall << kws << k << "\n";
|
|
result.push_back(k);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string as_string(std::vector<Katom>::const_iterator begin, std::vector<Katom>::const_iterator end, bool strip_whitespace)
|
|
{
|
|
auto include =
|
|
[&](katom_t t)
|
|
{ return t != katom_t::replaced and t != katom_t::ignored; };
|
|
std::string result {};
|
|
for (auto ki = begin; ki < end; ki++) {
|
|
katom_t t = ki->m_type;
|
|
if (include(t)) {
|
|
std::string text = !ki->m_display.empty() ? ki->m_display : ki->m_text;
|
|
result += text;
|
|
}
|
|
}
|
|
if (strip_whitespace)
|
|
result = trim(result);
|
|
return result;
|
|
}
|
|
|
|
std::string as_string(const katom_list& ks, bool strip_whitespace)
|
|
{
|
|
return as_string(ks.begin(), ks.end(), strip_whitespace);
|
|
}
|
|
|
|
katom_list trim(katom_list& katoms, std::set<katom_t> trim_types)
|
|
{
|
|
katom_iter begin =
|
|
std::find_if(katoms.begin(), katoms.end(),
|
|
[&trim_types](const Katom& k) { return !trim_types.contains(k.m_type); });
|
|
if (begin == katoms.end()) {
|
|
return katom_list{};
|
|
}
|
|
katom_iter end = katoms.end() - 1;
|
|
while (trim_types.contains(end->m_type)) {
|
|
--end;
|
|
}
|
|
return katom_list(begin, end+1);
|
|
}
|
|
|
|
katom_list trim(const katom_list& katoms, bool trim_inactive)
|
|
{
|
|
katom_list result = katoms;
|
|
std::set<katom_t> trim_types {katom_t::space, katom_t::newline};
|
|
if (trim_inactive) {
|
|
trim_types.insert(katom_t::replaced);
|
|
trim_types.insert(katom_t::ignored);
|
|
}
|
|
return trim(result, trim_types);
|
|
}
|
|
|
|
strings_t line_split(const std::string& s)
|
|
{
|
|
std::istringstream is(s);
|
|
strings_t result {};
|
|
std::string line;
|
|
while (std::getline(is, line)) {
|
|
result.push_back(line);
|
|
}
|
|
result.push_back("\n");
|
|
return result;
|
|
}
|
|
|
|
std::pair<std::string, strings_t> line_split(const fs::path& pathname)
|
|
{
|
|
std::string source {};
|
|
strings_t lines {};
|
|
std::ifstream infile(pathname);
|
|
std::string line;
|
|
while (std::getline(infile, line)) {
|
|
source += line + "\n";
|
|
lines.push_back(line);
|
|
}
|
|
if (!source.empty()) {
|
|
source.pop_back();
|
|
}
|
|
return {source, lines};
|
|
}
|
|
|
|
// I'm, like, this is
|
|
// some weird shit;
|
|
// what the fuck?
|
|
|
|
katom_list katomize(const strings_t& lines, const std::string& source_desc)
|
|
{
|
|
(void)K::log(3);
|
|
katom_list katoms {};
|
|
int i = 0;
|
|
for (std::string line : lines) {
|
|
(void)K::log(4, line);
|
|
katom_list ks = split_into_katoms(line + "\n", source_desc, ++i);
|
|
katoms.insert(katoms.end(), ks.begin(), ks.end());
|
|
}
|
|
katoms.pop_back();
|
|
return katoms;
|
|
}
|
|
|
|
// Whitespace
|
|
|
|
std::pair<katom_iter,katom_iter> whitespace_span(const katom_list& katoms, const katom_iter& start, katom_t start_type)
|
|
{
|
|
katom_iter ki = start;
|
|
if (ki != katoms.begin()) {
|
|
--ki;
|
|
while (ki != katoms.begin() && ki->is_whitespace()) {
|
|
--ki;
|
|
}
|
|
if (!ki->is_whitespace()) {
|
|
++ki;
|
|
}
|
|
}
|
|
katom_iter begin = ki;
|
|
while (ki != katoms.end() && (ki->is_whitespace() || ki->m_type == start_type
|
|
|| ki->m_type == katom_t::ignored || ki->m_type == katom_t::replaced)) {
|
|
++ki;
|
|
}
|
|
//katom_iter end = ki;
|
|
return {begin, ki}; //end};
|
|
}
|
|
|
|
std::vector<Katom> find_katoms_of_type(const katom_list& katoms, katom_t type)
|
|
{
|
|
auto result = katoms | std::views::filter([type](const Katom& k) { return k.m_type == type; });
|
|
return std::vector(result.begin(), result.end());
|
|
}
|
|
|
|
katom_iter find_katom_of_type(katom_iter begin, katom_iter end, katom_t type)
|
|
{
|
|
return std::find_if(begin, end, [&](const Katom& k) { return k.m_type == type; });
|
|
}
|
|
|
|
// #-
|
|
|
|
void remove_whitespace(katom_list& katoms) // Lint error
|
|
{
|
|
(void)K::log(3);
|
|
auto begin = katoms.begin();
|
|
while (begin < katoms.end()) {
|
|
auto ki = find_katom_of_type(begin, katoms.end(), katom_t::ws_remove);
|
|
if (ki == katoms.end()) break;
|
|
auto [b, e] = whitespace_span(katoms, ki, katom_t::ws_remove);
|
|
std::for_each(b, e, [](Katom& k) { k.m_type = katom_t::ignored; });
|
|
begin = e;
|
|
}
|
|
}
|
|
|
|
// #+n and #/n
|
|
|
|
int whitespace_arg(Katom& k)
|
|
{
|
|
int result = 1;
|
|
std::smatch match{};
|
|
if (std::regex_match(k.m_text, match, std::regex(R"(#[+/](\d*))"))) {
|
|
if (!match[1].str().empty()) {
|
|
result = stoi(match[1]);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void insert_whitespace(katom_list& katoms, katom_t type, const std::string& c)
|
|
{
|
|
(void)K::log(3);
|
|
for (Katom k : find_katoms_of_type(katoms, type)) {
|
|
katom_iter ki = find_katom(katoms.begin(), katoms.end(), k.m_index);
|
|
auto count = whitespace_arg(k);
|
|
auto [b, e] = whitespace_span(katoms, ki, type);
|
|
std::for_each(b, e, [](Katom& ka) { ka.m_type = katom_t::ignored; });
|
|
katom_list inserted = std::vector<Katom>{};
|
|
for (int i = 0; i < count; ++i) {
|
|
inserted.push_back(Katom(c, katom_t::ws_added, k.m_loc));
|
|
}
|
|
katoms.insert(e, inserted.begin(), inserted.end());
|
|
}
|
|
}
|
|
|
|
void process_whitespace_modifiers(katom_list& katoms)
|
|
{
|
|
(void)K::log(4);
|
|
remove_whitespace(katoms);
|
|
insert_whitespace(katoms, katom_t::ws_space, " ");
|
|
insert_whitespace(katoms, katom_t::ws_newline, "\n");
|
|
}
|
|
|
|
katom_list trim_whitespace(katom_list katoms)
|
|
{
|
|
return trim(katoms, {katom_t::space, katom_t::newline});
|
|
}
|