Initial commit: Klammertext source distribution

Curated source subset assembled by klammertext-dev's doc/make_dist.sh: the Klammermachine (mac), the Standard Klammer Set (sks), the commands (com), editor plugins and install guides (doc), a test subset (tst), and lib/bin placeholders. Builds with 'make -C com'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-18 18:48:23 +02:00
commit 2ba7ceee7a
272 changed files with 27634 additions and 0 deletions

1
mac/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.d

67
mac/Makefile Normal file
View File

@@ -0,0 +1,67 @@
# Klammertext mac/ Makefile
# Improved version with automatic header dependency tracking
K := $(KLAMMERTEXT_HOME)
KS := $(K)/sks
include $(K)/mac/env/makefile.env
# Source files
BASENAMES := util error locator file argv character ktype katom katom_list \
log show command argument argument_set argtype argtype_set \
state eval eval_python eval_cpp klammer klammer_set deftype \
target target_set machine
SOURCES := $(addsuffix .cpp,$(BASENAMES))
OBJECTS := $(addsuffix .o,$(BASENAMES))
HEADERS := $(addsuffix .h,$(BASENAMES))
DEPFILES := $(addsuffix .d,$(BASENAMES))
# Shared library
LIBDIR := ../lib
LIBRARY := $(LIBDIR)/libklammertext.so
# Compiler flags for dependency generation
DEPFLAGS = -MMD -MP -MF $(@:.o=.d)
# Pattern rule for object files with automatic dependency generation
%.o : %.cpp
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $< -o $@
# Default target
.PHONY: all sks clean redo clang
all : $(LIBRARY)
$(MAKE) sks
# Create lib directory
$(LIBDIR):
mkdir -p $(LIBDIR)
# Build shared library
$(LIBRARY): $(OBJECTS) | $(LIBDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(SHARED) $(SONAME) -o $@ $(OBJECTS) $(LDLIBS)
# Build sks components
sks :
$(MAKE) -C $(KS)/kutil
$(MAKE) -C $(KS)/target
$(MAKE) -C $(KS)/document
clean :
rm -f $(OBJECTS) $(DEPFILES) $(LIBRARY) *~
redo :
ifneq ($(filter clang,$(MAKECMDGOALS)),)
@:
else
$(MAKE) clean
$(MAKE) -j all
endif
# "make clang" = incremental build; "make clang redo" = full clean rebuild
clang :
$(MAKE) $(or $(filter-out clang,$(MAKECMDGOALS)),all) COMPILER=clang
# Include generated dependency files (if they exist)
-include $(DEPFILES)

84
mac/argtype.cpp Normal file
View File

@@ -0,0 +1,84 @@
#include <set>
#include <regex>
#include <sstream>
#include "argtype.h"
#include "error.h"
#include "util.h"
Argtype::Argtype(std::string name, std::string desc, std::string symbolic_pattern, std::string pattern,
std::string python_cast, modify_string_f python_format,
const Locator& loc)
: m_name(name)
, m_desc(desc)
, m_symbolic_pattern(symbolic_pattern)
, m_pattern(pattern)
, m_python_cast(python_cast)
, m_python_format(python_format)
, m_regex(std::regex(pattern))
, m_loc(loc)
{
}
std::string pyformat_string(const strings_t& v)
{
std::string p = v[0];
std::string delim = "\"";
if (contains(p, "\"\"\"")) {
delim = "'''";
} else if (contains(p, "'''")) {
delim = "\"\"\"";
} else if (contains(p, "\"")) {
delim = "'";
}
return delim + p + delim;
}
std::string pyformat_bool(const strings_t& v)
{
std::string value = v[0];
std::set<std::string> true_values { "1", "true", "True", "yes" };
std::set<std::string> false_values { "0", "false", "False", "no" };
if (true_values.contains(value)) {
return "True";
} else if (false_values.contains(value)) {
return "False";
} else {
throw Argument_error("The argument\"" + value + "\" is not a Boolean value");
}
}
std::string pyformat_number(const strings_t& value)
{
return value[0];
}
std::string pylist(strings_t words)
{
std::transform(words.begin(), words.end(), words.begin(),
[](const std::string& s) { return pyformat_string({s}); });
return "[" + join(words, ", ") + "]";
}
std::string pyformat_list(const strings_t& value)
{
return pylist(value);
}
std::string pyformat_dlist(const strings_t& value)
{
return pylist(value);
}
std::string Argtype::python_value(const std::string& var_name, std::vector<std::string> value, size_t name_size)
{
std::stringstream ss {};
ss << " " << std::left << std::setw(name_size) << var_name << " = ";
if (m_python_format) {
ss << m_python_format(value);
} else {
ss << m_python_cast << "(" << pyformat_string(value) << ")";
}
return ss.str();
}

51
mac/argtype.h Normal file
View File

@@ -0,0 +1,51 @@
#pragma once
#include <string>
#include <variant>
#include <functional>
#include <regex>
#include "locator.h"
using argtype_t = std::variant<bool,double,std::string,std::vector<std::string>>;
using modify_string_f = std::function<std::string(std::vector<std::string>)>;
class Argtype
{
public:
Argtype()
: m_name("default")
, m_desc("default argument type")
, m_symbolic_pattern(".+")
, m_pattern(".+")
, m_python_cast("str")
, m_python_format()
, m_loc()
{};
Argtype(std::string name, std::string desc, std::string symbolic_pattern, std::string pattern,
std::string python_cast, modify_string_f python_format,
const Locator& loc);
std::string python_value(const std::string& var_name, std::vector<std::string> value, size_t name_size);
std::string m_name {};
std::string m_desc {};
std::string m_symbolic_pattern {};
std::string m_pattern {};
std::string m_python_cast {};
modify_string_f m_python_format {};
std::regex m_regex {};
int m_count {1};
int m_mincount {1};
int m_maxcount {1};
Locator m_loc;
};
std::string pyformat_string(const std::vector<std::string>& value);
std::string pyformat_bool(const std::vector<std::string>& value);
std::string pyformat_number(const std::vector<std::string>& value);
std::string pyformat_list(const std::vector<std::string>& value);
std::string pyformat_dlist(const std::vector<std::string>& value);

155
mac/argtype_set.cpp Normal file
View File

@@ -0,0 +1,155 @@
#include <regex>
#include <sstream>
#include <algorithm>
#include <numeric>
#include "argtype_set.h"
#include "error.h"
#include "show.h"
#include "log.h"
#include "util.h"
#include "character.h"
#include "katom.h"
Parameter_set& Argtype_set::parameters()
{
static Parameter_set instance("name | desc :pattern .* :python_cast str");
return instance;
}
Argtype_set::Argtype_set()
{
(void)(void)K::log(2);
Locator loc = current_locator();
for (auto [name, desc, pattern, python_cast, python_format] : base_argtypes) {
add(name, desc, pattern, python_cast, python_format, loc);
}
}
std::string Argtype_set::replace_symbols(const std::string& pattern, const Locator& loc)
{
std::smatch match {};
std::regex symbol_pat(R"('(\w+)')");
std::string expanded { pattern };
for (const std::string& symbol : find_all(pattern, symbol_pat, 0)) {
std::string name { symbol.begin()+1, symbol.end()-1 };
if (m_types.find(name) != m_types.end()) {
expanded = string_replace(
expanded, symbol, R"((?:)" + m_types[name].m_pattern + R"())");
} else {
std::stringstream ss;
ss << "Argtype symbol " << symbol << " not defined.\n\n"
<< "Defined argtypes:\n";
ss << describe();
throw Definition_error(ss.str(), loc, false);
}
}
return expanded;
}
void Argtype_set::check_for_existing_definition(
const std::string& name, const Locator& loc)
{
if (count(m_names.begin(), m_names.end(), name) > 0) {
std::stringstream ss {};
ss << "Argument type '" << name << "' is already defined at "
<< m_types[name].m_loc;
throw Definition_error(ss.str(), loc);
}
}
void Argtype_set::add(const std::string& name, const std::string& desc,
const std::string& pattern,
const std::string& python_cast, modify_string_f python_format,
const Locator& loc)
{
// (void)K::log(3, name, ":", abbrev(string_replace(desc, "\n", "/"), 50), pattern);
check_for_existing_definition(name, loc);
std::string expanded_pattern = replace_symbols(pattern, loc);
m_name_size = std::max(m_name_size, name.size()); // For display
m_pattern_size = std::max(m_pattern_size, expanded_pattern.size());
m_types[name] = Argtype(name, desc, pattern, expanded_pattern, python_cast, python_format, loc);
m_names.push_back(name);
}
void Argtype_set::add(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, std::vector<Katom>& katoms)
{
(void)K::log(3);
//Argument_set parameters("name | desc :pattern .* :python_cast str");
auto [positional, optional, rest] =
argument_split(begin + 1, end); //, Argtype_set::parameters.m_positional.size());
check_for_existing_definition(positional[0][0].m_text, begin->m_loc);
auto values = Argtype_set::parameters().value_map(positional, optional, rest, begin->m_loc);
// std::for_each(begin, end+1, [](Katom& k) { k.m_type = katom_t::replaced; });
modify_string_f pyformat {};
std::string pycast {};
add(values["name"], values["desc"], values["pattern"],
pycast, pyformat,
begin->m_loc);
modify_type(katom_t::replaced, begin, end);
auto next_iter = end;
ignore_whitespace(next_iter, katoms);
}
Argtype Argtype_set::get(const std::string& name, const Locator& loc) const
{
if (is_not_in(name, m_names)) {
std::stringstream msg {};
msg << "Type '" << name << "' is not an argument type";
throw Argument_error(msg.str(), loc);
}
return m_types.at(name);
}
std::string Argtype_set::eval(
const std::string& value, const std::string& type_name, const Locator& loc)
{
std::regex re = m_types[type_name].m_regex;
std::smatch match {};
if (std::regex_match(value, match, re)) {
return value;
} else {
//return "NO MATCH";
std::stringstream ss {};
ss << "Argument \"" << value << "\" does not match the pattern for \""
<< type_name << "\"\n";
throw Definition_error(ss.str(), loc);
}
}
std::string Argtype_set::describe(bool long_form, int indent_width) const
{
std::string indent(' ', indent_width);
std::size_t name_width = std::accumulate(
m_names.begin(), m_names.end(), 0,
[&] (size_t w, const std::string& name) { return std::max(w, name.size()); });
std::stringstream result {};
for (const auto& name : m_names) {
std::string label { "Regex:" };
int pat_width = name_width + 2 + indent_width + label.size();
result << indent << std::setw(name_width) << name << sp_arrow;
if (long_form) {
result << m_types.at(name).m_desc << "\n";
result << std::setw(pat_width) << "regex: " << m_types.at(name).m_symbolic_pattern;
if (m_types.at(name).m_symbolic_pattern != m_types.at(name).m_pattern)
result << sp_arrow << m_types.at(name).m_pattern;
result << "\n";
} else {
// result << abbrev(m_types.at(name).m_desc) << "\n";
result << regex_split(m_types.at(name).m_desc, std::regex("\\n"), true)[0] << "\n";
}
}
if (long_form) {
result << "\nA previously defined type can be included in the definition of a new type\n"
<< "by surrounding the name of the existing type in single quotation marks.\n";
}
return result.str();
}

88
mac/argtype_set.h Normal file
View File

@@ -0,0 +1,88 @@
#pragma once
#include <string>
#include <vector>
#include <map>
#include "argtype.h"
#include "katom.h"
#include "argument_set.h"
class Argtype_set
{
public:
static Parameter_set& parameters();
Argtype_set();
std::string replace_symbols(const std::string& pattern, const Locator& loc);
void check_for_existing_definition(const std::string& name, const Locator& loc);
void add(const std::string& name, const std::string& desc,
const std::string& pattern, const std::string& python_cast, modify_string_f python_format,
const Locator& loc);
void add(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, std::vector<Katom>& katoms);
Argtype get(const std::string& name, const Locator& loc) const;
std::string eval(const std::string& value, const std::string& type_name, const Locator& loc);
std::string describe(bool long_form=false, int indent_width=4) const;
inline bool is_defined(std::string name) {
return count(m_names.begin(), m_names.end(), name) > 0;
}
std::vector<std::string> m_names {};
std::map<std::string, Argtype> m_types {};
size_t m_name_size = 0;
size_t m_pattern_size = 0;
};
const
std::string default_argtype = "string";
const
std::vector<std::tuple<std::string, std::string, std::string, std::string, modify_string_f>> base_argtypes = {
{ "string", "arbitrary text",
R"((?:.|\n)*)", "str",
pyformat_string },
{ "word", "a series of characters without a space",
R"([^\s]+)", "str",
pyformat_string },
{ "bool", "a Boolean value of 'false', 'False', '0', 'true', 'True', or '1'",
R"(0|1|true|false|True|False)",
"(lambda b : True if b not in {'0','false','False'} else False)",
pyformat_bool },
{ "uint", "an integer greater than or equal to zero",
R"(\d\d*)", "int",
pyformat_number },
{ "int","an integer",
R"([-+]?'uint')", "int",
pyformat_number },
{ "float", "a floating-point number",
R"([-+]?\d+(\.\d*)?)", "float",
pyformat_number },
{ "fraction", "a number in the form 'n/d'",
R"('int'/'uint')", "(lambda f : float(f.split('/')[0]) / float(f.split('/')[1]))",
pyformat_number },
{ "list", "list of elements separated by whitespace",
R"((?:.|\n)*)", "(lambda s : s.split())",
pyformat_list },
{ "dlist", "list of elements separated by the first word",
R"((?:.|\n)*)", "",
pyformat_dlist },
{ "rest", "a list of strings delimited by the bar character",
R"(.*)", "str",
pyformat_string },
{ "literal", "literal text passed without interpretation",
R"((?:.|\n)*)", "str",
pyformat_string },
};

12
mac/argument.cpp Normal file
View File

@@ -0,0 +1,12 @@
#include "argument.h"
#include "argtype.h"
Parameter::Parameter(const std::string& name, Argtype argtype, const Locator& loc,
bool optional, std::string default_value)
: m_name(name)
, m_argtype(argtype)
, m_optional(optional)
, m_default(default_value)
, m_loc(loc)
{
}

37
mac/argument.h Normal file
View File

@@ -0,0 +1,37 @@
#pragma once
#include "argtype.h"
// The Parameter class describes both parameters (in definitions) and
// arguments (in applications). Parameters are the primary concept:
// a designer declares parameters, and a writer supplies arguments to
// fill them. The class is named for the definition side because
// definitions come first; the Argument alias is used at application sites.
class Parameter
{
public:
Parameter()
: m_name("_default")
, m_argtype(Argtype())
, m_optional(false)
, m_default("")
, m_loc()
, m_target()
{};
Parameter(const std::string& name, Argtype argtype, const Locator& loc,
bool optional=false, std::string default_value = "");
~Parameter() = default;
std::string python_value(std::string value);
bool undefined() const { return m_name == "_default"; };
std::string m_name {};
Argtype m_argtype; // {};
bool m_optional {};
std::string m_default {};
Locator m_loc;
std::string m_target {};
};
using Argument = Parameter;

420
mac/argument_set.cpp Normal file
View File

@@ -0,0 +1,420 @@
#include <ranges>
#include <algorithm>
#include <utility>
#include "show.h"
#include "log.h"
#include "katom.h"
#include "argtype_set.h"
#include "argument_set.h"
#include "util.h"
bool operator==(Parameter_set lhs, Parameter_set rhs)
{
return as_string(lhs.m_katoms.begin(), lhs.m_katoms.end(), true) ==
as_string(rhs.m_katoms.begin(), rhs.m_katoms.end(), true);
}
std::regex parameter_regex(bool optional=false)
{
//std::string pattern = R"(([A-Za-z]\w*)(?:(\.\w*)(?:(\.\w*)))?)";
std::string pattern = R"((?:([A-Za-z]\w*))|(?:([A-Za-z]\w*)\.(\w+))|(?:([A-Za-z]\w*)\.(\w+)\.(\w+)))";
if (optional) {
pattern = R"((?::([A-Za-z]\w*))|(?::([A-Za-z]\w*)\.(\w+))|(?::([A-Za-z]\w*)\.(\w*)\.(\w+)))";
// x x
}
// (void)K::log(3, pattern);
return std::regex(pattern);
}
Parameter_set::Parameter_set(const std::string parameter_string)
{
(void)K::log(3);
Argtype_set argtypes {};
parse_parameters(
katomize(line_split(parameter_string), Locator().str()),
argtypes);
}
Parameter_set::Parameter_set(const std::vector<Katom>& katoms)
: m_katoms(katoms)
{
(void)K::log(3);
Argtype_set argtypes {};
parse_parameters(m_katoms, argtypes);
}
Parameter_set::Parameter_set(const std::vector<Katom>& katoms, const Argtype_set& argtypes)
: m_katoms(katoms)
{
(void)K::log(3);
parse_parameters(m_katoms, argtypes);
}
// Parameter parsing
Parameter parse_positional_parameter(const katom_list& katoms, const Argtype_set& argtypes)
{
// (void)K::log(3, katoms);
if (katoms.size() > 1) {
throw Argument_error("Multiple katoms for positional argument: " +
as_string(katoms.begin(), katoms.end(), true) +
"\nPositional arguments are separated by the bar (|) character.",
katoms[0].m_loc, false);
}
Katom k = katoms[0];
std::string name = k.m_text;
std::smatch match {};
if (!std::regex_match(name, match, parameter_regex())) {
throw Argument_error(
"The structure of the word \"" + name + "\" is not correct for a positional parameter",
k.m_loc);
} else {
std::string match_name = std::string(match[1]) + std::string(match[2]) + std::string(match[4]);
std::string match_type = std::string(match[3]) + std::string(match[5]);
std::string match_target = match[6];
if (match_type.empty()) {
match_type = "string";
}
return Parameter(match_name, argtypes.get(match_type, k.m_loc), k.m_loc);
}
}
Parameter parse_optional_parameter(const katom_list& katoms, const Argtype_set& argtypes)
{
//(void)K::log(3);
Katom k = katoms[0];
std::string default_value {};
if (katoms.size() > 1) {
default_value = to_string(katoms.cbegin() + 1, katoms.cend(), true);
}
std::string name = k.m_text;
std::smatch match {};
if (!std::regex_match(name, match, parameter_regex(true))) {
throw Argument_error(
"The structure of the word \"" + name +
"\" is not correct for an optional parameter",
k.m_loc);
} else {
std::string match_name = std::string(match[1]) + std::string(match[2]) + std::string(match[4]);
std::string match_type = std::string(match[3]) + std::string(match[5]);
std::string match_target = match[6];
if (match_type.empty()) {
match_type = "string";
}
return Parameter(match_name, argtypes.get(match_type, k.m_loc),
k.m_loc, true, default_value);
}
}
void check_for_missing_parameter(const katom_list& katoms)
{
(void)K::log(3, katoms.size());
// Yeah, yeah, "algorithms."
auto ki = katoms.begin();
while (ki < katoms.end() - 1) {
ki = std::find_if(ki, katoms.end(), [](const Katom& k) {
return k.m_type == katom_t::bar; });
if (ki == katoms.end()) {
break;
}
auto kstart = ki;
ki = std::find_if(ki + 1, katoms.end(), [](const Katom& k) {
return !k.is_whitespace(); });
if (ki == katoms.end()) {
throw Argument_error(
"A parameter list ends with a bar character", kstart->m_loc);
}
auto type_after_bar = ki->m_type;
if (type_after_bar == katom_t::bar) {
throw Argument_error(
"A parameter name was missing between two bar characters", kstart->m_loc);
} else if (type_after_bar == katom_t::option_name) {
throw Argument_error(
"A parameter name was missing between a bar character and an option name",
kstart->m_loc);
}
++ki;
}
}
bool is_boundary(katom_list::const_iterator ki)
{
return ki->m_type == katom_t::option_name || ki->m_type == katom_t::bar;
}
std::vector<std::vector<Katom>>
function_symbol_parts(katom_list::const_iterator kbegin, katom_list::const_iterator kend)
{
std::vector<std::vector<Katom>> parts;
katom_list part {};
auto ki = kbegin;
while (ki != kend && ki->is_whitespace()) {
ki++;
}
if (ki == kend) {
return {};
}
if (ki->m_type != katom_t::option_name) {
part.push_back(Katom("|", katom_t::bar, kbegin->m_loc));
}
while (ki < kend) {
if (!part.empty() && is_boundary(ki)) {
parts.push_back(trim(part));
part = {};
}
part.push_back(*ki);
ki++;
}
if (!part.empty()) {
parts.push_back(trim(part));
}
// std::cout << "PARTS:\n";
// for (size_t i = 0; i < parts.size(); i++) {
// std::cout << i << sp_arrow << parts[i] << "\n";
// }
return parts;
}
katom_list trim_part(katom_list part)
{
return trim(part, {katom_t::space, katom_t::newline, katom_t::bar});
}
std::tuple<katom_lists,katom_lists>
parameter_split(katom_list::const_iterator kbegin, katom_list::const_iterator kend)
{
(void)K::log(3); //, "begin:", *kbegin, "end:", *(kend - 1));
// "distance:", std::distance(kbegin, kend));
auto parts = function_symbol_parts(kbegin, kend);
// std::cout << "parts: " << parts << "\n";
katom_lists positional {};
katom_lists optional {};
for (auto p : parts) {
if (p[0].m_type == katom_t::option_name) {
optional.push_back(p);
} else {
positional.push_back(trim_part(p));
}
}
return {positional, optional};
}
void Parameter_set::parse_parameters(const katom_list& katoms, const Argtype_set& argtypes)
{
(void)K::log(3, trim(katoms));
if (katoms.empty()) {
return;
}
check_for_missing_parameter(katoms);
auto [positional, optional] = parameter_split(katoms.cbegin(), katoms.cend());
for (auto req : positional) {
auto pos = parse_positional_parameter(req, argtypes);
if (pos.m_argtype.m_name == "rest") {
m_rest.push_back(pos);
} else {
m_positional.push_back(pos);
}
}
for (auto opt : optional) {
auto param = parse_optional_parameter(opt, argtypes);
if (std::ranges::count(m_optional_names, param.m_name) > 0) {
throw Argument_error(
"Optional parameter \":" + param.m_name + "\" already defined",
katoms[0].m_loc);
}
m_optional.push_back(param);
m_optional_names.push_back(param.m_name);
}
if (!m_rest.empty()) {
m_positional_count = m_positional.size();
}
}
void describe_arguments(
std::string label,
std::vector<std::vector<Katom>> positional,
std::vector<std::vector<Katom>> optional,
std::vector<Katom> rest)
{
std::cout << label << ":\n"
<< " positional: " << positional << "\n"
<< " optional: " << optional << "\n"
<< " rest: " << rest << "\n";
}
void Parameter_set::describe_parameters()
{
(void)K::log(3);
std::cout << " positional: ";
if (!m_positional.empty()) {
for (auto p : m_positional) {
std::cout << p << " ";
}
} else {
std::cout << "[none]";
}
std::cout << "\n optional: ";
if (!m_optional.empty()) {
for (auto p : m_optional) {
std::cout << p << " ";
}
} else {
std::cout << "[none]";
}
std::cout << "\n rest: ";
if (!m_rest.empty()) {
std::cout << kall << m_rest << kreset << "\n";
} else {
std::cout << "[none]";
}
std::cout << "\n";
}
std::tuple<katom_lists,katom_lists,katom_list>
argument_split(katom_list::const_iterator kbegin, katom_list::const_iterator kend,
long unsigned int positional_limit)
{
// (void)K::log(3, "begin:", *(kbegin+1), "end:", *(kend - 1),
// "distance:", std::distance(kbegin, kend), "limit:", positional_limit);
(void)K::log(3);
// msg() << std::pair(kbegin, kend) << "\n";
auto parts = function_symbol_parts(kbegin, kend);
katom_lists positional {};
katom_lists optional {};
katom_list rest {};
for (auto p : parts) {
if (p[0].m_type == katom_t::option_name) {
optional.push_back(p);
} else if (positional.size() < positional_limit) {
positional.push_back(trim_part(p));
} else {
rest.insert(rest.end(), p.begin(), p.end());
}
}
return {positional, optional, trim_part(rest)};
}
// Parameter/argument mapping
void Parameter_set::check_positional(const katom_lists& positional_arguments, const Locator& loc)
{
(void)K::log(3, "required:", m_positional.size(), positional_arguments.size()); //, positional_arguments);
auto positional_count = m_positional.size();
auto given_count = positional_arguments.size();
if (positional_count > given_count) {
// std::cout << "Given less than required\n";
std::vector<Parameter> missing(m_positional.begin() + given_count, m_positional.end());
//std::cout << "missing: " << missing << "\n";
auto missing_count = missing.size();
std::stringstream ss {};
ss << "Positional " << plural("argument", missing_count) << " " << to_be(missing_count)
<< " missing:\n";
std::cout << ss.str();
for (auto arg : missing) {
ss << " " << arg.m_name << "\n";
}
//std::cout << ss.str();
throw Argument_error(ss.str(), loc, false);
} else if (positional_count < given_count) {
//std::cout << "DESCRIBE\n";
//describe_parameters();
std::stringstream ss {};
ss << "Too many positional arguments were given; "
<< positional_count << " needed but " << given_count << " given";
throw Argument_error(ss.str(), loc);
}
}
std::map<std::string, std::string>
Parameter_set::check_optional(const katom_lists& optional_arguments, const Locator& loc)
{
(void)K::log(3, optional_arguments.size());
std::vector<std::string> optional_names_used {};
std::map<std::string, std::string> values {};
for (const auto& opt : optional_arguments) {
std::string name(opt[0].m_text, 1);
if (std::ranges::count(m_optional_names, name) == 0) {
throw Argument_error("Optional argument \":" + name + "\" not defined", loc);
}
if (std::ranges::count(optional_names_used, name) > 0) {
throw Argument_error("Optional argument \":" + name + "\" already provided "
+ "with a value of:\n" + values[name], loc, false);
}
katom_list value_katoms(opt.begin()+1, opt.end());
std::string value = trim(to_string(value_katoms));
values[name] = value;
optional_names_used.push_back(name);
}
return values;
}
const std::map<std::string, std::string>
Parameter_set::value_map(
const katom_lists& positional, const katom_lists& optional, const katom_list& rest,
const Locator& loc)
{
(void)K::log(3, "positional:", positional.size(), "optional:", optional.size(), "rest:", rest.size());
std::map<std::string, std::string> values {};
check_positional(positional, loc);
for (size_t i = 0; i < m_positional.size(); ++i) {
auto param = m_positional[i];
std::string arg = as_string(positional[i].begin(), positional[i].end(), true);
values[param.m_name] = arg;
}
auto optional_values = check_optional(optional, loc);
for (auto [key, value] : optional_values) {
values[key] = value;
}
for (auto opt : m_optional) {
values.try_emplace(opt.m_name, opt.m_default);
}
if (active(rest)) {
if (!m_rest.empty()) {
values[m_rest[0].m_name] = as_string(rest.begin(), rest.end(), true);
} else {
std::stringstream ss {};
ss << "More positional arguments were given (" << positional.size() + rest.size()
<< ") than defined (" << m_positional.size() << ")";
throw Argument_error(ss.str(), loc);
}
}
return values;
}
// Parameter/argument substitution
std::string replace_arguments(
const std::map<std::string, std::string>& values,
const std::string& parameterized_text, const Locator& loc)
{
(void)K::log(3);
std::string result = parameterized_text;
for (auto [name, value] : values) {
result = string_replace(result, '*' + name + '*', value);
}
auto matches = find_all(result, std::regex(R"((\*.*?\*))"));
std::vector<std::string> unmatched;
unmatched.reserve(matches.size());
std::copy(matches.begin(), matches.end(), std::back_inserter(unmatched));
auto unmatched_count = unmatched.size();
if (unmatched_count > 0) {
std::stringstream ss {};
ss << "Undefined " << plural("argument", unmatched_count) << " in klammer:\n";
for (const auto& arg : unmatched) {
ss << " " << arg << "\n";
}
ss << "To prevent the \"*\" character from specifying an argument, "
<< "precede it with the \"^\" character.";
throw Argument_error(ss.str(), loc, false);
}
return result;
}

63
mac/argument_set.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
#include <limits>
#include <tuple>
#include <vector>
#include "katom.h"
#include "argument.h"
#include "locator.h"
class Argtype_set;
std::tuple<std::vector<std::vector<Katom>>,std::vector<std::vector<Katom>>,std::vector<Katom>>
argument_split(std::vector<Katom>::const_iterator kbegin, std::vector<Katom>::const_iterator kend,
long unsigned int positional_limit = std::numeric_limits<int>::max());
class Parameter_set
{
public:
Parameter_set() {};
~Parameter_set() = default;
Parameter_set(const std::string parameter_string);
Parameter_set(const std::vector<Katom>& katoms);
Parameter_set(const std::vector<Katom>& katoms, const Argtype_set& argtypes);
void parse_parameters(const std::vector<Katom>& katoms, const Argtype_set& argtypes);
void describe_parameters();
void check_positional(
const std::vector<std::vector<Katom>>& positional_arguments, const Locator& loc);
std::map<std::string, std::string> check_optional(
const std::vector<std::vector<Katom>>& optional_arguments, const Locator& loc);
const std::map<std::string, std::string> value_map(
const std::vector<std::vector<Katom>>& positional,
const std::vector<std::vector<Katom>>& optional,
const std::vector<Katom>& rest,
const Locator& loc);
bool empty() const { return m_katoms.size() == 0; };
std::vector<Katom> m_katoms {};
//Argtype_set m_argtypes {};
std::vector<Parameter> m_positional {};
std::vector<Parameter> m_optional {};
std::vector<std::string> m_optional_names {};
std::vector<Parameter> m_rest {};
size_t m_positional_count = std::numeric_limits<int>::max();
};
bool operator==(Parameter_set lhs, Parameter_set rhs);
// The Argument_set alias is used at application sites, where the set
// describes arguments rather than parameters. See argument.h.
using Argument_set = Parameter_set;
void describe_arguments(
std::string label,
std::vector<std::vector<Katom>> positional,
std::vector<std::vector<Katom>> optional,
std::vector<Katom> rest);
std::string replace_arguments(
const std::map<std::string, std::string>& values,
const std::string& parameterized_text, const Locator& loc);

569
mac/argv.cpp Normal file
View File

@@ -0,0 +1,569 @@
#include <numeric>
#include "file.h"
#include "argv.h"
#include "error.h"
#include "log.h"
#include "util.h"
#include "show.h"
std::string Argv::delimiter = "--";
std::ostream& operator<<(std::ostream& os, const Arg& arg)
{
os << "<" << arg.m_type << " " << arg.m_name
<< " " << q_(arg.m_value) << ">";
return os;
}
std::string wrap_around(const std::string& text, std::size_t indent, std::size_t width=96)
{
std::string result {};
std::size_t current = indent;
std::string margin(indent, ' ');
for (const std::string& word : word_split(text)) {
if (current + 1 + word.size() > width) {
result += "\n" + margin;
current = indent;
}
result += word + " ";
current += word.size() + 1;
}
return result;
}
std::string flag_name(const std::string& name)
{
std::string result {};
if (name.size() == 1) {
result = "-" + name;
} else {
result = "--" + name;
}
return result;
}
std::string Arg::symbol()
{
std::string result = m_name;
if (m_type != "req") {
result = flag_name(m_name);
/*
if (m_name.size() == 1) {
result = "-" + m_name;
} else {
result = "--" + m_name;
}
*/
} else {
result = "<" + result + ">";
}
return result;
}
void Arg::make_regex(const std::string& key)
{
std::string pat {};
if (regex_symbols.find(key) != regex_symbols.end()) {
pat = regex_symbols[key];
m_rgx_symbol = key;
// std::cout << "rgx_symbol: " << m_rgx_symbol << "\n";
} else if (key.find("(") != std::string::npos) {
pat = key;
}
if (pat.size() == 0) {
throw Definition_error("No regex pattern defined for \"" + key + "\".");
}
m_pattern = pat;
m_rgx = std::regex(m_pattern);
}
void Argv::update_width(Arg arg)
{
m_syntax_size = std::max(m_syntax_size, arg.m_syntax.size());
}
std::string get_regex_desc(const std::string& desc)
{
if (regex_desc.find(desc) != regex_desc.end()) {
return regex_desc[desc];
} else {
return desc;
}
}
void Argv::flag(const std::string& name, const std::string& desc)
{
(void)K::log(2, name, desc);
Arg arg {};
arg.m_type = "flag";
arg.m_name = name;
arg.m_desc = get_regex_desc(desc);
arg.m_rgx = std::regex(R"((\w+))");
arg.m_syntax = arg.symbol();
arg.m_value = "false";
m_args[name] = arg;
m_names.push_back(name);
m_flag_names.push_back(name);
m_hyphen_markers.push_back(flag_name(name));
update_width(arg);
}
void Argv::req(const std::string& name, const std::string& desc, const std::string& regex_pattern)
{
(void)K::log(2, name, desc, regex_pattern);
Arg arg {};
arg.m_type = "req";
arg.m_name = name;
arg.m_desc = get_regex_desc(desc);
arg.make_regex(regex_pattern);
arg.m_syntax = "<" + name + ">";
m_args[name] = arg;
m_names.push_back(name);
m_req_names.push_back(name);
update_width(arg);
}
void Argv::opt(const std::string& name, const std::string& desc, const std::string& parameter, const std::string& default_value, const std::string& regex_pattern)
{
(void)K::log(2, name, desc, parameter, default_value, regex_pattern);
Arg arg {};
arg.m_type = "opt";
arg.m_name = name;
arg.m_parameter = parameter;
arg.m_default_value = default_value;
arg.m_value = default_value;
arg.m_desc = get_regex_desc(desc);
arg.make_regex(regex_pattern);
arg.m_syntax = arg.symbol() + " <" + arg.m_parameter + ">";
m_args[name] = arg;
m_names.push_back(name);
m_opt_names.push_back(name);
m_hyphen_markers.push_back(flag_name(name));
update_width(arg);
}
void Argv::usage_line(Arg arg)
{
std::cout.fill(' ');
std::cout << " " << std::left << std::setw(m_syntax_size) << arg.m_syntax << " "
<< wrap_around(arg.m_desc, m_syntax_size + 6) << "\n";
}
void Argv::usage(const std::string& command)
{
std::cout << "\nUsage: " << command << " ";
for (const std::string& name : m_req_names) {
std::cout << "<" + name + "> ";
}
if (m_opt_names.size() + m_flag_names.size() > 5) {
std::cout << "[<optional-arguments>]\n";
} else {
for (const std::string& name : m_names) {
if (m_args[name].m_type == "req") {
continue;
}
std::cout << "[" + m_args[name].m_syntax + "] ";
}
std::cout << "\n";
}
if (!m_req_names.empty()) {
//std::cout << "\n" << plural("Argument", m_req_names.size()) << ":\n";
std::cout << "\n" << "Arguments:\n";
for (const std::string& req_name : m_req_names) {
usage_line(m_args[req_name]);
}
}
int flag_count = m_flag_names.size();
int opt_count = m_opt_names.size();
if (flag_count > 0 || opt_count > 0) {
std::cout << "\n" << plural("Option", flag_count + opt_count) << ":\n";
}
for (const auto& name : m_names) {
if (m_args[name].m_type == "req") {
continue;
}
usage_line(m_args[name]);
}
std::cout << "\n";
}
void Argv::check_flags_and_options(std::string command, strings_t& words)
{
std::vector<std::string> not_defined {};
for (auto word : words) {
if (word[0] == '-' && word != Argv::delimiter && !is_in(word, m_hyphen_markers)) {
not_defined.push_back(word);
}
}
if (!not_defined.empty()) {
std::stringstream ss {};
ss << "The following flags were not defined for command " << q_(command) << ":\n";
for (auto w : not_defined) {
ss << " " << w << "\n";
}
throw Argument_error(ss.str(), Locator(), false);
}
}
void Argv::parse_flags(strings_t& words, string_map& named_args)
{
std::vector<std::string> flag_args {};
for (std::string flag : m_flag_names) {
// std::cout << "Flag: " << flag << "\n";
if (is_in(flag_name(flag), words)) {
flag_args.push_back(flag);
remove_element(words, flag_name(flag));
named_args[flag] = "true";
} else {
named_args[flag] = "false";
}
}
/*
std::vector<std::string> not_defined {};
for (auto word : words) {
if (word[0] == '-' && word != Argv::delimiter) {
not_defined.push_back(word);
}
}
if (!not_defined.empty()) {
std::stringstream ss {};
ss << "The following flags were not defined for command " << q_(command) << ":\n";
for (auto w : not_defined) {
ss << " " << w << "\n";
}
throw Argument_error(ss.str(), Locator(), false);
}
*/
// std::cout << "Flags found: " << flag_args << "\n";
}
void Argv::parse_optional(strings_t& words, string_map& named_args)
{
// msg() << "parse_optional: " << words << "\n";
std::map<std::string, std::string> opt_args {};
for (std::string opt : m_opt_names) {
// std::cout << "Opt: " << opt << sp_arrow << m_args[opt].m_pattern << "\n";
auto it = std::ranges::find(words, flag_name(opt));
if (it != words.end()) {
// msg() << "words: " << words.size() << " " << words << "\n";
size_t index = std::distance(words.begin(), it);
// std::cout << " Found: " << words[index] << "\n";
//std::vector<std::string> opt_args = {};
index++;
/*
if (index >= words.size()) {
break;
}
*/
std::string opt_arg = words[index] + " ";
index++;
/*
if (index >= words.size()) {
break;
}
*/
std::regex opt_regex = m_args[opt].m_rgx;
// std::cout << "Regex match? " << std::regex_match(trim(opt_arg), opt_regex) << "\n";
while (index < words.size() && words[index][0] != '-'
// && std::regex_match(trim(opt_arg), opt_regex)
&& std::regex_match(trim(opt_arg + words[index]), opt_regex)
) {
// opt_args.push_back(words[index++]);
opt_arg += words[index++] + " " ;
}
opt_arg = trim(opt_arg);
// std::cout << " Value: " << opt_arg << "[" << index << "]\n";
opt_args[opt] = opt_arg;
words.erase(it, words.begin() + index);
// std::cout << " Remaining: " << words << "\n";
named_args[opt] = opt_arg;
} else { // Not found
}
}
// std::cout << "opt_args:\n";
// if (opt_args.empty()) {
// std::cout << "[none]";
// } else {
// std::cout << opt_args;
// }
// std::cout << "\n";
}
void Argv::parse_positional(std::string command, //strings_t words,
std::string pos_args, string_map& named_args)
{
for (std::string req : m_req_names) {
auto arg = m_args[req];
auto [substring, rest, found] = regex_split_prefix(arg.m_rgx, pos_args);
if (!found) {
std::stringstream ss {};
ss << "The argument " << q_(req) << " was not found in:\n " << command;
throw Argument_error(ss.str(), Locator(), false);
}
named_args[req] = substring;
pos_args = rest;
}
// std::cout << "Remaining words: " << words << "\n" << pos_args << "\n";
}
std::map<std::string, std::string>
Argv::classify_arguments(int argc, char* argv[], bool full_parse)
{
(void)K::log(2, argc);
if (argc == 1) {
return {};
}
std::map<std::string, std::string> named_args {};
std::vector<std::string> words(argv + 1, argv + argc);
if (full_parse) {
check_flags_and_options(argv[0], words);
}
parse_flags(words, named_args);
parse_optional(words, named_args);
parse_positional(argv_to_string(argc, argv), join(words, " "), named_args);
words.erase(std::remove(words.begin(), words.end(), Argv::delimiter), words.end());
// std::cout << "Named args:\n" << named_args << "\n";
return named_args;
}
void Argv::check_required(
const std::vector<std::string>& req_args, const std::string& command)
{
(void)K::log(2);
auto required = m_req_names.size();
auto given = req_args.size();
if (required > given) {
std::string missing = m_req_names[required - given - 1];
if (given == 0 && m_args[missing].m_rgx_symbol == "'list'") {
return;
}
std::stringstream ss {};
ss << "The required argument \"" << missing
<< "\" was not provided in command \"" << command << "\"";
throw Argument_error(ss.str());
} else if (required < given) {
std::stringstream ss {};
ss << "Too many required arguments were given for command \""
<< command << "\" (" << required << " needed but "
<< given << " given" << ")";
throw Argument_error(ss.str());
}
}
void Argv::check_flags(const string_map& arg_map, const std::string& command)
{
(void)K::log(3);
// std::cout << "arg_map:\n" << arg_map << "\n";
strings_t undefined {};
for (const auto& pair : arg_map) {
auto [key, value] = pair;
// std::cout << "m_type: " << m_args[key].m_type << "\n";
if (key[0] != '_') {
if (m_args[key].m_type != "req"
&& !contains(m_flag_names, key)
&& !contains(m_opt_names, key)) {
undefined.push_back(key);
}
}
}
if (!undefined.empty()) {
std::stringstream ss {};
ss << "Undefined arguments given for command \"" << command << "\":";
for (const std::string& undef : undefined) {
ss << " " << flag_name(undef);
}
throw Argument_error(ss.str(), Locator());
}
}
void Argv::parse(int argc, char* argv[], bool full_parse)
{
(void)K::log(2);
command_name = argv[0];
describe();
auto input_args = classify_arguments(argc, argv, full_parse);
// std::cout << "parse() classify:\n" << input_args << "\n";
for (auto [key, value] : input_args) {
m_args[key].m_value = value;
}
// std::cout << "m_flag_names: " << m_flag_names << "\n";
/*
for (const std::string& name : m_flag_names) {
if (input_args.find(name) != input_args.end()) {
m_args[name].m_value = "true";
}
}
*/
/*
for (const std::string& name : m_opt_names) {
if (input_args.find(name) != input_args.end()) {
std::smatch match {};
if (std::regex_match(input_args[name], match, m_args[name].m_rgx)) {
m_args[name].m_value = input_args[name];
} else {
usage(file_basename(argv[0]));
throw Argument_error(
"Incorrect value for option \"" + name + "\":\n" + m_args[name].m_desc + "\n",
Locator(), false);
}
}
}
std::cout << "input_args:\n" << input_args << "\n";
if (full_parse) {
for (auto [key, value] : input_args) {
auto arg = m_args[key];
if (arg.m_type == "req") {
m_args[key].m_value = value;
}
}
*/
/*
std::vector<std::string> required_args {};
for (auto [key, value] : input_args) {
// std::cout << " full_parse: key: " << key << " value: " << value << "\n";
if (key[0] == '_' && !trim(value).empty()) {
required_args.push_back(value);
}
}
check_required(required_args, command_name);
for (unsigned int i = 0; i < required_args.size(); ++i) {
m_args[m_req_names[i]].m_value = required_args[i];
}
*/
//check_flags(input_args, command_name);
// }
// std::cout << "FINAL:\n"
// << m_args;
}
std::string Argv::get(const std::string& name, bool missing_is_error)
{
if (m_args.count(name) > 0) {
return m_args.at(name).m_value;
} else if (missing_is_error) {
throw Argument_error("Command-line argument \"" + name + "\" is not defined");
} else {
return "";
}
}
bool Argv::as_bool(const std::string& name)
{
(void)K::log(2, name);
return get(name) == "true";
}
int Argv::as_int(const std::string& name)
{
(void)K::log(2, name);
auto value = get(name);
if (!std::regex_match(value, std::regex(R"([-+]?\d+)"))) {
throw Argument_error("Argument \"" + value + "\" is not an integer");
}
return std::stoi(value);
}
int Argv::as_integer_range(const std::string& name, int low, int high)
{
(void)K::log(2, name);
auto value = get(name);
std::stringstream ss {};
ss << "Argument \"" << value << "\" is not an integer in the range of "
<< low << " to " << high;
if (!std::regex_match(value, std::regex(R"([-+]?\d+)"))) {
throw Argument_error(ss.str());
}
int result = std::stoi(value);
if (result < low || result > high) {
throw Argument_error(ss.str());
}
return result;
}
int Argv::as_verbosity(const std::string& name)
{
(void)K::log(2, name);
auto value = get(name);
if (!std::regex_match(value, std::regex(regex_symbols["'verbosity'"]))) {
throw Argument_error("Argument \"" + value + "\" is not a verbosity level");
}
return std::stoi(value);
}
std::string Argv::as_string(const std::string& name)
{
(void)K::log(2, name);
return get(name);
}
strings_t Argv::as_vector(const std::string& name)
{
(void)K::log(2, name);
return regex_split(get(name), std::regex(R"(\s+)"));
}
std::pair<std::string, strings_t> Argv::as_input(const std::string& name, bool allow_empty)
{
(void)K::log(2, name);
const std::string& input_arg = get(name);
std::regex filename_rgx(R"(([./\w]+\.kt?))");
strings_t input_filenames = find_all(input_arg, filename_rgx, 1);
const std::string& input_text = trim(std::regex_replace(input_arg, filename_rgx, ""));
if (input_text.empty() && input_filenames.empty()) {
if (allow_empty) {
return {{},{}};
} else {
throw Argument_error("No input text or filenames specified");
}
}
(void)K::log(2, "Input text", input_text, 1);
for (const auto& f : input_filenames) {
(void)K::log(2, "Input filename", f);
}
return { input_text, input_filenames };
}
void Argv::describe()
{
std::size_t width = std::accumulate(
m_names.begin(), m_names.end(), 0,
[&] (size_t w, const std::string& name) {
return std::max(w, m_args[name].symbol().size()); });
for (const std::string& name : m_names) {
std::string value = m_args[name].m_value;
if (value.empty()) {
value = "<none>";
}
std::stringstream ss {};
ss << " "<< std::setfill(' ') << std::setw(width)
<< m_args[name].symbol() << " : " << value;
}
/*
if (verbose_level == 1) {
std::cout << "\n";
}
*/
}

103
mac/argv.h Normal file
View File

@@ -0,0 +1,103 @@
#pragma once
// Delusions of generality, but it's really just for Klammertext commands.
#include <map>
#include <ranges>
#include <algorithm>
#include <regex>
inline std::map<std::string, std::string> regex_symbols {
{"'text'", R"((^[^-](?:\s|.)*))" },
{"'word'", R"((([^\s]+)))" },
{"'list'", R"((^[^-]?(?:\s|.)*))" },
// {"int", R"((\d))" },
// {"ints", R"((\d[ \d]*))" },
{"'verbosity'", R"(([01234]))" },
// {"targets", R"((html|tex|pdf|txt))" },
//{"'katom_display'", R"((none ?|all ?|type ?|index ?|ignored ?|replaced ?)*)" },
{"'katom_display'", R"(( *|none|all|type|index|ignored|replaced)*)" },
};
inline std::map<std::string, std::string> regex_desc {
{"'verbosity'", "Verbosity during processing (0,1,2,3,4); default is 0." }
};
std::regex make_regex(const std::string& key);
class Arg
{
public:
std::string symbol();
void make_regex(const std::string& key);
std::string m_type {};
std::string m_name {};
std::string m_pattern {};
std::string m_rgx_symbol {};
std::regex m_rgx {};
std::string m_default_value {};
std::string m_parameter {};
std::string m_syntax {};
std::string m_desc {};
std::string m_value {};
};
std::ostream& operator<<(std::ostream& os, const Arg& arg);
class Argv
{
public:
static std::string delimiter;
void flag(const std::string& name, const std::string& desc);
void req(const std::string& name, const std::string& desc, const std::string& regex_pattern="'text'");
void opt(const std::string& name, const std::string& desc="", const std::string& parameter="", const std::string& default_value="", const std::string& regex_pattern="text");
void update_width(Arg arg);
void check_flags_and_options(std::string command, std::vector<std::string>& words);
void parse_flags(std::vector<std::string>& words, std::map<std::string, std::string>& named_args);
void parse_optional(std::vector<std::string>& words, std::map<std::string, std::string>& named_args);
void parse_positional(
std::string command, // std::vector<std::string> words,
std::string pos_args, std::map<std::string, std::string>& named_args);
std::map<std::string, std::string> classify_arguments(int argc, char* argv[], bool full_parse=true);
void check_required(
const std::vector<std::string>& req_args, const std::string& command_name);
void check_flags(const std::map<std::string, std::string>& arg_map, const std::string& command_name);
void parse(int argc, char* argv[], bool full_parse=true);
std::string get(const std::string& name, bool missing_is_error=true);
bool as_bool(const std::string& name);
int as_int(const std::string& name);
int as_integer_range(const std::string& name, int low, int high);
int as_verbosity(const std::string& name);
std::string as_string(const std::string& name);
std::vector<std::string> as_vector(const std::string& name);
std::pair<std::string, std::vector<std::string>> as_input(const std::string& name, bool allow_empty=false);
void usage_line(Arg arg);
void usage(const std::string& command_name);
void describe();
// void describe(Argv original);
bool is_flag(std::string name) {
return std::ranges::count(m_flag_names, name) > 0;
}
bool is_opt(std::string name) {
return std::ranges::count(m_opt_names, name) > 0;
}
std::string m_command {};
std::map<std::string, Arg> m_args {};
std::vector<std::string> m_names {};
std::vector<std::string> m_req_names {};
std::vector<std::string> m_flag_names {};
std::vector<std::string> m_opt_names {};
std::vector<std::string> m_hyphen_markers {};
long unsigned int m_syntax_size = 0;
};

1
mac/basenames.mk Normal file
View File

@@ -0,0 +1 @@
BASENAMES := util error locator file argv character ktype katom katom_list log show command argument argument_set argtype argtype_set state eval eval_python eval_cpp klammer klammer_set target target_set machine

304
mac/character.cpp Normal file
View File

@@ -0,0 +1,304 @@
#include <fstream>
#include "util.h"
#include "character.h"
#include "log.h"
#include "show.h"
inline
std::string klammertext_special_characters { "@|*^#" };
inline
std::string encoding_marker { "UU" };
inline
std::string diacritic_symbols = "-'`h~\"cbrdwa";
inline
std::string diacritic_symbols_order = "'`h~\"c-brdwa";
std::string utf8char(int cp)
{
char c[5]={ 0x00,0x00,0x00,0x00,0x00 };
if (cp<=0x7F) {
c[0] = cp;
} else if(cp<=0x7FF) {
c[0] = (cp>>6)+192;
c[1] = (cp&63)+128;
} else if(0xd800<=cp && cp<=0xdfff) {
return "Invalid Unicode: " + std::to_string(cp);
} else if(cp<=0xFFFF) {
c[0] = (cp>>12)+224;
c[1]= ((cp>>6)&63)+128;
c[2]=(cp&63)+128;
} else if (cp<=0x10FFFF) {
c[0] = (cp>>18)+240;
c[1] = ((cp>>12)&63)+128;
c[2] = ((cp>>6)&63)+128;
c[3]=(cp&63)+128;
}
return std::string(c);
}
std::string unicode_hex_to_char(std::string s, int width=4) //, std::string marker)
{
(void)K::log(4, s);
std::string result {s};
std::sregex_iterator end {};
std::regex re;
switch (width) {
case 2: re = hex2_re; break;
case 4: re = hex4_re; break;
case 5: re = hex5_re; break;
}
for (std::sregex_iterator p { s.begin(), s.end(), re }; p!= end; ++p) {
int codepoint = stoi((*p)[1].str(), nullptr, 16);
auto c = utf8char(codepoint);
std::regex hit_re { regex_escape((*p)[0]) };
result = std::regex_replace(result, hit_re, c);
}
return result;
}
std::string process_diacritics(std::string s)
{
(void)K::log(4);
std::regex diacritic_re("\\^([^\\s`'~@|^:*#])([" + diacritic_symbols + "])");
std::string result {s};
std::sregex_iterator end {};
for (std::sregex_iterator p { s.begin(), s.end(), diacritic_re }; p!= end; ++p) {
std::regex hit { regex_escape((*p)[0].str()) };
std::string ch = (*p)[1].str();
std::string d = (*p)[2].str();
result = std::regex_replace(result, hit, ch + unicode_hex_to_char(diacritics[d].first));
}
return result;
}
std::string extended_latin_symbol_pattern()
{
std::string result {};
std::string sep = "";
for (const auto& nr : extended_latin_symbols) {
result += sep;
result += nr;
sep = "|";
}
return result;
}
std::string process_extended_latin(std::string s)
{
(void)K::log(4);
std::regex diacritic_re("\\^(" + extended_latin_symbol_pattern() + ")\\^");
std::string result {s};
std::sregex_iterator end {};
for (std::sregex_iterator p { s.begin(), s.end(), diacritic_re }; p!= end; ++p) {
std::regex hit { regex_escape((*p)[0].str()) };
std::string ch = (*p)[1].str();
result = std::regex_replace(result, hit, unicode_hex_to_char(extended_latin[ch].first));
}
return result;
}
std::string process_pinyin(std::string s)
{
(void)K::log(4);
std::regex pinyin_re(R"(\^([aeiou])([1-4]))");
std::string result {s};
std::sregex_iterator end {};
for (std::sregex_iterator p { s.begin(), s.end(), pinyin_re }; p!= end; ++p) {
std::regex hit { regex_escape((*p)[0].str()) };
std::string vowel = (*p)[1].str();
std::string tone = (*p)[2].str();
result = std::regex_replace(result, hit,
vowel + unicode_hex_to_char(pinyin_tones[tone].first));
}
return result;
}
std::string process_unicode_codepoint(std::string s)
{
(void)K::log(4);
//return std::regex_replace(s, unicode_re, hidehat + "$1" + hidehat);
std::string result {s};
std::sregex_iterator end {};
for (std::sregex_iterator p { s.begin(), s.end(), unicode_re }; p!= end; ++p) {
std::regex hit_re { regex_escape((*p)[0].str()) };
result = std::regex_replace(result, hit_re, unicode_hex_to_char((*p)[1].str()));
}
return result;
}
// Old xhide/xrestore/hide/restore functions removed. The ^X mechanism
// is handled by the katomizer (katom_t::special) and the general KTESC
// escape mechanism in Target::escape_text/resolve_escapes.
std::string encode(const std::string& s)
{
if (s.find("^") == std::string::npos)
return s;
(void)(void)K::log(3);
std::string result = s;
bool dbg = verbose_level > 3;
std::string lit_start = "__LITSTART__";
std::string lit_end = "__LITEND__";
result = string_replace(result, "^'", lit_start);
result = string_replace(result, "'^", lit_end);
if (result.find("^") == std::string::npos)
return s;
if (dbg) std::cout << "start: " << result << "\n";
result = process_extended_latin(result);
if (dbg) std::cout << "extended_latin: " << result << "\n";
result = process_unicode_codepoint(result);
if (dbg) std::cout << "unicode: " << result << "\n";
result = process_diacritics(result);
if (dbg) std::cout << "diacrit: " << result << "\n";
result = process_pinyin(result);
if (dbg) std::cout << "pinyin: " << result << "\n";
result = string_replace(result, lit_start, "^'");
result = string_replace(result, lit_end, "'^");
return result;
}
// Decode
// Display
void write_kt_example_file(std::stringstream& kt, std::string kt_filename)
{
kt << "|| @line@\n";
kt << "@\n";
std::cout << "Writing " << kt_filename << "...";
std::ofstream out(kt_filename);
out << kt.str();
out.close();
std::cout << "done\n";
}
void diacritics_examples(const std::string& kt_filename)
{
if (diacritics.size() != diacritic_symbols_order.size()) {
//throw Internal_error("Mismatch between diacritics order list and their definitions");
std::cout << "Mismatch error\n";
}
std::stringstream kt {};
bool write_kt_file = kt_filename.size() != 0;
if (write_kt_file)
kt << "@table :caption Diacritics (with typical base characters) |\n"
<< " @i-Displayed | @i-Written | @i-Name\n";
else
std::cout << boldblack
<< "\nDiacritics (with typical base characters)\n" << black;
std::string line_sep = "|| @line@ ";
for (char symbol : diacritic_symbols_order) {
std::string sym { symbol };
auto [code, name] = diacritics[sym];
std::string letter = diacritic_example_letter[sym];
std::string written = "^" + letter + sym;
if (sym == "~") {
written = "^" + letter + "=7e=";
}
if (write_kt_file) {
std::string display = "^" + letter + sym;
kt << line_sep << display << " | @t ^" << written << " @ | " << name << "\n";
line_sep = "|| ";
}
else {
std::cout << " " << letter << unicode_hex_to_char(code)
<< " " << "^" << letter << symbol << " " << name << "\n";
}
}
if (write_kt_file)
write_kt_example_file(kt, kt_filename);
}
void extended_latin_examples(const std::string& kt_filename)
{
std::stringstream kt {};
bool write_kt_file = kt_filename.size() != 0;
std::string title = "Extended Latin characters and ligatures";
if (write_kt_file)
kt << "@table :caption " << title << " |\n"
<< " @i-Displayed | @i-Written | @i-Name\n";
else
std::cout << "\n" << boldblack << title << black << "\n";
std::string line_sep = "|| @line@ ";
for (const std::string& symbol : extended_latin_symbols) {
auto [code, name] = extended_latin[symbol];
if (write_kt_file) {
std::string coded = "^" + symbol + "^";
std::string file_literal = "^^ #- " + symbol + " #- ^^";
kt << line_sep << coded << " | @t " << file_literal << " @ | " << name << "\n";
line_sep = "|| ";
}
else {
std::string screen_literal = "^" + symbol + "^";
std::cout << " " << unicode_hex_to_char(code)
<< " " << std::setw(4) << std::left << screen_literal << " " << name << "\n";
}
}
if (write_kt_file)
write_kt_example_file(kt, kt_filename);
}
void pinyin_examples(const std::string& kt_filename)
{
std::stringstream kt {};
bool write_kt_file = kt_filename.size() != 0;
if (write_kt_file)
kt << "@table :caption Mandarin pinyin tones (using vowel ``a'') |\n"
<< " @i-Displayed | @i-Written | @i-Name\n";
else
std::cout << boldblack
<< "\nMandarin pinyin tones (using vowel \"a\")\n" << black;
std::string line_sep = "|| @line@ ";
for (auto [symbol, codename] : pinyin_tones) {
auto [code, name] = codename;
// std::string hat_code = "^a" + code;
std::string literal = "^a" + symbol;
if (write_kt_file) {
kt << line_sep << literal << " | @t ^" << literal << " @ | " << name << "\n";
line_sep = "|| ";
} else
std::cout << " a" << unicode_hex_to_char(code)
<< " "<< std::setw(4) << std::left << literal << " " << name << "\n";
}
if (write_kt_file)
write_kt_example_file(kt, kt_filename);
}
void show_special_characters()
{
std::cout << std::setfill(' ');
diacritics_examples();
extended_latin_examples();
pinyin_examples();
}

105
mac/character.h Normal file
View File

@@ -0,0 +1,105 @@
#pragma once
// https://jakubmarian.com/special-characters-diacritics-used-in-european-languages/
#include <string>
#include <map>
#include <iomanip>
#include <tuple>
#include <vector>
#include <utility>
#include <regex>
#include <unistd.h>
const std::regex unicode_re(R"(\^(([0-9A-Fa-f]{5})|([0-9A-Fa-f]{4})|([0-9A-Fa-f]))\^)");
const std::regex unicode_hide_re(R"(=([0-9A-Fa-f]{2})=)");
const std::regex hex2_re(R"(([0-9A-Fa-f]{2}))");
const std::regex hex4_re(R"(([0-9A-Fa-f]{4}))");
const std::regex hex5_re(R"(([0-9A-Fa-f]{5}))");
// The hide_special_characters vector was removed. The ^X mechanism for
// Klammertext special characters is handled by the katomizer (type
// katom_t::special) and the general KTESC escape mechanism for
// target-specific characters.
inline
std::map<std::string, std::string> diacritic_example_letter {
{"'", "e"},
{"`", "a"},
{"h", "o"},
{"~", "n"},
{"\"", "u"},
{"c", "c"},
{"-", "o"},
{"b", "g"},
{"r", "a"},
{"d", "e"},
{"w", "s"},
{"a", "o"}};
inline
std::map<std::string, std::pair<std::string, std::string>> diacritics {
{"`", {"0300", "grave accent"}},
{"'", {"0301", "acute accent"}},
{"h", {"0302", "circumflex"}},
{"~", {"0303", "tilde"}},
{"-", {"0304", "macron"}},
{"\"", {"0308", "diaresis"}},
// {"v", {"0305", "vinculum"}},
{"b", {"0306", "breve"}},
{"d", {"0307", "dot above"}},
{"r", {"030A", "ring above"}},
{"w", {"030C", "wedge"}},
{"c", {"0327", "cedilla"}},
{"a", {"030B", "double acute accent"}}};
inline
std::vector<std::string> extended_latin_symbols = {
"s",
"i", "I", "t", "T", "e", "E", "o", "O", "d", "D",
"ae", "AE", "oe", "OE"
};
inline
std::map<std::string, std::pair<std::string, std::string>> extended_latin {
{"ae", {"00E6", "ae ligature"}},
{"AE", {"00C6", "ae ligature capital"}},
{"oe", {"0153", "oe ligature"}},
{"OE", {"0152", "oe ligature capital"}},
{"i", {"0131", "dotless i"}},
{"I", {"0130", "capital dotted i"}},
{"t", {"00FE", "thorn"}},
{"T", {"00DE", "thorn capital"}},
{"e", {"00F0", "eth"}},
{"E", {"00D0", "eth capital"}},
{"o", {"00F8", "o stroke"}},
{"O", {"00D8", "o stroke capital"}},
{"s", {"00DF", "Eszett"}},
{"d", {"0111", "d stroke"}},
{"D", {"0110", "d stroke capital"}}};
inline
std::map<std::string, std::pair<std::string, std::string>> pinyin_tones {
{"1", {"0304", "high"}},
{"2", {"0301", "rising"}},
{"3", {"030C", "falling-rising"}},
{"4", {"0300", "falling"}}};
inline
bool is_tty() { return isatty(fileno(stdout)); }
//const char* italic_on() { return tty() ? "\033[3m" : ""; }
//const char* italic_off() { return tty() ? "\033[0m" : ""; }
inline
const std::string italic_on() { return is_tty() ? "\033[3m" : ""; }
inline
const std::string italic_off() { return is_tty() ? "\033[0m" : ""; }
std::string encode(const std::string& s);
void diacritics_examples(const std::string& kt_filename = "");
void extended_latin_examples(const std::string& kt_filename = "");
void pinyin_examples(const std::string& kt_filename = "");
void show_special_characters();

109
mac/command.cpp Normal file
View File

@@ -0,0 +1,109 @@
#include <tuple>
#include "argv.h"
#include "file.h"
#include "log.h"
using namespace std::string_literals;
fs::path construct_command_pathname(char* command)
{
return fs::path(fs::current_path().string() + "/" + std::string(command));
}
void set_verbose_level(int argc, char* argv[])
{
//command_name = absolute_pathname(argv[0]);
command_name = std::string(argv[0]);
command_pathname = construct_command_pathname(argv[0]);
//std::cout << "set_verbose_level: " << command_pathname << "\n";
Argv args {};
args.opt("v", "'verbosity'", "level", "0", "'verbosity'");
args.parse(argc, argv, false);
verbose_level = args.as_verbosity("v");
}
bool show_usage(int argc, char* argv[])
{
return argc == 1 || (argc == 3 && std::string(argv[1]) == "-v"s);
}
std::string construct_output_filename(
const std::string& output_dir, const std::string& output_basename, const std::string& target)
{
std::string result {};
if (target == "html") {
result = output_dir + "/" + output_basename + "/index.html";
} else {
result = output_dir + "/" + output_basename + "." + target;
}
return result;
}
bool only_definitions(std::vector<std::string> filenames)
{
if (filenames.empty()) return false;
bool result = true;
for (auto f : filenames) {
if (fs::path(f).extension() != ".k") {
result = false;
break;
}
}
return result;
}
std::tuple<std::string, std::string, std::string, std::string, bool, bool>
parse_args(
const std::vector<std::string>& input_filenames, std::string target, std::string output_basename, bool display_only)
{
// output_dir
std::string output_target = target;
std::string output_dir = "";
std::string ext = extension(output_basename);
// std::string output_filename = "";
bool write_files = true;
if (target.empty() && output_basename.empty()) {
output_target = "any";
}
if (output_basename == "-") {
write_files = false;
} else if (!output_basename.empty()) {
output_dir = file_directory(output_basename);
output_basename = file_basename(output_basename);
} else if (!input_filenames.empty()) {
output_dir = ""; // defaults to cwd via absolute_pathname below
output_basename = file_basename(input_filenames[0]);
}
output_dir = absolute_pathname(output_dir);
if (output_target.empty()) {
target = ext;
}
std::string output_filename =
construct_output_filename(output_dir, output_basename, target);
if (output_basename.empty() && input_filenames.empty()) {
display_only = true;
} else if (only_definitions(input_filenames)) {
display_only = true;
}
std::vector<std::pair<std::string,std::string>> vars = {
{"Output target", output_target},
{"Output directory", output_dir},
{"Output basename", output_basename},
{"Output filename", output_filename},
{"Write file", write_files ? "true" : "false"}};
int verbose = 1;
for (auto [label, value] : vars) {
if (label == "Output filename") {
//verbose = 2;
}
(void)K::log(int(verbose), label + ":", value);
}
return {output_target, output_dir, output_basename, output_filename, write_files, display_only};
}

17
mac/command.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include <string>
//#include <filesystem>
#include <vector>
#include "file.h"
void set_verbose_level(int argc, char* argv[]);
bool show_usage(int argc, char* argv[]);
fs::path construct_command_pathname(char* command);
std::tuple<std::string, std::string, std::string, std::string, bool, bool>
parse_args(
const std::vector<std::string>& input_filenames, std::string target,
std::string output_basename, bool display_only);

56
mac/deftype.cpp Normal file
View File

@@ -0,0 +1,56 @@
#include "deftype.h"
#include "error.h"
defmode_t defmode_from_katom(katom_t type)
{
switch (type) {
case katom_t::klammer_definition:
case katom_t::klammer_instance:
return defmode_t::def_create;
case katom_t::klammer_override:
return defmode_t::def_override;
case katom_t::klammer_default:
return defmode_t::def_default;
default:
throw Internal_error("defmode_from_katom: not a definition type");
}
}
static const std::map<std::pair<defmode_t,defmode_t>, defmode_result> transition_table {
// create + create = error
{ {defmode_t::def_create, defmode_t::def_create},
{false, false, "Klammer NAME already defined at AT"} },
// create + override = replace with warning
{ {defmode_t::def_create, defmode_t::def_override},
{true, true, "Klammer NAME at AT overridden"} },
// create + default = ignore silently
{ {defmode_t::def_create, defmode_t::def_default},
{false, false, ""} },
// override + create = error
{ {defmode_t::def_override, defmode_t::def_create},
{false, false, "Klammer NAME already overridden at AT"} },
// override + override = replace with warning
{ {defmode_t::def_override, defmode_t::def_override},
{true, true, "Klammer NAME at AT overridden again"} },
// override + default = ignore silently
{ {defmode_t::def_override, defmode_t::def_default},
{false, false, ""} },
// default + create = replace silently
{ {defmode_t::def_default, defmode_t::def_create},
{true, false, ""} },
// default + override = replace with warning
{ {defmode_t::def_default, defmode_t::def_override},
{true, true, "Default klammer NAME at AT overridden"} },
// default + default = error
{ {defmode_t::def_default, defmode_t::def_default},
{false, false, "Default klammer NAME already defined at AT"} }
};
const defmode_result& defmode_transition(defmode_t existing, defmode_t incoming)
{
auto it = transition_table.find({existing, incoming});
if (it == transition_table.end()) {
throw Internal_error("defmode_transition: unknown combination");
}
return it->second;
}

17
mac/deftype.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include <string>
#include <map>
#include "ktype.h"
enum class defmode_t { def_create, def_override, def_default };
struct defmode_result {
bool replace;
bool warn;
std::string message;
};
defmode_t defmode_from_katom(katom_t type);
const defmode_result& defmode_transition(defmode_t existing, defmode_t incoming);

12
mac/env/lsan.supp vendored Normal file
View File

@@ -0,0 +1,12 @@
# LeakSanitizer suppressions for Klammertext debug builds.
#
# Suppress unactionable leaks from the embedded Python interpreter and
# OpenImageIO module initialization — one-time-init allocations that
# libraries conventionally leave for the OS to reclaim at exit. Real
# leaks in Klammertext's own C++ code are still reported.
#
# Each "leak:<pattern>" line suppresses any leak whose stack trace has
# a frame matching the substring (in a function name or library path).
leak:libpython
leak:OpenImageIO

68
mac/env/makefile.env vendored Normal file
View File

@@ -0,0 +1,68 @@
# mac/env/makefile.env — single cross-platform build environment for Klammertext.
#
# Included identically by every component Makefile:
# include $(KLAMMERTEXT_HOME)/mac/env/makefile.env
#
# The platform is auto-detected with uname; the compiler is chosen with
# make COMPILER=gcc (default)
# make COMPILER=clang
# There is no per-host/per-OS file and no DESKTOP_SESSION/HOST/SITE selector.
ifndef KLAMMERTEXT_HOME
$(error KLAMMERTEXT_HOME is not set -- source mac/env/runtime.env first)
endif
include $(KLAMMERTEXT_HOME)/mac/env/optimize.env
UNAME_S := $(shell uname -s)
CPP_VERSION := c++20
# Default compiler: clang on macOS, gcc elsewhere. gcc-built Klammertext
# binaries crash at runtime on macOS (a documented gcc/macOS codegen issue), so
# gcc there is only a compile-time conformance check (via `./dbg/rebuild.sh
# gcc`, which compiles under g++ then rebuilds with clang). Override with
# `make COMPILER=...`.
ifeq ($(UNAME_S),Darwin)
COMPILER ?= clang # gcc | clang
else
COMPILER ?= gcc # gcc | clang
endif
# Python: version- and platform-agnostic, no hardcoded paths.
PYTHON_INC := $(shell python3-config --includes)
PYTHON_LIB := $(shell python3-config --ldflags --embed)
# Flags common to all platforms.
CPPFLAGS = -I$(KLAMMERTEXT_HOME)/mac $(PYTHON_INC)
CXXFLAGS = -Wall -Wextra -Weffc++ -Wshadow -std=$(CPP_VERSION) -fPIC $(OPTIMIZE) $(SANITIZE)
ifeq ($(UNAME_S),Darwin)
# ---------- macOS (Apple Silicon) ----------
# Package-manager prefix, auto-detected: Homebrew (/opt/homebrew) or MacPorts
# (/opt/local). Override with `make MACOS_PREFIX=...`. Only the prefix differs
# between the two: the compiler is Apple Clang either way, and all Python flags
# come from python3-config (prefix-agnostic), so nothing else is PM-specific.
MACOS_PREFIX ?= $(shell test -d /opt/homebrew && echo /opt/homebrew || (test -d /opt/local && echo /opt/local || echo /opt/homebrew))
# Newest optional gcc (conformance check only): Homebrew g++-NN or MacPorts g++-mp-NN.
GCC := $(shell g=$$(ls $(MACOS_PREFIX)/bin/g++-[0-9]* $(MACOS_PREFIX)/bin/g++-mp-[0-9]* 2>/dev/null | sort -V | tail -1); echo $${g:-g++})
CLANG := clang++ # Apple clang (or brew/port llvm)
CPPFLAGS += -I$(MACOS_PREFIX)/include
LDFLAGS = -L$(MACOS_PREFIX)/lib
LDLIBS = $(if $(NOPYTHON),,$(PYTHON_LIB))
SHARED = -dynamiclib
SONAME = -install_name @rpath/$(notdir $@)
ORIGIN := @loader_path
EXPORT_DYNAMIC = -Wl,-export_dynamic
else
# ---------- Linux ----------
GCC := /usr/bin/g++
CLANG := $(shell command -v clang++-18 2>/dev/null || command -v clang++ 2>/dev/null)
LDFLAGS = -L/usr/lib/x86_64-linux-gnu
LDLIBS = -ldl $(if $(NOPYTHON),,$(PYTHON_LIB))
SHARED = -shared
SONAME = -Wl,-soname,$(notdir $@)
ORIGIN := $$ORIGIN
EXPORT_DYNAMIC = -rdynamic
endif
# Resolve the compiler choice (gcc by default; clang with COMPILER=clang).
CXX := $(if $(filter clang,$(COMPILER)),$(CLANG),$(GCC))

46
mac/env/makefile.env.hollis.DISABLED vendored Normal file
View File

@@ -0,0 +1,46 @@
KLAMMERTEXT_HOME = /home/ack/projects/klammertext/K
include $(KLAMMERTEXT_HOME)/mac/env/optimize.env
CPP_VERSION = c++20
#GCC_LIB = /usr/lib/gcc/x86_64-linux-gnu/7.4.0
CXX = /usr/bin/g++
#IMPORT = -fmodules -fsearch-include-path bits/std.cc
IMPORT =
# Hack for now; to be generalized:
ifneq ("$(wildcard /usr/include/python3.14)","")
PYTHON = python3.14
else ifneq ("$(wildcard /usr/include/python3.13)","")
PYTHON = python3.13
else
PYTHON = python3.12
endif
# -no-pie?
CXXFLAGS = -Wall -Wextra -Weffc++ -fPIC $(PROFILE) -std=$(CPP_VERSION) $(IMPORT) $(OPTIMIZE) \
-I$(KLAMMERTEXT_HOME)/mac \
-I/usr/include \
-I/usr/include/$(PYTHON)
# -L$(GCC_LIB) \
LDFLAGS = \
-L/usr/lib/x86_64-linux-gnu
LDLIBS = \
-ldl
ifndef NOPYTHON
LDLIBS += -l$(PYTHON)
endif
#GCC_ROOT = /h/dev/pkg/gcc-$(GCC_VERSION)
#GCC_LIB = $(GCC_ROOT)/$(GCC_DIR)/lib/gcc/$(GCC_DIR)/$(GCC_VERSION)
#$(GCC_LIB)
LD_LIBRARY_PATH=\
/usr/lib64\
:/usr/lib/x86_64-linux-gnu

33
mac/env/makefile.env.jatke.DISABLED vendored Normal file
View File

@@ -0,0 +1,33 @@
KLAMMERTEXT_HOME = /home/ack/projects/klammertext/K
include $(KLAMMERTEXT_HOME)/mac/env/optimize.env
CPP_VERSION = c++20
CXX = /usr/bin/g++
# Hack for now; to be generalized:
ifneq ("$(wildcard /usr/include/python3.14)","")
PYTHON = python3.14
else ifneq ("$(wildcard /usr/include/python3.13)","")
PYTHON = python3.13
else
PYTHON = python3.12
endif
CPPFLAGS = \
-I$(KLAMMERTEXT_HOME)/mac \
-I/usr/include \
-I/usr/include/$(PYTHON)
CXXFLAGS = -Wall -Wextra -Weffc++ -Wshadow -std=$(CPP_VERSION) -fPIC $(OPTIMIZE) $(SANITIZE)
LDFLAGS = \
-L/usr/lib/x86_64-linux-gnu
LDLIBS = \
-ldl
ifndef NOPYTHON
LDLIBS += -l$(PYTHON)
endif

33
mac/env/makefile.env.pop.DISABLED vendored Normal file
View File

@@ -0,0 +1,33 @@
KLAMMERTEXT_HOME = /home/ack/projects/klammertext/K
include $(KLAMMERTEXT_HOME)/mac/env/optimize.env
CPP_VERSION = c++20
CXX = /usr/bin/g++
# Hack for now; to be generalized:
ifneq ("$(wildcard /usr/include/python3.14)","")
PYTHON = python3.14
else ifneq ("$(wildcard /usr/include/python3.13)","")
PYTHON = python3.13
else
PYTHON = python3.12
endif
CPPFLAGS = \
-I$(KLAMMERTEXT_HOME)/mac \
-I/usr/include \
-I/usr/include/$(PYTHON)
CXXFLAGS = -Wall -Wextra -Weffc++ -Wshadow -std=$(CPP_VERSION) -fPIC $(OPTIMIZE) $(SANITIZE)
LDFLAGS = \
-L/usr/lib/x86_64-linux-gnu
LDLIBS = \
-ldl
ifndef NOPYTHON
LDLIBS += -l$(PYTHON)
endif

33
mac/env/makefile.env.ubuntu.DISABLED vendored Normal file
View File

@@ -0,0 +1,33 @@
KLAMMERTEXT_HOME = /home/ack/projects/klammertext/K
include $(KLAMMERTEXT_HOME)/mac/env/optimize.env
CPP_VERSION = c++20
CXX = /usr/bin/g++
# Hack for now; to be generalized:
ifneq ("$(wildcard /usr/include/python3.14)","")
PYTHON = python3.14
else ifneq ("$(wildcard /usr/include/python3.13)","")
PYTHON = python3.13
else
PYTHON = python3.12
endif
CPPFLAGS = \
-I$(KLAMMERTEXT_HOME)/mac \
-I/usr/include \
-I/usr/include/$(PYTHON)
CXXFLAGS = -Wall -Wextra -Weffc++ -Wshadow -std=$(CPP_VERSION) -fPIC $(OPTIMIZE) $(SANITIZE)
LDFLAGS = \
-L/usr/lib/x86_64-linux-gnu
LDLIBS = \
-ldl
ifndef NOPYTHON
LDLIBS += -l$(PYTHON)
endif

16
mac/env/optimize.env vendored Normal file
View File

@@ -0,0 +1,16 @@
ifdef OPTIMIZE
# Performance build: make OPTIMIZE=1
# `override` so a command-line `make OPTIMIZE=1` is remapped to -O3 too;
# without it, a command-line assignment wins over this `:=` and the literal
# `1` leaks into CXXFLAGS (g++ then treats `1` as an input file).
override OPTIMIZE := -O3
SANITIZE :=
else ifdef NOPYTHON
OPTIMIZE := -O0 -g -DNOPYTHON
SANITIZE :=
else
# Debug build (default): includes AddressSanitizer
OPTIMIZE := -O0 -g
SANITIZE := -fsanitize=address -fno-omit-frame-pointer
endif

72
mac/env/runtime.env vendored Normal file
View File

@@ -0,0 +1,72 @@
# mac/env/runtime.env — single self-configuring runtime environment for Klammertext.
#
# Source this from your shell profile (~/.bashrc etc.):
# source /path/to/klammertext/K/mac/env/runtime.env
#
# This is the runtime counterpart of the shared mac/env/makefile.env: one file
# for every machine, with all machine-specific values AUTO-DETECTED. There is no
# per-host runtime.env.<host> file and no HOST/OS/SITE selector.
# - KLAMMERTEXT_HOME : derived from this file's own location (self-locating)
# - KLAMMERTEXT_TEXLIVE_BIN : newest ~/external/texlive/<year>/bin/<arch>
# Machine-unique, non-committable additions (extra library paths such as NVIDIA
# iray, local tools, etc.) go in an optional, gitignored mac/env/runtime.env.local
# sourced at the end -- NOT in this shared file, so one machine's bundled
# libraries can't shadow another's system libraries.
# --- KLAMMERTEXT_HOME: self-locate (bash sets BASH_SOURCE; zsh sets $0) --------
# This file lives at $KLAMMERTEXT_HOME/mac/env/runtime.env, so go up two levels.
_kt_self="${BASH_SOURCE[0]:-$0}"
export KLAMMERTEXT_HOME="$(cd "$(dirname "$_kt_self")/../.." && pwd)"
unset _kt_self
_kt_uname="$(uname -s)"
# --- KLAMMERTEXT_TEXLIVE_BIN: newest installed TeX Live under ~/external -------
# Klammertext's TeX Live installs are named by 4-digit year (2024, 2025, 2026).
# Match only those so unrelated dirs (e.g. a "texlive-2022-min" system install)
# are never selected; sort -V then picks the newest year.
if [ "$_kt_uname" = "Darwin" ]; then
_kt_arch="universal-darwin"
else
_kt_arch="$(uname -m)-linux" # e.g. x86_64-linux
fi
# Use `find` with a -path pattern, NOT a shell glob: on macOS/zsh an unmatched
# glob (no texlive installed) is a hard "no matches found" error, whereas find
# just returns nothing. The -path pattern keeps the 4-digit-year restriction.
_kt_tl="$(find "$HOME/external/texlive" -maxdepth 3 -type d \
-path "*/[0-9][0-9][0-9][0-9]/bin/$_kt_arch" 2>/dev/null \
| sort -V | tail -1)"
[ -n "$_kt_tl" ] && export KLAMMERTEXT_TEXLIVE_BIN="$_kt_tl"
unset _kt_tl _kt_arch
# --- PATH (TeX Live appended only if one was found) ---------------------------
export PATH="$KLAMMERTEXT_HOME/bin:$KLAMMERTEXT_HOME/tst${KLAMMERTEXT_TEXLIVE_BIN:+:$KLAMMERTEXT_TEXLIVE_BIN}:$PATH"
# --- Per-platform: LSan suppressions + shared library search path -------------
# The shared library path deliberately includes ONLY Klammertext's own .so dirs,
# NOT third-party bundled-library dirs. In particular NVIDIA iray's platforms/
# ships its own libfreetype.so and Qt6; if added here they shadow the system
# libraries, and iray's freetype drags in unversioned libharfbuzz.so/libbz2.so
# (absent without -dev packages), which breaks `import OpenImageIO` (SKS @image).
# Machine-specific library paths (iray included) go in runtime.env.local.
if [ "$_kt_uname" = "Darwin" ]; then
# macOS: no LD/DYLD_LIBRARY_PATH needed — libklammertext.so is found via the
# binaries' @loader_path rpath and document.so is dlopen'd by absolute path.
# LeakSanitizer is unsupported on macOS, so LSAN_OPTIONS does not apply.
:
else
# LSan suppressions for unactionable libpython/OpenImageIO leaks (see
# lsan.supp). Real leaks in Klammertext code are still reported; no effect
# in performance builds (OPTIMIZE=1, which disables ASan).
export LSAN_OPTIONS="suppressions=$KLAMMERTEXT_HOME/mac/env/lsan.supp:print_suppressions=0"
export LD_LIBRARY_PATH="$KLAMMERTEXT_HOME/mac:$KLAMMERTEXT_HOME/sks/kutil:$KLAMMERTEXT_HOME/sks/document:$KLAMMERTEXT_HOME/doc/handbook${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
fi
unset _kt_uname
# --- Optional per-machine escape hatch (gitignored, absent by default) ---------
# Use an if-block (not `[ -f ] && .`) so that when the local file is absent this
# file's final exit status is 0 -- otherwise `source runtime.env` returns
# non-zero, breaking `source runtime.env && ...` and `set -e` callers.
if [ -f "$KLAMMERTEXT_HOME/mac/env/runtime.env.local" ]; then
. "$KLAMMERTEXT_HOME/mac/env/runtime.env.local"
fi

35
mac/error.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include "error.h"
#include "show.h"
#include "util.h"
#include "file.h"
bool display_source(const std::string& filename_arg)
{
if (filename_arg.empty()) {
return false;
} else {
fs::path filename = fs::path(filename_arg).filename();
return sks_commands.count(filename) == 0;
}
}
void Error::print_message(const std::string& epilog)
{
if (epilog != "")
//desc += " " + epilog + "\n";
m_desc += epilog + "\n";
if (m_just)
m_desc = justify(m_desc, 80, 0);
std::cerr << "\n" << red << command_name << " (" << m_type << " error)";
if (display_source(m_loc.m_filename)) {
std::cerr << ":\n " << m_loc.m_filename;
}
if (m_loc.m_line > -1) {
std::cerr << ", line " << m_loc.m_line;
}
if (m_loc.m_chr > -1) {
std::cerr << ", character " << m_loc.m_chr + 1;
}
std::cerr << ":\n\n" << m_desc << reset << "\n";
std::cout << reset;
}

77
mac/error.h Normal file
View File

@@ -0,0 +1,77 @@
#pragma once
#include <string>
#include "locator.h"
inline std::string command_name { "Command executed on the command line" };
inline std::string command_pathname { "Pathname of command executed on the command line" };
class Error : std::exception {
public:
Error(std::string error_type, std::string description,
Locator locator = Locator(), bool do_justify = true)
: m_type(error_type)
, m_desc(description)
, m_loc(locator)
, m_just(do_justify)
{}
void print_message(const std::string& epilog="");
std::string m_type {};
std::string m_desc {};
Locator m_loc; // {};
bool m_just { true };
};
class Parsing_error : public Error {
public:
explicit Parsing_error(
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
: Error("parsing", description, locator, do_justify) {};
};
class File_error : public Error {
public:
explicit File_error(
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
: Error("file", description, locator, do_justify) {};
};
class Target_error : public Error {
public:
explicit Target_error(
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
: Error("target", description, locator, do_justify) {};
};
class Definition_error : public Error {
public:
explicit Definition_error(
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
: Error("definition", description, locator, do_justify) {};
};
class Argument_error : public Error {
public:
explicit Argument_error(
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
: Error("argument", description, locator, do_justify) {};
};
class Environment_error : public Error {
public:
explicit Environment_error(
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
: Error("environment", description, locator, do_justify) {};
};
class Internal_error : public Error {
public:
explicit Internal_error(
const std::string& description, const Locator& locator=Locator(), bool do_justify=true)
: Error("internal", description, locator, do_justify) {};
};

159
mac/eval.cpp Normal file
View File

@@ -0,0 +1,159 @@
#include "util.h"
#include "eval.h"
#include "eval_python.h"
#include "eval_cpp.h"
#include "log.h"
#include "show.h"
#include "katom.h"
#include "file.h"
#include <unistd.h>
std::string shell(State state, std::string command, Locator loc)
{
command = state.subst(command);
FILE* pipe = popen(command.c_str(), "r");
if (!pipe) {
throw Parsing_error(
"Could not run command:\n" + command, loc, false);
}
char buffer[128];
std::string result = "";
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
result += buffer;
}
pclose(pipe);
// std::cout << "Command output:\n" << result << "\n";
return result;
}
bool is_haskell_file(const std::string& text)
{
std::string trimmed = trim(text);
if (trimmed.size() < 4) return false;
if (trimmed.find(' ') != std::string::npos) return false;
if (trimmed.find('\n') != std::string::npos) return false;
return trimmed.substr(trimmed.size() - 3) == ".hs";
}
std::string run_haskell(const std::string& hsfile, Locator loc)
{
std::string command = "runghc " + hsfile + " 2>&1";
FILE* pipe = popen(command.c_str(), "r");
if (!pipe) {
throw Parsing_error(
"Could not run runghc.", loc, false);
}
char buffer[128];
std::string result = "";
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
result += buffer;
}
int status = pclose(pipe);
if (status != 0) {
throw Parsing_error(
"Haskell evaluation failed:\n" + result, loc, false);
}
return result;
}
std::string haskell(State state, std::string code, Locator loc)
{
if (system("which runghc > /dev/null 2>&1") != 0) {
throw Parsing_error(
"@eval with the :haskell argument requires runghc, which was not found in PATH.\n"
"Install it using GHCup; see https://www.haskell.org/ghcup/install/.",
loc, false);
}
code = state.subst(code);
if (is_haskell_file(code)) {
return run_haskell(trim(code), loc);
}
const char* tmpdir = std::getenv("TMPDIR");
if (!tmpdir) tmpdir = "/tmp";
std::string tmpl = std::string(tmpdir) + "/klammertext_haskell_XXXXXX";
std::vector<char> tmppath(tmpl.begin(), tmpl.end());
tmppath.push_back('\0');
int fd = mkstemp(tmppath.data());
if (fd < 0) {
throw Parsing_error(
"Could not create temporary file for Haskell evaluation.", loc, false);
}
std::string hsfile = std::string(tmppath.data()) + ".hs";
close(fd);
rename(tmppath.data(), hsfile.c_str());
FILE* f = fopen(hsfile.c_str(), "w");
if (!f) {
unlink(hsfile.c_str());
throw Parsing_error(
"Could not write temporary Haskell file.", loc, false);
}
fprintf(f, "%s\n", code.c_str());
fclose(f);
std::string result = run_haskell(hsfile, loc);
unlink(hsfile.c_str());
return result;
}
void check_cpp_arguments(katom_list args, Locator loc)
{
if (args.size() != 4 && args.size() != 5) {
std::stringstream ss {};
ss << "Incorrect @eval format for a C++ function. Either:\n"
<< " @eval :cpp <library-basename> @\n"
<< "or\n"
<< " @eval :cpp <library-basename> <function-name> @\n"
<< "In the first case, the library basename is used for the function name.";
throw Argument_error(ss.str(), loc, false);
}
}
katom_list Eval::eval(katom_iter begin, katom_iter end)
{
(void)K::log(3, *begin, *(end - 1));
//msg() << "in Eval::eval:\n" << ktype << kall << kindex << std::pair(begin, end) << "\n";
katom_iter first = after_whitespace(begin + 1);
std::string eval_result = "[unevaluated]";
std::string first_word = first->m_text;
int offset = first_word[0] == ':' ? 1 : 0;
std::string command = as_string(first + offset, end - 1, true);
if (offset == 0 || first_word == ":python") { // Default is Python
Eval_python E_python(m_machine, begin->m_loc);
eval_result = E_python.eval(command);
} else if (first_word == ":shell") {
eval_result = shell(m_machine.m_state, command, begin->m_loc);
} else if (first_word == ":haskell") {
eval_result = haskell(m_machine.m_state, command, begin->m_loc);
} else if (first_word == ":cpp") {
//msg() << ":cpp: " << first_word << *(begin + 4) << "\n";
for (auto ki = begin; ki < end; ki++) {
//msg() << " " << kall << kindex << *ki << "\n";
}
katom_list args = text_katoms(begin, end);
check_cpp_arguments(args, begin->m_loc);
//msg() << "args: |" << args << "|\n";
std::string lib_text = args[2].m_text;
std::string khome = m_machine.m_state.value("KLAMMERTEXT_HOME", false);
if (!khome.empty()) {
lib_text = string_replace(lib_text, "*KLAMMERTEXT_HOME*", khome);
}
fs::path libpath(lib_text + ".so");
libpath = fs::absolute(libpath);
std::string funcname = args.size() == 4 ? libpath.stem().string() : args[3].m_text;
Eval_cpp E_cpp(m_machine, begin->m_loc);
eval_result = E_cpp.eval(libpath, funcname);
}
katom_list result {};
Machine M = m_machine;
M.read(eval_result);
M.apply(M.m_state.value("K_target"), false, false);
result = trim(M.m_katoms);
return result;
}

30
mac/eval.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#include <string>
#include "machine.h"
#include "katom.h"
enum class eval_t {
shell,
python,
cpp,
};
class Eval
{
public:
explicit Eval(Machine& machine, Locator loc)
: m_machine(machine),
m_loc(loc)
{};
Eval(const Eval&) = delete;
Eval& operator=(const Eval&) = delete;
//~Eval_cpp();
std::vector<Katom> eval(
std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end);
Machine m_machine;
Locator m_loc;
};

43
mac/eval_cpp.cpp Normal file
View File

@@ -0,0 +1,43 @@
#include <memory>
#include <dlfcn.h>
#include "eval_cpp.h"
#include "util.h"
#include "log.h"
#include "file.h"
#include "show.h"
Eval_cpp::Eval_cpp(Machine& machine, Locator loc)
: m_machine(machine)
, m_loc(loc)
{
(void)K::log(3);
}
std::string Eval_cpp::eval(fs::path library_path, std::string function_name)
{
(void)K::log(3, library_path, function_name);
// msg() << "library path: " << library_path.string().c_str() << "\n";
void* handle = dlopen(library_path.string().c_str(), RTLD_LAZY);
if (!handle) {
const char* error = dlerror();
std::string error_desc = error ? error : "unknown error";
throw File_error("Cannot open library: " + library_path.string() + "\n " + error_desc, m_loc);
}
dlerror();
typedef std::string (*func_t)(Machine);
func_t func = (func_t) dlsym(handle, function_name.c_str());
const char* dlsym_error = dlerror();
if (dlsym_error) {
std::string error_desc(dlsym_error);
dlclose(handle);
std::stringstream ss {};
ss << "Cannot load symbol " << function_name
<< " from library " << library_path << ":\n " << error_desc;
throw File_error(ss.str(), m_loc, false);
}
std::string result = func(m_machine);
dlclose(handle);
return result;
}

19
mac/eval_cpp.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <string>
#include "machine.h"
class Eval_cpp
{
public:
explicit Eval_cpp(Machine& machine, Locator loc);
Eval_cpp(const Eval_cpp&) = delete;
Eval_cpp& operator=(const Eval_cpp&) = delete;
//~Eval_cpp();
std::string eval(fs::path library_path, std::string function_name);
Machine m_machine {};
Locator m_loc;
};

229
mac/eval_python.cpp Normal file
View File

@@ -0,0 +1,229 @@
#include "eval_python.h"
#include "show.h"
#include "util.h"
#include "log.h"
std::regex Eval_python::statement_delimiter("\\s*;\\s*");
Eval_python::Eval_python(Machine& machine, Locator loc)
: m_machine(machine)
, m_loc(loc)
, m_globals(nullptr)
, m_locals(nullptr)
{
(void)K::log(3);
// Only initialize if Python is not already initialized
if (!Py_IsInitialized()) {
#if PY_VERSION_HEX >= 0x030B0000
// Python 3.11+ uses PyConfig API
PyConfig config;
PyConfig_InitPythonConfig(&config);
Py_InitializeFromConfig(&config);
PyConfig_Clear(&config);
#else
Py_Initialize();
#endif
}
add_module_path("..");
add_module_path(".");
m_globals = PyDict_New();
m_locals = PyDict_New();
PyDict_SetItemString(m_globals, "__builtins__", PyEval_GetBuiltins());
import_module("inspect", false);
if (!m_machine.m_state.m_frames.empty()) {
PyRun_String(m_machine.m_state.python_code().c_str(), Py_file_input, m_globals, m_locals);
}
}
Eval_python::~Eval_python()
{
(void)K::log(3, "destructor");
// Clean up our objects BEFORE finalizing Python
if (m_globals) {
Py_DECREF(m_globals);
m_globals = nullptr;
}
if (m_locals) {
Py_DECREF(m_locals);
m_locals = nullptr;
}
// Don't call Py_Finalize() here - it can cause double-free if other
// Eval_python objects exist or if Python is used elsewhere.
// Python will clean up automatically at program exit.
}
std::string remove_string_values(std::string s)
{
return std::regex_replace(s, std::regex(R"(\".*?\")"), "\"\"");
}
void Eval_python::add_module_path(const std::string& path)
{
PyObject* sys_path = PySys_GetObject("path"); // Borrowed reference
if (sys_path) {
PyObject* py_path = PyUnicode_FromString(path.c_str());
if (py_path) {
PyList_Insert(sys_path, 0, py_path); // Insert at front for priority
Py_DECREF(py_path);
}
}
}
strings_t Eval_python::parse_modules(std::string code)
{
(void)K::log(3, code);
code = remove_string_values(code); // Hack! Don't look for module patterns in strings.
std::regex module_re(R"(([A-Za-z]\w*)\.[A-Za-z_]\w*)");
auto code_begin = std::sregex_iterator(code.begin(), code.end(), module_re);
auto code_end = std::sregex_iterator();
std::vector<std::string> modules;
for (std::sregex_iterator it = code_begin; it != code_end; ++it) {
modules.push_back((*it).str(1));
}
return modules;
}
void Eval_python::import_module(std::string module_name, bool verify)
{
(void)K::log(3, module_name);
std::string module_check =
"\"" + module_name + "\" in locals() and inspect.isclass(" + module_name + ")";
if (verify && eval_expression(module_check, false) == "True") {
return;
}
PyObject* module = PyImport_ImportModule(module_name.c_str());
if (module == nullptr) {
// Extract the Python traceback before clearing the error.
// This reveals the actual source of the failure (e.g., a syntax
// error in a transitively imported module), not just the top-level
// module name that failed to load.
std::string detail;
PyObject* ptype;
PyObject* pvalue;
PyObject* ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
if (pvalue) {
PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
PyObject* str = PyObject_Str(pvalue);
if (str) {
detail = PyUnicode_AsUTF8(str);
Py_DECREF(str);
}
// Format the traceback if available
if (ptraceback) {
PyObject* tb_module = PyImport_ImportModule("traceback");
if (tb_module) {
PyObject* format_tb = PyObject_GetAttrString(tb_module, "format_exception");
if (format_tb) {
PyObject* args = PyTuple_Pack(3, ptype, pvalue, ptraceback);
PyObject* tb_list = PyObject_CallObject(format_tb, args);
if (tb_list) {
PyObject* separator = PyUnicode_FromString("");
PyObject* joined = PyUnicode_Join(separator, tb_list);
if (joined) {
detail = PyUnicode_AsUTF8(joined);
Py_DECREF(joined);
}
Py_DECREF(separator);
Py_DECREF(tb_list);
}
Py_XDECREF(args);
Py_DECREF(format_tb);
}
Py_DECREF(tb_module);
}
}
}
Py_XDECREF(ptype);
Py_XDECREF(pvalue);
Py_XDECREF(ptraceback);
PyErr_Clear();
std::string message = "Cannot import module \"" + module_name + "\"";
if (!detail.empty()) {
message += ":\n\n" + detail;
}
throw Argument_error(message, m_loc, false);
}
// PyDict_SetItemString steals a reference, so we don't need to DECREF module
// The dictionary will own the reference
PyDict_SetItemString(m_globals, module_name.c_str(), module);
}
std::string Eval_python::get_result(PyObject* result_object)
{
std::string result {};
if (result_object) {
const char* value = PyUnicode_AsUTF8(result_object);
result = std::string(value);
Py_DECREF(result_object);
} else {
std::cout << red;
PyErr_Print();
throw Parsing_error("Python code error in @eval", m_loc);
}
return result;
}
std::string Eval_python::eval_expression(std::string expression, bool import_modules)
{
(void)K::log(3, expression);
// msg() << "expression: " << expression << "\n";
if (import_modules && expression.find('.') != std::string::npos) {
for (auto m : parse_modules(expression)) {
import_module(m);
}
}
return get_result(
PyRun_String(
std::string("str(" + expression +")").c_str(),
Py_eval_input, m_globals, m_locals));
}
std::string Eval_python::eval_statements(std::string script)
{
(void)K::log(3);
strings_t statements = regex_split(script, statement_delimiter);
for (auto iter = statements.begin(); iter < statements.end() - 1; iter++) {
(void)K::log(3, " Run: " + (*iter));
PyRun_String(iter->c_str(), Py_file_input, m_globals, m_locals);
}
(void)K::log(3, " Result from: " + statements.back());
return get_result(
PyRun_String(std::string("str("+statements.back()+")").c_str(),
Py_eval_input, m_globals, m_locals));
}
std::string Eval_python::eval(std::string code)
{
(void)K::log(3, code);
code = m_machine.m_state.subst(code, true);
/*
msg() << "\n"
<< std::string(80, '-') << "\n"
<< code << "\n"
<< std::string(80, '-') << "\n";
*/
if (std::regex_search(code, statement_delimiter)) {
return eval_statements(code);
} else {
return eval_expression(code);
}
}
std::string Eval_python::eval_katom_list(
katom_list& katoms, const katom_iter& begin, const katom_iter& end)
{
(void)K::log(3, katoms);
katom_iter code_begin = begin + 1;
katom_iter code_end = end - 1;
std::string code_result = eval(as_string(code_begin, code_end, true));
msg() << "code_result: " << code_result << "\n";
katom_list code_katoms = m_machine.process(code_result, command_name);
for (auto kiter = code_begin; kiter < code_end; kiter++) {
kiter->m_type = katom_t::replaced;
}
katoms.insert(end, code_katoms.begin(), code_katoms.end());
return code_result;
}

32
mac/eval_python.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#include <string>
#include <Python.h>
#include "machine.h"
class Eval_python
{
public:
static std::regex statement_delimiter;
explicit Eval_python(Machine& machine, Locator loc);
Eval_python(const Eval_python&) = delete;
Eval_python& operator=(const Eval_python&) = delete;
~Eval_python();
void add_module_path(const std::string& path);
std::vector<std::string> parse_modules(std::string code);
void import_module(std::string module_name, bool verify = true);
std::string get_result(PyObject* result_object);
std::string eval_expression(std::string expression, bool import_modules = true);
std::string eval_statements(std::string script);
std::string eval(std::string code);
//std::string eval_katom_list(const katom_iter& begin, const katom_iter& end);
std::string eval_katom_list(
std::vector<Katom>& katoms,
const std::vector<Katom>::iterator& begin, const std::vector<Katom>::iterator& end);
Machine m_machine;
Locator m_loc;
PyObject* m_globals;
PyObject* m_locals;
};

571
mac/file.cpp Normal file
View File

@@ -0,0 +1,571 @@
#include <fstream>
#include <algorithm>
#include <cstring>
#include "file.h"
#include "error.h"
#include "log.h"
#include "util.h"
#include "show.h"
std::string file_basename(const std::string& filename)
{
fs::path p(filename);
return p.stem().string();
}
std::string extension(const std::string& filename)
{
auto pos = filename.find_last_of(".");
if (pos != std::string::npos)
return filename.substr(pos + 1);
return "";
}
std::string file_directory(const std::string& filename)
{
fs::path p(filename);
return p.parent_path().string();
}
std::string absolute_pathname(const std::string& filename, const std::string& base)
{
fs::path p(filename);
if (filename.empty()) {
return fs::current_path().string();
}
if (!base.empty()) {
// Resolve filename relative to base's directory
fs::path b(base);
fs::path base_dir = fs::is_directory(b) ? b : b.parent_path();
p = base_dir / p;
}
return fs::absolute(p).string();
}
std::string relative_pathname(const std::string& filename)
{
/*
fs::path relative_to_current(const fs::path& input,
const fs::path& currentFile) {
const auto base = fs::absolute(currentFile).parent_path();
return fs::relative(fs::absolute(input), base);
*/
fs::path p(absolute_pathname(filename)); // + "/" + filename);
auto relpath = fs::relative(p, fs::current_path());
std::cout << "Absolute: " << fs::current_path() << " " << p << "->" << relpath << "\n";
return relpath.string();
}
size_t count_substrings(const std::string& text, const std::string& substring) {
size_t count = 0;
size_t pos = 0;
while ((pos = text.find(substring, pos)) != std::string::npos) {
count++;
pos += substring.length();
}
return count;
}
fs::path relative_to_cwd(const fs::path& input)
{
const auto base = fs::current_path();
std::error_code ec;
auto abs_input = fs::weakly_canonical(input, ec);
if (ec) abs_input = fs::absolute(input);
auto rel = fs::relative(abs_input, base, ec);
if (ec) rel = abs_input.lexically_relative(base);
auto result = rel.empty() ? abs_input : rel;
if (count_substrings(result.string(), "../") > 3) {
result = input;
}
return result;
}
std::string relative_pathname(const std::string& filename, std::string base)
{
fs::path p(absolute_pathname(filename));
auto relpath = relative(p, base);
relpath = relpath.lexically_normal();
return relpath.string();
}
bool file_exists(const std::string& pathname, bool error_if_not, bool is_directory)
{
fs::path p(pathname);
bool exists = fs::exists(p);
bool regular = fs::is_regular_file(p);
bool directory = fs::is_directory(p);
bool valid = exists and (regular or directory);
std::string filetype = is_directory ? "Directory " : "File";
Locator no_source("", -1, -1);
if (error_if_not and not valid) {
if (not exists)
throw File_error(filetype + " '" + pathname + "' does not exist", no_source);
else if (not is_directory and not regular)
throw File_error(filetype + " '" + pathname + "' is not a regular text file", no_source);
else if (is_directory and regular)
throw File_error(filetype + " '" + pathname + "' is not a directory", no_source);
}
return valid;
}
std::string string_from_file(const std::string& pathname, bool strip_surrounding_whitespace)
{
std::regex klammertext_filename_re { R"(.*\.kt?)" };
fs::path p(pathname);
std::string result {};
if (fs::exists(p)) {
if (fs::is_regular_file(p)) {
std::ifstream stream { pathname };
if (!stream.is_open()) {
throw File_error("Error opening file \"" + pathname + "\"", Locator("", -1, -1));
} else {
std::ostringstream buffer {};
stream >> std::noskipws >> buffer.rdbuf();
if (stream.fail() && !stream.eof()) {
throw File_error("Error reading file \"" + pathname + "\"", Locator("", -1, -1));
} else {
result = buffer.str();
result = string_replace(result, "\r\n", ""); // Urgh.
if (std::regex_match(pathname, klammertext_filename_re)) {
//std::cout << "Read Klammertext source file: " << pathname << "\n";
// result = encode(result);
} else {
//std::cout << "Read file: " << pathname << "\n";
}
if (strip_surrounding_whitespace)
result = trim(result);
return result;
}
}
} else {
throw File_error("File \"" + pathname + "\" is not a regular text file", Locator("", -1, -1));
}
} else {
throw File_error("File '" + pathname + "' does not exist", Locator("", -1, -1));
}
return result;
}
void string_to_file(const std::string& pathname, std::string contents)
{
fs::create_directories(file_directory(fs::absolute(pathname)));
std::ofstream out(pathname);
if (!out) {
throw File_error("Could not write file " + pathname);
}
out << contents;
out.close();
}
strings_t get_subdirectories(const std::string& s, std::regex name_match_re)
{
strings_t result {};
for (auto& p : fs::recursive_directory_iterator(s)) {
std::smatch match {};
//std::cout << "Check dir: " << p.path().string() << "\n";
std::string base = file_basename(p.path().string());
if (fs::is_directory(p) and std::regex_match(base, match, name_match_re))
result.push_back(p.path().string());
}
return result;
}
strings_t get_files_in_directory(const std::string& dir)
{
strings_t result {};
try {
for (const auto& entry : fs::directory_iterator(dir)) {
if (entry.is_regular_file()) {
// std::cout << entry.path().filename() << std::endl;
result.push_back(entry.path().filename());
}
}
} catch (const fs::filesystem_error& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
}
return result;
}
std::string find_file(const std::string& basename, strings_t search_path, bool error_if_not_found)
{
bool found = false;
std::string pathname { "" };
for (const std::string& s : search_path) {
pathname = s + "/" + basename;
if (file_exists(pathname)) {
found = true;
break;
}
}
if (error_if_not_found and not found) {
std::stringstream ss {};
std::sort(search_path.begin(), search_path.end());
ss << "File with basename \"" << basename << "\" not found in search path:\n "
<< join(search_path, "\n ");
throw File_error(ss.str(), Locator(), false);
}
return pathname;
}
std::vector<fs::path>
find_file_recursive(const fs::path& root, const std::string& filename, bool only_one) //, Locator loc)
{
std::vector<fs::path> result {};
if (!fs::exists(root) || !fs::is_directory(root)) {
return result;
}
for (const auto& entry : fs::recursive_directory_iterator(root)) {
// msg() << "Entry: " << entry.path().string() << "\n";
if (entry.is_regular_file() && entry.path().filename() == filename) {
result.push_back(entry.path());
}
}
if (only_one && result.size() > 1) {
std::stringstream ss {};
ss << "More than one file named " + q_(filename) + " found:\n";
for (auto f : result) {
ss << " " << f << "\n";
}
std::cerr << ss.str();
throw File_error("More than one file named " + q_(filename) + " found");
}
return result;
}
std::vector<fs::path>
find_file_from_roots(const std::vector<std::string>& roots, const std::string& filename, bool only_one)
{
(void)K::log(3, filename);
std::vector<fs::path> result {};
for (std::string root : roots) {
std::vector<fs::path> filenames = find_file_recursive(root, filename, only_one);
for (auto f : filenames) {
if (std::ranges::count(result, f) == 0) {
result.push_back(f);
}
}
// result.insert(result.end(), filenames.begin(), filenames.end());
}
if (result.empty()) {
throw File_error("File \"" + filename + "\" not found");
}
if (only_one && result.size() > 1) {
std::stringstream ss {};
ss << "More than one file named " + q_(filename) + " found:\n";
for (auto f : result) {
ss << " " << f << "\n";
}
std::cerr << ss.str();
throw File_error("More than one file named " + q_(filename) + " found");
}
for (auto f : result) {
if (!fs::exists(f)) {
throw File_error("File \"" + filename + "\" does not exist");
}
}
return result;
}
fs::path klammertext_filename(const std::string& basename, bool error_if_missing, bool make_directory_if_missing)
{
std::string home { get_env_var("KLAMMERTEXT_HOME") };
std::string result = home + "/" + basename;
if (make_directory_if_missing)
fs::create_directory(file_directory(result));
if (error_if_missing and !file_exists(result, false, true)) {
throw File_error(
"Klammertext file does not exist: " + result);
}
return fs::path(result);
}
strings_t sks_dirs()
{
std::string home { get_env_var("KLAMMERTEXT_HOME") };
strings_t result = get_subdirectories(home + "/sks", std::regex(R"([a-zA-Z]\w*)"));
result.emplace(result.begin(), home + "/sks/kutil");
// result.push_back(home + "/doc/handbook"); // Not included in container yet
return result;
}
strings_t get_sks_directories(const std::string& s, bool include_argument)
{
strings_t result;
if (include_argument)
result.push_back(s);
for (auto& p : fs::recursive_directory_iterator(s)) {
auto basename = file_basename(p.path().string());
auto parent = p.path().parent_path();
if (fs::is_directory(p)
and basename[0] != '_'
and basename != "css"
and basename != "sty"
and basename != "js"
//and basename != "font"
and parent != "font"
and parent != "fonts")
result.push_back(p.path().string());
}
return result;
}
std::string cache_directory(std::string subdirectory, std::string parent_directory)
{
if (parent_directory.empty()) {
// /dev/shm is a fast RAM-backed tmpfs on Linux; it does not exist on
// macOS, so fall back to the platform temp directory there.
if (fs::is_directory("/dev/shm")) {
parent_directory = "/dev/shm";
} else {
parent_directory = fs::temp_directory_path().string();
}
}
std::string result = parent_directory + "/_klammertext_cache/" + subdirectory;
// msg() << "Cache directory: " << result << "\n";
return result;
}
std::time_t to_time_t(const fs::file_time_type& ftime)
{
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
return std::chrono::system_clock::to_time_t(sctp);
}
bool in_modification_order(std::string filename1, std::string filename2)
{
if ((!file_exists(filename1)) || (!file_exists(filename2))) {
return false;
} else {
auto time1 = fs::last_write_time(fs::path(filename1));
auto time2 = fs::last_write_time(fs::path(filename2));
return time1 < time2;
}
}
void write_to_cache(std::string cache_dir, std::string basename, std::string text)
{
if (!file_exists(cache_dir)) {
//std::cout << "Creating cache directory: " << cache_dir << "\n";
fs::create_directories(cache_dir);
}
// msg() << "Writing file to cache: " << basename << "\n";
std::string output_filename = cache_dir + "/" + basename;
string_to_file(output_filename, text);
}
std::string read_from_cache(std::string cache_dir, std::string basename)
{
std::string input_filename = cache_dir + "/" + basename;
// msg() << "Reading file from cache: " << input_filename << "\n";
return string_from_file(input_filename);
}
bool cache_requires_update(std::string cache_dir, std::string file_to_cache, std::string basename)
{
std::string cache_filename = cache_dir + "/" + basename;
return !in_modification_order(file_to_cache, cache_filename);
}
std::vector<fs::path> pathnames_with_extension(
const fs::path& dir, const std::string extension)
{
std::vector<fs::path> files;
for (const auto& entry : fs::recursive_directory_iterator(dir)) {
if (!entry.is_regular_file()) continue;
auto ext = entry.path().extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if (ext == "." + extension) files.push_back(entry.path());
}
return files;
}
/*
std::string find_file(const fs::path& root, const std::string& name)
{
//std::vector<fs::path> matches;
strings_t matches {};
auto normalize = [&](std::string s) {
if (!case_insensitive) return s;
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return std::tolower(c); });
return s;
};
const std::string norm_ext = normalize("." + ext);
for (const auto& entry : fs::recursive_directory_iterator(root)) {
if (!entry.is_regular_file())
continue;
//std::string entry_ext = normalize(entry.path().extension().string());
if (entry == name) {
matches.push_back(entry.path().string());
}
}
return matches[0];
}
*/
// std::vector<fs::path> find_files_with_extension(
strings_t find_files_with_extension(
const fs::path& root, const std::string& ext, bool case_insensitive)
{
//std::vector<fs::path> matches;
strings_t matches {};
auto normalize = [&](std::string s) {
if (!case_insensitive) return s;
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return std::tolower(c); });
return s;
};
const std::string norm_ext = normalize("." + ext);
for (const auto& entry : fs::recursive_directory_iterator(root)) {
if (!entry.is_regular_file())
continue;
std::string entry_ext = normalize(entry.path().extension().string());
if (entry_ext == norm_ext)
matches.push_back(entry.path().string());
}
return matches;
}
std::string combine_files(
std::vector<std::string> filenames,
std::string prolog, std::string epilog,
std::function <std::string(std::string)> processor)
{
std::string result = prolog;
for (std::string f : filenames) {
result += "\n/* " + file_basename(f) + " */\n";
result += string_from_file(f);
}
result += "\n" + epilog + "\n";
if (processor) {
result = processor(result);
}
return result;
}
bool files_differ(const fs::path& p1,
const fs::path& p2,
std::size_t buffer_size) // 64 KiB
{
// 1. Check existence and type
if (!fs::exists(p1) || !fs::exists(p2)) return true;
if (!fs::is_regular_file(p1) || !fs::is_regular_file(p2)) return true;
// 2. Compare sizes
auto s1 = fs::file_size(p1);
auto s2 = fs::file_size(p2);
if (s1 != s2) return true;
// 3. Open both files in binary mode
std::ifstream f1(p1, std::ios::binary);
std::ifstream f2(p2, std::ios::binary);
if (!f1 || !f2) return true; // treat I/O error as "different"
// 4. Compare in chunks
std::vector<char> buf1(buffer_size);
std::vector<char> buf2(buffer_size);
while (f1 && f2) {
f1.read(buf1.data(), buffer_size);
f2.read(buf2.data(), buffer_size);
std::streamsize r1 = f1.gcount();
std::streamsize r2 = f2.gcount();
if (r1 != r2) return true; // should not happen if sizes equal
if (r1 == 0) break; // EOF both
if (std::memcmp(buf1.data(), buf2.data(), static_cast<std::size_t>(r1)) != 0)
return true;
}
return false; // no difference found
}
// Copy a single file with an explicit binary read/write stream, forcing the
// destination to be world-readable. Deliberately NOT std::filesystem::copy_file
// or fs::copy: under Apple's `container` runtime the HTML output directory is a
// virtiofs bind mount, and libstdc++'s copy_file/copy create the destination
// with openat(O_WRONLY|O_CREAT|O_TRUNC, 0200) — a mode lacking the owner-read
// bit, which virtiofs rejects with EACCES (apple/container #1344, an OS-level
// Virtualization.framework bug), leaving a 0-byte --w------- file and aborting
// output. A stream copy creates the destination owner-readable and works
// identically on virtiofs, on in-VM filesystems, and under Docker. Used for
// every file Klammertext writes into the output tree (fonts, CSS, JS, ...).
void copy_file_stream(const fs::path& src, const fs::path& dst)
{
{
std::ifstream in(src, std::ios::binary);
if (!in)
throw File_error("Cannot read file for copy:\n " + src.string());
std::ofstream out(dst, std::ios::binary | std::ios::trunc);
if (!out)
throw File_error("Cannot create output file:\n " + dst.string());
// Guard against the empty-source failbit quirk of rdbuf insertion.
if (in.peek() != std::ifstream::traits_type::eof())
out << in.rdbuf();
out.flush();
if (!out || in.bad())
throw File_error("Failed to copy file:\n " + src.string()
+ "\n -> " + dst.string());
}
fs::permissions(dst,
fs::perms::owner_read | fs::perms::owner_write |
fs::perms::group_read | fs::perms::others_read,
fs::perm_options::replace);
}
void copy_preserving_basename(
strings_t filenames, std::string output_directory, std::string link_directory)
{
fs::path outdir(output_directory + "/" + link_directory);
fs::create_directories(outdir);
for (std::string filename : filenames) {
fs::path pname(filename);
auto out_path = outdir / pname.filename();
// Preserve the previous copy_options::update_existing behavior: skip
// when the destination already exists and is no older than the source.
if (fs::exists(out_path) &&
fs::last_write_time(out_path) >= fs::last_write_time(pname))
continue;
copy_file_stream(pname, out_path);
}
}
fs::path resolve_relative_to(const fs::path& relative, const fs::path& base)
{
fs::path base_dir = is_directory(base) ? base : base.parent_path();
return fs::weakly_canonical(base_dir / relative);
}

66
mac/file.h Normal file
View File

@@ -0,0 +1,66 @@
#pragma once
#include <string>
#include <vector>
#include <filesystem>
#include <functional>
namespace fs = std::filesystem;
std::string file_basename(const std::string& filename);
std::string extension(const std::string& filename);
std::string file_directory(const std::string& filename);
std::string absolute_pathname(const std::string& filename, const std::string& base="");
std::string relative_pathname(const std::string& filename);
std::string relative_pathname(const std::string& filename, const std::string& base);
fs::path relative_to_cwd(const fs::path& input);
bool file_exists(const std::string& pathname, bool error_if_not=false, bool is_directory=false);
std::string string_from_file(const std::string& pathname, bool strip_surrounding_whitespace=false);
void string_to_file(const std::string& pathname, std::string contents);
std::vector<std::string> get_files_in_directory(const std::string& dir);
std::string find_file(const std::string& basename, std::vector<std::string> search_path,
bool error_if_not_found=true);
std::vector<fs::path>
find_file_recursive(const fs::path& root, const std::string& filename,
bool only_one=true); //, Locator loc=Locator());
std::vector<fs::path>
find_file_from_roots(const std::vector<std::string>& roots, const std::string& filename, bool only_one);
fs::path klammertext_filename(
const std::string& basename, bool error_if_missing=true, bool make_directory_if_missing=false);
std::vector<std::string> sks_dirs();
std::vector<std::string> get_sks_directories(const std::string& s, bool include_argument=true);
std::string cache_directory(std::string subdirectory, std::string parent_directory="");
std::time_t to_time_t(const fs::file_time_type& ftime);
bool in_modification_order(std::string filename1, std::string filename2);
void write_to_cache(std::string cache_dir, std::string basename, std::string text);
std::string read_from_cache(std::string cache_dir, std::string basename);
bool cache_requires_update(std::string cache_dir, std::string file_to_cache, std::string basename);
std::vector<fs::path> pathnames_with_extension(
const fs::path& dir, const std::string extension
);
//std::string find_file(const fs::path& root, const std::string& name);
std::vector<std::string> find_files_with_extension(
const fs::path& root, const std::string& ext, bool case_insensitive=false);
std::string combine_files(
std::vector<std::string> filenames,
std::string prolog="", std::string epilog="",
std::function <std::string(std::string)> processor = nullptr);
bool files_differ(const fs::path& p1,
const fs::path& p2,
std::size_t buffer_size = 1 << 16); // 64 KiB
// Copy a single file with an explicit binary stream (NOT std::filesystem
// copy_file/copy), forcing the destination world-readable. Required for output
// onto Apple `container` virtiofs mounts — see the definition in file.cpp.
void copy_file_stream(const fs::path& src, const fs::path& dst);
void copy_preserving_basename(
std::vector<std::string> filenames, std::string output_directory, std::string link_directory);
fs::path resolve_relative_to(const fs::path& relative, const fs::path& base=std::filesystem::current_path());

1
mac/headers_only.mk Normal file
View File

@@ -0,0 +1 @@
HEADERS_ONLY := alias env

397
mac/katom.cpp Normal file
View File

@@ -0,0 +1,397 @@
#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;
}
}
if (verbose_level > 0) {
std::cerr << command_name
<< " [warning]: Word not parsed in "
<< source_desc << ", line " << line+1 << ":\n"
<< " " << s << "\n"
<< "To include a special character (@, |, #, and ^), put \"^\" before it.\n";
//return std::vector{ std::make_shared<Katom>(s, Locator(), katom_t::word) };
//return std::vector{ std::make_shared<Katom>(s, katom_t::word, Locator(source_desc, line, chr)) };
}
return std::vector{ Katom(s, katom_t::word, Locator(source_desc, line, chr)) };
}
}
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(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(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});
}

