A klammer that reaches itself, directly or through a cycle, expanded until the C++ stack was exhausted: the process died from SIGSEGV with no message and no location. The former limit guarded only the top-level fixed-point iteration, never the descent through klammer application. A depth guard now raises a recursion error naming the klammer and where it was applied. The same loop's termination test moves from "the katom list stopped growing" to "a pass applied no klammer", since a klammer whose body expands to nothing is a reduction that adds no katoms; exceeding the round limit is now an error rather than a message followed by rendering a document with live klammers still in it. ktext --check locates every klammer application written in a document or in a klammer body and checks name existence, argument count, option names, and target coverage without applying anything, reporting all problems at once. This is possible because Klammertext has no catcodes: katom structure is fixed when a file is read, so a klammer body has a determinate shape before it is expanded. The check therefore reaches what the engine cannot -- the branch of a @cond that is not selected, and bodies a given render never enters. @cond's set of truth values is an open language question, so its meaning is unchanged here; an unrecognized predicate now warns, giving its value and location. tst/ gains recursion_test.sh (7 cases) and check_test.sh (19 cases), and this snapshot's test Makefile is generated from the shipped suite list so the two cannot drift apart. (from dev c27e63802406) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
142 lines
4.9 KiB
C++
142 lines
4.9 KiB
C++
#pragma once
|
|
|
|
#include <string>
|
|
|
|
// #include "source.h"
|
|
#include "katom_list.h"
|
|
#include "klammer_registry.h"
|
|
#include "klammerset_registry.h"
|
|
#include "target_registry.h"
|
|
#include "argtype_registry.h"
|
|
#include "state.h"
|
|
|
|
#include "error.h"
|
|
|
|
class Machine
|
|
{
|
|
public:
|
|
using input_sources_t =
|
|
std::vector<std::variant<fs::path, std::string>>;
|
|
|
|
Machine();
|
|
|
|
// Copy constructor
|
|
Machine(const Machine& other)
|
|
: m_argtypes(other.m_argtypes)
|
|
, m_state(other.m_state)
|
|
, m_targets(other.m_targets)
|
|
, m_klammers(other.m_klammers)
|
|
, m_klammersets(other.m_klammersets)
|
|
, m_result(other.m_result)
|
|
{}
|
|
|
|
// Copy assignment operator
|
|
Machine& operator=(const Machine& other) {
|
|
if (this != &other) {
|
|
m_argtypes = other.m_argtypes;
|
|
m_state = other.m_state;
|
|
m_targets = other.m_targets;
|
|
m_klammers = other.m_klammers;
|
|
m_klammersets = other.m_klammersets;
|
|
m_result = other.m_result;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void process_cond_katoms(std::vector<Katom>& katoms);
|
|
void process_eval_katoms(std::vector<Katom>& katoms);
|
|
void mark_literal_klammer_content(std::vector<Katom>& katoms);
|
|
void escape_target_characters(const Target& target, std::vector<Katom>& katoms);
|
|
|
|
// std::vector<Katom>
|
|
void process_katoms(
|
|
std::vector<Katom>& katoms, const std::string& source,
|
|
bool nonascii=true, bool literal=true, bool ignore=true, bool whitespace=true,
|
|
bool klammers=true, bool eval=true, bool cond=true, bool read=true);
|
|
|
|
std::vector<Katom> process(
|
|
std::string text, const std::string& source,
|
|
bool nonascii=true, bool literal=true, bool ignore=true, bool whitespace=true,
|
|
bool klammers=true, bool eval=true, bool cond=true, bool read=true);
|
|
|
|
void read(const fs::path& pathname);
|
|
void read(const std::string& s);
|
|
|
|
void expand_read_katoms(
|
|
std::vector<Katom>& katoms, std::string source_filename,
|
|
bool nonascii=true, bool literal=true, bool ignore=true, bool ws=true,
|
|
bool klammers=true, bool eval=true, bool cond=true, bool read=true);
|
|
|
|
void expand_constant_klammers(katom_list& katoms, const Katom& op, const Katom& cl);
|
|
void load_klammerset_files(const Klammerset& klammerset, katom_iter insert_at);
|
|
void extract_machine_definitions();
|
|
void extract_klammer_definitions();
|
|
void extract_klammer_definitions(katom_list katoms);
|
|
void update_state(const std::map<std::string, std::string>& arg_map);
|
|
|
|
katom_list apply_klammer(Klammer& klammer, const std::string& target, katom_iter arguments_begin, katom_iter arguments_end);
|
|
|
|
void apply_klammer_registry(Klammer_registry& klammer_registry,
|
|
katom_list& katoms, const std::string& target, katom_iter begin, katom_iter end);
|
|
|
|
// Returns the number of klammers applied in this pass. The top-level
|
|
// fixed point loops while that count is nonzero: reduction is detected by
|
|
// a redex having been reduced, not by the katom list having grown.
|
|
int apply(Klammer_registry& klammer_registry, katom_list& katoms, const std::string& target);
|
|
|
|
std::string run_phase_functions();
|
|
std::string apply(const std::string& target_name, bool final_processing=true, bool escape_characters=true);
|
|
|
|
Argtype_registry m_argtypes {};
|
|
State m_state {};
|
|
Target_registry m_targets {};
|
|
Klammer_registry m_klammers {};
|
|
Klammerset_registry m_klammersets {};
|
|
input_sources_t m_sources {};
|
|
std::string m_result {};
|
|
std::vector<Katom> m_katoms {};
|
|
// False on the sub-Machine that re-reads an @eval result (Eval::eval):
|
|
// result text is machine-generated -- a renderer's raw target markup
|
|
// (e.g. a LaTeX column spec "@{}...") legitimately fails katom parsing
|
|
// and must not produce "Word not parsed" warnings.
|
|
bool m_warn_unparsed = true;
|
|
};
|
|
|
|
/*
|
|
class Machine {
|
|
private:
|
|
std::string m_name;
|
|
int m_id;
|
|
std::vector<std::string> m_sources;
|
|
bool m_active;
|
|
double m_value;
|
|
|
|
public:
|
|
// Default constructor
|
|
Machine() : m_name(""), m_id(0), m_sources(), m_active(false), m_value(0.0) {}
|
|
|
|
// Copy constructor
|
|
Machine(const Machine& other)
|
|
: m_name(other.m_name) // Copy name
|
|
, m_id(other.m_id) // Copy id
|
|
, m_sources() // Initialize m_sources as empty
|
|
, m_active(other.m_active) // Copy active status
|
|
, m_value(other.m_value) // Copy value
|
|
{
|
|
// m_sources is now empty, ready for new initialization
|
|
}
|
|
|
|
// Assignment operator (if needed)
|
|
Machine& operator=(const Machine& other) {
|
|
if (this != &other) {
|
|
m_name = other.m_name;
|
|
m_id = other.m_id;
|
|
m_sources.clear(); // Clear and leave empty
|
|
m_active = other.m_active;
|
|
m_value = other.m_value;
|
|
}
|
|
return *this;
|
|
}
|
|
};
|
|
*/
|