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:
1
mac/.gitignore
vendored
Normal file
1
mac/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.d
|
||||
67
mac/Makefile
Normal file
67
mac/Makefile
Normal 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
84
mac/argtype.cpp
Normal 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
51
mac/argtype.h
Normal 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
155
mac/argtype_set.cpp
Normal 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
88
mac/argtype_set.h
Normal 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
12
mac/argument.cpp
Normal 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
37
mac/argument.h
Normal 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
420
mac/argument_set.cpp
Normal 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
63
mac/argument_set.h
Normal 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
569
mac/argv.cpp
Normal 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
103
mac/argv.h
Normal 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
1
mac/basenames.mk
Normal 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
304
mac/character.cpp
Normal 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
105
mac/character.h
Normal 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
109
mac/command.cpp
Normal 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
17
mac/command.h
Normal 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
56
mac/deftype.cpp
Normal 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
17
mac/deftype.h
Normal 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
12
mac/env/lsan.supp
vendored
Normal 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
68
mac/env/makefile.env
vendored
Normal 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
46
mac/env/makefile.env.hollis.DISABLED
vendored
Normal 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
33
mac/env/makefile.env.jatke.DISABLED
vendored
Normal 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
33
mac/env/makefile.env.pop.DISABLED
vendored
Normal 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
33
mac/env/makefile.env.ubuntu.DISABLED
vendored
Normal 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
16
mac/env/optimize.env
vendored
Normal 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
72
mac/env/runtime.env
vendored
Normal 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
35
mac/error.cpp
Normal 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
77
mac/error.h
Normal 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
159
mac/eval.cpp
Normal 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
30
mac/eval.h
Normal 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
43
mac/eval_cpp.cpp
Normal 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
19
mac/eval_cpp.h
Normal 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
229
mac/eval_python.cpp
Normal 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
32
mac/eval_python.h
Normal 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
571
mac/file.cpp
Normal 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
66
mac/file.h
Normal 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
1
mac/headers_only.mk
Normal file
@@ -0,0 +1 @@
|
||||
HEADERS_ONLY := alias env
|
||||
397
mac/katom.cpp
Normal file
397
mac/katom.cpp
Normal 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
205
mac/katom.h
Normal 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
428
mac/katom_list.cpp
Normal 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
52
mac/katom_list.h
Normal 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
493
mac/klammer.cpp
Normal 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
106
mac/klammer.h
Normal 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
175
mac/klammer_set.cpp
Normal 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
19
mac/klammer_set.h
Normal 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
207
mac/ktype.cpp
Normal 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
305
mac/ktype.h
Normal 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
137
mac/locator.cpp
Normal 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
52
mac/locator.h
Normal 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
117
mac/log.cpp
Normal 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
63
mac/log.h
Normal 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
528
mac/machine.cpp
Normal 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
128
mac/machine.h
Normal 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
559
mac/show.cpp
Normal 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
158
mac/show.h
Normal 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
308
mac/state.cpp
Normal 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
80
mac/state.h
Normal 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
108
mac/target.cpp
Normal 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
49
mac/target.h
Normal 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
160
mac/target_set.cpp
Normal 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
74
mac/target_set.h
Normal 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
516
mac/util.cpp
Normal 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
107
mac/util.h
Normal 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user