205
mac/katom.h Normal file
View File

@@ -0,0 +1,205 @@
#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
Katom(const Katom& 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)
{}
// 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;
}
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 {};
};
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);
std::vector<std::string> line_split(std::string s);
std::pair<std::string, std::vector<std::string>> line_split(fs::path pathname);
std::vector<Katom> katomize(const std::vector<std::string>& lines, const std::string& source_desc);
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;
}

428
mac/katom_list.cpp Normal file
View File

@@ -0,0 +1,428 @@
// #include <algorithm>
#include <numeric>
#include "katom_list.h"
#include "util.h"
#include "ktype.h"
#include "file.h"
#include "log.h"
#include "show.h"
#include "character.h"
std::string to_string(std::vector<Katom>::const_iterator begin, std::vector<Katom>::const_iterator end, bool trim_result)
{
(void)K::log(3);
std::string result =
std::accumulate(
begin, end,
std::string(""),
[](const std::string& s, const Katom& k)
{ return k.is_active() ? s + k.m_text : s; });
if (trim_result) {
result = trim(result);
}
return result;
}
std::string to_string(const katom_list& katoms, bool trim_result)
{
return to_string(katoms.cbegin(), katoms.cend(), trim_result);
}
bool level_increase(const Katom& k)
{
return k.m_type == katom_t::define_begin
|| k.m_type == katom_t::literal_begin
|| k.m_type == katom_t::ignore_begin
|| k.m_type == katom_t::read_begin
|| k.m_type == katom_t::eval_begin
|| k.m_type == katom_t::cond_begin
|| k.m_type == katom_t::machine_begin
|| k.m_type == katom_t::apply_begin;
}
bool level_decrease(const Katom& k)
{
return k.m_type == katom_t::apply_end
|| k.m_type == katom_t::define_end
|| k.m_type == katom_t::literal_end
|| k.m_type == katom_t::ignore_end
|| k.m_type == katom_t::machine_end;
}
const katom_list::iterator
find_katom_named(const katom_list::iterator begin, const katom_list::iterator end, std::string name)
{
auto result = find_if(
begin, end, [&](const Katom& ki) {
return ki.m_text == ("@" + name); });
return result;
}
// Spans
//katom_iter get_katom_iterator(katom_list& katoms, size_t index)
const katom_list::iterator
find_katom(const katom_list::iterator begin, const katom_list::iterator end, size_t index)
{
// msg() << "find_katom: " << std::pair(begin, end) << "\n";
auto result = find_if(
begin, end, [&](const Katom& ki) {
return ki.m_index == index && ki.m_type != katom_t::ignored && ki.m_type != katom_t::replaced; });
if (result == end) {
std::stringstream ss {};
ss << "Katom with index " << index << " not found in katom list";
throw Internal_error(ss.str(), begin->m_loc);
} else {
return result;
}
}
std::pair<katom_iter, katom_iter>
find_span_katoms(katom_list& katoms, const Katom& begin, const Katom& end) // Lint error
{
//(void)K::log(3, begin, end);
//static std::mutex katoms_mutex;
//std::lock_guard<std::mutex> lock(katoms_mutex);
katom_iter kbegin = find_katom(katoms.begin(), katoms.end(), begin.m_index);
katom_iter kend = find_katom(kbegin, katoms.end(), end.m_index) + 1;
//(void)K::log(3, kbegin->m_text, (kbegin+1)->m_text, (kbegin+2)->m_text, "...", kend->m_text);
//(void)K::log(3, "resolved:", kbegin, (kbegin+2), "..."); //, kend);
(void)K::log(3, "resolved:", kbegin, kend - 1);
return { kbegin, kend };
}
std::pair<katom_iter, katom_iter>
find_span_katoms(katom_iter katoms_begin, katom_iter katoms_end, const Katom& begin, const Katom& end) // Lint error
{
katom_iter kbegin = find_katom(katoms_begin, katoms_end, begin.m_index);
katom_iter kend = find_katom(katoms_begin, katoms_end, end.m_index) + 1;
//(void)K::log(3, kbegin->m_text, (kbegin+1)->m_text, (kbegin+2)->m_text, "...", kend->m_text);
//(void)K::log(3, "resolved:", kbegin, (kbegin+2), "..."); //, kend);
(void)K::log(3, "resolved:", kbegin, kend - 1);
return { kbegin, kend };
}
void missing_open(const Katom& k, bool error_exit)
{
(void)K::log(2);
std::stringstream ss {};
ss << "A klammer ends without a beginning: " << k;
if (error_exit) {
throw Parsing_error(ss.str(), k.m_loc, false);
} else {
std::cout << " " << ss.str() << "\n";
}
}
//void missing_close(std::vector<katom_ptr>& bounds, bool error_exit)
void missing_close(const katom_list& bounds, bool error_exit)
{
(void)K::log(2);
std::stringstream ss {};
if (bounds.size() == 1) {
ss << " Beginning of a span that does not end:\n";
} else {
ss << " A span ends that does not have a beginning:\n";
}
for (const auto& k : bounds) {
ss << " " << k.m_loc << " " << k.m_src << "\n";
}
if (error_exit) {
throw Parsing_error(ss.str(), bounds[0].m_loc, false);
} else {
std::cout << ss.str() << "\n";
}
}
void bad_close(const Katom& open, const Katom& close, bool error_exit)
{
(void)K::log(2);
std::stringstream ss {};
ss << " Klammer begins with " << open << " but ends with " << close << ".\n"
<< " " << open.m_loc << " " << open << "\n"
<< " " << close.m_loc << " " << close;
if (error_exit) {
throw Parsing_error(ss.str(), open.m_loc, false);
} else {
std::cout << ss.str() << "\n";
}
}
std::string trim_span_markers(const Katom& katom)
{
// Trim @ as well as ^ (for literal span)
return trim_char(trim_char(katom.m_text, '@'), '^');
}
void check_named_katom_span(const Katom& begin, const Katom& end)
{
(void)K::log(4);
std::string end_text = trim_span_markers(end);
if (!end_text.empty() &&
//!(begin.m_type == katom_t::ignore_begin && end.m_type == katom_t::ignore_end)) {
!(begin_ignore(begin) && end_ignore(end))) {
std::string begin_text = trim_span_markers(begin);
if (begin_text != end_text) {
std::stringstream ss;
ss << " A named end katom does not match:\n"
<< " " << begin.m_loc << " " << begin << "\n"
<< " " << end.m_loc << " " << end;
throw Parsing_error(ss.str(), end.m_loc, false);
}
}
}
spans_t find_spans(
katom_iter begin, katom_iter end,
std::function<bool(const Katom&)> level_inc,
std::function<bool(const Katom&)> level_dec,
bool error_exit,
std::string name)
{
(void)K::log(4, name);
bool _dbg = false;
spans_t spans {};
katom_list bounds {};
for (auto k = begin; k < end; k++) {
if (_dbg) msg() << ktype << kall << kignored << kreplaced << " " << k << " type: " << type_to_name(k->m_type) << "\n";
if (level_inc(*k)) {
if (_dbg) msg() << boldblack << " level_inc: " << k << black << "\n";
bounds.push_back(*k);
} else if (level_dec(*k)) {
if (_dbg) msg() << boldblack << " level_dec: " << k << " bounds: " << bounds << black << "\n";
if (!bounds.empty()) {
auto span_start = bounds.back();
if (_dbg) msg() << " span_start of this level: " << span_start << "\n";
if (katom_spans[span_start.m_type] == k->m_type) {
check_named_katom_span(span_start, *k);
spans.push_back({std::move(span_start), std::move(*k)});
bounds.pop_back();
} else {
bad_close(span_start, *k, error_exit);
}
} else {
missing_open(*k, error_exit);
}
}
}
if (!bounds.empty()) {
missing_close(bounds, error_exit);
}
if (_dbg) msg() << black;
// Post-order invariant: if span A is nested inside span B, then A
// appears before B in the list. This is a necessary consequence of
// stack-based bracket matching and is required for correct inside-out
// evaluation of nested structures. See doc/taxonomy.md, "Spans".
if (verbose_level >= 2) {
for (size_t i = 0; i < spans.size(); i++) {
for (size_t j = i + 1; j < spans.size(); j++) {
bool j_inside_i =
spans[j].first.m_index > spans[i].first.m_index &&
spans[j].second.m_index < spans[i].second.m_index;
if (j_inside_i) {
std::stringstream ss;
ss << "Post-order span invariant violated: span "
<< j << " (" << spans[j].first << ")"
<< " is nested inside span "
<< i << " (" << spans[i].first << ")"
<< " but appears after it";
throw Internal_error(ss.str(), spans[j].first.m_loc);
}
}
}
}
return spans;
}
spans_t find_spans(
katom_list& katoms,
std::function<bool(const Katom&)> level_inc,
std::function<bool(const Katom&)> level_dec,
bool error_exit,
std::string name)
{
return find_spans(katoms.begin(), katoms.end(), level_inc, level_dec, error_exit, name);
}
void describe_spans(const katom_list& katoms)
{
katom_list non_const_katoms = katoms;
auto spans = find_spans(non_const_katoms, level_increase, level_decrease);
std::vector<std::tuple<std::string, Katom, Katom>> span_specs {};
size_t width = 0;
std::string margin = " ";
string_map relpath {};
for (auto [op,cl] : spans) {
std::string loc = locator_range(op.m_loc, cl.m_loc, relpath);
width = std::max(width, loc.size());
span_specs.push_back(std::make_tuple(margin + loc, op, cl));
}
width += margin.size();
for (auto [span,op,cl] : span_specs) {
std::cout << std::setw(width) << std::left << span << " "
<< ktype << kreplaced
<< op << right_arrow << cl << "\n";
}
}
// Nonascii
void encode_nonascii_characters(katom_list& katoms)
{
for (Katom& k : katoms) {
if (is_nonascii(k)) {
k.m_text = encode(k.m_text);
}
}
}
// Literal
void mark_literal_katoms(katom_list& katoms)
{
(void)K::log(4);
for (auto [op, cl] : find_spans(katoms, begin_literal, end_literal, true, "literal")) {
auto [begin, end] = find_span_katoms(katoms, op, cl);
//if (begin->m_type == katom_t::literal_begin) {
if (begin_literal(*begin)) {
//begin->m_type = katom_t::replaced;
mark_as_replaced(*begin);
//end->m_type = katom_t::replaced;
mark_as_replaced(*(end - 1));
//std::for_each(begin + 1, end, [](Katom& k) { k.m_type = katom_t::literal; });
std::for_each(begin + 1, end - 1, mark_as_literal);
}
}
}
// Ignore
void mark_ignored_katoms(katom_list& katoms)
{
(void)K::log(4);
//auto is_ignore_begin = [](const Katom& k) { return k.m_type == katom_t::ignore_begin; };
//auto is_ignore_end = [](const Katom& k) { return k.m_type == katom_t::ignore_end; };
for (auto [op,cl] : find_spans(katoms, begin_ignore, end_ignore, true, "ignored")) {
auto [begin, end] = find_span_katoms(katoms, op, cl);
// std::for_each(begin, end + 1, [](Katom& k) { k.m_type = katom_t::ignored; });
std::for_each(begin, end + 1, mark_as_ignored);
//if (end + 1 != katoms.end() && (end+1)->m_type == katom_t::newline) {
if (end + 1 != katoms.end() && is_newline(*(end + 1))) {
//(end+1)->m_type = katom_t::ignored;
mark_as_ignored(*(end + 1));
}
}
bool ignore_line = false;
bool ignore_rest = false;
std::vector<katom_iter> after_line {};
for (Katom& k : katoms) {
//if (k.m_type == katom_t::ignore_rest) {
if (is_ignore_rest(k)) {
ignore_rest = true;
// } else if (k.m_type == katom_t::ignore_line) {
} else if (is_ignore_line(k)) {
ignore_line = true;
}
//if (!ignore_rest && ignore_line && k.m_type == katom_t::newline) {
if (!ignore_rest && ignore_line && is_newline(k)) {
//k.m_type = katom_t::ignored;
mark_as_ignored(k);
ignore_line = false;
}
if (ignore_line or ignore_rest) {
//k.m_type = katom_t::ignored;
mark_as_ignored(k);
}
}
size_t i = 0;
while (i < katoms.size()) {
if (katoms[i].m_initial_type == katom_t::ignore_end) {
// std::cout << "IGNORED: " << kall << ktype << kignored << katoms[i] << " " << katoms[i+1] << black << "\n";
i++;
while (i < katoms.size() && katoms[i].is_whitespace()) {
katoms[i].m_type = katom_t::ignored;
i++;
}
} else {
i++;
}
}
}
// Klammers:
void process_klammer_katoms(katom_list& katoms)
{
(void)K::log(4);
for (auto [op, cl] : find_spans(katoms, begin_klammer_def, end_klammer_def , true, "define")) {
auto [begin, end] = find_span_katoms(katoms, op, cl);
for (auto k = begin + 1; k < end - 1; k++) {
//k->m_type = katom_t::literal;
mark_as_literal(*k);
}
}
}
// Application: read, cond, and defined klammers
// Cond
/*
void check_bar_count(katom_iter begin, katom_iter end)
{
//int count = std::count_if(begin, end, [](const Katom& k) { return k.m_type == katom_t::bar; });
int count = std::count_if(begin, end, is_bar); //[](const Katom& k) { return k.m_type == katom_t::bar; });
if (count != 1 && count != 2) {
std::stringstream ss {};
ss << "Incorrectly formatted @cond klammer. There should only be one or two bar characters:\n"
<< " @cond <predicate> | <result-if-true @\nor:\n"
<< " @cond <predicate> | <result-if-true> | <result-if-false> @";
throw Argument_error(ss.str(), begin->m_loc, false);
}
}
bool is_true(const std::string& s)
{
return s == "True" || s == "true" || s == "1";
}
void process_cond_katoms(katom_list& katoms)
{
if (std::find_if(katoms.begin(), katoms.end(), begin_cond) != katoms.end()) {
(void)K::log(3);
//for (auto [op, cl] : find_spans(katoms, begin_cond, end_apply, true, "cond")) {
for (auto [op, cl] : find_spans(katoms, level_increase, level_decrease, true, "cond")) {
auto [begin, end] = find_span_katoms(katoms, op, cl);
// msg() << "find_spans: " << std::pair(begin, end) << "\n";
if (begin_cond(*begin)) {
check_bar_count(begin, end);
auto bar_1 = std::find_if(begin, end, is_bar);
std::string predicate = to_string(begin + 1, bar_1, true);
auto bar_2 = std::find_if(bar_1 + 1, end - 1, is_bar);
katom_list true_clause {};
katom_list false_clause {};
if (bar_2->m_type == katom_t::bar) {
true_clause = katom_list(bar_1 + 1, bar_2);
false_clause = katom_list(bar_2 + 1, end - 1);
} else {
true_clause = katom_list(bar_1 + 1, end - 1);
}
katom_list result = is_true(predicate)
? trim_whitespace(true_clause) : trim_whitespace(false_clause);
std::for_each(begin, end, mark_as_replaced);
katoms.insert(end, result.begin(), result.end());
}
}
}
}
*/

