2026-07-18 18:48:23 +02:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <limits>
|
|
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
|
|
#include "ktype.h"
|
|
|
|
|
#include "locator.h"
|
|
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
|
|
inline bool show_rewrite_rules = false;
|
|
|
|
|
|
|
|
|
|
class Katom
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
static size_t index;
|
|
|
|
|
Katom(const std::string& src, katom_t type, Locator loc);
|
|
|
|
|
|
|
|
|
|
// Copy constructor
|
2026-07-26 23:22:59 +02:00
|
|
|
Katom(const Katom& other)
|
2026-07-18 18:48:23 +02:00
|
|
|
: m_index(other.m_index)
|
|
|
|
|
, m_text(other.m_text)
|
|
|
|
|
, m_src(other.m_src)
|
|
|
|
|
, m_loc(other.m_loc)
|
|
|
|
|
, m_type(other.m_type)
|
|
|
|
|
, m_initial_type(other.m_initial_type)
|
|
|
|
|
, m_display(other.m_display)
|
2026-07-26 23:22:59 +02:00
|
|
|
, m_unparsed(other.m_unparsed)
|
An output policy for the three commands, and @cond as a true special form
A snapshot of the development tree. The substantial changes since the last one:
COMMAND OUTPUT POLICY. The three commands display text in exactly three cases,
and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before
termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext
that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging
used to share the stream and land inside the document. A bare command prints
its usage and succeeds rather than failing. Colour is emitted only to a
terminal, per stream, and NO_COLOR is honoured.
"-v 1" reports every decision whose outcome you could not have read off your own
input: the klammerset that was loaded and from which file, a font's directory, a
":files" name's file, how "-o" was expanded. Higher levels are the trace.
The commands no longer warn and continue: an anomaly is an error, described with
its location. Two exceptions remain, each for a stated reason -- a condition
that is expected and temporary by design, and a judgment that is a heuristic
rather than exact.
@cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when
the file is read. Two consequences for a writer:
* a state variable reaches the predicate. "@@@state Flag :value true @@@
@cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and
silently take the false branch. The document now behaves like a klammer
body, whose arguments are bound before its conditionals are decided.
* nothing in a discarded branch happens -- it is not read, not evaluated, not
expanded. An @eval in the branch not taken used to run anyway.
Its predicate relation is total and strict: true, True, 1; false, False, 0, and
empty; anything else is an error at the @cond rather than silently false.
@eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard
error out of the document (it appears under "-v 1") and treat a nonzero exit as
an error naming what the command reported. A command that exits nonzero on
purpose -- "grep" finding no match -- says so with "|| true".
KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the
order given, sharing one namespace, with the definition modes deciding
collisions. "none" means none and may not be combined with other symbols. A
klammerset with symbol X is declared in a file X/X.k, which is what lets two
sets require the same third set without loading it twice.
TESTS. Four new suites: the kdiag command's interface, the @eval primitive's
contract with the outside world, and verbosity at both tiers. Three suites
that could not run on macOS at all now do.
Assembled from dev commit 6c8ee6c22fca.
2026-08-16 01:37:59 +02:00
|
|
|
, m_deferred(other.m_deferred)
|
2026-07-18 18:48:23 +02:00
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
// Copy assignment operator
|
|
|
|
|
Katom& operator=(const Katom& other) {
|
|
|
|
|
if (this != &other) {
|
|
|
|
|
m_index = other.m_index;
|
|
|
|
|
m_text = other.m_text;
|
|
|
|
|
m_src = other.m_src;
|
|
|
|
|
m_loc = other.m_loc;
|
|
|
|
|
m_type = other.m_type;
|
|
|
|
|
m_initial_type = other.m_initial_type;
|
|
|
|
|
m_display = other.m_display;
|
2026-07-26 23:22:59 +02:00
|
|
|
m_unparsed = other.m_unparsed;
|
An output policy for the three commands, and @cond as a true special form
A snapshot of the development tree. The substantial changes since the last one:
COMMAND OUTPUT POLICY. The three commands display text in exactly three cases,
and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before
termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext
that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging
used to share the stream and land inside the document. A bare command prints
its usage and succeeds rather than failing. Colour is emitted only to a
terminal, per stream, and NO_COLOR is honoured.
"-v 1" reports every decision whose outcome you could not have read off your own
input: the klammerset that was loaded and from which file, a font's directory, a
":files" name's file, how "-o" was expanded. Higher levels are the trace.
The commands no longer warn and continue: an anomaly is an error, described with
its location. Two exceptions remain, each for a stated reason -- a condition
that is expected and temporary by design, and a judgment that is a heuristic
rather than exact.
@cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when
the file is read. Two consequences for a writer:
* a state variable reaches the predicate. "@@@state Flag :value true @@@
@cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and
silently take the false branch. The document now behaves like a klammer
body, whose arguments are bound before its conditionals are decided.
* nothing in a discarded branch happens -- it is not read, not evaluated, not
expanded. An @eval in the branch not taken used to run anyway.
Its predicate relation is total and strict: true, True, 1; false, False, 0, and
empty; anything else is an error at the @cond rather than silently false.
@eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard
error out of the document (it appears under "-v 1") and treat a nonzero exit as
an error naming what the command reported. A command that exits nonzero on
purpose -- "grep" finding no match -- says so with "|| true".
KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the
order given, sharing one namespace, with the definition modes deciding
collisions. "none" means none and may not be combined with other symbols. A
klammerset with symbol X is declared in a file X/X.k, which is what lets two
sets require the same third set without loading it twice.
TESTS. Four new suites: the kdiag command's interface, the @eval primitive's
contract with the outside world, and verbosity at both tiers. Three suites
that could not run on macOS at all now do.
Assembled from dev commit 6c8ee6c22fca.
2026-08-16 01:37:59 +02:00
|
|
|
m_deferred = other.m_deferred;
|
2026-07-18 18:48:23 +02:00
|
|
|
}
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline bool show_index;
|
|
|
|
|
static inline bool show_type;
|
|
|
|
|
static inline bool show_id;
|
|
|
|
|
static inline bool show_whitespace;
|
|
|
|
|
static inline bool show_all;
|
|
|
|
|
static inline bool show_replaced;
|
|
|
|
|
static inline bool show_ignored;
|
|
|
|
|
|
|
|
|
|
bool is_whitespace() const {
|
|
|
|
|
return m_initial_type == katom_t::space
|
|
|
|
|
|| m_initial_type == katom_t::newline
|
|
|
|
|
|| m_type == katom_t::ws_added; }
|
|
|
|
|
bool is_word() const { return m_initial_type == katom_t::word; }
|
|
|
|
|
bool is_text() const { return m_initial_type == katom_t::text; }
|
|
|
|
|
//bool is_text() { return m_initial_type == katom_t::text; }
|
|
|
|
|
bool is_literal() const { return m_type == katom_t::literal; }
|
|
|
|
|
bool is_active() const {
|
|
|
|
|
return m_type != katom_t::ignored
|
|
|
|
|
&& m_type != katom_t::replaced; };
|
|
|
|
|
bool is_nonascii() const {
|
|
|
|
|
return m_type == katom_t::nonascii; };
|
|
|
|
|
size_t m_index;
|
|
|
|
|
std::string m_text{};
|
|
|
|
|
std::string m_src{};
|
|
|
|
|
Locator m_loc;
|
|
|
|
|
katom_t m_type;
|
|
|
|
|
katom_t m_initial_type;
|
|
|
|
|
std::string m_display {};
|
2026-07-26 23:22:59 +02:00
|
|
|
// Set when the word matched no katom type and fell back to katom_t::word.
|
|
|
|
|
// The warning is deferred to warn_unparsed_katoms(), after removal and
|
|
|
|
|
// literal marking, so removed text (comments, #[...]# blocks) never warns.
|
|
|
|
|
bool m_unparsed {};
|
An output policy for the three commands, and @cond as a true special form
A snapshot of the development tree. The substantial changes since the last one:
COMMAND OUTPUT POLICY. The three commands display text in exactly three cases,
and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before
termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext
that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging
used to share the stream and land inside the document. A bare command prints
its usage and succeeds rather than failing. Colour is emitted only to a
terminal, per stream, and NO_COLOR is honoured.
"-v 1" reports every decision whose outcome you could not have read off your own
input: the klammerset that was loaded and from which file, a font's directory, a
":files" name's file, how "-o" was expanded. Higher levels are the trace.
The commands no longer warn and continue: an anomaly is an error, described with
its location. Two exceptions remain, each for a stated reason -- a condition
that is expected and temporary by design, and a judgment that is a heuristic
rather than exact.
@cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when
the file is read. Two consequences for a writer:
* a state variable reaches the predicate. "@@@state Flag :value true @@@
@cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and
silently take the false branch. The document now behaves like a klammer
body, whose arguments are bound before its conditionals are decided.
* nothing in a discarded branch happens -- it is not read, not evaluated, not
expanded. An @eval in the branch not taken used to run anyway.
Its predicate relation is total and strict: true, True, 1; false, False, 0, and
empty; anything else is an error at the @cond rather than silently false.
@eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard
error out of the document (it appears under "-v 1") and treat a nonzero exit as
an error naming what the command reported. A command that exits nonzero on
purpose -- "grep" finding no match -- says so with "|| true".
KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the
order given, sharing one namespace, with the definition modes deciding
collisions. "none" means none and may not be combined with other symbols. A
klammerset with symbol X is declared in a file X/X.k, which is what lets two
sets require the same third set without loading it twice.
TESTS. Four new suites: the kdiag command's interface, the @eval primitive's
contract with the outside world, and verbosity at both tiers. Three suites
that could not run on macOS at all now do.
Assembled from dev commit 6c8ee6c22fca.
2026-08-16 01:37:59 +02:00
|
|
|
// Inside an unresolved @cond span, and therefore INERT: the passes with
|
|
|
|
|
// observable effects -- @eval and @read -- skip it, so a branch that is
|
|
|
|
|
// never selected never runs anything. @cond is a non-strict special form
|
|
|
|
|
// (doc/cond_evaluation_order.md), and this flag is the mechanism; the flag
|
|
|
|
|
// is not the semantics. Cleared on the selected branch when the @cond is
|
|
|
|
|
// resolved in the apply fold, which then processes that branch normally.
|
|
|
|
|
bool m_deferred {};
|
2026-07-18 18:48:23 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool active(const std::vector<Katom>& katoms);
|
|
|
|
|
int active_count(const std::vector<Katom>& katoms);
|
|
|
|
|
std::vector<Katom> split_into_katoms(std::string s, const std::string& source, int source_line);
|
|
|
|
|
void restore_initial_type(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end);
|
|
|
|
|
void modify_type(katom_t new_type, std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end);
|
|
|
|
|
void modify_type(katom_t old_type, katom_t new_type,
|
|
|
|
|
std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end);
|
|
|
|
|
void ignore_whitespace(std::vector<Katom>::iterator& begin, std::vector<Katom>& katoms);
|
|
|
|
|
std::vector<Katom>::iterator after_whitespace(std::vector<Katom>::iterator begin);
|
|
|
|
|
std::vector<Katom> text_katoms(
|
|
|
|
|
std::vector<Katom>::iterator& begin, std::vector<Katom>::iterator& end);
|
|
|
|
|
|
|
|
|
|
std::string as_string(std::vector<Katom>::const_iterator begin, std::vector<Katom>::const_iterator end, bool strip_whitespace);
|
|
|
|
|
|
|
|
|
|
std::string as_string(const std::vector<Katom>& katoms, bool strip_whitespace);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<Katom> trim(std::vector<Katom>& katoms, std::set<katom_t> trim_types = {katom_t::space, katom_t::newline});
|
|
|
|
|
std::vector<Katom> trim(const std::vector<Katom>& katoms, bool trim_inactive = false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<std::vector<Katom>> bar_split(std::vector<Katom>::iterator kbegin, std::vector<Katom>::iterator kend);
|
|
|
|
|
|
Klammerset: the @@@klammerset construct, its search path, and const correctness
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)
2026-07-30 23:50:07 +02:00
|
|
|
std::vector<std::string> line_split(const std::string& s);
|
|
|
|
|
std::pair<std::string, std::vector<std::string>> line_split(const fs::path& pathname);
|
2026-07-18 18:48:23 +02:00
|
|
|
std::vector<Katom> katomize(const std::vector<std::string>& lines, const std::string& source_desc);
|
|
|
|
|
|
2026-07-26 23:22:59 +02:00
|
|
|
void warn_unparsed_katoms(std::vector<Katom>& katoms, bool warn = true);
|
2026-07-18 18:48:23 +02:00
|
|
|
void process_whitespace_modifiers(std::vector<Katom>& katoms);
|
|
|
|
|
std::vector<Katom> trim_whitespace(std::vector<Katom> katoms);
|
|
|
|
|
|
|
|
|
|
// escape_backslash() and unescape_backslash() (the ___BS___ hack) were
|
|
|
|
|
// removed. Backslash is now handled by the general target escape mechanism
|
|
|
|
|
// via the :escape parameter on @@@target.
|
|
|
|
|
|
|
|
|
|
inline bool is_bar(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::bar;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool is_nonascii(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::nonascii;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool is_newline(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::newline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool is_ignore_rest(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::ignore_rest;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool is_ignore_line(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::ignore_line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void mark_as_replaced(Katom& k) {
|
|
|
|
|
k.m_type = katom_t::replaced;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void mark_as_literal(Katom& k) {
|
|
|
|
|
k.m_type = katom_t::literal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void mark_as_ignored(Katom& k) {
|
|
|
|
|
k.m_type = katom_t::ignored;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool begin_read(Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::read_begin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool begin_ignore(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::ignore_begin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool end_ignore(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::ignore_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool begin_literal(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::literal_begin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool end_literal(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::literal_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool begin_eval(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::eval_begin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool begin_cond(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::cond_begin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool begin_klammer_def(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::define_begin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool end_klammer_def(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::define_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool begin_klammer_apply(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::apply_begin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool end_klammer_apply(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::apply_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool begin_machine_def(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::machine_begin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool end_machine_def(const Katom& k) {
|
|
|
|
|
return k.m_type == katom_t::machine_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inline bool begin_apply(const Katom& k)
|
|
|
|
|
{
|
|
|
|
|
const std::set<katom_t> opens {
|
|
|
|
|
katom_t::read_begin,
|
|
|
|
|
katom_t::eval_begin,
|
|
|
|
|
katom_t::cond_begin,
|
|
|
|
|
katom_t::apply_begin};
|
|
|
|
|
return opens.contains(k.m_type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool end_apply(const Katom& k)
|
|
|
|
|
{
|
|
|
|
|
return k.m_type == katom_t::apply_end;
|
|
|
|
|
}
|