52
mac/katom_list.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include "katom.h"
#include "state.h"
std::string to_string(std::vector<Katom>::const_iterator begin, std::vector<Katom>::const_iterator end, bool trim_result=false);
std::string to_string(const std::vector<Katom>& katoms, bool trim_result=false);
// std::vector<Katom> process_katoms(
// const std::string& s, State& state, 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);
bool level_increase(const Katom& k);
bool level_decrease(const Katom& k);
const katom_list::iterator
find_katom_named(const katom_list::iterator begin, const katom_list::iterator end, std::string name);
const std::vector<Katom>::iterator
find_katom(const std::vector<Katom>::iterator begin, const std::vector<Katom>::iterator end, size_t index);
std::vector<std::pair<Katom, Katom>>
find_spans(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end,
std::function<bool(const Katom&)> level_inc,
std::function<bool(const Katom&)> level_dec,
bool error_exit,
std::string name);
std::vector<std::pair<Katom, Katom>>
find_spans(std::vector<Katom>& katoms,
std::function<bool(const Katom&)> level_inc,
std::function<bool(const Katom&)> level_dec,
bool error_exit=true,
std::string name="all");
void describe_spans(const std::vector<Katom>& katoms);
std::pair<std::vector<Katom>::iterator, std::vector<Katom>::iterator>
find_span_katoms(std::vector<Katom>& katoms, const Katom& begin, const Katom& end);
std::pair<std::vector<Katom>::iterator, std::vector<Katom>::iterator>
find_span_katoms(
std::vector<Katom>::iterator kbegin, std::vector<Katom>::iterator kend, const Katom& begin, const Katom& end);
void encode_nonascii_characters(std::vector<Katom>& katoms);
void mark_literal_katoms(std::vector<Katom>& katoms);
void mark_ignored_katoms(std::vector<Katom>& katoms);
void process_klammer_katoms(std::vector<Katom>& katoms);

493
mac/klammer.cpp Normal file
View File

@@ -0,0 +1,493 @@
#include <algorithm>
#include "klammer.h"
#include "show.h"
#include "log.h"
#include "argument_set.h"
#include "eval.h"
#include "util.h"
#include "character.h"
using namespace std::literals;
std::regex Klammer::name_re = std::regex(R"((\w+)(?:\.(\w+))?)");
std::tuple<std::string, std::string>
parse_name(Target_set targets, Katom name_katom)
{
std::string name_with_target = trim_char(name_katom.m_text, '@');
std::smatch match {};
if (!std::regex_match(name_with_target, match, Klammer::name_re)) {
throw Parsing_error(
"The klammer name \"" + name_with_target + "\" is not correctly defined. "
"The form is \"<klammer-name>\" for general klammers or \"<klammer-name>.<target-name>\" "
"for a specialized target. The klammer defined as \"<klammer-name>.k\" specifies the "
"arguments and contains a description of the klammer in the definition body.",
name_katom.m_loc);
}
std::string klammer_name = match[1];
std::string target_name = match[2];
if (target_name.empty()) {
target_name = Target_set::general_name;
}
if (!targets.has(target_name)) {
throw Target_error(
"The target \"" + target_name + "\" in klammer definition \"" + name_with_target + "\" "
"is not defined. Enter \"kdesc -t\" to see the targets defined by the Standard Klammer Set.",
name_katom.m_loc);
}
return { klammer_name, target_name };
}
std::tuple<Katom, Parameter_set, katom_list, Locator>
parse_definition_katoms(std::string klammer_name, Argtype_set argtypes, katom_iter& begin, katom_iter& end)
{
(void)K::log(3, *begin, *(end - 1));
katom_iter deftype = std::find_if(
begin, end, [](const Katom& k) { return is_deftype(k.m_type); });
if (deftype == end) {
throw Argument_error(
"The klammer definition does not contain a definition separator that specifies\n"
"how the klammer should be defined. The klammer syntax is:\n\n"
" @@<name>[.<target>] <parameters> : <body> @@ definition\n"
" @@<name>[.<target>] :: <body> @@ instance (uses .k parameters)\n"
" @@<name>[.<target>] ::: <body> @@ override existing definition\n"
" @@<name>[.<target>] <parameters> :::: <body> @@ default (can be overridden)",
begin->m_loc, false);
}
katom_list parameter_katoms(begin, deftype);
// Resolve text-removal markers (#, #[...]#, ##) in the parameter list.
// The interior of a @@...@@ definition is held verbatim until now (so a
// literal klammer body receives its content untouched), which means the
// parameter declarations still contain any removal markers the writer
// used. Apply removal here, to the parameters only -- the body is left
// verbatim by add_target_definition.
mark_ignored_katoms(parameter_katoms);
std::erase_if(parameter_katoms,
[](const Katom& k) { return k.m_type == katom_t::ignored; });
parameter_katoms = trim_whitespace(parameter_katoms);
if ((deftype->m_type == katom_t::klammer_instance ||
deftype->m_type == katom_t::klammer_override) &&
!parameter_katoms.empty()) {
std::string sym = deftype->m_type == katom_t::klammer_instance ? "::" : ":::";
throw Definition_error(
"The \"" + klammer_name + "\" klammer uses the \"" + sym + "\" symbol but defines parameters.",
begin->m_loc);
}
Parameter_set parameters(parameter_katoms, argtypes);
katom_list body_katoms(deftype + 1, end - 1);
body_katoms = trim_whitespace(body_katoms);
return { *deftype, parameters, body_katoms, begin->m_loc };
}
void Klammer::add_target_definition(
std::string target_name, Argtype_set argtypes, katom_iter begin, katom_iter end)
{
(void)K::log(3, *begin, *(end-1));
auto [deftype, parameters, body, loc] =
parse_definition_katoms(m_name, argtypes, begin, end); // targets, begin, end);
// msg() << "Klammer " << m_name << " add: " << target_name << "\n";
// parameters.describe_parameters();
std::regex variable_re(R"(\*(\w+)\*)");
int i = 0;
variable_map_t varmap {};
for (auto k : body) {
std::smatch match {};
std::string txt = k.m_text;
while (std::regex_search(txt, match, variable_re) &&
(k.m_type == katom_t::karg || k.m_type == katom_t::text)) {
//std::cout << " txt: " << txt << "\n";
std::string varname = match[1];
//m_varmap[target_name][varname].push_back(i);
varmap[varname].push_back(i);
txt = std::regex_replace(txt, std::regex(R"(\*)" + varname + R"(\*)"), "");
}
i++;
}
// m_body[target_name] = body;
m_defs.push_back({target_name, deftype.m_initial_type, parameters, body, varmap, begin->m_loc});
m_defloc[target_name] = begin->m_loc;
m_defmode[target_name] = defmode_from_katom(deftype.m_initial_type);
// std::cout << "Klammer " << m_name << "." << target_name << ": " << m_defloc[target_name] << "\n";
}
void Klammer::remove_target_definition(const std::string& target_name)
{
std::erase_if(m_defs,
[&target_name](const auto& def) { return def.target == target_name; });
m_defloc.erase(target_name);
m_defmode.erase(target_name);
m_body.erase(target_name);
m_varmap.erase(target_name);
}
// Rationalize multiple definitions
std::string error_list(std::string label, auto components, std::string after="")
{
std::stringstream ss {};
ss << label << ":\n";
for (auto c : components) {
ss << " " << c.loc.desc() << "\n";
}
ss << after;
return ss.str();
}
auto Klammer::target_defs(std::vector<std::string> target_names)
{
std::vector<Klammer::components> defs {};
for (auto target : target_names) {
auto target_defs = collect_if(
m_defs, [target](const auto& def) { return def.target == target; });
defs.insert(defs.end(), target_defs.begin(), target_defs.end());
}
return defs;
}
auto Klammer::instance_defs()
{
return collect_if(
m_defs,
[] (const auto& def) {
return def.deftype == katom_t::klammer_instance
|| def.deftype == katom_t::klammer_override; });
}
void Klammer::disallow_instances() //Klammer::components declaration)
{
auto instances = instance_defs();
if (!instances.empty()) {
int icount = instances.size();
std::stringstream ss {};
ss << "There " << to_be(icount) << " " << icount << " "
<< plural("instance", icount) << " (defined by \"::\"), but "
<< "no declarations (defined by a \".k\" target)";
throw Definition_error(
error_list(ss.str(), instances), instances[0].loc, false);
}
}
bool Klammer::copy_to_instances(Target_set targets)
{
auto instances = instance_defs();
if (!instances.empty()) {
auto definitions = collect_if(
m_defs,
[] (const auto& def) {
return def.deftype != katom_t::klammer_instance
&& def.deftype != katom_t::klammer_override; });
int dcount = definitions.size();
if (dcount != 1) {
int icount = instances.size();
std::stringstream ss {};
ss << "There " << to_be(icount) << " " << icount << " "
<< plural("instance", icount) << " (defined by \"::\"), but "
<< dcount << " "<< plural("definition", dcount) << " (defined by \":\")";
throw Definition_error(
error_list(ss.str(), definitions), definitions[0].loc, false);
} else {
copy_components(definitions[0].parameters, m_defs, targets);
return true;
}
} else {
return false;
}
}
void Klammer::copy_components(
Parameter_set parameters, std::vector<Klammer::components> cs, Target_set targets)
{
(void)K::log(4);
for (auto target_name : targets.m_names) {
if (target_name == Target_set::declare_name ||
target_name == Target_set::general_name) {
continue;
}
m_parameters = parameters;
}
for (auto c : cs) {
m_body[c.target] = c.body;
m_varmap[c.target] = c.varmap;
}
}
// Verify that there is no more than one general klammer definition
void Klammer::check_for_multiple_general_klammers()
{
auto general_klammers = target_defs({Target_set::general_name});
if (general_klammers.size() > 1) {
throw Definition_error(
error_list(
"There is more than one general klammer (a klammer in which no target is defined)",
general_klammers),
general_klammers[0].loc, false);
}
}
void Klammer::check_for_declaration_and_definitions()
{
auto declares = target_defs({Target_set::declare_name});
if (!declares.empty()) {
std::vector<Klammer::components> definitions {};
for (auto def : m_defs) {
if (def.target != Target_set::declare_name) {
if (def.deftype == katom_t::klammer_definition ||
def.deftype == katom_t::klammer_default) {
msg() << def << "\n";
definitions.push_back(def);
}
}
}
if (!definitions.empty()) {
auto defsize = definitions.size();
std::string desc = defsize == 1 ? "a definition" :
std::to_string(defsize) + " definitions";
throw Definition_error(
error_list("A klammer has both a declaration (.k) as well as " + desc + "\n(instances are defined by \"::\")",
definitions),
declares[0].loc, false);
}
}
}
// If a general definition exists, use it for targets not defined, but check signatures
void Klammer::copy_general_klammer_to_undefined(Target_set targets)
{
(void)K::log(4);
auto general_klammers = target_defs({Target_set::general_name});
auto declares = target_defs({Target_set::declare_name});
// Check matching signatures (though this case already handled)
if (general_klammers.size() == 1) {
if (declares.empty() && !m_parameters.m_katoms.empty()) {
auto general_parameters = general_klammers[0].parameters;
if (general_parameters != m_parameters) {
// m_parameters.describe_parameters();
throw Definition_error(
error_list("The general parameters are different than the defined parameters",
general_klammers),
m_parameters.m_katoms[0].m_loc, false);
}
}
auto [target, deftype, parameters, body, varmap, loc] = general_klammers[0];
if (m_parameters.m_katoms.empty()) {
m_parameters = parameters;
}
for (auto target_name : targets.m_names) {
// std::cout << "General copy, considering " << target_name << "\n";
if (m_body.count(target_name) == 0 && target_name != Target_set::declare_name) {
// std::cout << " Copying to " << target_name << "\n";
m_body[target_name] = body;
m_defloc[target_name] = loc;
m_varmap[target_name] = varmap;
}
}
}
}
// Three declaration cases: none, one, many
void Klammer::no_declarations(Target_set targets)
{
(void)K::log(4);
// std::cout << boldblack << "No declarations\n" << black;
check_for_multiple_general_klammers();
std::vector<Klammer::components> defs {};
std::vector<std::string> target_names = targets.applicable();
std::vector<Parameter_set> all_parameter_sets {};
// Are all parameters the same?
for (auto def : m_defs) {
if (std::ranges::find(target_names, def.target) != target_names.end()) {
// std::cout << " Found: " << def.target << "\n";
all_parameter_sets.push_back(def.parameters);
} else {
// std::cout << " Not found: " << def.target << "\n";
}
}
if (!all_equal<Parameter_set>(all_parameter_sets)) {
// std::cout << " Not all equal\n";
throw Definition_error(
error_list("There is no declaration (.k) target for klammer \"" + m_name + "\"\n"
"but the parameters of all targets are not the same",
m_defs,
"Use a .k klammer to define the parameters and describe the klammer,\n"
"with \"::\" and no parameters for all targets."),
m_defs[0].loc, false);
} else {
// std::cout << " All equal\n";
copy_components(m_defs[0].parameters, m_defs, targets);
}
copy_general_klammer_to_undefined(targets);
}
void Klammer::one_declaration(Target_set targets, Klammer::components declare)
{
(void)K::log(4);
// std::cout << boldblack << "One declaration\n" << black;
check_for_multiple_general_klammers();
check_for_declaration_and_definitions();
copy_components(declare.parameters, m_defs, targets);
copy_general_klammer_to_undefined(targets);
}
void Klammer::many_declarations(std::vector<Klammer::components> declares)
{
(void)K::log(4);
// std::cout << boldblack << "Many declarations\n" << black;
throw Definition_error(
error_list("More than one declaration (.k) klammer", declares),
declares[0].loc, false);
}
void Klammer::rationalize(Target_set targets)
{
(void)K::log(3, m_name);
auto declares = target_defs({Target_set::declare_name});
auto declare_count = declares.size();
if (declare_count == 0) {
disallow_instances();
if (!copy_to_instances(targets)) {
no_declarations(targets);
}
} else if (declare_count == 1) {
one_declaration(targets, declares[0]);
} else {
many_declarations(declares);
}
}
std::string klammer_name_from_katom(std::string s, Locator loc)
{
std::regex rgx(R"(@(\w+).*)");
std::smatch match {};
if (std::regex_match(s, match, rgx)) {
return match[1];
} else {
throw Definition_error("The form of the klammer name " + q_(s) + " is not correct", loc);
}
}
void label(const std::string& s)
{
int w = 13;
std::cout << std::right << std::setw(w) << std::setfill(' ') << s << ": ";
}
void show_args(const std::string& label_text, std::vector<Argument> arguments)
{
if (!arguments.empty()) {
label(label_text);
for (auto a : arguments) {
std::cout << a << " ";
}
std::cout << '\n';
}
}
strings_t Klammer::get_target_names() const
{
strings_t names {};
for (auto [target, body] : m_body) {
std::stringstream ss {};
// ss << name << target.m_loc.m_line;
ss << target;
names.push_back(ss.str());
}
return names; // return join(names, ","s);
}
strings_t Klammer::get_locations()
{
strings_t locs {};
for (auto [target, locator] : m_defloc) {
std::cout << target << right_arrow << locator << "\n";
}
return {};
}
std::string Klammer::signature_text()
{
std::string result {};
bool has_pos = false;
bool has_opt = false;
for (auto pos : m_parameters.m_positional) {
result += pos.m_name;
std::string type = pos.m_argtype.m_name;
if (type != default_argtype) {
result += "." + type;
}
result += " | ";
has_pos = true;
}
if (result.size() >= 2)
result.resize(result.size() - 2);
auto opt_count = m_parameters.m_optional.size();
if (opt_count <= 3 && !has_pos) {
result += " ";
}
for (auto opt : m_parameters.m_optional) {
if (opt_count > 3) {
result += "\n :" + opt.m_name;
} else {
result += ":" + opt.m_name;
}
std::string type = opt.m_argtype.m_name;
if (type != default_argtype) {
result += "." + type;
}
// result += ":" + opt.m_name + "." + opt.m_argtype.m_name;
if (!opt.m_default.empty()) {
result += " " + italic_on() + opt.m_default + italic_off();
}
result += " ";
has_opt = true;
}
result = trim_right(result);
if (has_pos) {
result = " " + result;
}
if (opt_count > 3) {
result += "\n";
} else if (has_opt or has_pos) {
result += " ";
}
result += "@\n";
return result;
}
std::string Klammer::description_text()
{
std::string result = " [" + m_name + ": no description]";
if (m_body.contains("k")) {
result = to_string(m_body["k"], true);
result = justify(result, 80, 1);
}
return result;
}
std::string Klammer::describe(int margin)
{
std::string result {};
result += "@" + m_name + signature_text() + description_text();
result = add_margin(result, margin) + "\n";
return result;
}

106
mac/klammer.h Normal file
View File

@@ -0,0 +1,106 @@
#pragma once
#include <regex>
#include "deftype.h"
#include "argument_set.h"
#include "target_set.h"
#include "locator.h"
class Klammer
{
public:
Klammer() = default;
Klammer(const std::string& name)
: m_name(name)
{};
using variable_map_t = std::map<std::string, std::vector<int>>;
using target_variable_map_t = std::map<std::string, variable_map_t>;
static std::regex name_re; // = std::regex(R"((\w+)(?:\.(\w+))?)");
struct components {
std::string target;
katom_t deftype;
Parameter_set parameters;
std::vector<Katom> body;
variable_map_t varmap;
Locator loc;
};
// target-name -> [variable -> index]
void add_target_definition(
std::string target_name, Argtype_set argtypes,
std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end);
void remove_target_definition(const std::string& target_name);
auto target_defs(std::vector<std::string> target_names);
auto instance_defs();
void disallow_instances(); //Klammer::components declaration);
bool copy_to_instances(Target_set targets);
void copy_components(
Parameter_set parameters, std::vector<Klammer::components> cs, Target_set targets);
void check_for_multiple_general_klammers();
void check_for_declaration_and_definitions();
void copy_general_klammer_to_undefined(Target_set targets);
void no_declarations(Target_set targets);
void one_declaration(Target_set targets, Klammer::components declare);
void many_declarations(std::vector<Klammer::components> declares);
void rationalize(Target_set target);
/*
void add_description(const std::string& desc, Katom definition_type);
auto user_defs();
auto klammer_defines_parameters();
auto klammer_uses_parameters();
void check_for_target_errors(Target_set targets);
bool explicit_parameters_match();
void copy_components(Klammer::components cs, Target_set targets);
*/
std::string signature_text();
std::string description_text();
std::string describe(int margin=0);
bool has_literal_param() const {
for (const auto& p : m_parameters.m_positional)
if (p.m_argtype.m_name == "literal") return true;
for (const auto& p : m_parameters.m_rest)
if (p.m_argtype.m_name == "literal") return true;
return false;
}
strings_t get_target_names() const;
strings_t get_locations();
// Initial instantiation:
std::string m_name {};
// Collection: target, deftype, parameters, body, locator
std::vector<Klammer::components> m_defs {};
// After rationalization:
Parameter_set m_parameters {};
std::map<std::string, std::vector<Katom>> m_body {};
std::string m_desc {};
std::map<std::string, Locator> m_defloc {}; // target -> Locator
std::map<std::string, defmode_t> m_defmode {}; // target -> defmode
target_variable_map_t m_varmap {}; // target -> map: variable -> index
};
std::string klammer_name_from_katom(std::string s, Locator loc);
std::tuple<std::string,std::string>
parse_name(Target_set targets, Katom name_katom);
std::tuple<Katom, Parameter_set, std::vector<Katom>, Locator>
parse_definition_katoms(Argtype_set argtypes, //Target_set targets,
std::vector<Katom>::iterator& begin, std::vector<Katom>::iterator& end);
/*
klammer_definition_args parse_klammer_definition_katoms(
katom_list& katoms, Argtype_set& argtypes);
void check_for_undefined_arguments(
std::string name, Parameters parameters, katom_list body_katoms, Locator loc);
*/

175
mac/klammer_set.cpp Normal file
View File

@@ -0,0 +1,175 @@
#include "klammer.h"
#include "klammer_set.h"
#include "show.h"
#include "util.h"
#include "log.h"
#include "error.h"
/*
bool Klammer_set::has(std::string name, std::string target)
{
return m_klammers.count(name) > 0;
}
*/
void Klammer_set::add(Argtype_set argtypes, Target_set& targets, katom_iter begin, katom_iter end, katom_list& katoms)
{
(void)K::log(3, *begin, *(end - 1));
restore_initial_type(begin, end);
auto [klammer_name, target_name] = parse_name(targets, *begin);
if (!targets.has(target_name)) {
throw Argument_error("The target \"" + target_name + "\" is not defined", begin->m_loc);
}
// Find the incoming definition mode
katom_t incoming_deftype = katom_t::klammer_definition;
for (auto it = begin + 1; it != end - 1; ++it) {
if (is_deftype(it->m_type)) {
incoming_deftype = it->m_type;
break;
}
}
defmode_t incoming_mode = defmode_from_katom(incoming_deftype);
if (m_klammers.count(klammer_name) == 0) {
m_klammers[klammer_name] = Klammer(klammer_name);
} else if (m_klammers[klammer_name].m_defloc.count(target_name) > 0) {
defmode_t existing_mode = m_klammers[klammer_name].m_defmode[target_name];
const auto& result = defmode_transition(existing_mode, incoming_mode);
std::string name_target = klammer_name + "." + target_name;
std::string at_desc = m_klammers[klammer_name].m_defloc[target_name].desc();
if (!result.replace) {
if (result.message.empty()) {
// Silent ignore (e.g., create + default)
modify_type(katom_t::replaced, begin, end);
ignore_whitespace(end, katoms);
return;
}
std::string msg = result.message;
msg = string_replace(msg, "NAME", q_(name_target));
msg = string_replace(msg, "AT", at_desc);
throw Definition_error(msg, begin->m_loc);
}
if (result.warn) {
std::string msg = result.message;
msg = string_replace(msg, "NAME", q_(name_target));
msg = string_replace(msg, "AT", at_desc);
warning(msg, begin->m_loc);
}
m_klammers[klammer_name].remove_target_definition(target_name);
}
m_klammers[klammer_name].add_target_definition(target_name, argtypes, begin + 1, end - 1);
// This add's target:
Target target = targets.get(target_name, begin->m_loc);
if (!target.m_provides.empty()) {
for (auto provide_name : target.m_provides) {
if (m_klammers[klammer_name].m_defloc.count(provide_name) > 0) {
m_klammers[klammer_name].remove_target_definition(provide_name);
}
m_klammers[klammer_name].add_target_definition(provide_name, argtypes, begin + 1, end - 1);
}
}
modify_type(katom_t::replaced, begin, end);
auto next_iter = end;
if (next_iter < katoms.end()) {
// std::cout << "next_iter: " << kindex << *next_iter << "\n";
} else {
// std::cout << "next_iter past end. katoms length: " << katoms.size() << "\n";
}
ignore_whitespace(next_iter, katoms);
}
void Klammer_set::rationalize(Target_set targets)
{
(void)K::log(3);
for (auto k : m_klammers) {
m_klammers[k.first].rationalize(targets);
}
}
/*
bool Klammer_set::has(std::string name, std::string target)
{
return m_klammers.count(name) > 0;
}
*/
void Klammer_set::check_klammer(std::string name, std::string target, Locator loc)
{
if (m_klammers.count(name) == 0) {
throw Definition_error("The klammer " + q_(name) + " is not defined for an unspecified target", loc);
}
Klammer k = m_klammers[name];
if (k.m_defloc.count(target) == 0) {
std::string desc;
if (target == Target_set::general_name) {
desc = "an unspecified target";
} else {
desc = "target " + q_(target);
}
throw Definition_error("The " + q_(name) + " klammer is not defined for " + desc, loc);
}
}
const std::vector<Katom>* Klammer_set::constant_body(const std::string& name) const
{
auto it = m_klammers.find(name);
if (it == m_klammers.end()) return nullptr;
const Klammer& k = it->second;
// A constant klammer has no parameters at all — neither in the general
// definition nor inherited from a .k declaration. A "::" instance has
// empty d.parameters (it inherits), so we must also check that no other
// definition (especially .k) declares parameters for this klammer.
for (const auto& d : k.m_defs) {
if (!d.parameters.empty()) return nullptr;
}
for (const auto& d : k.m_defs) {
if (d.target == Target_set::general_name && d.parameters.empty()) {
return &d.body;
}
}
return nullptr;
}
int max_length(std::map<std::string, Klammer> ss)
{
size_t result = 0;
for_each(ss.begin(), ss.end(),
[&result](const auto& s) { result = std::max(result, s.first.size()); });
return result;
}
std::string Klammer_set::instance_list(int margin) const
{
std::stringstream ss {};
std::string tab(margin, ' ');
auto name_width = max_length(m_klammers);
for (auto [name, k] : m_klammers) {
ss << tab << std::setfill(' ') << std::setw(name_width) << name
<< sp_arrow << k << "\n";
}
return ss.str();
}
std::string Klammer_set::describe(int margin) const
{
/*
strings_t names {};
std::vector<strings_t> targets {};
strings_t locations {};
*/
std::string result;
for (auto [name, k] : m_klammers) {
result += k.describe(margin) + "\n";
/*
names.push_back(name);
targets.push_back(k.get_target_names());
std::cout << name << " " << k.get_target_names() << "\n";
locator_summary(k.get_locations());
//locations.push_back(
*/
}
return result;
}

19
mac/klammer_set.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include "klammer.h"
#include "target_set.h"
class Klammer_set
{
public:
Klammer_set() = default;
void add(Argtype_set argtypes, Target_set& targets,
std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, std::vector<Katom>& katoms);
void rationalize(Target_set targets);
void check_klammer(std::string name, std::string target, Locator loc);
const std::vector<Katom>* constant_body(const std::string& name) const;
std::string instance_list(int margin) const;
std::string describe(int margin=0) const;
std::map<std::string, Klammer> m_klammers {};
};

207
mac/ktype.cpp Normal file
View File

@@ -0,0 +1,207 @@
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <utility>
#include "locator.h"
#include "ktype.h"
#include "log.h"
#include "file.h"
#include "show.h"
#include "util.h"
/*
bool is_error(katom_t t)
{
return
t == katom_t::undefined ||
t == katom_t::no_matching_end ||
t == katom_t::no_matching_begin ||
t == katom_t::karg_error ||
t == katom_t::klammer_definition_type_error ||
t == katom_t::eval_error;
}
*/
bool is_active(katom_t type)
{
return type != katom_t::ignored
&& type != katom_t::replaced;
}
bool is_printable(katom_t type)
{
return printable_katom_types.find(type) != printable_katom_types.end();
}
bool is_deftype(katom_t type)
{
return
type == katom_t::klammer_definition ||
type == katom_t::klammer_instance ||
type == katom_t::klammer_override ||
type == katom_t::klammer_default;
}
std::string pattern_display(Ktype t)
{
std::string pat{};
if (t.m_type == katom_t::space) {
pat = "<space>";
} else if (t.m_type == katom_t::newline) {
pat = "\\n";
} else if (t.m_type == katom_t::ws_space) {
pat = "#+ or #+<number>";
} else if (t.m_type == katom_t::ws_newline) {
pat = "#/ or #/<number>";
} else if (t.m_type == katom_t::special) {
pat = "^@, ^|, ^#, ^:, or ^^";
} else if (t.m_type == katom_t::apply_end) {
pat = "@ or <name>@";
} else if (t.m_type == katom_t::define_end) {
pat = "@@ or <name>@@";
} else if (t.m_type == katom_t::machine_end) {
pat = "@@@ or <name>@@@";
} else if (t.m_type == katom_t::ws_added) {
pat = "<space> or \\n";
} else if (t.m_type == katom_t::word) {
pat = "<only-letters>";
} else if (t.m_type == katom_t::text) {
pat = "<no-special-chars>";
} else if (t.m_type == katom_t::nonascii) {
pat = "^<code> or ^<code>^";
} else {
pat = t.m_pattern;
pat = string_replace(pat, definition_name, "<name>");
pat = string_replace(pat, "\\", "");
}
return pat;
}
std::string regex_display(Ktype t)
{
std::string rgx{};
if (t.m_type == katom_t::space) {
rgx = "<space>";
} else if (t.m_type == katom_t::newline) {
rgx = "\\n";
} else {
rgx = t.m_pattern;
}
return rgx;
}
Ktype find_katom_type(katom_t type)
{
for (auto t : katom_types) {
if (t.m_type == type) {
return t;
}
}
throw Internal_error("Unknown katom_t: " + std::to_string((int)type));
}
void describe_katoms(bool show_regex)
{
size_t name_w = 0;
size_t pattern_w = 0;
size_t regex_w = 0;
size_t description_w = 0;
std::stringstream ss {};
ss << R"(
A "katom" is an individual element in the Klammertext input text.
Each katom has a type, listed below. You can see how Klammertext
divides up text into katoms with the "kdiag" command.)" << "\n\n";
if (show_regex) {
ss << R"(The "Regex" column is the argument to the std::regex C++ function.)";
} else {
ss << R"(In the katom patterns, "<name>" is a word that begins with a letter
and only contains letters, numbers, or the underscore (_) or period (.) characters. )";
}
ss << "A <number> is an integer greater than or equal to 1. "
"A <code> is one of the non-ASCII codes that are displayed by the command "
"\"kdesc -c\".";
for (auto t : katom_types) {
name_w = std::max(name_w, t.m_name.size());
pattern_w = std::max(pattern_w, pattern_display(t).size());
regex_w = std::max(regex_w, regex_display(t).size());
description_w = std::max(description_w, t.m_description.size());
}
pattern_w++;
regex_w++;
std::cout << justify(ss.str()) << "\n\n"
<< std::setfill(' ') << boldblack << std::right << std::setw(6) << "Index"
<< std::left
<< std::setw(name_w) << " Name" << " ";
if (show_regex) {
std::cout << std::setw(regex_w) << " Regex" << " ";
} else {
std::cout << std::setw(pattern_w) << " Pattern" << " ";
}
std::cout << std::setw(description_w) << "Description"
<< "\n" << black;
for (auto typ : katom_type_display_order) {
auto t = find_katom_type(typ);
std::cout << " " << std::right << std::setw(3)
<< static_cast<int>(t.m_type) << " "
<< std::left
<< std::setw(name_w) << t.m_name << " ";
if (show_regex) {
std::cout << std::setw(regex_w) << regex_display(t) << " ";
} else {
std::cout << std::setw(pattern_w) << pattern_display(t) << " ";
}
std::cout << std::setw(description_w) << t.m_description << "\n";
}
std::cout << "\n";
}
void describe_rewrite_patterns()
{
if (verbose_level > 0) {
std::string desc_text = "doc/katom_patterns.desc";
std::cout << "\n"
<< justify(string_from_file(klammertext_filename(desc_text)))
<< "\n\n";
}
size_t width1 = 0;
size_t width2 = 0;
for (auto [desc, pattern, replace] : katom_rewrite_rules) {
width1 = std::max(width1, desc.size());
width2 = std::max(width2, pattern.m_pattern.size());
}
width1 += 2;
width2 += 2;
std::cout << boldblack << std::setfill(' ')
<< std::setw(width1) << std::left << " Description"
<< std::setw(width2) << std::left << " Pattern"
<< " Replacement" << black << "\n";
for (auto [desc, pattern, replace] : katom_rewrite_rules) {
desc[0] = toupper(desc[0]);
std::cout << " "
<< std::left
<< std::setw(width1) << desc
<< std::setw(width2) << pattern.m_pattern
<< replace
<< "\n";
}
if (verbose_level > 0) {
std::string notes_text = "doc/katom_patterns.notes";
std::cout << "\n\n"
<< justify(string_from_file(klammertext_filename(notes_text)))
<< "\n\n";
}
}
std::string type_to_name(katom_t type)
{
auto var = std::find_if(katom_types.begin(), katom_types.end(),
[&] (Ktype t) { return t.m_type == type; });
return var->m_name;
}

305
mac/ktype.h Normal file
View File

@@ -0,0 +1,305 @@
#pragma once
#include <vector>
#include <map>
#include <string>
#include <regex>
#include <tuple>
#include <set>
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_.]*)";
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_begin,
ignore_end,
ignore_rest,
ignore_line,
ws_remove,
ws_space,
ws_newline,
special,
nonascii,
literal_begin,
literal_end,
ws_added,
literal,
eval_result,
replaced,
ignored,
};
inline const
std::vector<katom_t> katom_type_display_order {
katom_t::space,
katom_t::word,
katom_t::newline,
katom_t::text,
katom_t::apply_begin,
katom_t::apply_end,
katom_t::bar,
katom_t::double_bar,
katom_t::option_name,
katom_t::read_begin,
katom_t::eval_begin,
katom_t::cond_begin,
katom_t::define_begin,
katom_t::define_end,
katom_t::klammer_definition,
katom_t::klammer_instance,
katom_t::klammer_override,
katom_t::klammer_default,
katom_t::karg,
katom_t::machine_begin,
katom_t::machine_end,
katom_t::ignore_begin,
katom_t::ignore_end,
katom_t::ignore_rest,
katom_t::ignore_line,
katom_t::ws_remove,
katom_t::ws_space,
katom_t::ws_newline,
katom_t::special,
katom_t::nonascii,
katom_t::literal_begin,
katom_t::literal_end,
katom_t::ws_added,
katom_t::literal,
katom_t::eval_result,
katom_t::replaced,
katom_t::ignored
};
class Ktype;
inline std::map<katom_t, std::string> katom_type_names {};
inline std::map<katom_t, std::string> katom_type_descs {};
inline std::vector<katom_t> katom_type_list {};
class Ktype {
public:
// Ktype() = default;
Ktype(katom_t type, std::string name, std::string pattern, std::string description, bool use_equal = false)
: m_type(type),
m_name(name),
m_pattern(pattern),
m_description(description),
m_use_equal(use_equal),
m_rgx(std::regex(pattern)) {
katom_type_list.push_back(type);
katom_type_names.insert({m_type, m_name});
katom_type_descs.insert({m_type, m_description});
}
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;
std::string m_description;
bool m_use_equal;
std::regex m_rgx;
};
inline const
std::vector<Ktype> katom_types {
Ktype(katom_t::space, "space", " ", "One space character", false),
Ktype(katom_t::word, "word", "[a-zA-Z]+", "Text only containing letters", false),
Ktype(katom_t::newline, "newline", "\n", "One newline character", false),
Ktype(katom_t::bar, "bar", "\\|", "Bar character used as positional argument separator", false),
Ktype(katom_t::double_bar, "double-bar", "\\|\\|", "Separator for compound positional arguments", false),
Ktype(katom_t::read_begin, "read-begin", at_s+read_s, "Beginning of the file input klammer", false),
Ktype(katom_t::eval_begin, "eval-begin", at_s+eval_s, "Beginning of the evaluation klammer", false),
Ktype(katom_t::cond_begin, "cond-begin", at_s+cond_s, "Beginning of the conditional (if/then/else) klammer", false),
Ktype(katom_t::apply_begin, "apply-begin", at_s + definition_name, "Beginning of a klammer call"),
Ktype(katom_t::apply_end, "apply-end", "(" + definition_name + ")?" + at_s, "End of a klammer call"),
Ktype(katom_t::option_name, "option-name", ":" + definition_name, "Optional argument name"),
Ktype(katom_t::define_begin, "define-begin", at2_s + definition_name, "Beginning of a klammer definition"),
Ktype(katom_t::define_end, "define-end", "(" + definition_name + ")?" + at2_s, "End of a klammer definition"),
Ktype(katom_t::klammer_default, "klammer-default", "::::", "Define klammer default value for possible override"),
Ktype(katom_t::klammer_override, "klammer-override", ":::", "Override existing klammer definition"),
Ktype(katom_t::klammer_definition, "klammer-definition", ":", "Klammer definition, including parameters"),
Ktype(katom_t::klammer_instance, "klammer-instance", "::", "Klammer definition using previously defined parameters"),
Ktype(katom_t::karg, "klammer-arg", "\\*" + definition_name +"\\*", "Klammer argument in klammer body definition"),
Ktype(katom_t::machine_begin, "machine-begin", at3_s + definition_name, "Beginning of a processor modification definition"),
Ktype(katom_t::machine_end, "machine-end", "(" + definition_name + ")?" + at3_s, "End of a processor modification definition"),
Ktype(katom_t::ignore_begin, "ignore-begin", ignore_begin_s, "Beginning of text to remove"),
Ktype(katom_t::ignore_end, "ignore-end", ignore_end_s, "End of text to remove"),
Ktype(katom_t::ignore_rest, "ignore-rest", ignore_rest_s, "Remove all text to the end of file or string"),
Ktype(katom_t::ignore_line, "ignore-line", ignore_line_s, "Remove all text to the first newline, inclusive"),
Ktype(katom_t::ws_remove, "ws-remove", ws_remove_s, "Remove all whitespace at this point"),
Ktype(katom_t::ws_space, "ws-space", ws_space_s, "Remove all whitespace, leaving <number> spaces (default: 1)"),
Ktype(katom_t::ws_newline, "ws-newline", ws_newline_s, "Remove all whitespace, leaving <number> newlines (default: 1)"),
Ktype(katom_t::special, "special-char", R"(\^[@|#^:*])", "Klammertext special character treated as regular text"),
//Ktype(katom_t::nonascii, "non-ascii-char", R"(\^\w(?:.|\^))", "Non-ASCII character"),
Ktype(katom_t::nonascii, "non-ascii-char", R"((\^(\w(?:.|\^)))|(\^[A-Fa-f0-9]{1,5}\^))", "Non-ASCII character"),
// 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"(\^')", "Begin unprocessed text"),
Ktype(katom_t::literal_end, "literal-end", R"('\^)", "End unprocessed text"),
Ktype(katom_t::text, "text", R"([^^@#|]+)", "No special characters, whitespace, or \":\" at the beginning"), // spaces, @, #, |, or ^,
Ktype(katom_t::ws_added, "ws-added", "<space> or \\n", "Added whitespace characters from #+ and #/"),
Ktype(katom_t::literal, "literal", "", "Literal katom (changed from its original type by ^'...'^)"),
Ktype(katom_t::eval_result, "eval-result", "", "Katom produced by @eval klammer"),
Ktype(katom_t::replaced, "replaced", "", "A katom replaced by definitions, applications, or file input"),
Ktype(katom_t::ignored, "ignored", "", "A katom ignored by the action of a \"#\" katom"),
// Ktype(katom_t::undefined, "undefined", "", "Undefined pattern")
};
class Rgx {
public:
explicit Rgx(std::string pattern)
: m_pattern(pattern)
, m_regex(std::regex(pattern)) {}
std::string m_pattern;
std::regex m_regex;
};
inline const
std::vector<std::tuple<std::string, Rgx, std::string>> katom_rewrite_rules {
{ "bar precedence", Rgx(R"((.*?)(\^\|)(.*))"), "$1 $2 $3" },
{ "literal without space", Rgx(R"((\^')(.*?)('\^))"), "$1 $2 $3" },
{ "literal start to the left", Rgx(R"((.+)(\^'))"), "$1 $2" },
{ "literal start to the right", Rgx(R"((\^')(.+))"), "$1 $2" },
{ "literal end to the left", Rgx(R"(('\^)(.+))"), "$1 $2" },
{ "literal end to the right", Rgx(R"((.+)('\^))"), "$1 $2" },
//{ "successive non-ascii characters", Rgx(R"((.?)(\^\w[-\"^`'~hcbrdwa])(\^\w)(.?))"), "$1 $2 $3 $4" },
// { "special character", Rgx(R"((.*?)(\^[@\|\^#\*])(.*))"), "$1 $2 $3" },
{ "special character", Rgx(R"((.*?)(\^[@|^#*:])(.*))"), "$1 $2 $3" },
{ "non-diacritic non-ascii", Rgx(R"((.*?)(\^[a-zA-Z0-9]{5}\^)(.*?))"), "$1 $2 $3" },
{ "non-diacritic non-ascii 2", Rgx(R"((.*?)(\^[a-zA-Z0-9]{1,4}\^)(.*?))"), "$1 $2 $3" },
{ "diacritic non-ascii", Rgx(R"((.*?)(\^[a-zA-z][^^])(.*?))"), "$1 $2 $3" },
{ "double bar separator", Rgx(R"(([^\s\^]+?)(\|\|)(.*))"), "$1 $2 $3" },
{ "bar separator", Rgx(R"(([^\s\^]+?)(\|)(.*))"), "$1 $2 $3" },
{ "embedded ignore begin", Rgx(R"((.*?)#\[(.*))"), "$1 #[ $2" },
{ "embedded ignore end", Rgx(R"((.*?)\]#(.*))"), "$1 ]# $2" },
{ "embedded remove whitespace", Rgx(R"((.*?)#-(.*))"), "$1 #- $2" },
{ "single argument for special character", Rgx(R"((@\w+)-(\^[@\|:\*\^])@?)"), "$1 $2 @" },
// { "special character", Rgx(R"((.*?)(\^[@\|:\*\^])(.*))"), "$1 $2 $3" },
{ "klammer body arguments", Rgx(R"((.*?)(\*\w+\*)(.*))"), "$1 $2 $3" },
{ "trailing punctuation", Rgx(R"((\w?)@([().,:;?'!]))"), "$1@ $2" },
{ "trailing punctuation", Rgx(R"(@([().,:;?'!].*))"), "@ $1" },
{ "trailing punctuation, shortcut", Rgx(R"((@\w+)-(\w+)([().,:;?'!][^\s]*))"), "$1 $2 @ $3" },
// { "single argument shortcut", Rgx(R"((@\w+)-([^\s@]+)@?)"), "$1 $2 @" },
{ "no argument klammer", Rgx(R"((@\w+)@)"), "$1 @" },
{ "left parenthesis", Rgx(R"(([(])@(\w+))"), "$1 @$2" },
{ "embedded klammer", Rgx(R"(([^@]+)(@[^@]+@)([^@]+))"), "$1 $2 $3" },
{ "embedded klammer to the right", Rgx(R"(([^@]+)(@[^@]+@))"), "$1 $2" },
{ "embedded klammer to the left", Rgx(R"((@[^@]+@)([^@]+))"), "$1 $2" },
{ "embedded add whitespace", Rgx(R"(([^\s])(#\+\d*)([^\s]))"), "$1 $2 $3" },
{ "embedded add whitespace to the left", Rgx(R"(([^\s])(#\+\d*))"), "$1 $2" },
{ "embedded add whitespace to the right", Rgx(R"((#\+\d*)([^\s]))"), "$1 $2" },
{ "embedded ignore on the left", Rgx(R"(#([^-+/\s]))"), "# $1" },
{ "embedded ignore on the right", Rgx(R"(([^\s])#)"), "$1 #" },
};
inline const
std::set<katom_t> printable_katom_types {
katom_t::word,
katom_t::space,
katom_t::newline,
katom_t::literal,
katom_t::ws_added
};
inline const
std::set<katom_t> 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<katom_t> 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_t, katom_t> 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);
void describe_katoms(bool show_regex = false);
void describe_rewrite_patterns();
bool is_deftype(katom_t type);
std::string type_to_name(katom_t type);

137
mac/locator.cpp Normal file
View File

@@ -0,0 +1,137 @@
#include <iostream>
#include "character.h"
#include "locator.h"
#include "show.h"
#include "file.h"
#include "util.h"
std::string abbreviate_location(
const std::string& location, bool make_map,
std::map<std::string, std::string>& relpath)
{
std::string result = location;
fs::path basename = fs::path(location).filename();
if (sks_commands.contains(basename)) {
result = basename;
} else {
if (make_map) {
if (relpath.count(location)) {
result = relpath[location];
} else {
result = fs::relative(location, fs::current_path());
relpath[location] = result;
}
}
}
return result;
}
Locator::Locator(fs::path filename, int line, int chr)
: m_filename(filename)
, m_line(line)
, m_chr(chr)
{
string_map relpath {};
//m_filename = abbreviate_location(m_filename, false, relpath);
}
std::ostream &nformat(std::ostream &os)
{
os << std::setfill('0') << std::setw(3);
return os;
}
std::string Locator::str(bool relative) const
{
// relative_pathname(m_filename, fs::current_path().string()) << ":"
std::string filename = m_filename;
if (relative) {
filename = relative_to_cwd(m_filename);
}
std::stringstream ss {};
ss << "[" << filename << ":"
<< nformat << m_line << "."
<< nformat << m_chr+1 << "]";
return ss.str();
}
std::string Locator::desc(bool relative) const
{
// relative_pathname(m_filename, fs::current_path().string()) << ":"
std::string filename = m_filename;
if (relative) {
filename = relative_to_cwd(m_filename);
}
std::stringstream ss {};
ss << filename << ", line " << m_line << ", character " << m_chr + 1;
return ss.str();
}
std::string Locator::abbrev(bool include_chr) const
{
fs::path fname(m_filename);
std::stringstream ss {};
ss << "[" << fname.filename().string() << ":"
<< nformat << m_line;
if (include_chr) {
ss << "." << nformat << m_chr+1;
}
ss << "]";
return ss.str();
}
std::string locator_range(const Locator& start_loc, const Locator& end_loc, string_map& relpath)
{
std::string start_filename = abbreviate_location(start_loc.m_filename, true, relpath);
std::string end_filename = abbreviate_location(end_loc.m_filename, true, relpath);
std::stringstream ss {};
std::cout << std::setfill('0') << std::setw(3);
if (start_filename != end_filename) {
ss << start_loc << right_arrow << end_loc;
} else {
ss << "[" << start_filename << ":";
if (start_loc.m_line == end_loc.m_line) {
ss << nformat << start_loc.m_line << "."
<< nformat << start_loc.m_chr+1 << right_arrow
<< nformat << end_loc.m_chr+1;
} else {
ss << nformat << start_loc.m_line << "."
<< nformat << start_loc.m_chr+1
<< right_arrow
<< nformat << end_loc.m_line << "."
<< nformat << end_loc.m_chr+1;
}
ss << "]";
}
std::cout << std::setfill(' ');
return ss.str();
}
Locator current_locator(const std::source_location location)
{
// file_name() may be null (Homebrew GCC on macOS) -> avoid path(nullptr).
return Locator(location.file_name() ? location.file_name() : "",
location.line(), location.column());
}
std::string locator_summary(std::vector<Locator> locators)
{
std::map<std::string, std::vector<int>> file_locs {};
for (auto loc : locators) {
if (!file_locs.contains(loc.m_filename)) {
file_locs[loc.m_filename] = {};
}
file_locs[loc.m_filename].push_back(loc.m_line);
}
for (auto [filename, lines] : file_locs) {
std::cout << " " << filename << ": " << lines << "\n";
}
return "";
}

52
mac/locator.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <iostream>
#include <string>
// #include <filesystem>
#include <source_location>
#include <map>
#include "file.h"
inline std::string klammertext_home_var = "KLAMMERTEXT_HOME";
class Locator
{
public:
explicit Locator(const std::source_location location =
std::source_location::current())
// std::source_location::file_name() can return nullptr on some
// toolchains (e.g. Homebrew GCC on macOS); guard against
// fs::path(nullptr) -> strlen(NULL).
: m_filename(location.file_name()
? fs::absolute(location.file_name()) : fs::path{})
, m_line(int(location.line()))
, m_chr(int(location.column()))
{};
Locator(fs::path filename, int line, int chr);
std::string str(bool relative = false) const;
std::string desc(bool relative = false) const;
std::string abbrev(bool include_chr=true) const;
std::string m_filename {};
int m_line;
int m_chr;
//std::string m_desc {};
};
std::ostream &nformat(std::ostream &os);
std::string locator_range(
const Locator& start_loc, const Locator& end_loc,
std::map<std::string, std::string>& relpath);
Locator current_locator(
const std::source_location location = std::source_location::current());
std::string locator_summary(std::vector<Locator> locators);
inline
std::string showloc(Locator loc=Locator()) {
return loc.abbrev(false) + " ";
}

117
mac/log.cpp Normal file
View File

@@ -0,0 +1,117 @@
#include <source_location>
#include "log.h"
#include "show.h"
#include "util.h"
int verbose_level = 0;
using log_arg = std::variant<bool,int,float,std::string,const char*,Locator>;
std::ostream& operator<<(std::ostream& os, const log_arg& arg)
{
bool quoted_string = verbose_level > 2;
if (std::holds_alternative<bool>(arg)) {
os << (std::get<bool>(arg) ? "true" : "false");
} else if (std::holds_alternative<int>(arg)) {
os << std::get<int>(arg);
} else if (std::holds_alternative<float>(arg)) {
os << std::get<float>(arg);
}
else if (std::holds_alternative<std::string>(arg)) {
std::string s = std::get<std::string>(arg);
if (quoted_string)
os << "\"";
if (!quoted_string && s.empty()) {
os << "<none>";
} else {
os << s;
}
if (quoted_string)
os << "\"";
} else if (std::holds_alternative<const char*>(arg)) {
os << std::get<const char*>(arg);
// } else if (std::holds_alternative<strings_t>(arg)) {
// os << std::get<strings_t>(arg);
} else if (std::holds_alternative<Locator>(arg)) {
os << std::get<Locator>(arg);
// } else if (std::holds_alternative<Source>(arg)) {
// os << std::get<Source>(arg);
// } else if (std::holds_alternative<katom_t>(arg)) {
// os << std::get<katom_t>(arg);
// } else if (std::holds_alternative<Katom>(arg)) {
// os << std::get<Katom>(arg);
}
/*
} else if (std::holds_alternative<katom_list>(arg)) {
os << std::get<katom_list>(arg);
} else if (std::holds_alternative<katom_iter>(arg)) {
os << std::get<katom_iter>(arg);
}
*/
return os;
}
void log_indent(const std::string& filename, const std::string& color)
{
std::cout
<< color
<< std::setfill(' ')
<< std::setw(22-static_cast<int>(std::size(filename)))
<< std::right;
}
void log_filepos(const std::string& filename, int line, const std::string& color)
{
// if (show_verbose_location) {
log_indent(filename, color);
std::cout
<< "[" << filename << ":"
<< std::setw(3) << std::setfill('0')
<< line << "] " << reset;
// }
}
std::string prettify_name(const std::string& s)
{
std::string result = s;
result = string_replace(result, "std::__cxx11::basic_string<char>", "std::string");
result = string_replace(result, " >", ">");
std::smatch match;
std::regex re(R"(.*?(\w+)\(.*)");
if (verbose_level < 4 && std::regex_search(result, match, re)) {
std::stringstream ss;
ss << match[1] << "()";
result = ss.str();
}
return result;
}
void display_location(int log_level, std::source_location location)
{
if (verbose_level >= log_level) {
std::string color = blue; // cyan;
if (log_level == 1) {
color = magenta;
}
if (verbose_level > 1) {
log_filepos(location.file_name() ? location.file_name() : "",
location.line(), color);
}
if (verbose_level > 3) {
std::cout << location.function_name();
} else if (verbose_level > 1) {
std::cout << prettify_name(location.function_name());
}
}
}
void warning(const std::string& message, const Locator& loc)
{
std::cerr << " " << red << command_name << " (warning): " << message << "\n";
if (loc.m_filename != "") {
std::cerr << " " << loc.desc();
}
std::cerr << "\n" << reset;
}

63
mac/log.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
#include <iostream>
#include <source_location>
#include <sstream>
#include <string>
#include <variant>
#include <vector>
#include "error.h"
#include "locator.h"
// #include "source.h"
#include "katom_list.h"
extern int verbose_level;
extern bool show_verbose_location;
std::ostream& operator<<(std::ostream& os, const std::variant<bool,int,float,std::string,const char*,Locator>& arg);
void display_location(int log_level, std::source_location location);
namespace K {
template <typename... Ts>
struct log
{
log(int log_level, Ts&&... ts, const std::source_location& location = std::source_location::current()) {
if (verbose_level >= log_level) {
display_location(log_level, location);
if (verbose_level > 1 && sizeof...(ts) > 0) {
std::cout << ": ";
} else if (verbose_level == 1) {
std::cout << command_name << ": ";
}
if (log_level > 0) {
((std::cout << std::forward<Ts>(ts) << " "), ...);
std::cout << '\n';
}
}
}
};
template <typename... Ts>
log(int log_level, Ts&&...) -> log<Ts...>;
}
/*
void log(int level = 3, const std::source_location location = std::source_location::current());
template<typename T, typename... Args>
void log(Args... args, int level = 3, const std::source_location location = std::source_location::current());
*/
/*
void log(std::vector<std::variant<bool,int,float,std::string,const char*,Locator,Source>> args={}, int level=3,
const std::source_location location
= std::source_location::current());
void xlog(std::vector<std::variant<bool,int,float,std::string,const char*,Locator,Source>> args={}, int verbose_override=1,
const std::source_location location
= std::source_location::current());
*/
void warning(const std::string& message, const Locator& loc);

528
mac/machine.cpp Normal file
View File

@@ -0,0 +1,528 @@
#include <set>
#include <utility>
#include "machine.h"
#include "error.h"
#include "show.h"
#include "util.h"
#include "file.h"
#include "log.h"
#include "eval.h"
Machine::Machine()
: m_argtypes(Argtype_set())
, m_state(State())
, m_targets(Target_set())
, m_klammers(Klammer_set())
{
(void)K::log(3);
m_state.add_environment_frame();
/*
if (sks) {
fs::path sks_filename(klammertext_filename("sks/sks.k"));
// msg() << "SKS filename: " << sks_filename << "\n";
read(sks_filename);
}
*/
}
void Machine::process_eval_katoms(katom_list& katoms)
{
(void)K::log(3);
if (std::find_if(katoms.begin(), katoms.end(), begin_eval) != katoms.end()) {
for (const auto& [op, cl] : find_spans(katoms, level_increase, level_decrease, true, "eval")) {
auto [begin, end] = find_span_katoms(katoms, op, cl);
if (begin_eval(*begin)) {
Eval E(*this, begin->m_loc);
katom_list eval_katoms = E.eval(begin, end);
std::for_each(begin, end, mark_as_replaced);
katoms.insert(end, eval_katoms.begin(), eval_katoms.end());
}
}
}
}
// Collect the bars that are direct argument separators of a @cond span:
// the bar katoms at nesting depth 0 within the span. A bar that lies
// inside a nested span — for example the "|" in an inner @frac a | b @, or
// in a nested @eval/@read/@cond — has positive depth and is excluded.
//
// This makes @cond's argument delimitation a property of the span tree
// (the operad's arity: each operator owns the bars at its own level) rather
// than of the flat katom range. Counting every bar in the range, as the
// original check did, conflated the arities of nested operators and rejected
// well-formed input such as
// @cond *bool* | @frac 1 | 2 @ | @frac 2 | 1 @ @
// because the inner @frac bars were miscounted as @cond separators.
//
// begin is the cond_begin katom; end is one past the closing apply_end, so
// *(end - 1) is the apply_end. Bars are returned in source order.
std::vector<katom_iter> cond_separator_bars(katom_iter begin, katom_iter end)
{
std::vector<katom_iter> bars {};
int depth = 0;
for (auto it = begin + 1; it != end - 1; ++it) {
if (is_bar(*it) && depth == 0) {
bars.push_back(it);
} else if (level_increase(*it)) {
++depth;
} else if (level_decrease(*it)) {
--depth;
}
}
return bars;
}
void check_bar_count(katom_iter begin, std::size_t count)
{
if (count != 1 && count != 2) {
std::stringstream ss {};
ss << "Incorrectly formatted @cond klammer. There should only be one or two bar characters:\n"
<< " @cond <predicate> | <result-if-true @\nor:\n"
<< " @cond <predicate> | <result-if-true> | <result-if-false> @";
throw Argument_error(ss.str(), begin->m_loc, false);
}
}
bool is_true(const std::string& s)
{
return s == "True" || s == "true" || s == "1";
}
void Machine::process_cond_katoms(katom_list& katoms)
{
if (std::find_if(katoms.begin(), katoms.end(), begin_cond) != katoms.end()) {
(void)K::log(3);
//for (auto [op, cl] : find_spans(katoms, begin_cond, end_apply, true, "cond")) {
for (const auto& [op, cl] : find_spans(katoms, level_increase, level_decrease, true, "cond")) {
auto [begin, end] = find_span_katoms(katoms, op, cl);
// msg() << "find_spans: " << std::pair(begin, end) << "\n";
if (begin_cond(*begin)) {
// Delimit @cond's arguments by the bars at depth 0 within the
// span, so that bars belonging to nested klammers are not
// mistaken for @cond's own separators (see cond_separator_bars).
std::vector<katom_iter> bars = cond_separator_bars(begin, end);
check_bar_count(begin, bars.size());
auto bar_1 = bars[0];
std::string predicate = to_string(begin + 1, bar_1, true);
katom_list true_clause {};
katom_list false_clause {};
if (bars.size() == 2) {
auto bar_2 = bars[1];
true_clause = katom_list(bar_1 + 1, bar_2);
false_clause = katom_list(bar_2 + 1, end - 1);
} else {
true_clause = katom_list(bar_1 + 1, end - 1);
}
// Splice only the selected branch. Its nested klammers remain
// unreduced here and are reduced by the outer fixed-point apply
// loop; the unselected branch is discarded without evaluation
// (@cond is a non-strict special form).
katom_list result = is_true(predicate)
? trim_whitespace(true_clause) : trim_whitespace(false_clause);
std::for_each(begin, end, mark_as_replaced);
katoms.insert(end, result.begin(), result.end());
}
}
}
}
void Machine::expand_constant_klammers(katom_list& katoms, const Katom& op, const Katom& cl)
{
auto [begin, end] = find_span_katoms(katoms, op, cl);
restore_initial_type(begin + 1, end - 1);
if (std::find_if(begin + 1, end - 1, begin_klammer_apply) == end - 1) return;
for (const auto& [app_op, app_cl] : find_spans(begin + 1, end - 1, begin_apply, end_apply, false, "def-time")) {
auto [app_begin, app_end] = find_span_katoms(katoms, app_op, app_cl);
if (app_begin->m_type == katom_t::apply_begin) {
std::string name = trim_char(app_begin->m_text, '@');
const auto* body = m_klammers.constant_body(name);
if (body) {
// Set both m_type and m_initial_type so that
// restore_initial_type() in add() won't resurrect them
for (auto it = app_begin; it != app_end; ++it) {
it->m_type = katom_t::replaced;
it->m_initial_type = katom_t::replaced;
}
katoms.insert(app_end, body->begin(), body->end());
}
}
}
}
//katom_list
void Machine::mark_literal_klammer_content(katom_list& katoms)
{
(void)K::log(4);
// Collect names of klammers that have a literal parameter
std::set<std::string> literal_names {};
for (const auto& [name, klammer] : m_klammers.m_klammers) {
if (klammer.has_literal_param())
literal_names.insert(name);
}
if (literal_names.empty()) return;
// Scan for matching @name ... name@ spans.
// Stop at ## (ignore-rest) since everything after it will be removed.
for (auto k = katoms.begin(); k != katoms.end(); ++k) {
if (k->m_type == katom_t::ignore_rest) break;
if (k->m_type != katom_t::apply_begin) continue;
std::string name = trim_char(k->m_text, '@');
if (literal_names.count(name) == 0) continue;
(void)K::log(2, "Literal klammer: " + name);
// Find the matching named closing delimiter
std::string close_text = name + "@";
auto close = k + 1;
int depth = 1;
while (close != katoms.end()) {
if (close->m_type == katom_t::apply_begin &&
trim_char(close->m_text, '@') == name)
depth++;
else if (close->m_type == katom_t::apply_end &&
trim_char(close->m_text, '@') == name)
depth--;
if (depth == 0) break;
++close;
}
if (close == katoms.end()) {
throw Parsing_error(
"Klammer " + q_(name) + " has a literal parameter and must be closed with "
+ q_(close_text),
k->m_loc);
}
// Count positional parameters before the literal one.
// The literal parameter is always last. Bars separate
// the preceding positional arguments and the literal content.
const auto& klammer = m_klammers.m_klammers[name];
int bars_before_literal = 0;
for (const auto& p : klammer.m_parameters.m_positional) {
if (p.m_argtype.m_name == "literal") break;
bars_before_literal++;
}
// Find where literal content starts.
// Skip bars_before_literal bars (separating preceding positional args).
// If options are present, skip past the bar after them.
// Options are identified by :name katoms before any bar.
auto literal_start = k + 1;
bool has_options = false;
for (auto j = k + 1; j < close; ++j) {
if (j->m_type == katom_t::option_name) {
has_options = true;
}
if (j->m_type == katom_t::bar) {
if (bars_before_literal > 0) {
bars_before_literal--;
literal_start = j + 1;
} else if (has_options) {
// This bar separates options from literal content
literal_start = j + 1;
break;
} else {
// No preceding args, no options: bar is part of literal
break;
}
}
}
std::for_each(literal_start, close, mark_as_literal);
k = close; // Skip past this span
}
}
void Machine::process_katoms(
katom_list& katoms, const std::string& source,
bool nonascii, bool literal, bool ignore, bool whitespace, bool klammers,
bool eval, bool cond, bool read)
{
mark_literal_klammer_content(katoms);
if (literal) mark_literal_katoms(katoms);
if (nonascii) encode_nonascii_characters(katoms);
if (ignore) mark_ignored_katoms(katoms);
if (whitespace) process_whitespace_modifiers(katoms);
if (klammers) process_klammer_katoms(katoms);
if (eval) process_eval_katoms(katoms);
if (cond) process_cond_katoms(katoms);
if (read) expand_read_katoms(
katoms, source,
nonascii, literal, ignore, whitespace, klammers, eval, cond, read);
// return katoms;
}
katom_list Machine::process(
std::string text, const std::string& source,
bool nonascii, bool literal, bool ignore, bool whitespace, bool klammers,
bool eval, bool cond, bool read)
{
katom_list katoms = katomize(line_split(text), source);
//katoms =
process_katoms(
katoms, source,
nonascii, literal, ignore, whitespace, klammers, eval, cond, read);
return katoms;
}
void Machine::read(const fs::path& pathname)
{
(void)K::log(3, pathname.string());
m_state.open_frame("Machine state: " + pathname.string());
std::string text = m_state.subst(trim_right(string_from_file(pathname)));
katom_list katoms = process(text, pathname);
m_katoms.insert(m_katoms.end(), katoms.begin(), katoms.end());
extract_machine_definitions();
extract_klammer_definitions();
m_sources.push_back(pathname);
}
void Machine::read(const std::string& s)
{
(void)K::log(3, s);
m_state.open_frame("Machine state: " + s);
std::string text = m_state.subst(trim_right(s));
katom_list katoms = process(text, command_pathname);
m_katoms.insert(m_katoms.end(), katoms.begin(), katoms.end());
extract_machine_definitions();
extract_klammer_definitions();
m_sources.push_back(s);
}
// Read
void Machine::expand_read_katoms(
katom_list& katoms, std::string current_filename,
bool nonascii, bool literal, bool ignore, bool whitespace,
bool klammers, bool eval, bool cond, bool read)
{
(void)K::log(3);
current_filename = resolve_relative_to(current_filename);
// msg() << "current_filename: " << current_filename << "\n";
if (std::find_if(katoms.begin(), katoms.end(), begin_read) != katoms.end()) {
(void)K::log(3);
for (const auto& [op, cl] : find_spans(katoms, begin_apply, end_apply, true, "read")) {
auto [begin, end] = find_span_katoms(katoms, op, cl);
if (begin_read(*begin)) {
std::string read_filename = to_string(begin + 1, end - 1, true);
// msg() << "read: " << resolve_relative_to(read_filename, current_filename) << "\n";
/*
std::string current_directory =
fs::path(current_filename).parent_path().string();
fs::path input_filename =
fs::path(current_directory + "/" + read_filename);
*/
fs::path input_filename = resolve_relative_to(read_filename, current_filename);
// msg() << "read: " << input_filename << "\n";
(void)K::log(2, input_filename.string());
if (!fs::exists(input_filename)) {
std::stringstream ss{};
ss <<"File " << input_filename << " does not exist";
throw File_error(ss.str(), begin->m_loc);
}
std::for_each(begin, end, mark_as_replaced);
input_filename = fs::canonical(input_filename);
std::string text = trim_right(string_from_file(input_filename.string()));
katom_list ks = katomize(line_split(text), input_filename);
// ks =
process_katoms(
// ks, command_pathname,
ks, input_filename,
nonascii, literal, ignore, whitespace, klammers, eval, cond, read);
katoms.insert(end, ks.begin(), ks.end());
}
}
}
}
void Machine::extract_machine_definitions()
{
(void)K::log(3);
if (m_katoms.empty()) {
return;
}
for (const auto& [op, cl] : find_spans(m_katoms, begin_machine_def, end_machine_def, true, command_name)) {
auto [begin, end] = find_span_katoms(m_katoms, op, cl);
//std::string name = trim_char(begin->m_text, '@');
std::string name = begin->m_text;
if (name == "@@@target") {
m_targets.add(begin, end, m_katoms);
} else if (name == "@@@argtype") {
m_argtypes.add(begin, end, m_katoms);
} else if (name == "@@@state") {
m_state.parse_state_katoms(begin, end, m_katoms);
}
}
}
void Machine::extract_klammer_definitions(katom_list katoms)
{
fmsg() << katoms << "\n";
(void)K::log(3);
for (const auto& [op, cl] : find_spans(katoms, begin_klammer_def, end_klammer_def, true, command_name)) {
expand_constant_klammers(katoms, op, cl);
auto [begin, end] = find_span_katoms(katoms, op, cl);
m_klammers.add(m_argtypes, m_targets, begin, end, katoms);
}
m_klammers.rationalize(m_targets);
}
void Machine::extract_klammer_definitions()
{
(void)K::log(3);
for (const auto& [op, cl] : find_spans(m_katoms, begin_klammer_def, end_klammer_def, true, command_name)) {
expand_constant_klammers(m_katoms, op, cl);
auto [begin, end] = find_span_katoms(m_katoms, op, cl);
m_klammers.add(m_argtypes, m_targets, begin, end, m_katoms);
}
m_klammers.rationalize(m_targets);
}
void Machine::update_state(const std::map<std::string, std::string>& arg_map)
{
for (const auto& [k, v] : arg_map) {
m_state.set(k, v);
}
}
katom_list Machine::apply_klammer(
Klammer& klammer, const std::string& target, katom_iter arguments_begin, katom_iter arguments_end)
{
(void)K::log(3, "argument substitution", *arguments_begin, *(arguments_end - 1));
m_state.replace("K_loc", arguments_begin->m_loc.str(), false);
auto [positional, optional, rest] =
argument_split(arguments_begin + 1, arguments_end - 1, klammer.m_parameters.m_positional.size());
auto values = klammer.m_parameters.value_map(positional, optional, rest, arguments_begin->m_loc);
// Resolve KTESC markers in argument values so that @eval code receives
// the original characters (e.g., filenames with underscores). The markers
// remain in the klammer body substitution for final target-specific output.
katom_list result(klammer.m_body[target].begin(), klammer.m_body[target].end());
auto varmap = klammer.m_varmap[target];
m_state.open_frame("Arguments for klammer " + q_(klammer.m_name));
m_state.set(values);
for (const auto& [name, indices] : varmap) {
std::regex arg("\\*" + name + "\\*");
for (auto i : indices) {
result[i].m_text = std::regex_replace(result[i].m_text, arg, m_state.value(name));
result[i].m_type = katom_t::text;
}
}
process_katoms(result, klammer.m_name);
apply(m_klammers, result, target);
m_state.close_frame();
// msg() << boldblack << "APPLY: " << std::pair(arguments_begin, arguments_end) << "\n"
// << boldblack << "RESULT: " << ktype << result << black << "\n";
modify_type(katom_t::replaced, arguments_begin, arguments_end);
return result;
}
void Machine::apply_klammer_set(
Klammer_set& klammer_set, katom_list& katoms, const std::string& target, katom_iter begin, katom_iter end)
{
(void)K::log(3, "Klammer");
std::string name = trim_char(begin->m_text, '@');
katom_list applied_katoms = apply_klammer(klammer_set.m_klammers[name], target, begin, end);
for (auto& k : applied_katoms) {
if (k.m_type == katom_t::bar || k.m_type == katom_t::double_bar || k.m_type == katom_t::option_name) {
k.m_type = katom_t::text;
}
}
katoms.insert(end, applied_katoms.begin(), applied_katoms.end());
}
void Machine::apply(
Klammer_set& klammer_set, katom_list& katoms, const std::string& target)
{
(void)K::log(3, "Klammer_set");
for (const auto& [op, cl] : find_spans(
katoms, begin_klammer_apply, end_klammer_apply, true, command_name)) {
auto [begin, end] = find_span_katoms(katoms, op, cl);
klammer_set.check_klammer(
klammer_name_from_katom(begin->m_text, begin->m_loc),
target, begin->m_loc);
apply_klammer_set(klammer_set, katoms, target, begin, end);
}
}
std::string Machine::run_phase_functions()
{
Target target = m_targets.get(m_state.value("K_target"), Locator());
if (!target.m_after_apply.empty()) {
(void)K::log(2, target);
Eval E(*this, Locator());
for (auto f : target.m_after_apply) {
f = "@eval " + f + " @";
auto katoms = katomize(line_split(f), "phase");
katom_list eval_katoms = E.eval(katoms.begin(), katoms.end() - 2);
// msg() << "eval_katoms: " << eval_katoms << "\n";
m_result = to_string(eval_katoms.begin(), eval_katoms.end());
}
}
return m_result;
}
void Machine::escape_target_characters(const Target& target, katom_list& katoms)
{
if (target.m_escapes.empty()) return;
for (auto& k : katoms) {
// Only escape writer content katoms — text, words, and newlines.
// Skip structural katoms (option names, bars, klammer delimiters)
// whose text is Klammertext syntax, not writer content.
if (k.m_type == katom_t::text ||
k.m_type == katom_t::word ||
k.m_type == katom_t::newline) {
k.m_text = target.escape_text(k.m_text);
}
}
}
std::string Machine::apply(const std::string& target_name, bool final_processing, bool escape_characters)
{
(void)K::log(3, "top level");
int recursive_limit = 5;
m_state.set("K_target", target_name);
m_state.subst(m_katoms.begin(), m_katoms.end());
// Escape target-specific characters in writer text before klammer application.
// Characters produced later by klammer bodies will not be escaped.
// Skipped for sub-Machine apply() calls (e.g., from @eval), where the
// text is already in target-specific form.
auto target = m_targets.get(target_name, Locator());
if (escape_characters)
escape_target_characters(target, m_katoms);
int apply_count = 0;
auto katom_size = m_katoms.size();
while (true) {
apply(m_klammers, m_katoms, target_name);
if (m_katoms.size() == katom_size) {
break;
}
if (++apply_count > recursive_limit) {
msg() << red << "Error: Recursive limit ("
<< recursive_limit << ") reached\n" << black;
break;
}
katom_size = m_katoms.size();
}
m_result = to_string(m_katoms.begin(), m_katoms.end());
if (final_processing) {
for (const auto& [old_str, new_str] : target.m_transforms) {
m_result = string_replace(m_result, old_str, new_str);
}
m_result = target.resolve_escapes(m_result);
m_result = run_phase_functions();
}
m_result = trim_char(m_result, '\n');
return m_result;
}

128
mac/machine.h Normal file
View File

@@ -0,0 +1,128 @@
#pragma once
#include <string>
// #include "source.h"
#include "katom_list.h"
#include "klammer_set.h"
#include "target_set.h"
#include "argtype_set.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_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_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 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_set(Klammer_set& klammer_set,
katom_list& katoms, const std::string& target, katom_iter begin, katom_iter end);
void apply(Klammer_set& klammer_set, 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_set m_argtypes {};
State m_state {};
Target_set m_targets {};
Klammer_set m_klammers {};
input_sources_t m_sources {};
std::string m_result {};
std::vector<Katom> m_katoms {};
};
/*
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;
}
};
*/

559
mac/show.cpp Normal file
View File

@@ -0,0 +1,559 @@
#include <algorithm>
#include <iterator>
#include "ktype.h"
#include "show.h"
#include "util.h"
#include "log.h"
#include "file.h"
std::ostream& msg(Locator loc)
{
std::cout << blue << loc.abbrev(false) << black << " ";
return std::cout;
}
std::ostream& fmsg(Locator loc)
{
std::cout << blue << loc.abbrev(false) << black
<< kall << ktype << kindex << kreplaced << " ";
return std::cout;
}
// std::vector<int>
std::ostream& operator<<(std::ostream& os, const std::vector<int>& ns)
{
if (!ns.empty()) {
auto rest = std::vector<int>(ns.begin() + 1, ns.end());
os << ns[0];
for (auto n : rest) {
os << ", " << n;
}
}
return os;
}
// strings_t
std::ostream& operator<<(std::ostream& os, const strings_t& ss)
{
if (!ss.empty()) {
for (const std::string& s : ss) {
os << "<" << s << ">";
}
}
return os;
}
// std::vector<fs::path>
std::ostream& operator<<(std::ostream& os, const std::vector<fs::path>& pp)
{
if (!pp.empty()) {
for (const fs::path& p : pp) {
os << "<" << p.string() << ">";
}
}
return os;
}
// std::map<std::string,std::string>
std::ostream& operator<<(std::ostream& os, const std::map<std::string,std::string>& sm)
{
size_t name_length = 0;
for (auto [k,v] : sm) {
name_length = std::max(k.size(), name_length);
}
for (auto [k,v] : sm) {
os << std::setfill(' ') << " " << std::setw(name_length)
<< k << sp_arrow << " " << display_string(v) << "\n";
}
return os;
}
// Katom iterator pair
std::ostream& operator<<(std::ostream& os, const std::pair<katom_iter,katom_iter>& iters)
{
for (auto e = iters.first; e < iters.second; e++) {
std::cout << e;
}
std::cout << "\n";
return os;
}
// Locator
std::ostream& operator<<(std::ostream& os, const Locator& loc)
{
os << loc.str();
return os;
}
// Katom
std::string subscript(int n)
{
strings_t chars {
"\u2080", "\u2081", "\u2082", "\u2083", "\u2084",
"\u2085", "\u2086", "\u2087", "\u2088", "\u2089" };
std::string prefix {};
if (n < 0)
prefix = "-";
std::string sn {std::to_string(std::abs(n))};
std::string result { prefix };
for (unsigned int i = 0; i < sn.size(); i++) {
std::string d {sn[i]};
result += chars[std::stoi(d)];
}
return result;
}
void os_char(std::ostream& os, katom_t type, unsigned char c)
{
if (type == katom_t::eval_result) {
os << c;
} else if ((Katom::show_all || Katom::show_whitespace) and c == ' ') {
if (is_active(type)) os << boldgreen;
const std::string space_s { "\u00B7" };
os << space_s;
if (is_active(type)) os << black;
} else if ((Katom::show_all || Katom::show_whitespace) and c == '\n') {
if (is_active(type)) os << boldgreen;
os << "/";
if (is_active(type)) os << black;
} else if (c != '\n')
os << c;
}
void os_body(std::ostream& os, const Katom& k)
{
bool bracketed = true;
if (k.is_whitespace() || k.is_word() || k.is_text() || k.is_literal() || k.is_nonascii()) {
bracketed = Katom::show_all;
}
if (bracketed)
os << left_bracket;
for (auto c : k.m_text) {
os_char(os, k.m_type, c);
}
if (bracketed) {
if (Katom::show_index) {
os << subscript(k.m_index);
}
if (Katom::show_type){
if (Katom::show_index) {
os << ".";
}
if (k.m_initial_type != k.m_type) {
os << subscript(static_cast<int>(k.m_initial_type)) << right_arrow;
}
os << subscript(static_cast<int>(k.m_type));
}
os << right_bracket;
}
}
std::ostream& operator<<(std::ostream& os, const Katom& k)
{
//const std::string space_s { "\u2423" };
/// const std::string space_s { "\u00B7" };
// const std::string tab { "\u2023" };
// const std::string left_index { "\u27EA" };
// const std::string right_index { "\u27EB" };
// const std::string diamond { "\u2B29" };
//bool in_red = mark_as_error(k.m_type);
//if (k.m_type == katom_t::ignored)
// show_type = false;
//if (in_red)
//if (is_error(k.m_type))
// os << red;
if (k.m_type == katom_t::replaced) {
os << blue; // cyan;
} else if (k.m_type == katom_t::ignored) {
os << yellow;
}
//else if (k.m_type == katom_t::literal)
// os << green;
else {
os << black;
}
if (is_active(k.m_type) or
(Katom::show_replaced and (k.m_type == katom_t::replaced)) or
(Katom::show_ignored and (k.m_type == katom_t::ignored))) {
os_body(os, k);
if (k.m_initial_type == katom_t::newline ||
(k.m_type == katom_t::ws_added && k.m_text == "\n")) {
os << "\n";
}
}
//os << black;
return os;
}
katom_list abbrev(const katom_list& ks, unsigned int max_length)
{
if (ks.size() > max_length) {
katom_list result(ks.begin(), (ks.begin())+(max_length));
Katom ellipsis = Katom("[...]", katom_t::word, Locator());
result.push_back(ellipsis);
result.push_back(ks.back());
std::for_each(result.begin(), result.end(), [](Katom& k) {
if (k.m_type == katom_t::newline) {
//std::cout << "NEWLINE\n";
k.m_type = katom_t::word;
k.m_text = "/";
k.m_src = "/";
k.m_initial_type = katom_t::word;
}
});
return result;
} else {
katom_list result(ks.begin(), ks.end());
std::for_each(result.begin(), result.end(), [](Katom& k) {
if (k.m_type == katom_t::newline) {
//std::cout << "NEWLINE\n";
k.m_type = katom_t::word;
k.m_text = "/";
k.m_initial_type = katom_t::word;
}
});
return result;
}
}
std::ostream& operator<<(std::ostream& os, const katom_lists& ks)
{
//os << double_bar;
//os << red < "|" << black;
for (const auto& k : ks) {
os << left_double_bracket << abbrev(k) << right_double_bracket << " ";
//os << k << red << "|" << black;
}
// os << "\n";
return os;
}
std::ostream& operator<<(std::ostream& os, const katom_ptr& k)
{
os << *k;
return os;
}
std::ostream& operator<<(std::ostream& os, const katom_iter& k)
{
os << *k;
return os;
}
std::ostream& operator<<(std::ostream& os, const std::vector<Katom>& ks)
{
for (const auto& k : ks) {
os << k;
}
return os;
}
std::ostream& operator<<(std::ostream& os, const std::vector<katom_iter>& ks)
{
for (const auto& k : ks) {
os << *k;
}
return os;
}
std::ostream &kindex(std::ostream &os)
{
Katom::show_index = true;
return os;
}
std::ostream &ktype(std::ostream &os)
{
Katom::show_type = true;
return os;
}
std::ostream &kws(std::ostream &os)
{
Katom::show_whitespace = true;
return os;
}
std::ostream &kall(std::ostream &os)
{
Katom::show_all = true;
return os;
}
std::ostream &kreplaced(std::ostream &os)
{
Katom::show_replaced = true;
return os;
}
std::ostream &kignored(std::ostream &os)
{
Katom::show_ignored = true;
return os;
}
std::ostream &kreset(std::ostream &os)
{
Katom::show_index = false;
Katom::show_type = false;
Katom::show_all = false;
Katom::show_replaced = false;
Katom::show_ignored = false;
Katom::show_whitespace = false;
os << black;
return os;
}
/*
std::ostream& operator<<(std::ostream& os, const std::vector<Katom> ks)
{
std::ranges::copy(ks, std::ostream_iterator<Katom>(os, ""));
return os;
}
*/
// Argtype
std::ostream& operator<<(std::ostream& os, const Argtype& a)
{
return os
<< left_bracket << "\U0001D504" << broken_bar
<< a.m_name << broken_bar
<< a.m_symbolic_pattern << broken_bar
<< a.m_count << broken_bar
<< a.m_mincount << broken_bar
<< a.m_maxcount << right_bracket;
}
// Parameter
std::ostream& operator<<(std::ostream& os, const Parameter& arg)
{
os << left_bracket << "\U0001D513" << broken_bar;
if (arg.m_optional)
os << ":";
os << arg.m_name << "." << arg.m_argtype.m_name;
//if (arg.m_default.size() != 0)
if (!arg.m_default.empty()) {
os << broken_bar << arg.m_default;
}
os << right_bracket;
return os;
}
// std::vector<Parameter>
std::ostream& operator<<(std::ostream& os, const std::vector<Parameter>& as)
{
for (const auto& a : as) {
os << a;
}
return os;
}
// Parameter_set
std::ostream& operator<<(std::ostream& os, const Parameter_set& as)
{
os << left_bracket << "\U0001D513\U0001D530" << broken_bar
<< as.m_positional.size() << broken_bar
<< as.m_optional.size() << broken_bar
// << (as.m_rest.undefined() ? "-" : "+")
<< (as.m_rest.empty() ? "-" : "+")
<< broken_bar << "[" << as.m_katoms.size() << "]"
<< right_bracket;
return os;
}
// Var
std::ostream& operator<<(std::ostream& os, const Var& v)
{
os << "<" << v.m_name << ">";
return os;
}
// Frame
std::ostream& operator<<(std::ostream& os, const Frame& f)
{
os << "{";
// for (auto [k,v] : f.m_vars) {
// os << v;
// }
os << join(f.names(), "|");
os << "}";
return os;
}
// State
std::ostream& operator<<(std::ostream& os, const State& s)
{
//for (int i = s.m_frames.size() - 1; i != 0; --i) {
for (auto f : s.m_frames) {
os << f << "\n";
}
return os;
}
// Klammer
std::ostream& operator<<(std::ostream& os, const Klammer& k)
{
std::string targets = join(k.get_target_names(), ","s);
os << left_bracket << "\U0001D50E" << broken_bar
<< k.m_name << broken_bar
<< "p" << k.m_parameters.m_positional.size() << broken_bar
<< "o" << k.m_parameters.m_optional.size() << broken_bar
<< (targets.empty() ? "?" : targets)
<< right_bracket;
return os;
}
// Klammer::variable_map_t
std::ostream& operator<<(std::ostream& os, const Klammer::variable_map_t& vm)
{
for (auto [k, v] : vm) {
os << " " << k << sp_arrow << v << "\n";
}
return os;
}
// Klammer::components
std::string katom_type_name(katom_t type)
{
// Could cache, but why bother - only for kdesc.
for (auto t : katom_types) {
if (t.m_type == type) {
return t.m_name;
}
}
throw Internal_error("Unknown katom_t: " + std::to_string((int)type));
}
std::ostream& operator<<(std::ostream& os, const Klammer::components& kc)
{
std::string del = " ";
auto [target, deftype, parameters, body, varmap, locator] = kc;
os << std::setw(4) << target << del
// << katom_types[(int)deftype].m_name << del
<< katom_type_name(deftype) << del
<< parameters << del << kall << ktype << red << body << del << locator;
return os;
}
// Klammer_set
std::ostream& operator<<(std::ostream& os, const Klammer_set& ks)
{
for (auto klam : ks.m_klammers) {
for (auto [k,v] : klam.second.m_defloc) {
os << " " << k << sp_arrow << v << "\n";
}
//klam.second.m_defloc.str() << "\n";
std::cout << " " << klam.first << sp_arrow << klam.second << " " << "\n";
}
return os;
}
// Target
void show_arrow_pair(std::ostream& os, std::pair<std::string,std::string> transform)
{
os << transform.first << sp_arrow << transform.second;
}
std::ostream& operator<<(std::ostream& os, const Target& t)
{
os << "<\U0001D517" << broken_bar << t.m_name << broken_bar << t.m_desc << broken_bar;
for (auto i : t.m_includes) {
os << i << right_arrow << t.m_name << broken_bar;
}
for (auto p : t.m_provides) {
os << t.m_name << right_arrow << p << broken_bar;
}
os << ">";
return os;
}
// Target_set
std::ostream& operator<<(std::ostream& os, const Target_set& ts)
{
size_t width = 0;
for (auto t : ts.m_targets) {
width = std::max(width, t.first.size());
}
for (auto [name, target] : ts.m_targets) {
os << std::setw(width) << name << sp_arrow << target << "\n";
}
return os;
}
// Machine
std::string describe_sources(Machine m)
{
std::stringstream ss {};
for (auto s : m.m_sources) {
if (std::holds_alternative<std::string>(s)) {
ss << " String: " << trim(std::get<std::string>(s)) << "\n";
} else {
ss << " Filename: " << std::get<fs::path>(s).string() << "\n";
}
}
return ss.str();
}
std::string label(std::string name, int count)
{
std::stringstream ss {};
ss << " " << name << " (" << count << "):\n";
return ss.str();
}
std::ostream& operator<<(std::ostream& os, const Machine& m)
{
std::string argtypes_desc = m.m_argtypes.describe(false, 8);
std::string state_desc = m.m_state.describe(false, 3);
std::string targets_desc = m.m_targets.describe(4);
std::string klammer_desc = m.m_klammers.describe(2);
std::string source_desc = describe_sources(m);
os << "\nMachine " << "\U000133DE \U00013000\n"
<< label("Sources", m.m_sources.size()) << source_desc << "\n"
<< label("Argtypes", m.m_argtypes.m_names.size()) << argtypes_desc << "\n"
<< label("Targets", m.m_targets.m_names.size()) << targets_desc << "\n"
<< label("Klammers", m.m_klammers.m_klammers.size()) << klammer_desc << "\n"
<< label("State", m.m_state.m_frames.size()) << state_desc;
return os;
}
void modify_stream(std::string name)
{
if (name == "all") std::cout << kall;
if (name == "type") std::cout << ktype;
if (name == "index") std::cout << kindex;
if (name == "ignored") std::cout << kignored;
if (name == "replaced") std::cout << kreplaced;
};

158
mac/show.h Normal file
View File

@@ -0,0 +1,158 @@
#pragma once
#include <iostream>
#include <vector>
#include "locator.h"
#include "katom.h"
#include "argtype.h"
#include "argument.h"
#include "klammer.h"
#include "klammer_set.h"
#include "state.h"
#include "target_set.h"
#include "machine.h"
#include "file.h"
using namespace std::string_literals;
const std::string middle_dot { "\uFF65" };
const std::string broken_bar { "\u00A6" };
const std::string right_arrow { "\uFFEB" };
const std::string sp_arrow { " \u2192 " };
const std::string right_bracket { "\u27E9" };
const std::string left_bracket { "\u27E8" };
const std::string bbar { "¦" };
const std::string left_double_bracket { "\u27E6" };
const std::string right_double_bracket { "\u27E7" };
const std::string left_square_bracket { "\u2045" };
const std::string right_square_bracket { "\u2046" };
const std::string check { "\u2713" };
const std::string black("\033[0;30m");
const std::string boldblack("\033[1;30m");
const std::string green("\033[0;32m");
const std::string boldgreen("\033[1;32m");
const std::string cyan("\033[0;36m");
const std::string blue("\033[0;34m");
const std::string boldblue("\033[1;34m");
const std::string magenta("\033[0;35m");
const std::string red("\033[31m");
const std::string yellow("\033[0;33m");
const std::string reset("\033[0m");
const auto seqout = [](auto x) { std::cout << "seq: " << x << "\n"; };
//const auto mapout = [](auto m) { auto const& key std::cout << m.first << sp_arrow << m.second << "\n"; };
const auto mapout = [](auto const& kv){
auto const& [k, v] = kv;
std::cout << k << sp_arrow << v << "\n";
};
inline const
std::set sks_commands = {
"ktext"s,
"kdesc"s,
"kdiag"s,
"argument_test"s,
"argtype_test"s
};
std::ostream& msg(Locator loc=Locator());
std::ostream& fmsg(Locator loc=Locator());
std::vector<Katom> abbrev(const std::vector<Katom>& ks, unsigned int max_length=16);
// std::vector<int>
std::ostream& operator<<(std::ostream& os, const std::vector<int>& ns);
// std::vector<std::string>
std::ostream& operator<<(std::ostream& os, const std::vector<std::string>& ss);
// std::vector<fs::path>
std::ostream& operator<<(std::ostream& os, const std::vector<fs::path>& pp);
// std::map<std::string,std::string>
std::ostream& operator<<(std::ostream& os, const std::map<std::string,std::string>& sm);
// Katom iterator pair
std::ostream& operator<<(
std::ostream& os,
const std::pair<std::vector<Katom>::iterator, std::vector<Katom>::iterator>& iters);
// Locator
std::ostream& operator<<(std::ostream& os, const Locator& loc);
// Katom
std::ostream& operator<<(std::ostream& os, const Katom& k);
std::ostream& operator<<(std::ostream& os, const std::vector<std::vector<Katom>>& ks);
std::ostream& operator<<(std::ostream& os, const std::shared_ptr<Katom>& k);
std::ostream& operator<<(std::ostream& os, const std::vector<Katom>::iterator& k);
std::ostream& operator<<(std::ostream& os, const std::vector<Katom>& ks);
std::ostream& operator<<(std::ostream& os, const std::vector<std::vector<Katom>::iterator>& ks);
std::ostream& kindex(std::ostream& os);
std::ostream& ktype(std::ostream& os);
std::ostream& klevel(std::ostream& os);
// std::ostream& kspans(std::ostream& os);
std::ostream& kws(std::ostream& os);
std::ostream& kall(std::ostream& os);
std::ostream& kreplaced(std::ostream& os);
std::ostream& kignored(std::ostream& os);
std::ostream& kreset(std::ostream& os);
inline static std::string newline_symbol = "/";
std::ostream& operator<<(std::ostream& os, const std::vector<Katom>& ks);
std::ostream& operator<<(std::ostream& os, const std::vector<Katom>& ks);
// Argtype
std::ostream& operator<<(std::ostream& os, const Argtype& a);
// Parameter (aliased as Argument at application sites)
std::ostream& operator<<(std::ostream& os, const Parameter& p);
// Parameter_set (aliased as Argument_set at application sites)
std::ostream& operator<<(std::ostream& os, const Parameter_set& as);
// std::vector<Parameter>
std::ostream& operator<<(std::ostream& os, const std::vector<Parameter>& as);
// Var
std::ostream& operator<<(std::ostream& os, const Var& v);
// Frame
std::ostream& operator<<(std::ostream& os, const Frame& f);
// State
std::ostream& operator<<(std::ostream& os, const State& s);
// Klammer
std::ostream& operator<<(std::ostream& os, const Klammer& k);
// Klammer::variable_map_t
std::ostream& operator<<(std::ostream& os, const Klammer::variable_map_t& vm);
// Klammer::components
std::ostream& operator<<(std::ostream& os, const Klammer::components& kc);
// Klammer_set
std::ostream& operator<<(std::ostream& os, const Klammer_set& ks);
// Target
std::ostream& operator<<(std::ostream& os, const Target& t);
// Target_set
std::ostream& operator<<(std::ostream& os, const Target_set& ts);
// Machine
std::ostream& operator<<(std::ostream& os, const Machine& m);
void modify_stream(std::string name);

308
mac/state.cpp Normal file
View File

@@ -0,0 +1,308 @@
#include <regex>
#include <iostream>
#include "show.h"
#include "file.h"
#include "error.h"
#include "state.h"
#include "log.h"
#include "util.h"
int State::class_id = 0;
bool Var::defined()
{
return !m_name.empty();
}
std::vector<std::string> Frame::names() const
{
std::vector<std::string> result;
result.reserve(m_vars.size());
std::transform(m_vars.begin(), m_vars.end(), std::back_inserter(result),
[](const auto& pair) { return pair.first; });
return result;
}
void Frame::set(std::string name, std::string value,
std::string delim, std::string desc, Locator loc)
{
Var v(name, value, delim, desc, loc);
m_vars[name] = v;
}
std::pair<Var, bool> Frame::get(std::string name)
{
std::pair<Var, bool> result {Var(), false};
if (m_vars.contains(name)) {
result = {m_vars[name], true};
}
return result;
}
// State
void State::open_frame(std::string name)
{
Frame f(name);
// m_frames.push_back(f);
m_frames.emplace(m_frames.begin(), f);
}
void State::close_frame()
{
if (m_frames.empty()) {
throw Internal_error("No frame to close", Locator());
}
// Preserve altered machine state:
string_map machine_state {};
for (auto [name, var] : m_frames[0].m_vars) {
if (contains(name, "K_")) {
machine_state[name] = var.m_value;
}
}
m_frames.erase(m_frames.begin());
for (auto [name, value] : machine_state) {
set(name, value, true);
}
}
void State::set(std::string name, std::string value, bool update,
std::string delim, std::string desc, Locator loc)
{
if (m_frames.empty()) {
std::stringstream ss {};
ss << "No open frame to set " << q_(name) << " to " << q_(value);
throw Internal_error(ss.str(), Locator());
}
auto [current, exists] = m_frames[0].get(name);
if (exists && current.m_value != klammerstate::no_value && !update) {
std::stringstream ss {};
ss << "Variable " << q_(name) << " is already defined at " << current.m_loc.desc()
<< ". Use ':replace <new-value>' to replace the current value of "
<< q_(current.m_value) << ".";
throw Argument_error(ss.str(), current.m_loc);
}
m_frames[0].set(name, value, delim, desc, loc);
}
void State::set(std::map<std::string, std::string> varmap)
{
for (auto [k, v] : varmap) {
set(k, v);
}
}
void State::replace(std::string name, std::string value, bool error_if_not_defined)
{
if (error_if_not_defined && !get(name).defined()) {
std::stringstream ss {};
ss << "Cannot replace value of nonexistent variable " << q_(name) << " with " << q_(value);
throw Argument_error(ss.str(), Locator());
}
set(name, value, true);
}
void State::add_environment_frame()
{
open_frame(klammerstate::shell_environment_name);
for (auto [name, value] : environment_variables()) {
set(name, value);
}
}
Var State::get(std::string name, bool error_if_not_defined, Locator loc)
{
for (auto f : m_frames) {
auto [result, found] = f.get(name);
if (found) {
return result;
}
}
if (error_if_not_defined) {
msg() << describe();
throw Argument_error("Variable " + q_(name) + " not defined", loc);
} else {
return Var();
}
}
std::string State::value(std::string name, bool error_if_not_defined, Locator loc)
{
return get(name, error_if_not_defined, loc).m_value;
}
std::string State::subst(std::string text, bool quote_values)
{
(void)K::log(3);
std::string result = text;
std::regex varpat(R"(\*(\w+)\*)");
for (std::sregex_iterator iter(text.begin(), text.end(), varpat), end; iter != end; ++iter) {
std::string match = iter->str();
std::string var = (*iter)[1].str();
//std::cout << "Found: " << iter->str() << sp_arrow << (*iter)[1].str() << "\n";
// std::cout << "Found: " << match << sp_arrow << var << "\n";
//std::string value = get(var).m_value;
auto var_value = value(var, false);
auto printable = q_(var_value);
if (var_value != klammerstate::no_value) {
if (quote_values) {
var_value = q_(var_value);
}
result = string_replace(result, match, var_value);
} else {
// throw Argument_error("Variable " + printable + " is not defined");
}
}
return result;
}
void State::subst(katom_iter begin, katom_iter end)
{
(void)K::log(3);
std::regex varpat(R"((.*?)\*(\w+)\*(.*))");
for (auto ki = begin; ki < end ; ki++) {
// msg() << kall << ktype << *ki << "\n";
std::smatch match;
if (std::regex_match(ki->m_text, match, varpat)
&& ki->m_type == katom_t::karg) {
//auto [var, found] = get(match[1]);
auto var_value = value(match[2], true, begin->m_loc);
if (var_value != klammerstate::no_value) {
msg() << "Found subst: " << match[1] << sp_arrow << var_value << "\n";
std::stringstream ss {};
ss << match[1] << var_value << match[3];
ki->m_text = ss.str(); // match[1] + var_value + match[3];
} else {
throw Argument_error("Variable " + q_(match[1]) + " is not defined", begin->m_loc);
}
}
}
}
void prohibit_change_of_description(
std::string name, bool defined, std::string old_desc, std::string new_desc, Locator old_loc, Locator loc)
{
if (defined && !old_desc.empty() && !new_desc.empty()) {
std::stringstream ss {};
ss << "The " << q_(name) << " variable's description is already defined";
if (new_desc != old_desc) {
ss << "; the description cannot be changed to " << q_(new_desc)
<< " from " << q_(old_desc);
}
ss << " at " << old_loc.desc() << ".";
throw Argument_error(ss.str(), loc);
}
}
// Parameter_set m_parameters = Parameter_set("name :value :append :replace :delim :desc");
void State::parse_state_katoms(katom_iter begin, katom_iter end, katom_list katoms)
{
(void)K::log(3, *begin, *(end-1));
// std::cout << "parse_katoms: " << std::pair(begin + 1, end - 1) << "\n";
auto [positional, optional, rest] = argument_split(begin + 1, end - 1);
auto args = m_parameters.value_map(positional, optional, rest, begin->m_loc);
// std::cout << std::setfill(' ') << "\nArgument values:\n" << args;
Var current = get(args["name"]);
bool defined = current.defined();
std::string delim = args["delim"];
delim = delim.empty() ? " " : delim;
prohibit_change_of_description(
args["name"], defined, current.m_desc, args["desc"], current.m_loc, begin->m_loc);
if (defined && !args["replace"].empty()) {
replace(args["name"], args["replace"]);
} else if (defined && !args["append"].empty()) {
replace(args["name"], current.m_value + delim + args["append"]);
} else if (!args["value"].empty()) {
set(args["name"], args["value"], false, delim, args["desc"], begin->m_loc);
}
modify_type(katom_t::replaced, begin, end);
auto next_iter = end;
ignore_whitespace(next_iter, katoms);
}
std::vector<std::string> State::all_names()
{
std::vector<std::string> result {};
for (Frame f : m_frames) {
for (auto [name, var] : f.m_vars) {
// std::cout << "Name: " << name << "\n";
result.push_back(name);
}
}
return result;
}
std::string State::python_code()
{
(void)K::log(3);
std::vector<std::string> names = all_names();
std::string margin = " ";
std::stringstream ss {};
ss << "import sys\n";
for (auto d : sks_dirs()) {
auto python_files = pathnames_with_extension(d, "py");
if (!python_files.empty()) {
ss << "sys.path.append('" << d << "')\n";
}
}
if (!m_frames.empty()) {
int name_length = max_length(names);
ss << "class K:\n"
<< margin << std::left << std::setw(name_length) << "K_eval_id" << " = "
<< State::class_id++ << "\n";
for (const auto& name : names) {
// auto [var_value, argtype] = value_type(name);
// ss << argtype.python_value(name, var_value, name_length) << "\n";
std::string v = value(name);
v = string_replace(v, "\\", "\\\\");
v = string_replace(v, "\"", "\\\"");
std::string var_value = qq_(v);
ss << margin << std::left << std::setw(name_length) << name << " = " << var_value << "\n";
}
}
// msg() << ss.str() << "\n";
return ss.str();
}
std::string State::describe(bool show_environment, int margin_size) const
{
std::string margin(margin_size, ' ');
int i = m_frames.size() - 1;
std::stringstream ss {};
for (auto f : m_frames) {
int width = max_key_length(f.m_vars);
ss << margin << "Frame " << i-- << ": " << f.m_name << "\n";
if ((f.m_name != klammerstate::shell_environment_name) ||
(show_environment && f.m_name == klammerstate::shell_environment_name)) {
for (auto [key, value] : f.m_vars) {
std::string print_value = value.m_value;
if (print_value == klammerstate::no_value) {
print_value = "<no-value>";
}
ss << margin << " " << std::setw(width) << std::left << key << " "
<< abbrev(print_value) << "\n";
}
}
}
ss << "\n";
return ss.str();
}

80
mac/state.h Normal file
View File

@@ -0,0 +1,80 @@
#pragma once
#include "util.h"
#include "locator.h"
//#include "argtype.h"
#include "argument_set.h"
namespace klammerstate {
inline std::string no_value = "\0";
}
class Var
{
public:
Var() = default;
Var(std::string name, std::string value=klammerstate::no_value,
std::string delim=":", std::string desc="", Locator loc=Locator())
: m_name(name)
, m_value(value)
, m_delim(delim)
, m_desc(desc)
, m_loc(loc)
{};
bool defined();
std::string m_name {};
std::string m_value {};
std::string m_delim {};
std::string m_desc {};
Locator m_loc {};
};
namespace klammerstate {
inline std::string shell_environment_name = "Shell environment";
}
class Frame
{
public:
Frame(std::string name)
: m_name(name)
{};
std::vector<std::string> names() const;
void set(std::string name, std::string value,
std::string delim=":", std::string desc="", Locator loc=Locator());
std::pair<Var, bool> get(std::string name);
std::string m_name {};
std::map<std::string, Var> m_vars {};
};
class State
{
public:
static int class_id;
void open_frame(std::string name);
void close_frame();
void set(std::string name, std::string value, bool update=false,
std::string delim=" ", std::string desc="", Locator loc=Locator());
void set(std::map<std::string, std::string> varmap);
void replace(std::string name, std::string value, bool error_if_not_defined=true);
void add_environment_frame();
Var get(std::string name, bool error_if_not_defined=false, Locator loc=Locator());
std::string value(std::string name, bool error_if_not_defined=true, Locator loc=Locator());
std::string subst(std::string text, bool quote_values=false);
void subst(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end);
void parse_state_katoms(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, katom_list katoms);
std::vector<std::string> all_names();
std::string python_code();
std::string describe(bool show_environment=false, int margin_size=2) const;
std::vector<Frame> m_frames {};
// @@@state Image_search_path :set :append :replace :argtype :desc
// Parameter_set m_parameters = Parameter_set("name :set :append :replace :argtype :delim :desc");
Parameter_set m_parameters = Parameter_set("name :value :append :replace :delim :desc");
};

108
mac/target.cpp Normal file
View File

@@ -0,0 +1,108 @@
#include "target.h"
#include "log.h"
#include "show.h"
//#include "text.h"
#include "util.h"
void Target::add_transform(std::string original, std::string transformed)
{
m_transforms.push_back({original, transformed});
}
void Target::add_transforms(std::string transforms)
{
add_transforms(parse_transforms(transforms));
}
void Target::add_transforms(string_pairs transforms)
{
for (auto [old_str, new_str] : transforms) {
add_transform(old_str, new_str);
}
}
void Target::transform(katom_list& katoms)
{
(void)K::log(3);
std::for_each(
katoms.begin(), katoms.end(),
[this] (Katom& k) {
// std::cout << "transform: " << k << "\n";
if (k.m_type != katom_t::literal) {
for (auto [a, b] : this->m_transforms) {
// std::cout << " " << a << right_arrow << b << "\n";
k.m_text = string_replace(k.m_text, a, b);
}
}
});
}
std::vector<std::pair<std::string, std::string>>
parse_transforms(std::string transform_string)
{
(void)K::log(3);
if (trim(transform_string).empty()) return {};
auto transforms = regex_split(transform_string, std::regex(R"(\s*\|\s*)"), true);
std::vector<std::pair<std::string, std::string>> result;
std::transform(transforms.begin(), transforms.end(), std::back_inserter(result),
[] (std::string s) {
strings_t v = word_split(s);
if (v.size() < 2) return std::pair(std::string{}, std::string{});
return std::pair(v[0], v[1]);
});
// Remove empty pairs
result.erase(std::remove_if(result.begin(), result.end(),
[](const auto& p) { return p.first.empty(); }), result.end());
return result;
}
void Target::add_escapes(std::string escape_spec)
{
auto words = word_split(escape_spec);
for (size_t i = 0; i + 1 < words.size(); i += 2) {
m_escapes.push_back({words[i], words[i+1]});
}
}
std::string Target::escape_marker(const std::string& ch)
{
std::stringstream ss {};
ss << "KTESC";
for (unsigned char c : ch)
ss << std::hex << std::setfill('0') << std::setw(4) << (int)c;
ss << "KTESC";
return ss.str();
}
std::string Target::escape_text(std::string text) const
{
for (const auto& [ch, repl] : m_escapes) {
text = string_replace(text, ch, escape_marker(ch));
}
return text;
}
std::string Target::unescape_text(std::string text) const
{
// Restore KTESC markers to original characters (for programmatic use)
for (const auto& [ch, repl] : m_escapes) {
text = string_replace(text, escape_marker(ch), ch);
}
return text;
}
std::string Target::resolve_escapes(std::string text) const
{
for (const auto& [ch, repl] : m_escapes) {
text = string_replace(text, escape_marker(ch), repl);
}
return text;
}
void Target::add_after_apply(std::string function_specs)
{
for (auto f : regex_split(function_specs, std::regex(R"(\s+;\s+)"), true)) {
// msg() << "Add " << m_name << " after-apply: " << f << "\n";
m_after_apply.push_back(f);
}
}

49
mac/target.h Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include <map>
#include <iomanip>
#include <sstream>
#include "katom.h"
#include "locator.h"
class Target
{
public:
Target() = default;
// Target(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, Argtype_set argtypes);
Target(std::string name, std::string desc, Locator loc)
: m_name(name)
, m_desc(desc)
, m_loc(loc)
{};
void add_transform(std::string original, std::string transformed);
void add_transforms(std::string transforms);
void add_transforms(std::vector<std::pair<std::string, std::string>> transforms);
void transform(std::vector<Katom>& katoms);
void add_escapes(std::string escape_spec);
std::string escape_text(std::string text) const;
std::string unescape_text(std::string text) const;
std::string resolve_escapes(std::string text) const;
static std::string escape_marker(const std::string& ch);
void add_after_apply(std::string function_specs);
std::string m_name {};
std::string m_desc {};
std::vector<std::string> m_includes {};
std::vector<std::string> m_provides {};
std::vector<std::string> m_after_apply {};
Locator m_loc {};
std::vector<std::pair<std::string, std::string>> m_transforms {};
std::vector<std::pair<std::string, std::string>> m_escapes {};
// Argtype_set m_argtypes {};
};
std::vector<std::pair<std::string, std::string>>
parse_transforms(std::string transform_string);

160
mac/target_set.cpp Normal file
View File

@@ -0,0 +1,160 @@
#include <sstream>
#include <iterator>
#include <algorithm>
#include "target_set.h"
#include "error.h"
#include "log.h"
#include "util.h"
#include "show.h"
#include "log.h"
#include "katom.h"
std::string Target_set::declare_name = "k";
std::string Target_set::general_name = "*";
Target_set::Target_set()
: m_parameters(Parameter_set("name | desc :after_apply :after_write :includes :escape | transforms.rest"))
{
Target k(declare_name, "Description of parameters and klammer result", Locator());
Target general(general_name, "General target, used when a target is not specified", Locator());
add(k);
add(general);
}
void Target_set::add(Target target)
{
(void)K::log(3, target);
check_for_previous_definition(target.m_name, target.m_loc);
m_targets[target.m_name] = target;
m_names.push_back(target.m_name);
m_descs.push_back(target.m_desc);
}
void Target_set::add(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, std::vector<Katom>& katoms)
{
(void)K::log(3, *begin, *(end - 1));
auto [positional, optional, rest] =
argument_split(begin + 1, end - 1, m_parameters.m_positional.size());
auto values = m_parameters.value_map(positional, optional, rest, begin->m_loc);
//std::cout << ktype << "Transformed: " << kreplaced << std::pair(begin, end) << "\n";
//std::cout << values << "\n";
check_for_previous_definition(values["name"], begin->m_loc);
Target target(values["name"], values["desc"], begin->m_loc);
target.add_transforms(values["transforms"]);
target.add_escapes(values["escape"]);
target.add_after_apply(values["after_apply"]);
// for (auto included_target : word_split(values["includes"])) {
// msg() << "Include: " << included_target << "\n";
// }
target.m_includes = word_split(values["includes"]);
// Inherit escapes from included targets
for (const auto& included : target.m_includes) {
if (m_targets.count(included)) {
for (const auto& esc : m_targets[included].m_escapes) {
target.m_escapes.push_back(esc);
}
}
}
m_targets[target.m_name] = target;
m_names.push_back(target.m_name);
for (auto [name, defined_target] : m_targets) {
// msg() << name << sp_arrow << defined_target << "\n";
if (is_in(name, target.m_includes)) {
defined_target.m_provides.push_back(target.m_name);
// msg() << " " << name << " provides " << target.m_name << "\n " << defined_target.m_provides << "\n";
m_targets[name] = defined_target;
}
}
// std::for_each(begin, end, [](Katom& k) { k.m_type = katom_t::replaced; });
modify_type(katom_t::replaced, begin, end);
auto next_iter = end;
ignore_whitespace(next_iter, katoms);
}
void Target_set::check_for_previous_definition(std::string name, Locator loc)
{
if (has(name)) {
Target current = m_targets[name];
throw Target_error("Target \"" + name + "\" is already defined:\n " + current.m_loc.desc(),
loc, false);
}
}
bool Target_set::has(std::string target_name)
{
return std::ranges::find(m_names, target_name) != m_names.end();
}
Target Target_set::get(std::string target_name, Locator loc)
{
if (has(target_name) || target_name == Target_set::general_name) {
return m_targets.at(target_name);
} else {
throw Target_error("Target " + target_name + " does not exist", loc);
}
}
void Target_set::transform(std::string target_name, katom_list& katoms)
{
(void)K::log(3);
m_targets[target_name].transform(katoms);
}
std::vector<std::string> Target_set::user_defined()
{
return collect_if(
m_names, [](auto name) {
return name != Target_set::declare_name && name != Target_set::general_name; });
}
std::vector<std::string> Target_set::applicable()
{
return collect_if(
m_names, [](auto name) {
return name != Target_set::declare_name; });
}
std::string Target_set::describe(int margin, bool long_format) const
{
std::string tab(margin, ' ');
std::stringstream ss {};
auto name_width = max_length(m_names);
auto desc_width = max_length(m_descs);
for (const std::string& name : m_names) {
const Target& t = m_targets.at(name);
if (long_format) {
ss << tab << std::setfill(' ') << std::setw(name_width) << std::right << name << " "
<< std::setw(desc_width) << std::left << t.m_desc << " "
<< t.m_loc.str() << "\n";
} else {
ss << tab << std::setfill(' ') << std::setw(name_width) << name << sp_arrow << t << "\n";
}
}
return ss.str();
}
/*
void Targets::describe()
{
std::string intro =
"A \"target\" specifies the output format of Klammertext processing. "
"Targets are identified by the typical filename extension of the format. "
"A klammer defines how it converts its arguments to the appropriate structure for one or more targets. "
"The special target \"k\" is used for a klammer definition that describes that klammer's "
"arguments and purpose in the various targets for which it is defined. "
"If the klammer definition does not specify a target, the klammer can be used for any target.";
std::cout << "Klammertext targets\n\n" << justify(intro) << "\n\n";
for (std::string name : names) {
if (name == Target::any_target_name)
continue;
targets[name]->describe();
std::cout << "\n";
}
}
*/

74
mac/target_set.h Normal file
View File

@@ -0,0 +1,74 @@
#pragma once
#include <map>
#include <string>
#include "target.h"
#include "argument_set.h"
#include "katom.h"
class Target_set
{
public:
static std::string declare_name;
static std::string general_name;
Target_set();
void add(Target target);
void add(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, std::vector<Katom>& katoms);
void check_for_previous_definition(std::string name, Locator loc);
/*
void add_transforms(std::string target_name, std::string transforms);
void add_transforms(
std::string target_name,
std::vector<std::pair<std::string, std::string>> transforms);
*/
bool has(std::string target_name);
Target get(std::string target_name, Locator loc);
void transform(std::string target_name, std::vector<Katom>& katoms);
std::vector<std::string> user_defined();
std::vector<std::string> applicable();
std::string describe(int margin=2, bool long_format=false) const;
//Argtype_set m_argtypes {};
std::map<std::string, Target> m_targets {};
std::vector<std::string> m_names {};
std::vector<std::string> m_descs {};
Parameter_set m_parameters {};
/*
std::string m_parameters_spec {"name | desc :after_apply :after_write :includes | transforms.rest"};
Parameter_set m_parameters =
katomize(line_split(m_parameters_spec), Locator().str());
*/
//Parameter_set m_parameters =
// Parameter_set(katomize({"name :desc | transforms.rest"}, "target_set"));
/*
// Targets(Statevar_set& statevars);
target_ptr check_target_existence(
std::string name, Locator loc, bool should_exist);
void add(std::string name, std::string desc, std::string include,
// Statevar_set& statevars,
std::string transform,
std::string before_apply, std::string after_apply, std::string after_write,
Locator loc);
target_ptr check_target_name(
std::string target, bool allow_all, Locator loc);
std::map<std::string, target_ptr> targets {};
std::vector<std::string> names {};
void describe_suffixes();
void describe();
*/
};

516
mac/util.cpp Normal file
View File

@@ -0,0 +1,516 @@
#include <stdlib.h>
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <iterator>
#include <set>
#include <sstream>
#include <utility>
#include <regex>
#include "util.h"
#include "show.h"
std::string trim_left(const std::string& s)
{
std::string result = s;
result.erase(result.begin(), std::find_if(result.begin(), result.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
return result;
}
std::string trim_right(const std::string& s)
{
std::string result = s;
result.erase(std::find_if(result.rbegin(), result.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), result.end());
return result;
}
std::string trim(std::string s)
{
return trim_left(trim_right(std::move(s)));
}
std::string trim_char_left(std::string s, char remove)
{
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [&](char c) { return c != remove; }));
return s;
}
std::string trim_char_right(std::string s, char remove)
{
s.erase(std::find_if(s.rbegin(), s.rend(), [&](char c) { return c != remove; }).base(), s.end());
return s;
}
std::string trim_char(std::string s, char remove)
{
return trim_char_left(trim_char_right(std::move(s), remove), remove);
}
std::string escape_regex(const std::string& input)
{
std::string result;
result.reserve(input.length() * 2); // Reserve space for potential escapes
for (char c : input) {
// Escape special regex characters
if (std::string("\\^$.|?*+()[{}]").find(c) != std::string::npos) {
result += '\\';
}
result += c;
}
return result;
}
std::string string_replace(const std::string& source, const std::string& old_str, const std::string& new_str)
{
return std::regex_replace(source, std::regex(escape_regex(old_str)), new_str);
/*
std::string result { source };
auto pos = result.find(old_str);
std::string old_str_e = old_str; // escape_regex(old_str);
while (pos != std::string::npos) {
// result = result.replace(pos, old_str.size(), new_str);
result = result.replace(pos, old_str_e.size(), new_str);
// pos = result.find(old_str);
pos = result.find(old_str_e);
// std::cout << " " << result << "\n";
}
return result;
*/
}
bool contains(const std::string& str, const std::string& substr)
{
return str.find(substr) != std::string::npos;
}
bool contains(const std::vector<std::string>& strings, const std::string& element)
{
return std::find(strings.begin(), strings.end(), element) != strings.end();
}
std::string regex_escape(const std::string& s)
{
/*
regex special { R"([\$.|?*+(){})" }; // ^ is reserved
return regex_replace(s, special, "\\[&$]");
*/
std::set chars { '\\', '|', '(', ')', '{', '}', '[', ']', '$', '^' };
std::string result {};
for (char c : s) {
if (chars.find(c) != chars.end())
result += "\\";
result += c;
}
return result;
}
strings_t regex_split(std::string s, std::regex re, bool trim_parts)
{
strings_t result = {};
if (s.size() == 0) {
return result;
}
auto it = std::sregex_token_iterator(s.begin(), s.end(), re, -1);
while (it != std::sregex_token_iterator()) {
std::string part { *it };
if (trim_parts)
part = trim(part);
result.push_back(part);
it++;
}
return result;
}
strings_t word_split(const std::string& s)
{
return regex_split(s, std::regex("\\s+"));
}
bool is_in(std::string s, strings_t v)
{
return find(v.begin(), v.end(), s) != v.end();
}
bool is_not_in(std::string s, strings_t v)
{
return find(v.begin(), v.end(), s) == v.end();
}
strings_t find_all(std::string str, std::regex pattern, int match_group)
{
std::sregex_iterator end {};
strings_t result;
for (std::sregex_iterator p {str.begin(), str.end(), pattern}; p!= end; ++p)
result.push_back((*p)[match_group]);
return result;
}
strings_t find_all(std::string str, std::string pattern, int match_group)
{
return find_all(str, std::regex(pattern), match_group);
}
strings_t split_into_paragraphs(const std::string& s)
{
std::string t {trim(s)};
std::string marker { "_PAR_" };
t = trim(std::regex_replace(t, std::regex(R"(\n *(\n *)+)"), marker)) + marker;
//return find_all(t, regex(R"(((\s|.)*?)" + marker + ")"), 1);
return find_all(t, std::regex(R"(((\s|.)*?)_PAR_)"), 1);
}
std::string add_margin(std::string s, unsigned int margin_size)
{
auto margin = std::string(margin_size, ' ');
return trim_right(
margin + std::regex_replace(s, std::regex(R"(\n)"), '\n' + margin));
}
std::string justify_string(const std::string& s, unsigned int width=80, bool french_spacing=false)
{
strings_t words = find_all(s, R"([^\s]+)");
std::stringstream ss {};
std::stringstream line {};
for (std::string w : words) {
if (line.str().size() + w.size() + 1 > width) {
ss << line.str() << '\n';
line.str("");
}
if (not french_spacing and w[w.size()-1] == '.')
w += " ";
line << w << " ";
}
if (!line.str().empty())
ss << line.str();
std::string result = trim(ss.str());
result = std::regex_replace(result, std::regex("~"), " ");
return result;
}
std::string justify(
const std::string& input_text, unsigned int text_width, unsigned int margin_width)
{
std::string result {};
text_width -= margin_width;
for (const std::string& par : split_into_paragraphs(trim(input_text))) {
result += justify_string(par, text_width) + "\n\n";
}
if (margin_width > 0) {
result = add_margin(result, margin_width);
}
return result;
}
std::string join(const strings_t& ss, const std::string& separator)
{
if (ss.empty()) {
return std::string();
} else if (ss.size() == 1) {
return ss[0];
} else {
std::stringstream strm {};
std::copy(ss.begin(), ss.end() - 1,
std::ostream_iterator<std::string>(strm, separator.c_str()));
strm << ss.back();
return strm.str();
}
}
std::string join(int argc, char* argv[], const std::string& separator)
{
std::string result {};
for (int i = 0; i < argc; ++i) {
result += std::string(argv[i]) + separator;
}
return result;
}
std::string argv_to_string(int argc, char* argv[])
{
if (argc == 0) {
return "";
}
std::string result = argv[0];
for (int i = 1; i < argc; ++i) {
result += " " + std::string(argv[i]);
}
return result;
}
std::string plural(const std::string& word, int count)
{
std::string result {word};
if (count != 1) {
if (*(word.end()-1) == 'y')
result = word.substr(0, word.size()-2) + "ies";
else
result = word + "s";
}
return result;
}
std::string plural(const std::string& word, const strings_t& things)
{
std::string result { word };
if (things.size() != 1) {
if (*(word.end()-1) == 'y')
result = word.substr(0, word.size()-2) + "ies";
else
result = word + "s";
}
return result;
}
std::string to_be(int count, bool present)
{
std::string result {};
if (count > 1) {
if (present) {
result = "are";
} else {
result = "were";
}
} else {
if (present) {
result = "is";
} else {
result = "was";
}
}
return result;
}
int max_length(strings_t ss)
{
size_t result = 0;
for_each(ss.begin(), ss.end(),
[&result](const std::string& s) { result = std::max(result, s.size()); });
return result;
}
/*
std::vector<std::string> map_key_lengths(std::map<std::string, auto> map)
{
int result = 0;
for (auto const& item: map) {
result = std::max(result, item.first.size());
}
}
int
std::map<int, int> m;
std::vector<int> key, value;
for(std::map<int,int>::iterator it = m.begin(); it != m.end(); ++it) {
key.push_back(it->first);
value.push_back(it->second);
std::cout << "Key: " << it->first << std::endl;
std::cout << "Value: " << it->second << std::endl;
}
*/
std::vector<std::pair<std::string, std::string>> environment_variables(bool allow_empty_definitions)
{
// std::cout << "read_environment:\n";
std::vector<std::pair<std::string, std::string>> result {};
extern char **environ;
for (int i = 0; environ[i]; i++) {
auto parts = regex_split(environ[i], std::regex("="), true);
if (!allow_empty_definitions && parts.size() < 2) {
throw Internal_error(
"Incorrect environment variable format:\n" + std::string(environ[i]),
Locator(), false);
}
std::string name = parts[0];
parts.erase(parts.begin());
std::string value = join(parts, "=");
// std::cout << name << sp_arrow << value << "\n";
result.push_back({name, value});
}
return result;
}
std::string replace_environment_variables(std::string str)
{
if (str.find('{') == std::string::npos || str.find('}') == std::string::npos) {
return str;
}
if (str.find('{') == std::string::npos || str.find('\n') != std::string::npos) {
return str;
}
if (str.size() < 3) {
return str;
}
std::regex variable_re(R"((.*?)\{([A-Z_]+)\})");
std::sregex_iterator end {};
std::string result {};
//sregex_iterator q {};
size_t endpos = 0;
for (std::sregex_iterator p {str.begin(), str.end(), variable_re}; p!= end; ++p) {
std::smatch m = *p;
std::string prefix = m[1];
std::string var = m[2];
endpos = m.position() + m.length();
std::string envvar = get_env_var(var);
result += prefix + envvar;
}
if (endpos < str.size() - 1) {
result += str.substr(endpos);
}
return result;
}
std::string abbrev(const std::string& s, unsigned int max_length, bool remove_newlines)
{
std::string result {s};
if (s.size() > max_length) {
if (remove_newlines) {
result = trim(result);
result = std::regex_replace(result, std::regex("\n"), broken_bar);
}
int suffix_size = 8;
std::string ellipsis { red + "[...]" + black };
int prefix_end = max_length - suffix_size - ellipsis.size();
result = result.replace(result.begin() + prefix_end,
result.end() - suffix_size,
ellipsis);
}
return result;
}
void remove_element(std::vector<std::string>& ss, std::string removed)
{
ss.erase(std::remove_if(ss.begin(), ss.end(),
[&removed](std::string s) { return s == removed; }),
ss.end());
}
void remove_duplicates(strings_t& ss)
{
// https://en.cppreference.com/w/cpp/algorithm/unique
std::sort(ss.begin(), ss.end());
auto last = std::unique(ss.begin(), ss.end());
ss.erase(last, ss.end());
}
std::string display_string(const std::string& s, unsigned int width, bool replace_newlines)
{
std::string suffix { "..." };
std::string result { s };
if (replace_newlines)
result = std::regex_replace(result, std::regex("\n"), "/");
auto rlen = result.length();
auto slen = suffix.length();
if ((rlen > slen) and (rlen - slen) > width) {
result = result.replace(result.begin()+width, result.end(), suffix); //substr(0, width) + suffix;
}
//result = '"' + result + '"';
return result;
}
std::pair<std::string,std::string> extract_parameter_type(std::string parameter_name)
{
std::regex name_pat { R"((\w+)\.(\w+))" };
std::smatch match {};
std::string type_name = "string";
std::string name = parameter_name;
if (std::regex_match(parameter_name, match, name_pat)) {
name = match[1];
type_name = match[2];
}
return { name, type_name };
}
std::tuple<std::string, std::string, bool> regex_split_prefix(const std::regex& pattern, const std::string& text)
{
std::smatch match;
if (std::regex_search(text, match, pattern) && match.position() == 0) {
// Match found at the beginning of the string
std::string matched = match.str();
std::string remainder = text.substr(matched.length());
return { matched, remainder, true };
} else {
// No match at the beginning
return { "", text, false };
}
}
std::vector<std::string> dlist_split(const std::string& s)
{
// If a [^\w] character surrounded by spaces exists in s, it is the delimiter.
// If not, the first space-delimited word is the delimiter.
if (s.find(" ") == std::string::npos) { // Only one element.
//std::vector<std::string> result {s};
//return result;
return {s};
} else {
std::smatch match{};
std::string elements_str{s};
std::string delimiter {};
if (std::regex_search(s, match, std::regex(R"(\s+([^\w])\s+)"))) {
delimiter = match[1];
} else {
std::string::const_iterator iter =
std::find_if(s.cbegin(), s.cend(), [](char c) { return c == ' '; });
delimiter = std::string(s.cbegin(), iter);
elements_str = std::string(iter, s.cend());
}
std::vector<std::string> elements {
regex_split(elements_str, std::regex(delimiter), true) };
return elements;
}
}
std::string get_env_var(const std::string& var) {
std::lock_guard<std::mutex> lock(env_mutex);
const char* val = getenv(var.c_str());
return val ? std::string(val) : "";
}
std::string freplace(const std::string src, std::regex pattern, std::function<std::string(std::smatch)> func)
{
std::string result {};
std::smatch match;
bool found = std::regex_search(src.begin(), src.end(), match, pattern);
auto pos = src.begin();
// int n = 0;
while (found) {
std::string part(pos, pos + match.position());
result += part;
result += func(match);
pos += match.position(0) + match.length(0);
found = std::regex_search(pos, src.end(), match, pattern);
// n++;
}
std::string part(pos, pos + match.position());
result += part;
// std::cout << "Found " << n << " matches\n";
return result;
}
std::string exec(const char* cmd)
{
std::array<char, 128> buffer;
std::string result;
FILE* pipe = popen(cmd, "r");
if (!pipe) throw std::runtime_error("popen() failed");
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
result += buffer.data();
}
pclose(pipe);
return result;
}

107
mac/util.h Normal file
View File

@@ -0,0 +1,107 @@
#pragma once
#include <vector>
#include <string>
#include <regex>
#include <mutex>
#include <map>
#include <memory>
#include <functional>
#include <tuple>
#include <utility>
#include <algorithm>
inline std::mutex env_mutex;
class Katom;
using strings_t = std::vector<std::string>;
using string_pairs = std::vector<std::pair<std::string, std::string>>;
using string_map = std::map<std::string, std::string>;
using katom_ptr = std::shared_ptr<Katom>;
using katom_list = std::vector<Katom>;
using katom_lists = std::vector<katom_list>;
using katom_list_map = std::map<std::string, katom_list>;
using katom_iter = katom_list::iterator;
using spans_t = std::vector<std::pair<Katom, Katom>>;
using argument_value_map = std::map<std::string, std::string>;
std::string trim_left(const std::string& s);
std::string trim_right(const std::string& s);
std::string trim(std::string s);
std::string trim_char_left(std::string s, char remove);
std::string trim_char_right(std::string s, char remove);
std::string trim_char(std::string s, char remove);
std::string string_replace(const std::string& source, const std::string& old_str, const std::string& new_str);
std::string regex_escape(const std::string& s);
bool contains(const std::string& str, const std::string& substr);
bool contains(const std::vector<std::string>& strings, const std::string& element);
std::vector<std::string> regex_split(std::string s, std::regex re, bool trim_parts=true);
std::vector<std::string> word_split(const std::string& s);
bool is_in(std::string s, std::vector<std::string> v);
bool is_not_in(std::string s, std::vector<std::string> v);
std::vector<std::string> find_all(std::string str, std::regex pattern, int match_group=0);
std::vector<std::string> find_all(std::string str, std::string pattern, int match_group=0);
std::string add_margin(std::string s, unsigned int margin_size);
std::string justify(const std::string& input_text, unsigned int text_width=80, unsigned int margin_width=0);
std::string join(const std::vector<std::string>& ss, const std::string& separator = " ");
std::string join(int argc, char* array[], const std::string& separator = " ");
std::string argv_to_string(int argc, char* argv[]);
std::string plural(const std::string& word, int count);
std::string plural(const std::string& word, const std::vector<std::string>& things);
std::string to_be(int count, bool present = true);
int max_length(std::vector<std::string> ss);
std::vector<std::pair<std::string, std::string>> environment_variables(bool allow_empty_definitions=true);
std::string replace_environment_variables(std::string str);
std::string abbrev(const std::string& s, unsigned int max_length=65, bool remove_newlines=true);
void remove_element(std::vector<std::string>& ss, std::string removed);
void remove_duplicates(std::vector<std::string>& ss);
std::string display_string(const std::string& s, unsigned int width=40, bool replace_newlines=true);
std::pair<std::string,std::string> extract_parameter_type(std::string parameter_name);
std::tuple<std::string, std::string, bool> regex_split_prefix(const std::regex& pattern, const std::string& text);
std::vector<std::string> dlist_split(const std::string& s);
std::string get_env_var(const std::string& var);
std::string freplace(const std::string src, std::regex pattern, std::function<std::string(std::smatch)> func);
std::string exec(const char* cmd);
inline std::string q_(std::string s)
{
return "\"" + s + "\"";
}
inline std::string qq_(std::string s)
{
if (s.find('\n') == std::string::npos) {
return "\"" + s + "\"";
} else {
return "\"\"\"" + s + "\"\"\"";
}
}
template <typename T, typename Pred>
std::vector<T> collect_if(const std::vector<T>& xs, Pred pred) {
std::vector<T> out;
out.reserve(xs.size());
for (const auto& v : xs) {
if (pred(v)) out.push_back(v);
}
out.shrink_to_fit();
return out;
}
template <typename T>
bool all_equal(const std::vector<T>& v) {
if (v.size() < 2) return true;
return std::adjacent_find(v.begin(), v.end(), std::not_equal_to<T>{}) == v.end();
}
template <typename T>
int max_key_length(std::map<std::string, T> map)
{
size_t result = 0;
for_each(map.begin(), map.end(),
[&result](const auto& item) { result = std::max(result, item.first.size()); });
return result;
}