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:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Build artifacts
|
||||||
|
*.o
|
||||||
|
*.d
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Editor / OS cruft
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
18
LICENSE.md
Normal file
18
LICENSE.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# License
|
||||||
|
|
||||||
|
Copyright © 2026 Andy Kopra. All rights reserved.
|
||||||
|
|
||||||
|
**This is a preliminary notice. A license will be published here.**
|
||||||
|
|
||||||
|
You may use this software and modify it for your own experimentation and use.
|
||||||
|
|
||||||
|
The name "Klammertext" and the definition of the Klammertext language are
|
||||||
|
reserved by the author. Nothing here grants permission to publish a modified
|
||||||
|
or alternative definition of the Klammertext language, or to distribute
|
||||||
|
software under the name "Klammertext," without the author's authorization.
|
||||||
|
|
||||||
|
Terms for redistribution, and for creating and sharing klammers, klammer sets,
|
||||||
|
commands, and other works built with Klammertext, will be set out in the
|
||||||
|
published license.
|
||||||
|
|
||||||
|
Contact: Andy Kopra <ack@acm.org>
|
||||||
35
README.md
Normal file
35
README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Klammertext
|
||||||
|
|
||||||
|
Klammertext is a markup language that produces multiple output formats —
|
||||||
|
HTML, LaTeX/PDF, and plain text — from a single source description. Its core
|
||||||
|
engine, the Klammermachine, is written in C++; the Standard Klammer Set (SKS)
|
||||||
|
adds a default library of formatting and document-structuring operators on top.
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
Installation guides are in [`doc/install/`](doc/install/):
|
||||||
|
|
||||||
|
- Linux, from source — `doc/install/linux_source_install.md`
|
||||||
|
- macOS, from source — `doc/install/macos_source_install.md`
|
||||||
|
- Linux, container — `doc/install/linux_container_install.md`
|
||||||
|
- macOS, container — `doc/install/macos_container_install.md`
|
||||||
|
|
||||||
|
## Building from source
|
||||||
|
|
||||||
|
With a C++20 compiler and `KLAMMERTEXT_HOME` set to this directory:
|
||||||
|
|
||||||
|
make -C com
|
||||||
|
|
||||||
|
This builds the Klammermachine library (into `lib/`), the SKS components, and
|
||||||
|
the three commands — `ktext`, `kdesc`, `kdiag` (into `bin/`). See the
|
||||||
|
source-install guide for prerequisites (TeX Live for PDF output, Python, and so
|
||||||
|
on).
|
||||||
|
|
||||||
|
## Editor support
|
||||||
|
|
||||||
|
Syntax highlighting and editing support for Emacs and Sublime Text are in
|
||||||
|
[`doc/edit/`](doc/edit/).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [`LICENSE.md`](LICENSE.md).
|
||||||
3
bin/.gitignore
vendored
Normal file
3
bin/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!README.md
|
||||||
5
bin/README.md
Normal file
5
bin/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# bin
|
||||||
|
|
||||||
|
This directory holds the built Klammertext command executables (`ktext`,
|
||||||
|
`kdesc`, `kdiag`) after you build Klammertext with `make -C com`.
|
||||||
|
It is empty in the repository.
|
||||||
4
com/.gitignore
vendored
Normal file
4
com/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
ktext
|
||||||
|
kdesc
|
||||||
|
kdiag
|
||||||
|
*.d
|
||||||
101
com/Makefile
Normal file
101
com/Makefile
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Klammertext com/ Makefile
|
||||||
|
# Improved version with automatic header dependency tracking
|
||||||
|
|
||||||
|
K := $(KLAMMERTEXT_HOME)
|
||||||
|
KS := $(K)/sks
|
||||||
|
KM := $(K)/mac
|
||||||
|
|
||||||
|
include $(KM)/env/makefile.env
|
||||||
|
|
||||||
|
# Commands to build
|
||||||
|
COMMANDS := kdiag kdesc ktext
|
||||||
|
|
||||||
|
# Build output directory: commands are linked directly into ../bin, so there is
|
||||||
|
# exactly one copy of each (no redundant executables in com/) and make still
|
||||||
|
# tracks them by path for incremental builds.
|
||||||
|
BINDIR := ../bin
|
||||||
|
BINCOMMANDS := $(addprefix $(BINDIR)/,$(COMMANDS))
|
||||||
|
|
||||||
|
# System install location (out-of-tree). Override on the command line, e.g.
|
||||||
|
# make install PREFIX=/opt DESTDIR=/tmp/stage
|
||||||
|
PREFIX ?= /usr/local
|
||||||
|
DESTDIR ?=
|
||||||
|
|
||||||
|
# Shared library location
|
||||||
|
LIBDIR := ../lib
|
||||||
|
LIBRARY := $(LIBDIR)/libklammertext.so
|
||||||
|
|
||||||
|
# Linker flags for commands
|
||||||
|
COM_LDFLAGS := $(EXPORT_DYNAMIC) -Wl,-rpath,'$(ORIGIN)/../lib' -L$(LIBDIR)
|
||||||
|
|
||||||
|
# Dependency files for command sources
|
||||||
|
DEPFILES := $(addsuffix .d,$(COMMANDS))
|
||||||
|
|
||||||
|
# Compiler flags for dependency generation ($(@F) keeps the .d files in com/,
|
||||||
|
# not in the ../bin output directory).
|
||||||
|
DEPFLAGS = -MMD -MP -MF $(@F).d
|
||||||
|
|
||||||
|
.PHONY: all clean redo clang mac sks install
|
||||||
|
|
||||||
|
# Default target: build dependencies first, then commands
|
||||||
|
all : | mac sks
|
||||||
|
$(MAKE) commands
|
||||||
|
|
||||||
|
# Separate target to build commands (called after dependencies are ready)
|
||||||
|
.PHONY: commands
|
||||||
|
commands : $(BINCOMMANDS)
|
||||||
|
|
||||||
|
# Ensure the output directory exists before linking into it.
|
||||||
|
$(BINDIR) :
|
||||||
|
mkdir -p $@
|
||||||
|
|
||||||
|
# Build mac/ objects
|
||||||
|
mac :
|
||||||
|
$(MAKE) -C $(KM) -j
|
||||||
|
|
||||||
|
# Build sks/ components (depends on mac)
|
||||||
|
sks : | mac
|
||||||
|
$(MAKE) -C $(KS)/kutil -j
|
||||||
|
$(MAKE) -C $(KS)/target -j
|
||||||
|
$(MAKE) -C $(KS)/document
|
||||||
|
|
||||||
|
# Explicit rules for each command - link against shared library, output to bin/
|
||||||
|
$(BINDIR)/kdiag : kdiag.cpp $(LIBRARY) | $(BINDIR)
|
||||||
|
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< -o $@ -lklammertext $(LDLIBS)
|
||||||
|
|
||||||
|
$(BINDIR)/kdesc : kdesc.cpp $(LIBRARY) | $(BINDIR)
|
||||||
|
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< -o $@ -lklammertext $(LDLIBS)
|
||||||
|
|
||||||
|
$(BINDIR)/ktext : ktext.cpp $(LIBRARY) | $(BINDIR)
|
||||||
|
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< -o $@ -lklammertext $(LDLIBS)
|
||||||
|
|
||||||
|
# Out-of-tree system install (copies; the build tree stays intact). The
|
||||||
|
# installed commands still need KLAMMERTEXT_HOME pointing at a Klammertext tree
|
||||||
|
# for sks/ and the Python modules; their rpath finds libklammertext.so in
|
||||||
|
# $(PREFIX)/lib.
|
||||||
|
install : all
|
||||||
|
install -d $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(PREFIX)/lib
|
||||||
|
install -m755 $(BINCOMMANDS) $(DESTDIR)$(PREFIX)/bin
|
||||||
|
install -m755 $(LIBRARY) $(DESTDIR)$(PREFIX)/lib
|
||||||
|
|
||||||
|
clean :
|
||||||
|
rm -f $(BINCOMMANDS) $(DEPFILES) *~
|
||||||
|
|
||||||
|
redo :
|
||||||
|
ifneq ($(filter clang,$(MAKECMDGOALS)),)
|
||||||
|
@:
|
||||||
|
else
|
||||||
|
$(MAKE) -C $(KM) clean
|
||||||
|
$(MAKE) -C $(KS)/kutil clean
|
||||||
|
$(MAKE) -C $(KS)/target clean
|
||||||
|
$(MAKE) -C $(KS)/document clean
|
||||||
|
$(MAKE) clean
|
||||||
|
$(MAKE) 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)
|
||||||
83
com/kdesc.cpp
Normal file
83
com/kdesc.cpp
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#include "argv.h"
|
||||||
|
#include "command.h"
|
||||||
|
#include "error.h"
|
||||||
|
#include "file.h"
|
||||||
|
#include "ktype.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "argtype_set.h"
|
||||||
|
#include "character.h"
|
||||||
|
#include "show.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
set_verbose_level(argc, argv);
|
||||||
|
|
||||||
|
Argv args {};
|
||||||
|
args.flag("c", "Special characters");
|
||||||
|
args.flag("a", "Argument types");
|
||||||
|
args.flag("k", "Katom types");
|
||||||
|
args.flag("r", "Katom rewrite patterns");
|
||||||
|
args.opt("input", "Input filename", "filename", "", "'text'");
|
||||||
|
args.flag("targets", "Show targets defined by the input file");
|
||||||
|
args.flag("klammers", "Show klammers defined by the input file");
|
||||||
|
args.opt("v", "'verbosity'", "n", "0", "'verbosity'");
|
||||||
|
|
||||||
|
if (show_usage(argc, argv)) {
|
||||||
|
args.usage(file_basename(argv[0]));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
auto p = [&](std::string name) { return args.get(name) == "true"; };
|
||||||
|
|
||||||
|
args.parse(argc, argv);
|
||||||
|
verbose_level = stoi(args.get("v"));
|
||||||
|
if (verbose_level > 0) {
|
||||||
|
args.describe();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p("c")) {
|
||||||
|
show_special_characters();
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p("a")) {
|
||||||
|
Argtype_set argtypes;
|
||||||
|
std::cout << boldblack << "\nStandard klammer argument types\n" << black;
|
||||||
|
std::cout << argtypes.describe() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p("k")) {
|
||||||
|
describe_katoms(verbose_level > 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p("r")) {
|
||||||
|
describe_rewrite_patterns();
|
||||||
|
}
|
||||||
|
|
||||||
|
Machine M;
|
||||||
|
|
||||||
|
strings_t input_filenames = args.as_vector("input");
|
||||||
|
std::cout << "input_filenames: " << input_filenames << "\n";
|
||||||
|
if (input_filenames.empty()) {
|
||||||
|
M.read(fs::path(M.m_state.value("KLAMMERTEXT_HOME") + "/sks/sks.k"));
|
||||||
|
} else {
|
||||||
|
for (auto fname : input_filenames) {
|
||||||
|
std::cout << "Read " << fname << "\n";
|
||||||
|
M.read(fs::path(absolute_pathname(fname)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p("targets")) {
|
||||||
|
std::cout << boldblack << "Targets\n" << black << M.m_targets.describe(2, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p("klammers")) {
|
||||||
|
std::cout << boldblack << "Klammers\n" << black << M.m_klammers.describe(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Error& e) {
|
||||||
|
e.print_message();
|
||||||
|
}
|
||||||
|
}
|
||||||
118
com/kdiag.cpp
Normal file
118
com/kdiag.cpp
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
#include "argument_set.h"
|
||||||
|
#include "argv.h"
|
||||||
|
#include "argument.h"
|
||||||
|
#include "command.h"
|
||||||
|
#include "error.h"
|
||||||
|
#include "file.h"
|
||||||
|
#include "katom.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "show.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
set_verbose_level(argc, argv);
|
||||||
|
|
||||||
|
Argv args {};
|
||||||
|
args.req("input", "Klammertext input text", "'text'");
|
||||||
|
args.flag("type", "Show katom types in subscript");
|
||||||
|
args.flag("index", "Show the list index of the katom");
|
||||||
|
args.flag("text", "Show text katoms with selected attributes");
|
||||||
|
args.flag("replaced","Show replaced katoms");
|
||||||
|
args.flag("ignored", "Show ignored katoms");
|
||||||
|
args.flag("all", "Show all katoms, including katoms replaced or ignored"); // in brackets with selected attributes");
|
||||||
|
args.flag("spans", "Show the beginning and ending katoms of spans");
|
||||||
|
args.flag("rewrite", "Show applied rewrite rules");
|
||||||
|
args.flag("args", "Show how the text would be parsed as klammer arguments");
|
||||||
|
args.opt("pos", "Number of positional arguments to parse for --args", "count", "-1", R"(([^\s]+))");
|
||||||
|
args.flag("read", "Process read: @read <filename> @");
|
||||||
|
args.flag("eval", "Process eval: @eval <expression> @");
|
||||||
|
args.flag("cond", "Process cond: @cond <condition> | <if-true> | <if_false> @");
|
||||||
|
args.flag("nonascii","Process encoded characters (not ASCII): ^... or ^...^");
|
||||||
|
args.flag("literal", "Process literal: ^'...'^");
|
||||||
|
args.flag("ignore", "Process ignored: #, ##, #[...]#");
|
||||||
|
args.flag("ws", "Process whitespace: #-, #+, #/");
|
||||||
|
args.flag("klammer", "Process klammer definitions: @@<name> ... @@");
|
||||||
|
args.flag("process", "Process all");
|
||||||
|
args.opt("v", "'verbosity'", "degree", "0", "'verbosity'");
|
||||||
|
|
||||||
|
if (show_usage(argc, argv)) {
|
||||||
|
args.usage(file_basename(argv[0]));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
auto p = [&](std::string name) { return args.get(name) == "true"; };
|
||||||
|
|
||||||
|
args.parse(argc, argv);
|
||||||
|
verbose_level = args.as_int("v");
|
||||||
|
if (verbose_level > 0) {
|
||||||
|
args.describe();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p("rewrite")) {
|
||||||
|
show_rewrite_rules = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Machine machine;
|
||||||
|
std::string input = args.as_string("input");
|
||||||
|
std::string command = construct_command_pathname(argv[0]);
|
||||||
|
katom_list katoms {};
|
||||||
|
if (p("process")) {
|
||||||
|
katoms = machine.process(input, command);
|
||||||
|
} else {
|
||||||
|
katoms = machine.process(
|
||||||
|
//args.as_string("input"), construct_command_pathname(argv[0]),
|
||||||
|
input, command,
|
||||||
|
p("nonascii"), p("literal"), p("ignore"), p("ws"), p("klammer"),
|
||||||
|
p("eval"), p("cond"), p("read"));
|
||||||
|
}
|
||||||
|
if (p("spans")) {
|
||||||
|
describe_spans(katoms);
|
||||||
|
} else {
|
||||||
|
if (p("ignored")) std::cout << kignored;
|
||||||
|
if (p("type")) std::cout << ktype;
|
||||||
|
if (p("index")) std::cout << kindex;
|
||||||
|
if (p("text")) std::cout << kall;
|
||||||
|
if (p("all")) std::cout << kall << kreplaced << kignored;
|
||||||
|
if (p("replaced")) std::cout << kreplaced;
|
||||||
|
std::cout << katoms << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p("args")) {
|
||||||
|
int req_count = args.as_int("pos");
|
||||||
|
bool limit_req = req_count != -1;
|
||||||
|
if (req_count == -1) {
|
||||||
|
req_count = std::numeric_limits<int>::max();
|
||||||
|
}
|
||||||
|
auto [required, optional, rest] =
|
||||||
|
argument_split(katoms.begin(), katoms.end(), req_count);
|
||||||
|
std::stringstream ss {};
|
||||||
|
ss << "required";
|
||||||
|
if (limit_req) {
|
||||||
|
ss << " (" << req_count << ")";
|
||||||
|
}
|
||||||
|
ss << ":";
|
||||||
|
std::string req_label = ss.str();
|
||||||
|
auto label_width = req_label.size() + 2;
|
||||||
|
std::cout << std::setfill(' ')
|
||||||
|
<< std::right << std::setw(label_width) << req_label << " " << required << "\n"
|
||||||
|
<< std::right << std::setw(label_width) << "optional:" << " " << optional << "\n";
|
||||||
|
if (limit_req) {
|
||||||
|
std::cout << std::right << std::setw(label_width) << "rest:" << " " << rest << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Error& e) {
|
||||||
|
std::string advice = "";
|
||||||
|
if (e.m_type == "target")
|
||||||
|
advice = "To include the Standard Klammer Set, add flag \"--sks\".";
|
||||||
|
e.print_message(advice);
|
||||||
|
}
|
||||||
|
std::cout << black;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
109
com/ktext.cpp
Normal file
109
com/ktext.cpp
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include "error.h"
|
||||||
|
#include "command.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "argv.h"
|
||||||
|
#include "file.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "machine.h"
|
||||||
|
#include "show.h"
|
||||||
|
#include "target_set.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
set_verbose_level(argc, argv);
|
||||||
|
Argv args {};
|
||||||
|
args.req("filenames", "Input files in Klammertext format. ", "'list'");
|
||||||
|
args.opt("s", "Text processed before input files.", "input-string", "", "'text'");
|
||||||
|
args.opt("t","Output target; default is general (unspecified)", "target",
|
||||||
|
Target_set::general_name, "'word'");
|
||||||
|
args.opt("o", "Output basename; meaning and default defined by target.", "basename",
|
||||||
|
"", "'word'");
|
||||||
|
args.opt("k", "File containing the klammerset definition; default is the Standard Klammer Set. With a value of \"none\", no klammerset is loaded.",
|
||||||
|
"pathname", "", "'word'");
|
||||||
|
args.flag("d", "Display the output to the screen, rather than writing files.");
|
||||||
|
args.flag("m", "Show the Klammermachine state at the beginning of processing.");
|
||||||
|
args.opt("v", "'verbosity'", "degree", "0", "'verbosity'");
|
||||||
|
|
||||||
|
if (show_usage(argc, argv)) {
|
||||||
|
args.usage(file_basename(argv[0]));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
args.parse(argc, argv);
|
||||||
|
if (verbose_level > 0) {
|
||||||
|
args.describe();
|
||||||
|
}
|
||||||
|
std::string input_text = args.as_string("s");
|
||||||
|
std::vector<std::string> input_filenames = args.as_vector("filenames");
|
||||||
|
|
||||||
|
if (input_text.empty() && input_filenames.empty()) {
|
||||||
|
throw Argument_error(
|
||||||
|
"You must specify input filenames and/or text", Locator());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [target, output_dir, output_basename, output_filename,
|
||||||
|
write_files, display_only] =
|
||||||
|
parse_args(input_filenames, args.as_string("t"), args.as_string("o"),
|
||||||
|
args.as_bool("d"));
|
||||||
|
|
||||||
|
if (*(output_filename.end() - 1) == '*'
|
||||||
|
&& !display_only) {
|
||||||
|
throw Argument_error(
|
||||||
|
"You must specify an output target or "
|
||||||
|
"display the results with the \"-d\" flag.",
|
||||||
|
Locator());
|
||||||
|
}
|
||||||
|
|
||||||
|
Machine M;
|
||||||
|
M.m_state.open_frame("ktext");
|
||||||
|
M.m_state.set("K_target", target);
|
||||||
|
M.m_state.set("K_output_dir", output_dir);
|
||||||
|
M.m_state.set("K_output_basename", output_basename);
|
||||||
|
M.m_state.set("K_stdout_only", display_only ? "true" : "false");
|
||||||
|
M.m_state.set("K_input_filenames", join(input_filenames, " "));
|
||||||
|
M.m_state.set("K_verbose_level", std::to_string(verbose_level));
|
||||||
|
|
||||||
|
if (!input_filenames.empty()) {
|
||||||
|
M.m_state.set(
|
||||||
|
"K_input_dir",absolute_pathname(file_directory(input_filenames[0])));
|
||||||
|
} else {
|
||||||
|
M.m_state.set("K_input_dir", fs::current_path().string());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string klammerset_filename = args.as_string("k");
|
||||||
|
if (klammerset_filename != "none") {
|
||||||
|
if (klammerset_filename.empty()) {
|
||||||
|
klammerset_filename = M.m_state.value("KLAMMERTEXT_HOME") + "/sks/sks.k";
|
||||||
|
}
|
||||||
|
K::log(1, "Reading klammerset filename: " + klammerset_filename);
|
||||||
|
M.read(fs::path(klammerset_filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!input_text.empty()) {
|
||||||
|
M.read(input_text + "\n");
|
||||||
|
}
|
||||||
|
for (auto p : input_filenames) {
|
||||||
|
M.read(fs::path(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.as_bool("m")) {
|
||||||
|
std::cout << M << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result = trim(M.apply(target));
|
||||||
|
if (display_only && !result.empty()) {
|
||||||
|
std::cout << result << "\n";
|
||||||
|
} else if (!result.empty()) {
|
||||||
|
msg() << "Output filename: " << output_filename << "\n";
|
||||||
|
string_to_file(output_filename, result + "\n");
|
||||||
|
(void)K::log(1, "Wrote file: " + output_filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Error& err) {
|
||||||
|
err.print_message();
|
||||||
|
std::cout << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
183
doc/edit/emacs/README.md
Normal file
183
doc/edit/emacs/README.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# Emacs mode for Klammertext
|
||||||
|
|
||||||
|
`klammertext-mode.el` is an Emacs major mode for editing Klammertext files. It
|
||||||
|
helps Klammertext authors see the structure of klammer application through
|
||||||
|
syntax highlighting.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Put the `emacs` directory somewhere on your system, then tell Emacs where it is
|
||||||
|
and load the mode. Add to `~/.emacs.d/init.el`:
|
||||||
|
|
||||||
|
```elisp
|
||||||
|
(add-to-list 'load-path "full-pathname-of-the-emacs-directory")
|
||||||
|
(require 'klammertext-mode)
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `full-pathname-of-the-emacs-directory` with the full path to the
|
||||||
|
directory that contains `klammertext-mode.el`.
|
||||||
|
|
||||||
|
The mode auto-activates for `.kt` and `.k` files. (The `.k` / `.kt` distinction
|
||||||
|
is a filing convention, not a lexical one — the same mode serves both.) You can
|
||||||
|
also switch to it manually with `M-x klammertext-mode`.
|
||||||
|
|
||||||
|
## What it highlights
|
||||||
|
|
||||||
|
**Text-removal ("ignore") constructs** — in two independently chosen colors,
|
||||||
|
one for the *removed content*, one for the *marker characters*:
|
||||||
|
|
||||||
|
| Construct | Meaning |
|
||||||
|
|--------------|----------------------------------|
|
||||||
|
| `#` ... | remove to end of line |
|
||||||
|
| `##` ... | remove to end of buffer |
|
||||||
|
| `#[ ... ]#` | remove enclosed text (nestable) |
|
||||||
|
|
||||||
|
**Klammer applications** — in two independent colors: one for *opening* a
|
||||||
|
klammer, one for *closing* it. The `@` and the name of an opening are one
|
||||||
|
syntactic unit and share the opening color; the close (named or bare) gets the
|
||||||
|
closing color, so you always have visual confirmation of where a klammer ends:
|
||||||
|
|
||||||
|
| Form | Color | Meaning |
|
||||||
|
|-----------|---------|----------------------------------|
|
||||||
|
| `@name` | opening | opening `@` + name (one unit) |
|
||||||
|
| `name@` | closing | named closing delimiter |
|
||||||
|
| `@` | closing | bare closing delimiter |
|
||||||
|
|
||||||
|
In the abbreviated form `@name-arg1-arg2` (equivalent to `@name arg1 | arg2 @`)
|
||||||
|
only the name is colored — the name ends at the first hyphen, and the
|
||||||
|
hyphen-separated arguments stay plain, just as `arg1`/`arg2` would be plain in
|
||||||
|
the long form.
|
||||||
|
|
||||||
|
Because a named closing carries the closing color across its name too, long
|
||||||
|
klammers that name their closing delimiter (`@document ... document@`) stand
|
||||||
|
out — which is exactly where naming the closing delimiter earns its keep
|
||||||
|
(accurate unmatched-delimiter error messages). Short bodies (`@i word @`) are
|
||||||
|
conventionally left with a bare `@` to keep the text uncluttered.
|
||||||
|
|
||||||
|
**Klammer definitions** (`@@`) are highlighted the same way, in their own pair
|
||||||
|
of colors — so definitions read as distinct from applications at a glance:
|
||||||
|
|
||||||
|
| Form | Color | Meaning |
|
||||||
|
|------------|---------|-----------------------------------|
|
||||||
|
| `@@name` | opening | opening `@@` + name (one unit) |
|
||||||
|
| `name@@` | closing | named closing delimiter |
|
||||||
|
| `@@` | closing | bare closing delimiter |
|
||||||
|
|
||||||
|
The name ends at the first non-name character, so a target suffix like
|
||||||
|
`@@name.html` colors only `@@name` and leaves `.html` plain. A definition's
|
||||||
|
body (between `@@name` and the closing `@@`) is highlighted like ordinary
|
||||||
|
Klammertext — e.g. an `@i … @` inside it shows as a normal application.
|
||||||
|
|
||||||
|
**System/target commands** (`@@@`) — `@@@target`, `@@@argtype`, `@@@state` — get
|
||||||
|
a third pair of colors, so the three `@`-levels (application, definition, system)
|
||||||
|
are visually distinct:
|
||||||
|
|
||||||
|
| Form | Color | Meaning |
|
||||||
|
|------------|---------|-----------------------------------|
|
||||||
|
| `@@@name` | opening | opening `@@@` + name (one unit) |
|
||||||
|
| `name@@@` | closing | named closing delimiter |
|
||||||
|
| `@@@` | closing | bare closing delimiter |
|
||||||
|
|
||||||
|
`@@@` commands do not nest, so each delimiter is colored independently; their
|
||||||
|
bodies (`| … |` option lists) are highlighted as ordinary Klammertext.
|
||||||
|
|
||||||
|
## The eight faces
|
||||||
|
|
||||||
|
All eight faces are defined by the `defconst klammertext--palette` at the
|
||||||
|
beginning of `klammertext-mode.el`.
|
||||||
|
|
||||||
|
| Face | Applies to |
|
||||||
|
|-----------------------------------|-------------------------------------|
|
||||||
|
| `klammertext-ignored-face` | removed content |
|
||||||
|
| `klammertext-marker-face` | `#`, `##`, `#[`, `]#` |
|
||||||
|
| `klammertext-klammer-open-face` | an application opening `@name` |
|
||||||
|
| `klammertext-klammer-close-face` | an application close `name@` or `@` |
|
||||||
|
| `klammertext-def-open-face` | a definition opening `@@name` |
|
||||||
|
| `klammertext-def-close-face` | a definition close `name@@` or `@@` |
|
||||||
|
| `klammertext-system-open-face` | a system opening `@@@name` |
|
||||||
|
| `klammertext-system-close-face` | a system close `name@@@` or `@@@` |
|
||||||
|
|
||||||
|
To experiment with a color (evaluate in `*scratch*`, or add to your init):
|
||||||
|
|
||||||
|
```elisp
|
||||||
|
(set-face-foreground 'klammertext-marker-face "cyan")
|
||||||
|
(set-face-foreground 'klammertext-system-close-face "chocolate4")
|
||||||
|
```
|
||||||
|
|
||||||
|
or `M-x customize-face RET klammertext-marker-face RET`.
|
||||||
|
|
||||||
|
## Matching delimiters (show-paren)
|
||||||
|
|
||||||
|
With `show-paren-mode` on (the default in Emacs 28+), placing point on a klammer
|
||||||
|
**application** delimiter highlights its partner, in both directions: on an
|
||||||
|
opening `@name` it highlights the closing `@`/`name@`, and on a close it
|
||||||
|
highlights the opening `@name`. Nesting is respected — in `@a @b x @ @`, the
|
||||||
|
outer `@a` matches the last `@`, not the first.
|
||||||
|
|
||||||
|
Matching covers applications only (`@`), not `@@`/`@@@`, since that is where
|
||||||
|
paired delimiters matter most. The matcher steps over `@@`/`@@@`, removed text,
|
||||||
|
other literal spans and escaped `^@`; the abbreviated `@name-arg` form has no
|
||||||
|
closing delimiter, so nothing is highlighted on it.
|
||||||
|
|
||||||
|
**Literal klammers** (those in `klammertext-literal-klammers`, e.g. `@code`) are
|
||||||
|
closed with the full `NAME@` form because their content is verbatim. These are
|
||||||
|
matched *by name* — `@code` ↔ `code@` — with the content treated as opaque, so a
|
||||||
|
stray `@` inside (`@code x @ y code@`) doesn't confuse the match, in either
|
||||||
|
direction. Register any klammer that declares a `literal` argument with
|
||||||
|
`(klammertext-add-literal-klammer "name")` in your init file so both its
|
||||||
|
highlighting and its delimiter matching work.
|
||||||
|
|
||||||
|
If a **named** close disagrees with its opening — e.g. `@doc … foo@` (should be
|
||||||
|
`doc@`) — the mismatched delimiter is shown in **bright red** (bold), and a
|
||||||
|
message describing the mismatch appears in the minibuffer, e.g.
|
||||||
|
|
||||||
|
> `Klammertext: closing foo@ does not match opening @doc`
|
||||||
|
|
||||||
|
An unbalanced delimiter (an opening with no close, or vice versa) is flagged the
|
||||||
|
same way. This turns the naming convention into a live check: name a long
|
||||||
|
klammer's closing delimiter and a typo'd or unbalanced name lights up
|
||||||
|
immediately. The red comes from `klammertext-mismatch-face`, which is remapped
|
||||||
|
over `show-paren-mismatch` **only in Klammertext buffers** (your global
|
||||||
|
`show-paren-mismatch` face is left untouched); customize it to taste.
|
||||||
|
|
||||||
|
This is wired in automatically (`show-paren-data-function`); you only need
|
||||||
|
`show-paren-mode` enabled. It relies on nothing in the syntax table — Klammertext
|
||||||
|
delimiters can't be expressed there — so it does not interfere with other
|
||||||
|
`@`/`#` characters.
|
||||||
|
|
||||||
|
### Jumping between matches
|
||||||
|
|
||||||
|
`klammertext-jump-to-match`, bound to **`C-c C-j`**, moves point to the matching
|
||||||
|
delimiter: from an opening `@name` to its close, and from a close back to its
|
||||||
|
opening `@name`. It uses the same matcher as the highlighting. The starting
|
||||||
|
position is pushed to the mark ring, so `C-u C-SPC` jumps back. (Also available
|
||||||
|
as `M-x klammertext-jump-to-match`.)
|
||||||
|
|
||||||
|
## Literal klammers
|
||||||
|
|
||||||
|
Inside a `literal` argument — for example the body of `@code ... code@` — `#`
|
||||||
|
and `@` are literal text, not Klammertext syntax. The mode highlights the
|
||||||
|
opening `@code` and closing `code@` but leaves the interior as normal text,
|
||||||
|
for any klammer registered in `klammertext-literal-klammers`. `@code` is
|
||||||
|
registered by default.
|
||||||
|
|
||||||
|
If you define your own klammer with a `literal` parameter (a relatively
|
||||||
|
advanced action — see the `literal` argument type in the project
|
||||||
|
documentation), register it in your init file:
|
||||||
|
|
||||||
|
```elisp
|
||||||
|
(klammertext-add-literal-klammer "myverbatim")
|
||||||
|
```
|
||||||
|
|
||||||
|
Because the list is consulted at fontification time, registering a klammer
|
||||||
|
while a buffer is already open takes effect after `M-x font-lock-update` (or
|
||||||
|
re-visiting the file).
|
||||||
|
|
||||||
|
## Known limitations (deliberate, for now)
|
||||||
|
|
||||||
|
- Unescaped `@` is always treated as a delimiter (as the Klammermachine does),
|
||||||
|
so an `@` in prose that is *not* meant as a klammer — e.g. an email address
|
||||||
|
written `foo@bar` instead of `foo^@bar` — will be highlighted. This reflects
|
||||||
|
what the machine actually sees.
|
||||||
|
- Very large multiline blocks or literal spans edited far from their opening
|
||||||
|
may occasionally need `M-x font-lock-update` to re-highlight correctly.
|
||||||
680
doc/edit/emacs/klammertext-mode.el
Normal file
680
doc/edit/emacs/klammertext-mode.el
Normal file
@@ -0,0 +1,680 @@
|
|||||||
|
;;; klammertext-mode.el --- Major mode for Klammertext files -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;; An Emacs major mode for editing Klammertext files. It highlights:
|
||||||
|
;;
|
||||||
|
;; Text-removal ("ignore") constructs:
|
||||||
|
;; # ... remove to end of line
|
||||||
|
;; ## ... remove to end of buffer
|
||||||
|
;; #[ ... ]# remove enclosed text (nestable)
|
||||||
|
;;
|
||||||
|
;; Klammer applications:
|
||||||
|
;; @name opening delimiter + name
|
||||||
|
;; name@ named closing delimiter
|
||||||
|
;; @ bare closing delimiter
|
||||||
|
;;
|
||||||
|
;; Klammer definitions (@@), analogous to applications:
|
||||||
|
;; @@name opening delimiter + name
|
||||||
|
;; name@@ named closing delimiter
|
||||||
|
;; @@ bare closing delimiter
|
||||||
|
;;
|
||||||
|
;; System/target commands (@@@), analogous again:
|
||||||
|
;; @@@name opening delimiter + name
|
||||||
|
;; name@@@ named closing delimiter
|
||||||
|
;; @@@ bare closing delimiter
|
||||||
|
;;
|
||||||
|
;; Independent faces carry each pair of colors: removed content vs. the removal
|
||||||
|
;; marker characters; and, for applications (@name), definitions (@@name) and
|
||||||
|
;; system commands (@@@name), each construct's opening vs. its close.
|
||||||
|
;;
|
||||||
|
;; The same mode serves both .kt (content) and .k (klammer definition) files:
|
||||||
|
;; the .k/.kt split is a filing convention, not a lexical difference.
|
||||||
|
;;
|
||||||
|
;; Everything is driven by ONE left-to-right scanner (`klammertext--fontify').
|
||||||
|
;; That is what makes the interactions correct: inside removed text and inside
|
||||||
|
;; literal-klammer spans the scanner jumps over the content, so it is never
|
||||||
|
;; re-interpreted as klammers or comments.
|
||||||
|
;;
|
||||||
|
;; It also matches klammer APPLICATION delimiters for `show-paren-mode' (both
|
||||||
|
;; directions, with mismatched-name flagging); see the show-paren section below.
|
||||||
|
;;
|
||||||
|
;; Not handled yet (deliberately):
|
||||||
|
;; * The bodies of definitions (@@) and system commands (@@@) are highlighted
|
||||||
|
;; like ordinary Klammertext (an @i ... @ inside shows as a normal
|
||||||
|
;; application), rather than being treated specially.
|
||||||
|
;; * Inside a `literal' argument (e.g. @code ... code@) neither # nor @ is a
|
||||||
|
;; marker. The scanner highlights the opening @code and closing code@ but
|
||||||
|
;; leaves the interior as normal text, for any klammer registered in
|
||||||
|
;; `klammertext-literal-klammers'.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(defgroup klammertext nil
|
||||||
|
"Editing Klammertext files."
|
||||||
|
:group 'text)
|
||||||
|
|
||||||
|
;; --- Faces: colors from a central palette table -------------------------
|
||||||
|
;;
|
||||||
|
;; All highlighting colors live in one table, `klammertext--palette', so they
|
||||||
|
;; can be tuned in a single place. Each face carries a DARK-background value
|
||||||
|
;; and a LIGHT-background value; Emacs picks automatically from the frame or
|
||||||
|
;; terminal background (there is no "dark mode" toggle to set).
|
||||||
|
;;
|
||||||
|
;; The DARK column is a systematic scheme (developed for the Sublime Text port):
|
||||||
|
;; three tier hues — application blue, definition green, system orange — each
|
||||||
|
;; opening bright and its close the same hue at 0.80 intensity.
|
||||||
|
;;
|
||||||
|
;; The LIGHT column is a hybrid tuned by eye: the three delimiter opens are the
|
||||||
|
;; original Emacs colors (RoyalBlue2 / green4 / orange3), each close = 0.60 x its
|
||||||
|
;; open (from the open name's X11 RGB #436eee/#008b00/#cd8500); the marker and
|
||||||
|
;; mismatch light values are the systematic ones; the ignored (gray) light value
|
||||||
|
;; is the systematic gray reduced by 0.90 (#9a9a9a -> #8b8b8b).
|
||||||
|
|
||||||
|
(defconst klammertext--palette
|
||||||
|
;; (face dark light description [extra-attrs])
|
||||||
|
'((klammertext-ignored-face "#8a8272" "#8b8b8b" "removed (ignored) content")
|
||||||
|
(klammertext-marker-face "#ff6b6b" "#994040" "removal markers # ## #[ ]#")
|
||||||
|
(klammertext-klammer-open-face "#89ddff" "RoyalBlue2" "application opening @name")
|
||||||
|
(klammertext-klammer-close-face "#6eb1cc" "#28428f" "application close name@ or bare @")
|
||||||
|
(klammertext-def-open-face "#c3e88d" "green4" "definition opening @@name")
|
||||||
|
(klammertext-def-close-face "#9cba71" "#005300" "definition close name@@ or bare @@")
|
||||||
|
(klammertext-system-open-face "#ffab70" "orange3" "system opening @@@name")
|
||||||
|
(klammertext-system-close-face "#cc895a" "#7b5000" "system close name@@@ or bare @@@")
|
||||||
|
(klammertext-mismatch-face "#ff5555" "#c02020" "mismatched/unbalanced delimiter"
|
||||||
|
(:weight bold)))
|
||||||
|
"Klammertext face colors: (FACE DARK LIGHT DESCRIPTION [EXTRA-ATTRS]).
|
||||||
|
DARK is the foreground on dark backgrounds, LIGHT on light backgrounds; each
|
||||||
|
face is generated with both. EXTRA-ATTRS, if present, is a plist merged into
|
||||||
|
both grounds. See the notes above the table.")
|
||||||
|
|
||||||
|
;; Generate the faces from the table. Using `custom-declare-face' (what
|
||||||
|
;; `defface' expands to) keeps each face customizable via M-x customize-face.
|
||||||
|
(dolist (entry klammertext--palette)
|
||||||
|
(let ((face (nth 0 entry))
|
||||||
|
(dark (nth 1 entry))
|
||||||
|
(light (nth 2 entry))
|
||||||
|
(desc (nth 3 entry))
|
||||||
|
(extra (nth 4 entry)))
|
||||||
|
(custom-declare-face
|
||||||
|
face
|
||||||
|
`((((background dark)) :foreground ,dark ,@extra)
|
||||||
|
(((background light)) :foreground ,light ,@extra))
|
||||||
|
(format "Klammertext highlighting for %s.\nColor is set from the `klammertext--palette' table." desc)
|
||||||
|
:group 'klammertext)))
|
||||||
|
|
||||||
|
;; --- Klammers whose literal content must not be interpreted -------------
|
||||||
|
|
||||||
|
(defcustom klammertext-literal-klammers nil
|
||||||
|
"Names of klammers whose content is a `literal' argument.
|
||||||
|
Such a klammer must be closed with the full NAME@ form (e.g. @code ... code@),
|
||||||
|
because its content is verbatim. This list is consulted in two places:
|
||||||
|
|
||||||
|
* Font-lock leaves the verbatim interior as normal text (#, @, etc. inside
|
||||||
|
are not interpreted).
|
||||||
|
* Delimiter matching (`show-paren-mode' and `klammertext-jump-to-match')
|
||||||
|
pairs the opening @NAME with its closing NAME@ *by name* rather than by
|
||||||
|
depth counting, so a literal close is matched even though its content may
|
||||||
|
contain unbalanced @ characters.
|
||||||
|
|
||||||
|
Any klammer that declares a `literal' argument should be registered here.
|
||||||
|
Register one with `klammertext-add-literal-klammer', e.g. in your init file:
|
||||||
|
(klammertext-add-literal-klammer \"mycode\")"
|
||||||
|
:type '(repeat string)
|
||||||
|
:group 'klammertext)
|
||||||
|
|
||||||
|
;; SYNC: the Sublime Text port in doc/sublime/ duplicates this list statically
|
||||||
|
;; (a Sublime syntax/plugin cannot read this Emacs defcustom). When you add or
|
||||||
|
;; remove a literal klammer, mirror it in BOTH:
|
||||||
|
;; * LITERAL_KLAMMERS in doc/sublime/Klammertext.py
|
||||||
|
;; * the @NAME literal rule + literal_NAME context in
|
||||||
|
;; doc/sublime/Klammertext.sublime-syntax
|
||||||
|
;; All three are currently seeded with just "code".
|
||||||
|
|
||||||
|
(defun klammertext-add-literal-klammer (name)
|
||||||
|
"Register NAME as a klammer whose literal content must not be interpreted.
|
||||||
|
NAME is the klammer name without the leading @ (e.g. \"code\")."
|
||||||
|
(add-to-list 'klammertext-literal-klammers name))
|
||||||
|
|
||||||
|
;; Seed the list through the same entry point future users will use.
|
||||||
|
(klammertext-add-literal-klammer "code")
|
||||||
|
|
||||||
|
;; --- Helpers -----------------------------------------------------------
|
||||||
|
|
||||||
|
(defun klammertext--escaped-p (pos)
|
||||||
|
"Non-nil if the character at POS is escaped by an odd run of ^ before it.
|
||||||
|
In Klammertext `^#' and `^@' are literal, so such a character is not a
|
||||||
|
marker or a delimiter."
|
||||||
|
(let ((n 0) (i (1- pos)))
|
||||||
|
(while (and (>= i (point-min)) (eq (char-after i) ?^))
|
||||||
|
(setq n (1+ n) i (1- i)))
|
||||||
|
(= (mod n 2) 1)))
|
||||||
|
|
||||||
|
(defun klammertext--name-char-p (ch)
|
||||||
|
"Non-nil if CH can be part of a klammer name (letter, digit or _).
|
||||||
|
A hyphen is NOT a name character: in the abbreviated form
|
||||||
|
@name-arg1-arg2 the hyphen separates the name from its arguments, so a
|
||||||
|
klammer name ends at the first hyphen."
|
||||||
|
(and ch (or (and (>= ch ?a) (<= ch ?z))
|
||||||
|
(and (>= ch ?A) (<= ch ?Z))
|
||||||
|
(and (>= ch ?0) (<= ch ?9))
|
||||||
|
(eq ch ?_))))
|
||||||
|
|
||||||
|
(defun klammertext--block-end (from)
|
||||||
|
"Return the position just after the ]# that closes a #[ block.
|
||||||
|
FROM is the position just after the opening #[. Counts nested #[ ... ]#
|
||||||
|
pairs; returns `point-max' if the block is never closed."
|
||||||
|
(goto-char from)
|
||||||
|
(let ((depth 1))
|
||||||
|
(while (and (> depth 0)
|
||||||
|
(re-search-forward "#\\[\\|]#" nil t))
|
||||||
|
(if (string= (match-string 0) "#[")
|
||||||
|
(setq depth (1+ depth))
|
||||||
|
(setq depth (1- depth))))
|
||||||
|
(if (> depth 0) (point-max) (point))))
|
||||||
|
|
||||||
|
(defun klammertext--set-match (wb we &rest groups)
|
||||||
|
"Set match data covering WB..WE with up to nine GROUPS.
|
||||||
|
Each group is a cons (BEG . END), or nil for an absent group (whose
|
||||||
|
highlight spec must use LAXMATCH)."
|
||||||
|
(let ((md (list wb we)))
|
||||||
|
(dotimes (_ 9)
|
||||||
|
(let ((g (pop groups)))
|
||||||
|
(setq md (append md (if g (list (car g) (cdr g)) (list nil nil))))))
|
||||||
|
(set-match-data md)))
|
||||||
|
|
||||||
|
;; --- Token emitters (called by the scanner) ----------------------------
|
||||||
|
;; Each returns non-nil when it has emitted a highlight token (and set the
|
||||||
|
;; match data + moved point past it), or nil to let the scanner keep going.
|
||||||
|
;; Groups: 1 removal-marker 2 removed-content 3 removal-close-marker
|
||||||
|
;; 4 app-open (@name) 5 app-close (name@ or bare @)
|
||||||
|
;; 6 def-open (@@name) 7 def-close (name@@ or bare @@)
|
||||||
|
;; 8 sys-open (@@@name) 9 sys-close (name@@@ or bare @@@)
|
||||||
|
|
||||||
|
(defun klammertext--emit-removal (pos _limit)
|
||||||
|
"POS is at a #. Point is at POS+1 on entry."
|
||||||
|
(let ((next (char-after (1+ pos))))
|
||||||
|
(cond
|
||||||
|
;; ## ... end of buffer
|
||||||
|
((eq next ?#)
|
||||||
|
(klammertext--set-match pos (point-max)
|
||||||
|
(cons pos (+ pos 2))
|
||||||
|
(cons (+ pos 2) (point-max))
|
||||||
|
nil nil nil)
|
||||||
|
(put-text-property pos (point-max) 'font-lock-multiline t)
|
||||||
|
(goto-char (point-max))
|
||||||
|
t)
|
||||||
|
;; #[ ... ]# (nestable)
|
||||||
|
((eq next ?\[)
|
||||||
|
(let* ((end (klammertext--block-end (+ pos 2)))
|
||||||
|
(close (if (and (>= end (+ pos 4))
|
||||||
|
(eq (char-before end) ?#)
|
||||||
|
(eq (char-before (1- end)) ?\]))
|
||||||
|
(- end 2) end)))
|
||||||
|
(klammertext--set-match pos end
|
||||||
|
(cons pos (+ pos 2))
|
||||||
|
(cons (+ pos 2) close)
|
||||||
|
(cons close end)
|
||||||
|
nil nil)
|
||||||
|
(put-text-property pos end 'font-lock-multiline t)
|
||||||
|
(goto-char end)
|
||||||
|
t))
|
||||||
|
;; #+ #/ #- are whitespace operators, NOT removals: keep scanning.
|
||||||
|
((memq next '(?+ ?/ ?-))
|
||||||
|
nil)
|
||||||
|
;; # ... end of line
|
||||||
|
(t
|
||||||
|
(let ((eol (line-end-position)))
|
||||||
|
(klammertext--set-match pos eol
|
||||||
|
(cons pos (1+ pos))
|
||||||
|
(cons (1+ pos) eol)
|
||||||
|
nil nil nil)
|
||||||
|
(goto-char eol)
|
||||||
|
t)))))
|
||||||
|
|
||||||
|
(defun klammertext--emit-klammer (pos _limit)
|
||||||
|
"POS is at an @. Point is at POS+1 on entry.
|
||||||
|
Dispatch by the length of the @-run at POS: a single @ is a klammer
|
||||||
|
APPLICATION (@name / name@ / @); @@ is a klammer DEFINITION (@@name /
|
||||||
|
name@@ / @@); @@@ is a system/target command (@@@name / name@@@ / @@@)."
|
||||||
|
(let ((before (and (> pos (point-min)) (char-before pos))))
|
||||||
|
(cond
|
||||||
|
;; Mid-run @ (previous char is @): the run's first @ drives everything,
|
||||||
|
;; so skip this one (e.g. the @ after an escaped ^@).
|
||||||
|
((eq before ?@)
|
||||||
|
(goto-char (1+ pos))
|
||||||
|
nil)
|
||||||
|
;; @@@ (or longer): system/target command (@@@target, @@@argtype, ...).
|
||||||
|
((and (eq (char-after (1+ pos)) ?@)
|
||||||
|
(eq (char-after (+ pos 2)) ?@))
|
||||||
|
(klammertext--emit-system pos before))
|
||||||
|
;; @@ : klammer definition delimiter
|
||||||
|
((eq (char-after (1+ pos)) ?@)
|
||||||
|
(klammertext--emit-def pos before))
|
||||||
|
;; single @ : klammer application
|
||||||
|
(t
|
||||||
|
(klammertext--emit-application pos before)))))
|
||||||
|
|
||||||
|
(defun klammertext--emit-application (pos before)
|
||||||
|
"Emit a single-@ klammer-application token at POS (groups 4 open / 5 close).
|
||||||
|
BEFORE is the character before POS."
|
||||||
|
(let ((after (char-after (1+ pos))))
|
||||||
|
(cond
|
||||||
|
;; @name : opening application (the @ and name are one unit). The name
|
||||||
|
;; ends at the first hyphen; any -arg1-arg2 abbreviation stays uncolored.
|
||||||
|
((klammertext--name-char-p after)
|
||||||
|
(goto-char (1+ pos))
|
||||||
|
(skip-chars-forward "A-Za-z0-9_")
|
||||||
|
(let* ((name-end (point))
|
||||||
|
(name (buffer-substring-no-properties (1+ pos) name-end)))
|
||||||
|
;; For a literal klammer, first locate the closing NAME@ and move the
|
||||||
|
;; scanner to its start (skipping the verbatim interior); the closing
|
||||||
|
;; is highlighted on the next scanner call. The search must happen
|
||||||
|
;; BEFORE `klammertext--set-match', because `re-search-forward'
|
||||||
|
;; clobbers the match data.
|
||||||
|
(if (member name klammertext-literal-klammers)
|
||||||
|
(let ((close (concat (regexp-quote name) "@")))
|
||||||
|
(if (re-search-forward close nil t)
|
||||||
|
(let ((close-end (point)))
|
||||||
|
(put-text-property pos close-end 'font-lock-multiline t)
|
||||||
|
(goto-char (- close-end (length name) 1)))
|
||||||
|
(put-text-property pos (point-max) 'font-lock-multiline t)
|
||||||
|
(goto-char (point-max))))
|
||||||
|
(goto-char name-end))
|
||||||
|
;; Set the match data for the opening LAST, so it survives to the
|
||||||
|
;; highlight step.
|
||||||
|
(klammertext--set-match pos name-end
|
||||||
|
nil nil nil
|
||||||
|
(cons pos name-end) ; 4: @name opening
|
||||||
|
nil)
|
||||||
|
t))
|
||||||
|
;; A @ preceded by name characters is either a named close NAME@, or the
|
||||||
|
;; bare close of a compact no-argument application @name@. They differ by
|
||||||
|
;; what precedes the name run: an @ there means the name belongs to the
|
||||||
|
;; opening (@name@), so this @ is a bare close and only it is coloured;
|
||||||
|
;; otherwise the whole NAME@ is the closing token.
|
||||||
|
((klammertext--name-char-p before)
|
||||||
|
(let ((name-start (save-excursion
|
||||||
|
(goto-char pos)
|
||||||
|
(skip-chars-backward "A-Za-z0-9_")
|
||||||
|
(point))))
|
||||||
|
(if (and (> name-start (point-min))
|
||||||
|
(eq (char-before name-start) ?@))
|
||||||
|
(klammertext--set-match pos (1+ pos) ; @name@ -> bare @
|
||||||
|
nil nil nil nil
|
||||||
|
(cons pos (1+ pos)))
|
||||||
|
(klammertext--set-match name-start (1+ pos) ; NAME@ named close
|
||||||
|
nil nil nil nil
|
||||||
|
(cons name-start (1+ pos))))
|
||||||
|
(goto-char (1+ pos))
|
||||||
|
t))
|
||||||
|
;; bare @ : unnamed closing delimiter
|
||||||
|
(t
|
||||||
|
(klammertext--set-match pos (1+ pos)
|
||||||
|
nil nil nil
|
||||||
|
nil
|
||||||
|
(cons pos (1+ pos))) ; 5: bare @ close
|
||||||
|
(goto-char (1+ pos))
|
||||||
|
t))))
|
||||||
|
|
||||||
|
(defun klammertext--emit-def (pos before)
|
||||||
|
"Emit a @@ klammer-definition delimiter token at POS (groups 6 open / 7 close).
|
||||||
|
POS and POS+1 are both @. BEFORE is the character before POS. Mirrors
|
||||||
|
`klammertext--emit-application', with @@ in place of @."
|
||||||
|
(let ((after (char-after (+ pos 2)))) ; char right after the @@
|
||||||
|
(cond
|
||||||
|
;; @@name : opening definition (the @@ and name are one unit). The name
|
||||||
|
;; ends at the first non-name char (a space, or the .target suffix).
|
||||||
|
((klammertext--name-char-p after)
|
||||||
|
(goto-char (+ pos 2))
|
||||||
|
(skip-chars-forward "A-Za-z0-9_")
|
||||||
|
(let ((name-end (point)))
|
||||||
|
(klammertext--set-match pos name-end
|
||||||
|
nil nil nil nil nil
|
||||||
|
(cons pos name-end) ; 6: @@name opening
|
||||||
|
nil)
|
||||||
|
(goto-char name-end)
|
||||||
|
t))
|
||||||
|
;; name@@ (named close) or the bare close of a compact no-body @@name@@.
|
||||||
|
;; As with applications, an @ before the name run means the name belongs
|
||||||
|
;; to the opening, so only the @@ is the closing token.
|
||||||
|
((klammertext--name-char-p before)
|
||||||
|
(let ((name-start (save-excursion
|
||||||
|
(goto-char pos)
|
||||||
|
(skip-chars-backward "A-Za-z0-9_")
|
||||||
|
(point))))
|
||||||
|
(if (and (> name-start (point-min))
|
||||||
|
(eq (char-before name-start) ?@))
|
||||||
|
(klammertext--set-match pos (+ pos 2) ; @@name@@ -> bare @@
|
||||||
|
nil nil nil nil nil nil
|
||||||
|
(cons pos (+ pos 2)))
|
||||||
|
(klammertext--set-match name-start (+ pos 2) ; NAME@@ named close
|
||||||
|
nil nil nil nil nil nil
|
||||||
|
(cons name-start (+ pos 2))))
|
||||||
|
(goto-char (+ pos 2))
|
||||||
|
t))
|
||||||
|
;; bare @@ : unnamed closing delimiter
|
||||||
|
(t
|
||||||
|
(klammertext--set-match pos (+ pos 2)
|
||||||
|
nil nil nil nil nil nil
|
||||||
|
(cons pos (+ pos 2))) ; 7: bare @@ close
|
||||||
|
(goto-char (+ pos 2))
|
||||||
|
t))))
|
||||||
|
|
||||||
|
(defun klammertext--emit-system (pos before)
|
||||||
|
"Emit a @@@ system/target delimiter token at POS (groups 8 open / 9 close).
|
||||||
|
POS, POS+1 and POS+2 are all @. BEFORE is the character before POS. These
|
||||||
|
commands (@@@target, @@@argtype, @@@state) do not nest, so each delimiter is
|
||||||
|
coloured independently, mirroring `klammertext--emit-def' with @@@ for @@."
|
||||||
|
(let ((after (char-after (+ pos 3)))) ; char right after the @@@
|
||||||
|
(cond
|
||||||
|
;; @@@name : opening command (the @@@ and name are one unit).
|
||||||
|
((klammertext--name-char-p after)
|
||||||
|
(goto-char (+ pos 3))
|
||||||
|
(skip-chars-forward "A-Za-z0-9_")
|
||||||
|
(let ((name-end (point)))
|
||||||
|
(klammertext--set-match pos name-end
|
||||||
|
nil nil nil nil nil nil nil
|
||||||
|
(cons pos name-end) ; 8: @@@name opening
|
||||||
|
nil)
|
||||||
|
(goto-char name-end)
|
||||||
|
t))
|
||||||
|
;; name@@@ (named close) or the bare close of a compact @@@name@@@.
|
||||||
|
((klammertext--name-char-p before)
|
||||||
|
(let ((name-start (save-excursion
|
||||||
|
(goto-char pos)
|
||||||
|
(skip-chars-backward "A-Za-z0-9_")
|
||||||
|
(point))))
|
||||||
|
(if (and (> name-start (point-min))
|
||||||
|
(eq (char-before name-start) ?@))
|
||||||
|
(klammertext--set-match pos (+ pos 3) ; @@@name@@@ -> bare @@@
|
||||||
|
nil nil nil nil nil nil nil nil
|
||||||
|
(cons pos (+ pos 3)))
|
||||||
|
(klammertext--set-match name-start (+ pos 3) ; NAME@@@ named close
|
||||||
|
nil nil nil nil nil nil nil nil
|
||||||
|
(cons name-start (+ pos 3))))
|
||||||
|
(goto-char (+ pos 3))
|
||||||
|
t))
|
||||||
|
;; bare @@@ : unnamed closing delimiter
|
||||||
|
(t
|
||||||
|
(klammertext--set-match pos (+ pos 3)
|
||||||
|
nil nil nil nil nil nil nil nil
|
||||||
|
(cons pos (+ pos 3))) ; 9: bare @@@ close
|
||||||
|
(goto-char (+ pos 3))
|
||||||
|
t))))
|
||||||
|
|
||||||
|
;; --- The single scanning matcher ---------------------------------------
|
||||||
|
|
||||||
|
(defun klammertext--fontify (limit)
|
||||||
|
"Font-lock matcher: emit the next Klammertext token up to LIMIT.
|
||||||
|
Removed and literal-klammer regions are jumped over, so their interiors
|
||||||
|
are never re-interpreted."
|
||||||
|
(let ((result nil))
|
||||||
|
(while (and (not result)
|
||||||
|
(re-search-forward "[#@]" limit t))
|
||||||
|
(let* ((pos (1- (point)))
|
||||||
|
(ch (char-after pos)))
|
||||||
|
(setq result
|
||||||
|
(cond
|
||||||
|
((klammertext--escaped-p pos) nil) ; ^# or ^@
|
||||||
|
((eq ch ?#) (klammertext--emit-removal pos limit))
|
||||||
|
(t (klammertext--emit-klammer pos limit))))))
|
||||||
|
result))
|
||||||
|
|
||||||
|
(defvar klammertext-font-lock-keywords
|
||||||
|
'((klammertext--fontify
|
||||||
|
(1 'klammertext-marker-face t t)
|
||||||
|
(2 'klammertext-ignored-face t t)
|
||||||
|
(3 'klammertext-marker-face t t)
|
||||||
|
(4 'klammertext-klammer-open-face t t)
|
||||||
|
(5 'klammertext-klammer-close-face t t)
|
||||||
|
(6 'klammertext-def-open-face t t)
|
||||||
|
(7 'klammertext-def-close-face t t)
|
||||||
|
(8 'klammertext-system-open-face t t)
|
||||||
|
(9 'klammertext-system-close-face t t)))
|
||||||
|
"Font-lock keywords for `klammertext-mode'.")
|
||||||
|
|
||||||
|
;; --- show-paren support (klammer applications only) --------------------
|
||||||
|
;;
|
||||||
|
;; show-paren cannot use the syntax table for Klammertext (the same @ is both
|
||||||
|
;; open and close, delimiters are multi-character, and open/close is decided by
|
||||||
|
;; context), so matching is driven by `show-paren-data-function'. Only single-@
|
||||||
|
;; APPLICATION delimiters are matched: @name <-> its closing @ or name@. The
|
||||||
|
;; matcher steps over @@/@@@ runs, removed text, other literal spans and escaped
|
||||||
|
;; ^@; the abbreviated @name-arg form opens no span. A LITERAL klammer (one in
|
||||||
|
;; `klammertext-literal-klammers', e.g. @code) is matched by name — @code <->
|
||||||
|
;; code@ — with its verbatim content opaque, since a depth scan would miscount
|
||||||
|
;; unbalanced @ inside it. A named close name@ whose name disagrees with its
|
||||||
|
;; opening @name is reported as a mismatch.
|
||||||
|
|
||||||
|
(defun klammertext--at-run-end (pos)
|
||||||
|
"Return the position just after the run of @ that begins at POS."
|
||||||
|
(let ((p pos)) (while (eq (char-after p) ?@) (setq p (1+ p))) p))
|
||||||
|
|
||||||
|
(defun klammertext--next-app-delim (limit)
|
||||||
|
"From point, find the next single-@ application delimiter before LIMIT.
|
||||||
|
Step over @@/@@@ runs, removed text, literal spans, escaped ^@, and the
|
||||||
|
abbreviated @name-arg form (which opens no span). Move point past the
|
||||||
|
delimiter (or skipped region) and return (POS . KIND) with KIND `open or
|
||||||
|
`close, or nil when none is found."
|
||||||
|
(catch 'found
|
||||||
|
(while (re-search-forward "[@#]" limit t)
|
||||||
|
(let ((hit (1- (point))))
|
||||||
|
(cond
|
||||||
|
((klammertext--escaped-p hit)) ; ^@ / ^# : keep going
|
||||||
|
((eq (char-after hit) ?#) ; removal: step over it
|
||||||
|
(let ((next (char-after (1+ hit))))
|
||||||
|
(goto-char (cond ((eq next ?#) (point-max))
|
||||||
|
((eq next ?\[) (klammertext--block-end (+ hit 2)))
|
||||||
|
((memq next '(?+ ?/ ?-)) (1+ hit))
|
||||||
|
(t (line-end-position))))))
|
||||||
|
((eq (char-after (1+ hit)) ?@) ; @@ / @@@ : step over run
|
||||||
|
(goto-char (klammertext--at-run-end hit)))
|
||||||
|
((klammertext--name-char-p (char-after (1+ hit))) ; @name : opening?
|
||||||
|
(goto-char (1+ hit))
|
||||||
|
(skip-chars-forward "A-Za-z0-9_")
|
||||||
|
(let ((name (buffer-substring-no-properties (1+ hit) (point))))
|
||||||
|
(cond
|
||||||
|
((member name klammertext-literal-klammers) ; literal span: skip
|
||||||
|
(let ((close (concat (regexp-quote name) "@")))
|
||||||
|
(unless (re-search-forward close nil t) (goto-char (point-max)))))
|
||||||
|
((eq (char-after) ?-)) ; @name-arg : no span
|
||||||
|
(t (throw 'found (cons hit 'open))))))
|
||||||
|
(t ; name@ / bare @ : closing
|
||||||
|
(goto-char (1+ hit))
|
||||||
|
(throw 'found (cons hit 'close))))))
|
||||||
|
nil))
|
||||||
|
|
||||||
|
(defun klammertext--match-forward (open-pos)
|
||||||
|
"OPEN-POS is the @ of an opening application. Return the matching close @
|
||||||
|
position, or nil if unbalanced."
|
||||||
|
(save-excursion
|
||||||
|
(goto-char (1+ open-pos))
|
||||||
|
(skip-chars-forward "A-Za-z0-9_") ; past the opening name
|
||||||
|
(let ((depth 1) (result nil) (go t))
|
||||||
|
(while (and go (> depth 0))
|
||||||
|
(let ((d (klammertext--next-app-delim nil)))
|
||||||
|
(if (null d)
|
||||||
|
(setq go nil)
|
||||||
|
(if (eq (cdr d) 'open)
|
||||||
|
(setq depth (1+ depth))
|
||||||
|
(setq depth (1- depth))
|
||||||
|
(when (= depth 0) (setq result (car d)))))))
|
||||||
|
result)))
|
||||||
|
|
||||||
|
(defun klammertext--match-backward (close-pos)
|
||||||
|
"CLOSE-POS is the @ of a closing application. Return the matching open @
|
||||||
|
position, or nil if unbalanced. Scans forward from `point-min' with a stack."
|
||||||
|
(save-excursion
|
||||||
|
(goto-char (point-min))
|
||||||
|
(let ((stack nil) (result nil) (go t))
|
||||||
|
(while go
|
||||||
|
(let ((d (klammertext--next-app-delim (1+ close-pos))))
|
||||||
|
(cond
|
||||||
|
((null d) (setq go nil))
|
||||||
|
((eq (cdr d) 'open) (push (car d) stack))
|
||||||
|
(t (let ((open (pop stack)))
|
||||||
|
(when (= (car d) close-pos)
|
||||||
|
(setq result open go nil)))))))
|
||||||
|
result)))
|
||||||
|
|
||||||
|
(defun klammertext--app-delim-info (pos)
|
||||||
|
"If the char at POS is a single-@ application delimiter, return (POS . KIND)
|
||||||
|
with KIND `open or `close; else nil. The abbreviated @name-arg form (which
|
||||||
|
opens no span) returns nil."
|
||||||
|
(when (and (eq (char-after pos) ?@)
|
||||||
|
(not (eq (char-before pos) ?@))
|
||||||
|
(not (eq (char-after (1+ pos)) ?@))
|
||||||
|
(not (klammertext--escaped-p pos)))
|
||||||
|
(if (klammertext--name-char-p (char-after (1+ pos)))
|
||||||
|
(let ((name-end (save-excursion (goto-char (1+ pos))
|
||||||
|
(skip-chars-forward "A-Za-z0-9_")
|
||||||
|
(point))))
|
||||||
|
(unless (eq (char-after name-end) ?-)
|
||||||
|
(cons pos 'open)))
|
||||||
|
(cons pos 'close))))
|
||||||
|
|
||||||
|
(defun klammertext--open-name (open-pos)
|
||||||
|
"Name of the opening @name at OPEN-POS."
|
||||||
|
(save-excursion (goto-char (1+ open-pos))
|
||||||
|
(buffer-substring-no-properties
|
||||||
|
(point) (progn (skip-chars-forward "A-Za-z0-9_") (point)))))
|
||||||
|
|
||||||
|
(defun klammertext--close-name (close-pos)
|
||||||
|
"Name of a named close NAME@ at CLOSE-POS, or nil for a bare @ (incl. @name@)."
|
||||||
|
(save-excursion
|
||||||
|
(goto-char close-pos)
|
||||||
|
(let ((ns (progn (skip-chars-backward "A-Za-z0-9_") (point))))
|
||||||
|
(when (and (< ns close-pos)
|
||||||
|
(not (eq (char-before ns) ?@)))
|
||||||
|
(buffer-substring-no-properties ns close-pos)))))
|
||||||
|
|
||||||
|
(defun klammertext--paren-mismatch (open-pos close-pos)
|
||||||
|
"Non-nil if OPEN-POS/CLOSE-POS is unbalanced, or the named close disagrees
|
||||||
|
with the opening name."
|
||||||
|
(or (null open-pos) (null close-pos)
|
||||||
|
(let ((cname (klammertext--close-name close-pos)))
|
||||||
|
(and cname (not (string= cname (klammertext--open-name open-pos)))))))
|
||||||
|
|
||||||
|
(defun klammertext--literal-delim-name (pos kind)
|
||||||
|
"If the application delimiter at POS (KIND `open or `close) belongs to a
|
||||||
|
literal klammer (one in `klammertext-literal-klammers'), return its name;
|
||||||
|
else nil. A literal klammer must be closed with the full NAME@ form because
|
||||||
|
its content is verbatim, so its @NAME open and NAME@ close are matched by
|
||||||
|
name, not by depth counting."
|
||||||
|
(let ((name (if (eq kind 'open)
|
||||||
|
(klammertext--open-name pos)
|
||||||
|
(klammertext--close-name pos))))
|
||||||
|
(and name (member name klammertext-literal-klammers) name)))
|
||||||
|
|
||||||
|
(defun klammertext--literal-match-forward (open-pos name)
|
||||||
|
"Return the @ of the NAME@ that closes the literal @NAME at OPEN-POS, or nil.
|
||||||
|
The verbatim content is opaque, so we search for the literal close string."
|
||||||
|
(save-excursion
|
||||||
|
(goto-char (+ open-pos 1 (length name)))
|
||||||
|
(when (search-forward (concat name "@") nil t)
|
||||||
|
(1- (point)))))
|
||||||
|
|
||||||
|
(defun klammertext--literal-match-backward (close-pos name)
|
||||||
|
"Return the @ of the @NAME that opens the literal NAME@ whose @ is at
|
||||||
|
CLOSE-POS, or nil. Literal spans do not nest, so the nearest preceding real
|
||||||
|
@NAME is the opener."
|
||||||
|
(save-excursion
|
||||||
|
(goto-char close-pos)
|
||||||
|
(let ((open-str (concat "@" name)) (result nil))
|
||||||
|
(while (and (not result) (search-backward open-str nil t))
|
||||||
|
(let ((op (point)))
|
||||||
|
(unless (or (eq (char-before op) ?@) ; @@NAME = definition
|
||||||
|
(klammertext--escaped-p op))
|
||||||
|
(setq result op))))
|
||||||
|
result)))
|
||||||
|
|
||||||
|
(defun klammertext--app-match (pos kind)
|
||||||
|
"Return the matching application delimiter for the delimiter at POS of KIND
|
||||||
|
\(`open or `close), or nil.
|
||||||
|
A literal klammer (in `klammertext-literal-klammers') matches by name
|
||||||
|
(@NAME <-> NAME@) with its content opaque; other klammers match by depth."
|
||||||
|
(let ((lit (klammertext--literal-delim-name pos kind)))
|
||||||
|
(cond
|
||||||
|
((and lit (eq kind 'open)) (klammertext--literal-match-forward pos lit))
|
||||||
|
((and lit (eq kind 'close)) (klammertext--literal-match-backward pos lit))
|
||||||
|
((eq kind 'open) (klammertext--match-forward pos))
|
||||||
|
(t (klammertext--match-backward pos)))))
|
||||||
|
|
||||||
|
(defun klammertext--report-mismatch (open-pos close-pos)
|
||||||
|
"Show a minibuffer message describing a klammer application mismatch.
|
||||||
|
Either position may be nil (an unbalanced delimiter)."
|
||||||
|
(message "%s"
|
||||||
|
(cond
|
||||||
|
((null close-pos)
|
||||||
|
(format "Klammertext: opening @%s has no matching close"
|
||||||
|
(klammertext--open-name open-pos)))
|
||||||
|
((null open-pos)
|
||||||
|
"Klammertext: closing delimiter has no matching open")
|
||||||
|
(t
|
||||||
|
(format "Klammertext: closing %s@ does not match opening @%s"
|
||||||
|
(or (klammertext--close-name close-pos) "?")
|
||||||
|
(klammertext--open-name open-pos))))))
|
||||||
|
|
||||||
|
(defun klammertext--show-paren-data ()
|
||||||
|
"`show-paren-data-function' for klammer applications, both directions.
|
||||||
|
Returns (HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH) or nil, and reports
|
||||||
|
any mismatch in the minibuffer."
|
||||||
|
(let* ((p (point))
|
||||||
|
(info (or (klammertext--app-delim-info p)
|
||||||
|
(and (> p (point-min))
|
||||||
|
(klammertext--app-delim-info (1- p))))))
|
||||||
|
(when info
|
||||||
|
(let* ((dpos (car info)) (kind (cdr info))
|
||||||
|
(match (klammertext--app-match dpos kind))
|
||||||
|
(open (if (eq kind 'open) dpos match))
|
||||||
|
(close (if (eq kind 'open) match dpos))
|
||||||
|
(mism (klammertext--paren-mismatch open close)))
|
||||||
|
(when mism (klammertext--report-mismatch open close))
|
||||||
|
(list dpos (1+ dpos) match (and match (1+ match)) mism)))))
|
||||||
|
|
||||||
|
;; --- Interactive: jump to the matching application delimiter -----------
|
||||||
|
|
||||||
|
(defun klammertext-jump-to-match ()
|
||||||
|
"Jump to the matching klammer application delimiter.
|
||||||
|
On an opening @name, move to its closing @ or name@; on a close, move to the
|
||||||
|
opening @name. Uses the same matcher as `show-paren-mode'. The starting
|
||||||
|
position is pushed to the mark ring, so \\`C-u C-SPC' jumps back."
|
||||||
|
(interactive)
|
||||||
|
(let* ((p (point))
|
||||||
|
(info (or (klammertext--app-delim-info p)
|
||||||
|
(and (> p (point-min))
|
||||||
|
(klammertext--app-delim-info (1- p))))))
|
||||||
|
(unless info
|
||||||
|
(user-error "Point is not on a klammer application delimiter (@)"))
|
||||||
|
(let* ((dpos (car info)) (kind (cdr info))
|
||||||
|
(match (klammertext--app-match dpos kind)))
|
||||||
|
(unless match
|
||||||
|
(user-error "No matching delimiter for this %s klammer"
|
||||||
|
(if (eq kind 'open) "opening" "closing")))
|
||||||
|
(push-mark nil t)
|
||||||
|
(goto-char match))))
|
||||||
|
|
||||||
|
;; --- The mode ----------------------------------------------------------
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(define-derived-mode klammertext-mode text-mode "Klammertext"
|
||||||
|
"Major mode for editing Klammertext files."
|
||||||
|
(setq-local font-lock-multiline t)
|
||||||
|
(setq-local font-lock-defaults '(klammertext-font-lock-keywords))
|
||||||
|
;; Match klammer application delimiters with `show-paren-mode' (which must be
|
||||||
|
;; enabled separately; it is on by default in Emacs 28+).
|
||||||
|
(setq-local show-paren-data-function #'klammertext--show-paren-data)
|
||||||
|
;; Show a mismatched delimiter in bright red rather than the default purple
|
||||||
|
;; `show-paren-mismatch', but only in Klammertext buffers.
|
||||||
|
(setq-local face-remapping-alist
|
||||||
|
(cons '(show-paren-mismatch klammertext-mismatch-face)
|
||||||
|
face-remapping-alist)))
|
||||||
|
|
||||||
|
(define-key klammertext-mode-map (kbd "C-c C-j") #'klammertext-jump-to-match)
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(add-to-list 'auto-mode-alist '("\\.kt\\'" . klammertext-mode))
|
||||||
|
;;;###autoload
|
||||||
|
(add-to-list 'auto-mode-alist '("\\.k\\'" . klammertext-mode))
|
||||||
|
|
||||||
|
(provide 'klammertext-mode)
|
||||||
|
;;; klammertext-mode.el ends here
|
||||||
60
doc/edit/sublime/Breakers.sublime-color-scheme
Normal file
60
doc/edit/sublime/Breakers.sublime-color-scheme
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Klammertext colors for the "Breakers" scheme (light ground).
|
||||||
|
// One hue system across all schemes: application = blue, definition =
|
||||||
|
// green, system = orange; each opens bright and its close is 80%% of the
|
||||||
|
// open (a klammer "begins bright and gets dark"). Shown at full intensity
|
||||||
|
// on dark grounds, at 60%% on light grounds for contrast. Delimiters are
|
||||||
|
// forced to normal style. Merged onto Breakers by filename; recolors only
|
||||||
|
// .klammertext scopes. (The highlighting was first developed as an Emacs
|
||||||
|
// major mode; see Klammertext_in_Sublime_Text.md.)
|
||||||
|
//
|
||||||
|
// #999999 removed text (Breakers's comment grey)
|
||||||
|
// #994040 removal markers
|
||||||
|
// #528599 @name open blue
|
||||||
|
// #426a7a name@ close darker blue
|
||||||
|
// #758b55 @@name open green
|
||||||
|
// #5e7044 name@@ close darker green
|
||||||
|
// #996743 @@@name open orange
|
||||||
|
// #7a5236 name@@@ close darker orange
|
||||||
|
{
|
||||||
|
"name": "Breakers",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"scope": "comment.line.klammertext, comment.block.klammertext",
|
||||||
|
"foreground": "#999999"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "punctuation.definition.comment.klammertext",
|
||||||
|
"foreground": "#994040"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "entity.name.function.begin.klammertext",
|
||||||
|
"foreground": "#528599",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "entity.name.function.end.klammertext",
|
||||||
|
"foreground": "#426a7a",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "storage.type.begin.klammertext",
|
||||||
|
"foreground": "#758b55",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "storage.type.end.klammertext",
|
||||||
|
"foreground": "#5e7044",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "keyword.control.begin.klammertext",
|
||||||
|
"foreground": "#996743",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "keyword.control.end.klammertext",
|
||||||
|
"foreground": "#7a5236",
|
||||||
|
"font_style": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
60
doc/edit/sublime/Celeste.sublime-color-scheme
Normal file
60
doc/edit/sublime/Celeste.sublime-color-scheme
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Klammertext colors for the "Celeste" scheme (light ground).
|
||||||
|
// One hue system across all schemes: application = blue, definition =
|
||||||
|
// green, system = orange; each opens bright and its close is 80%% of the
|
||||||
|
// open (a klammer "begins bright and gets dark"). Shown at full intensity
|
||||||
|
// on dark grounds, at 60%% on light grounds for contrast. Delimiters are
|
||||||
|
// forced to normal style. Merged onto Celeste by filename; recolors only
|
||||||
|
// .klammertext scopes. (The highlighting was first developed as an Emacs
|
||||||
|
// major mode; see Klammertext_in_Sublime_Text.md.)
|
||||||
|
//
|
||||||
|
// #9a9a9a removed text (Celeste's comment grey)
|
||||||
|
// #994040 removal markers
|
||||||
|
// #528599 @name open blue
|
||||||
|
// #426a7a name@ close darker blue
|
||||||
|
// #758b55 @@name open green
|
||||||
|
// #5e7044 name@@ close darker green
|
||||||
|
// #996743 @@@name open orange
|
||||||
|
// #7a5236 name@@@ close darker orange
|
||||||
|
{
|
||||||
|
"name": "Celeste",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"scope": "comment.line.klammertext, comment.block.klammertext",
|
||||||
|
"foreground": "#9a9a9a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "punctuation.definition.comment.klammertext",
|
||||||
|
"foreground": "#994040"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "entity.name.function.begin.klammertext",
|
||||||
|
"foreground": "#528599",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "entity.name.function.end.klammertext",
|
||||||
|
"foreground": "#426a7a",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "storage.type.begin.klammertext",
|
||||||
|
"foreground": "#758b55",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "storage.type.end.klammertext",
|
||||||
|
"foreground": "#5e7044",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "keyword.control.begin.klammertext",
|
||||||
|
"foreground": "#996743",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "keyword.control.end.klammertext",
|
||||||
|
"foreground": "#7a5236",
|
||||||
|
"font_style": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
44
doc/edit/sublime/Comments.tmPreferences
Normal file
44
doc/edit/sublime/Comments.tmPreferences
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<!--
|
||||||
|
Comment toggling for Klammertext. Companion to Klammertext.sublime-syntax.
|
||||||
|
|
||||||
|
Ctrl+/ (toggle_comment) -> line removal: "# "
|
||||||
|
Ctrl+Shift+/ (toggle_comment block:true) -> block removal: "#[ ... ]#"
|
||||||
|
|
||||||
|
These map onto Klammertext's own text-removal syntax:
|
||||||
|
# removes to end of line (the line comment)
|
||||||
|
#[ ... ]# removes enclosed text, nestable (the block comment)
|
||||||
|
-->
|
||||||
|
<key>name</key>
|
||||||
|
<string>Comments</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>text.klammertext</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>shellVariables</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>TM_COMMENT_START</string>
|
||||||
|
<key>value</key>
|
||||||
|
<string># </string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>TM_COMMENT_START_2</string>
|
||||||
|
<key>value</key>
|
||||||
|
<string>#[</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>TM_COMMENT_END_2</string>
|
||||||
|
<key>value</key>
|
||||||
|
<string>]#</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
20
doc/edit/sublime/Default.sublime-keymap
Normal file
20
doc/edit/sublime/Default.sublime-keymap
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Klammertext key bindings.
|
||||||
|
//
|
||||||
|
// Binds "jump to matching klammer delimiter" (the companion Klammertext.py
|
||||||
|
// command) to Ctrl+M — Sublime's own "go to matching bracket" key, repurposed
|
||||||
|
// for klammers, since the built-in cannot match context-dependent @ pairs.
|
||||||
|
//
|
||||||
|
// The "selector" context confines the binding to Klammertext files, so Ctrl+M
|
||||||
|
// keeps its normal meaning everywhere else.
|
||||||
|
//
|
||||||
|
// macOS users may prefer "super+m"; change the "keys" value below. This file
|
||||||
|
// (no platform suffix) is loaded on all platforms.
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"keys": ["ctrl+m"],
|
||||||
|
"command": "klammertext_jump_to_match",
|
||||||
|
"context": [
|
||||||
|
{ "key": "selector", "operator": "equal", "operand": "text.klammertext" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
471
doc/edit/sublime/Klammertext.py
Normal file
471
doc/edit/sublime/Klammertext.py
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
# Klammertext.py
|
||||||
|
#
|
||||||
|
# Sublime Text plugin for klammer APPLICATION (@) delimiters. Two features,
|
||||||
|
# both ports of doc/emacs/klammertext-mode.el, both reusing one matcher:
|
||||||
|
#
|
||||||
|
# 1. Jump between an opening and its close — the Sublime equivalent of the
|
||||||
|
# Emacs mode's `klammertext-jump-to-match' (bound C-c C-j). Command name
|
||||||
|
# klammertext_jump_to_match; keybinding in Default.sublime-keymap.
|
||||||
|
#
|
||||||
|
# 2. Live highlighting of the matching delimiter as the caret sits on one —
|
||||||
|
# the equivalent of the Emacs mode's show-paren support. Implemented as a
|
||||||
|
# ViewEventListener (see KlammertextMatchHighlighter at the bottom); no
|
||||||
|
# language server is involved. A mismatched named close or an unbalanced
|
||||||
|
# delimiter is highlighted in red with a status-bar message, mirroring the
|
||||||
|
# Emacs mode's klammertext-mismatch-face + minibuffer report.
|
||||||
|
#
|
||||||
|
# This is the companion to Klammertext.sublime-syntax. The syntax file only
|
||||||
|
# colors tokens; a tokenizer cannot match context-dependent delimiters, so the
|
||||||
|
# jump is implemented here as a TextCommand. The keybinding lives in the
|
||||||
|
# companion Default.sublime-keymap.
|
||||||
|
#
|
||||||
|
# Command name (for keymaps / the command palette): klammertext_jump_to_match
|
||||||
|
#
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# What it does (a direct port of the elisp matcher):
|
||||||
|
# * On an opening @name, move to its closing @ or name@.
|
||||||
|
# * On a close (bare @ or name@), move to the opening @name.
|
||||||
|
# * Triggers when the caret is ON the @ or immediately AFTER it (the same
|
||||||
|
# on-or-just-after rule the Emacs command uses).
|
||||||
|
# * Only single-@ APPLICATION delimiters match. @@/@@@ runs, removed text
|
||||||
|
# (#, ##, #[...]#), escaped ^@, and literal-klammer spans (@code ... code@)
|
||||||
|
# are stepped over, exactly as in the Emacs mode. The abbreviated
|
||||||
|
# @name-arg form opens no span.
|
||||||
|
# * Works at every caret when there are multiple selections.
|
||||||
|
#
|
||||||
|
# Literal klammers (identical to C-c C-j): a @code ... code@ span is opaque.
|
||||||
|
# The general depth scan still steps over such a span WHOLESALE when matching
|
||||||
|
# some OTHER klammer, so verbatim @ inside it never miscount. A literal
|
||||||
|
# klammer's OWN delimiters are matched BY NAME rather than by depth (see
|
||||||
|
# app_match): @code jumps to the next code@, and code@ to the nearest preceding
|
||||||
|
# @code — correct even when the content holds unbalanced @, e.g. @code x @ y
|
||||||
|
# code@. LITERAL_KLAMMERS lists these names; keep it in sync with the '@code'
|
||||||
|
# handling in Klammertext.sublime-syntax.
|
||||||
|
#
|
||||||
|
# LITERAL_KLAMMERS must stay in sync with the literal klammers recognized in
|
||||||
|
# Klammertext.sublime-syntax (seeded there as @code). The Emacs mode keeps this
|
||||||
|
# list in the `klammertext-literal-klammers' defcustom; a plugin has no access
|
||||||
|
# to it, so it is duplicated here.
|
||||||
|
|
||||||
|
import sublime
|
||||||
|
import sublime_plugin
|
||||||
|
|
||||||
|
# Klammer names whose content is a literal argument (verbatim interior).
|
||||||
|
#
|
||||||
|
# SYNC: this list is one of three copies that must agree. When you add or
|
||||||
|
# remove a literal klammer, mirror it in all three:
|
||||||
|
# * klammertext-literal-klammers in doc/emacs/klammertext-mode.el (the source
|
||||||
|
# of truth; a Sublime syntax/plugin cannot read that Emacs defcustom)
|
||||||
|
# * LITERAL_KLAMMERS here
|
||||||
|
# * the @NAME literal rule + literal_NAME context in Klammertext.sublime-syntax
|
||||||
|
# All three are currently seeded with just "code".
|
||||||
|
LITERAL_KLAMMERS = set(["code"])
|
||||||
|
|
||||||
|
|
||||||
|
# --- pure helpers (operate on the whole buffer as a string) ----------------
|
||||||
|
|
||||||
|
def name_char_p(ch):
|
||||||
|
"""True if CH can be part of a klammer name (letter, digit or _).
|
||||||
|
A hyphen is NOT a name char: @name-arg1 ends the name at the first hyphen."""
|
||||||
|
if ch is None:
|
||||||
|
return False
|
||||||
|
return (('a' <= ch <= 'z') or ('A' <= ch <= 'Z')
|
||||||
|
or ('0' <= ch <= '9') or ch == '_')
|
||||||
|
|
||||||
|
|
||||||
|
def escaped_p(s, pos):
|
||||||
|
"""True if the char at POS is escaped by an odd run of ^ before it.
|
||||||
|
In Klammertext ^# and ^@ are literal, so such a char is not a delimiter."""
|
||||||
|
n = 0
|
||||||
|
i = pos - 1
|
||||||
|
while i >= 0 and s[i] == '^':
|
||||||
|
n += 1
|
||||||
|
i -= 1
|
||||||
|
return (n % 2) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def block_end(s, frm):
|
||||||
|
"""Index just after the ]# that closes a #[ block opened at FROM (the index
|
||||||
|
just after the opening #[). Counts nested #[ ... ]#; len(s) if unclosed."""
|
||||||
|
depth = 1
|
||||||
|
i = frm
|
||||||
|
n = len(s)
|
||||||
|
while depth > 0:
|
||||||
|
a = s.find('#[', i)
|
||||||
|
b = s.find(']#', i)
|
||||||
|
if a == -1 and b == -1:
|
||||||
|
return n
|
||||||
|
if b == -1 or (a != -1 and a < b):
|
||||||
|
depth += 1
|
||||||
|
i = a + 2
|
||||||
|
else:
|
||||||
|
depth -= 1
|
||||||
|
i = b + 2
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
def at_run_end(s, pos):
|
||||||
|
"""Index just after the run of @ that begins at POS."""
|
||||||
|
p = pos
|
||||||
|
n = len(s)
|
||||||
|
while p < n and s[p] == '@':
|
||||||
|
p += 1
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def next_app_delim(s, i, limit):
|
||||||
|
"""From index I, find the next single-@ application delimiter before LIMIT.
|
||||||
|
Step over @@/@@@ runs, removed text, literal spans, escaped ^@, and the
|
||||||
|
abbreviated @name-arg form. Return (pos, kind, next_i) with kind 'open' or
|
||||||
|
'close' and next_i the index to resume from, or None when none is found."""
|
||||||
|
n = len(s)
|
||||||
|
if limit is None:
|
||||||
|
limit = n
|
||||||
|
while i < limit:
|
||||||
|
# find next @ or # at or after i (emacs re-search-forward "[@#]")
|
||||||
|
j = i
|
||||||
|
while j < limit and s[j] != '@' and s[j] != '#':
|
||||||
|
j += 1
|
||||||
|
if j >= limit:
|
||||||
|
return None
|
||||||
|
hit = j
|
||||||
|
i = hit + 1 # default: advance past the hit
|
||||||
|
if escaped_p(s, hit): # ^@ / ^# : keep going
|
||||||
|
continue
|
||||||
|
nxt = s[hit + 1] if hit + 1 < n else None
|
||||||
|
if s[hit] == '#': # removal: step over it
|
||||||
|
if nxt == '#':
|
||||||
|
i = n
|
||||||
|
elif nxt == '[':
|
||||||
|
i = block_end(s, hit + 2)
|
||||||
|
elif nxt in ('+', '/', '-'):
|
||||||
|
i = hit + 1
|
||||||
|
else: # to end of line
|
||||||
|
eol = s.find('\n', hit)
|
||||||
|
i = n if eol == -1 else eol
|
||||||
|
continue
|
||||||
|
# s[hit] == '@'
|
||||||
|
if nxt == '@': # @@ / @@@ : step over the run
|
||||||
|
i = at_run_end(s, hit)
|
||||||
|
continue
|
||||||
|
if name_char_p(nxt): # @name : opening?
|
||||||
|
k = hit + 1
|
||||||
|
while k < n and name_char_p(s[k]):
|
||||||
|
k += 1
|
||||||
|
name = s[hit + 1:k]
|
||||||
|
after = s[k] if k < n else None
|
||||||
|
if name in LITERAL_KLAMMERS: # literal span: skip to its close
|
||||||
|
close = name + '@'
|
||||||
|
idx = s.find(close, k)
|
||||||
|
i = n if idx == -1 else idx + len(close)
|
||||||
|
continue
|
||||||
|
elif after == '-': # @name-arg : opens no span
|
||||||
|
i = k
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return (hit, 'open', k)
|
||||||
|
else: # name@ / bare @ : closing
|
||||||
|
return (hit, 'close', hit + 1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def match_forward(s, open_pos):
|
||||||
|
"""OPEN_POS is the @ of an opening application. Return the matching close @
|
||||||
|
index, or None if unbalanced."""
|
||||||
|
n = len(s)
|
||||||
|
i = open_pos + 1
|
||||||
|
while i < n and name_char_p(s[i]): # past the opening name
|
||||||
|
i += 1
|
||||||
|
depth = 1
|
||||||
|
while depth > 0:
|
||||||
|
d = next_app_delim(s, i, None)
|
||||||
|
if d is None:
|
||||||
|
return None
|
||||||
|
pos, kind, nxt = d
|
||||||
|
i = nxt
|
||||||
|
if kind == 'open':
|
||||||
|
depth += 1
|
||||||
|
else:
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0:
|
||||||
|
return pos
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def match_backward(s, close_pos):
|
||||||
|
"""CLOSE_POS is the @ of a closing application. Return the matching open @
|
||||||
|
index, or None if unbalanced. Scans forward from 0 with a stack."""
|
||||||
|
stack = []
|
||||||
|
i = 0
|
||||||
|
limit = close_pos + 1
|
||||||
|
while True:
|
||||||
|
d = next_app_delim(s, i, limit)
|
||||||
|
if d is None:
|
||||||
|
return None
|
||||||
|
pos, kind, nxt = d
|
||||||
|
i = nxt
|
||||||
|
if kind == 'open':
|
||||||
|
stack.append(pos)
|
||||||
|
else:
|
||||||
|
open_pos = stack.pop() if stack else None
|
||||||
|
if pos == close_pos:
|
||||||
|
return open_pos
|
||||||
|
|
||||||
|
|
||||||
|
def app_delim_info(s, pos):
|
||||||
|
"""If the char at POS is a single-@ application delimiter, return
|
||||||
|
(pos, kind) with kind 'open' or 'close'; else None. The abbreviated
|
||||||
|
@name-arg form (which opens no span) returns None."""
|
||||||
|
n = len(s)
|
||||||
|
if not (0 <= pos < n):
|
||||||
|
return None
|
||||||
|
if s[pos] != '@':
|
||||||
|
return None
|
||||||
|
if pos > 0 and s[pos - 1] == '@':
|
||||||
|
return None
|
||||||
|
if pos + 1 < n and s[pos + 1] == '@':
|
||||||
|
return None
|
||||||
|
if escaped_p(s, pos):
|
||||||
|
return None
|
||||||
|
nxt = s[pos + 1] if pos + 1 < n else None
|
||||||
|
if name_char_p(nxt):
|
||||||
|
k = pos + 1
|
||||||
|
while k < n and name_char_p(s[k]):
|
||||||
|
k += 1
|
||||||
|
after = s[k] if k < n else None
|
||||||
|
if after == '-':
|
||||||
|
return None
|
||||||
|
return (pos, 'open')
|
||||||
|
return (pos, 'close')
|
||||||
|
|
||||||
|
|
||||||
|
# --- name / mismatch helpers (for the live highlighter) --------------------
|
||||||
|
|
||||||
|
def _name_forward(s, pos):
|
||||||
|
"""Index just past the run of name chars starting at POS."""
|
||||||
|
n = len(s)
|
||||||
|
k = pos
|
||||||
|
while k < n and name_char_p(s[k]):
|
||||||
|
k += 1
|
||||||
|
return k
|
||||||
|
|
||||||
|
|
||||||
|
def open_name(s, open_pos):
|
||||||
|
"""Name of the opening @name whose @ is at OPEN_POS."""
|
||||||
|
return s[open_pos + 1:_name_forward(s, open_pos + 1)]
|
||||||
|
|
||||||
|
|
||||||
|
def close_name(s, close_pos):
|
||||||
|
"""Name of a named close NAME@ whose @ is at CLOSE_POS, or None for a bare @
|
||||||
|
(including the compact @name@ form, whose name belongs to the opening)."""
|
||||||
|
ns = close_pos
|
||||||
|
while ns > 0 and name_char_p(s[ns - 1]):
|
||||||
|
ns -= 1
|
||||||
|
if ns < close_pos and (ns == 0 or s[ns - 1] != '@'):
|
||||||
|
return s[ns:close_pos]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def paren_mismatch(s, open_pos, close_pos):
|
||||||
|
"""True if the pair is unbalanced (either side None) or the named close
|
||||||
|
disagrees with the opening name."""
|
||||||
|
if open_pos is None or close_pos is None:
|
||||||
|
return True
|
||||||
|
cname = close_name(s, close_pos)
|
||||||
|
return cname is not None and cname != open_name(s, open_pos)
|
||||||
|
|
||||||
|
|
||||||
|
def token_region(s, pos, kind):
|
||||||
|
"""(start, end) of the whole delimiter token whose @ is at POS.
|
||||||
|
Opening: @ plus its name. Named close: the name plus @. Bare @: just @."""
|
||||||
|
if kind == 'open':
|
||||||
|
return (pos, _name_forward(s, pos + 1))
|
||||||
|
ns = pos
|
||||||
|
while ns > 0 and name_char_p(s[ns - 1]):
|
||||||
|
ns -= 1
|
||||||
|
if ns < pos and (ns == 0 or s[ns - 1] != '@'):
|
||||||
|
return (ns, pos + 1) # named close NAME@
|
||||||
|
return (pos, pos + 1) # bare @ (or @name@)
|
||||||
|
|
||||||
|
|
||||||
|
# --- matching dispatch: literal klammers by name, others by depth ----------
|
||||||
|
|
||||||
|
def literal_delim_name(s, pos, kind):
|
||||||
|
"""If the application delimiter at POS (kind 'open'/'close') belongs to a
|
||||||
|
literal klammer (name in LITERAL_KLAMMERS), return its name; else None.
|
||||||
|
A literal klammer's @NAME open and NAME@ close are matched by name, not by
|
||||||
|
depth counting, because its content is verbatim."""
|
||||||
|
name = open_name(s, pos) if kind == 'open' else close_name(s, pos)
|
||||||
|
if name and name in LITERAL_KLAMMERS:
|
||||||
|
return name
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def literal_match_forward(s, open_pos, name):
|
||||||
|
"""Index of the @ of the NAME@ that closes the literal @NAME at OPEN_POS, or
|
||||||
|
None. The content is opaque, so search for the literal close string."""
|
||||||
|
start = open_pos + 1 + len(name)
|
||||||
|
idx = s.find(name + '@', start)
|
||||||
|
return idx + len(name) if idx != -1 else None
|
||||||
|
|
||||||
|
|
||||||
|
def literal_match_backward(s, close_pos, name):
|
||||||
|
"""Index of the @ of the @NAME that opens the literal NAME@ whose @ is at
|
||||||
|
CLOSE_POS, or None. Literal spans do not nest, so the nearest preceding
|
||||||
|
real @NAME is the opener (not @@NAME, and not escaped)."""
|
||||||
|
open_str = '@' + name
|
||||||
|
end = close_pos
|
||||||
|
while True:
|
||||||
|
idx = s.rfind(open_str, 0, end)
|
||||||
|
if idx == -1:
|
||||||
|
return None
|
||||||
|
before = s[idx - 1] if idx > 0 else None
|
||||||
|
if before != '@' and not escaped_p(s, idx):
|
||||||
|
return idx
|
||||||
|
end = idx
|
||||||
|
|
||||||
|
|
||||||
|
def app_match(s, pos, kind):
|
||||||
|
"""Matching application delimiter for the delimiter at POS of KIND
|
||||||
|
('open'/'close'), or None. A literal klammer matches by name (@NAME <->
|
||||||
|
NAME@) with content opaque; other klammers match by depth."""
|
||||||
|
lit = literal_delim_name(s, pos, kind)
|
||||||
|
if lit is not None:
|
||||||
|
return (literal_match_forward(s, pos, lit) if kind == 'open'
|
||||||
|
else literal_match_backward(s, pos, lit))
|
||||||
|
return match_forward(s, pos) if kind == 'open' else match_backward(s, pos)
|
||||||
|
|
||||||
|
|
||||||
|
# --- the command -----------------------------------------------------------
|
||||||
|
|
||||||
|
class KlammertextJumpToMatchCommand(sublime_plugin.TextCommand):
|
||||||
|
"""Jump between a klammer application's opening and closing delimiter.
|
||||||
|
Sublime equivalent of the Emacs mode's C-c C-j."""
|
||||||
|
|
||||||
|
def run(self, edit):
|
||||||
|
view = self.view
|
||||||
|
s = view.substr(sublime.Region(0, view.size()))
|
||||||
|
new_regions = []
|
||||||
|
moved = False
|
||||||
|
message = None
|
||||||
|
|
||||||
|
for region in view.sel():
|
||||||
|
p = region.b
|
||||||
|
info = app_delim_info(s, p)
|
||||||
|
if info is None and p > 0:
|
||||||
|
info = app_delim_info(s, p - 1)
|
||||||
|
if info is None:
|
||||||
|
new_regions.append(region)
|
||||||
|
message = "point is not on a klammer application delimiter (@)"
|
||||||
|
continue
|
||||||
|
dpos, kind = info
|
||||||
|
match = app_match(s, dpos, kind)
|
||||||
|
if match is None:
|
||||||
|
new_regions.append(region)
|
||||||
|
message = ("no matching delimiter for this %s klammer"
|
||||||
|
% ("opening" if kind == 'open' else "closing"))
|
||||||
|
continue
|
||||||
|
new_regions.append(sublime.Region(match, match))
|
||||||
|
moved = True
|
||||||
|
|
||||||
|
view.sel().clear()
|
||||||
|
for r in new_regions:
|
||||||
|
view.sel().add(r)
|
||||||
|
|
||||||
|
if moved:
|
||||||
|
view.show(view.sel()[0].b)
|
||||||
|
elif message:
|
||||||
|
sublime.status_message("Klammertext: " + message)
|
||||||
|
|
||||||
|
def is_enabled(self):
|
||||||
|
# Only meaningful in Klammertext buffers.
|
||||||
|
return self.view.match_selector(0, "text.klammertext")
|
||||||
|
|
||||||
|
|
||||||
|
# --- live matched-delimiter highlighting (show-paren equivalent) -----------
|
||||||
|
|
||||||
|
class KlammertextMatchHighlighter(sublime_plugin.ViewEventListener):
|
||||||
|
"""Highlight the matching klammer application delimiter as the caret sits
|
||||||
|
on one. The Sublime equivalent of the Emacs mode's show-paren support —
|
||||||
|
driven by cursor movement, reusing the same context-sensitive matcher.
|
||||||
|
|
||||||
|
A matched pair is boxed (region.bluish); a mismatch or unbalanced delimiter
|
||||||
|
is boxed in red (region.redish) with a status-bar message. Both the token
|
||||||
|
under the caret and its match are boxed; the Emacs mode highlights only the
|
||||||
|
single @ character, but boxing the whole @name / name@ reads better here.
|
||||||
|
To highlight only the far delimiter, drop the first region in _update()."""
|
||||||
|
|
||||||
|
MATCH_KEY = 'klammertext_paren_match'
|
||||||
|
MISMATCH_KEY = 'klammertext_paren_mismatch'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_applicable(cls, settings):
|
||||||
|
return str(settings.get('syntax', '')).endswith('Klammertext.sublime-syntax')
|
||||||
|
|
||||||
|
def __init__(self, view):
|
||||||
|
super().__init__(view)
|
||||||
|
self._change_count = -1
|
||||||
|
self._text = ''
|
||||||
|
|
||||||
|
def _buffer(self):
|
||||||
|
# Re-read the buffer only when it has actually changed, so plain cursor
|
||||||
|
# movement over a large file does not re-copy the whole document.
|
||||||
|
cc = self.view.change_count()
|
||||||
|
if cc != self._change_count:
|
||||||
|
self._text = self.view.substr(sublime.Region(0, self.view.size()))
|
||||||
|
self._change_count = cc
|
||||||
|
return self._text
|
||||||
|
|
||||||
|
def on_selection_modified_async(self):
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def on_activated_async(self):
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _clear(self):
|
||||||
|
self.view.erase_regions(self.MATCH_KEY)
|
||||||
|
self.view.erase_regions(self.MISMATCH_KEY)
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
view = self.view
|
||||||
|
sel = view.sel()
|
||||||
|
if len(sel) == 0:
|
||||||
|
self._clear()
|
||||||
|
return
|
||||||
|
p = sel[0].b
|
||||||
|
s = self._buffer()
|
||||||
|
|
||||||
|
info = app_delim_info(s, p)
|
||||||
|
if info is None and p > 0:
|
||||||
|
info = app_delim_info(s, p - 1)
|
||||||
|
if info is None:
|
||||||
|
self._clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
dpos, kind = info
|
||||||
|
match = app_match(s, dpos, kind)
|
||||||
|
open_pos = dpos if kind == 'open' else match
|
||||||
|
close_pos = match if kind == 'open' else dpos
|
||||||
|
mism = paren_mismatch(s, open_pos, close_pos)
|
||||||
|
|
||||||
|
regions = [sublime.Region(*token_region(s, dpos, kind))]
|
||||||
|
if match is not None:
|
||||||
|
other_kind = 'close' if kind == 'open' else 'open'
|
||||||
|
regions.append(sublime.Region(*token_region(s, match, other_kind)))
|
||||||
|
|
||||||
|
flags = sublime.DRAW_NO_FILL
|
||||||
|
if mism:
|
||||||
|
view.erase_regions(self.MATCH_KEY)
|
||||||
|
view.add_regions(self.MISMATCH_KEY, regions, 'region.redish', '', flags)
|
||||||
|
if match is None:
|
||||||
|
if kind == 'open':
|
||||||
|
msg = "opening @%s has no matching close" % open_name(s, open_pos)
|
||||||
|
else:
|
||||||
|
msg = "closing delimiter has no matching open"
|
||||||
|
else:
|
||||||
|
msg = ("closing %s@ does not match opening @%s"
|
||||||
|
% (close_name(s, close_pos) or '?', open_name(s, open_pos)))
|
||||||
|
sublime.status_message("Klammertext: " + msg)
|
||||||
|
else:
|
||||||
|
view.erase_regions(self.MISMATCH_KEY)
|
||||||
|
view.add_regions(self.MATCH_KEY, regions, 'region.bluish', '', flags)
|
||||||
183
doc/edit/sublime/Klammertext.sublime-syntax
Normal file
183
doc/edit/sublime/Klammertext.sublime-syntax
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
%YAML 1.2
|
||||||
|
---
|
||||||
|
# Klammertext.sublime-syntax
|
||||||
|
#
|
||||||
|
# Sublime Text syntax highlighting for Klammertext (.kt and .k files).
|
||||||
|
# A port of the Emacs major mode doc/emacs/klammertext-mode.el.
|
||||||
|
#
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# What it highlights (mirrors the Emacs mode's eight token classes):
|
||||||
|
#
|
||||||
|
# Text removal (#):
|
||||||
|
# # ... remove to end of line (marker + removed text)
|
||||||
|
# ## ... remove to end of file (marker + removed text)
|
||||||
|
# #[ ... ]# remove enclosed text, nestable (markers + removed text)
|
||||||
|
# #- #+ #/ whitespace operators: NOT removals, left unhighlighted
|
||||||
|
# (matched only so the '#' above does not eat the line)
|
||||||
|
#
|
||||||
|
# Klammer applications (@), definitions (@@), system commands (@@@):
|
||||||
|
# @name @@name @@@name opening (@ and name are one unit)
|
||||||
|
# name@ name@@ name@@@ named closing
|
||||||
|
# @ @@ @@@ bare closing
|
||||||
|
#
|
||||||
|
# Escapes: ^@ ^# ^| ^^ the caret makes the next character literal, so it
|
||||||
|
# is consumed and NOT treated as a delimiter. Left unscoped, to
|
||||||
|
# match the Emacs mode, which shows escaped characters as ordinary
|
||||||
|
# text. (A run of carets pairs left-to-right: ^^ is a literal
|
||||||
|
# caret, a leftover single ^ escapes the following character —
|
||||||
|
# the '\^.' rule reproduces exactly that parity.)
|
||||||
|
#
|
||||||
|
# Literal klammers: @code ... code@ interior is verbatim (no # or @
|
||||||
|
# interpreted). To add another literal klammer 'foo', copy the
|
||||||
|
# '@code' rule and the 'literal_code' context below, replacing
|
||||||
|
# code -> foo.
|
||||||
|
#
|
||||||
|
# SYNC: the literal-klammer set is duplicated in three places that
|
||||||
|
# must agree (a .sublime-syntax file is static and cannot read the
|
||||||
|
# Emacs defcustom). When you add or remove one, mirror it in all:
|
||||||
|
# * klammertext-literal-klammers in
|
||||||
|
# doc/emacs/klammertext-mode.el (the source of truth)
|
||||||
|
# * LITERAL_KLAMMERS in Klammertext.py
|
||||||
|
# * the @NAME rule + literal_NAME context here
|
||||||
|
# All three are currently seeded with just 'code'.
|
||||||
|
#
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# How open vs. close is decided (the same rule the Emacs scanner uses):
|
||||||
|
# * a delimiter whose NAME follows the @-run (@name) is an OPENING;
|
||||||
|
# * a bare @-run, or one whose NAME precedes it (name@), is a CLOSING.
|
||||||
|
# Because this tokenizer runs left-to-right, an opening consumes "@name" as one
|
||||||
|
# unit, so a trailing bare @ in the compact form @name@ is naturally a close.
|
||||||
|
# The (?![A-Za-z0-9_@]) look-ahead on every closing keeps "foo@bar" correct:
|
||||||
|
# @ is followed by a name, so it opens @bar and 'foo' stays plain text.
|
||||||
|
#
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Scope -> color. Colors live in the color scheme, not here. The package ships
|
||||||
|
# additive .sublime-color-scheme overrides for all five of Sublime's built-in
|
||||||
|
# schemes (Breakers, Celeste, Mariana, Monokai, Sixteen); each merges onto its
|
||||||
|
# scheme by filename and recolors only .klammertext scopes. They use one hue
|
||||||
|
# system — application blue, definition green, system orange, each opening bright
|
||||||
|
# and its close the same hue darker — shown at full intensity on dark grounds and
|
||||||
|
# scaled down on light grounds. Exact values are in each override's header.
|
||||||
|
#
|
||||||
|
# Without a matching override (e.g. a third-party scheme) a stock scheme still
|
||||||
|
# gives a meaningful default from these scope names: three klammer-family colors
|
||||||
|
# (function / storage / keyword), muted removed text (comment), plain escapes.
|
||||||
|
# To get the full palette on another scheme, copy one of the shipped overrides
|
||||||
|
# to <SchemeName>.sublime-color-scheme.
|
||||||
|
#
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Install: put this file — together with its companions Klammertext.py,
|
||||||
|
# Default.sublime-keymap and Comments.tmPreferences — in a dedicated package
|
||||||
|
# folder named 'Klammertext' under Packages/ (Preferences -> Browse Packages
|
||||||
|
# opens Packages/):
|
||||||
|
# ~/.config/sublime-text/Packages/Klammertext/ (Linux)
|
||||||
|
# ~/Library/Application Support/Sublime Text/Packages/Klammertext/ (macOS)
|
||||||
|
# A dedicated folder (not Packages/User/) keeps the bundled keymap from
|
||||||
|
# merging into your personal one. Sublime picks it all up live and applies
|
||||||
|
# the syntax to .kt and .k files. (The syntax file alone also works from
|
||||||
|
# Packages/User/ if you only want highlighting.)
|
||||||
|
#
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Known differences from the Emacs mode (deliberate, matching its own limits):
|
||||||
|
# * @@ and @@@ definition BODIES are highlighted as ordinary Klammertext,
|
||||||
|
# not treated specially — same as the Emacs mode.
|
||||||
|
# * Delimiter MATCHING (jump + live highlight) is not in this syntax file —
|
||||||
|
# Sublime's built-in bracket matching needs fixed character pairs, which @
|
||||||
|
# (both open and close, decided by context) cannot provide. It lives in
|
||||||
|
# the companion Klammertext.py instead: klammertext_jump_to_match (C-c C-j
|
||||||
|
# equivalent) and a ViewEventListener that highlights the matching
|
||||||
|
# delimiter as the caret moves (show-paren equivalent), both reusing one
|
||||||
|
# context-sensitive matcher. This is a plugin concern, not a tokenizer one.
|
||||||
|
# * Comment toggling is provided by the companion Comments.tmPreferences:
|
||||||
|
# Ctrl-/ inserts '# ' (line removal), Ctrl-Shift-/ wraps in '#[ ... ]#'
|
||||||
|
# (block removal).
|
||||||
|
|
||||||
|
name: Klammertext
|
||||||
|
file_extensions:
|
||||||
|
- kt
|
||||||
|
- k
|
||||||
|
scope: text.klammertext
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
variables:
|
||||||
|
# A klammer name: letters, digits, underscore. A hyphen is NOT a name char
|
||||||
|
# (the abbreviated form @name-arg1-arg2 ends the name at the first hyphen).
|
||||||
|
name: '[A-Za-z0-9_]+'
|
||||||
|
# A closing delimiter must not be followed by a name char (that would be an
|
||||||
|
# opening @name) or another @ (that would be a longer @-run).
|
||||||
|
not_delim: '(?![A-Za-z0-9_@])'
|
||||||
|
|
||||||
|
contexts:
|
||||||
|
main:
|
||||||
|
# --- escapes: ^X makes X literal; consumed so # / @ are not delimiters ---
|
||||||
|
- match: '\^.'
|
||||||
|
|
||||||
|
# --- text removal (#) ---
|
||||||
|
- match: '##'
|
||||||
|
scope: punctuation.definition.comment.klammertext
|
||||||
|
push: removal_file
|
||||||
|
- match: '#\['
|
||||||
|
scope: punctuation.definition.comment.klammertext
|
||||||
|
push: removal_block
|
||||||
|
# whitespace operators #- #+ #/ (with optional count): not removals.
|
||||||
|
# Matched (and left unscoped) so the '#' line rule below does not consume
|
||||||
|
# the rest of the line. Add a scope here if you would rather color them.
|
||||||
|
- match: '#[-+/]\d*'
|
||||||
|
- match: '#'
|
||||||
|
scope: punctuation.definition.comment.klammertext
|
||||||
|
push: removal_line
|
||||||
|
|
||||||
|
# --- literal klammer: interior is verbatim (seeded default: @code) ---
|
||||||
|
- match: '@code(?![A-Za-z0-9_])'
|
||||||
|
scope: entity.name.function.begin.klammertext
|
||||||
|
push: literal_code
|
||||||
|
|
||||||
|
# --- system / target commands @@@ ---
|
||||||
|
- match: '@@@{{name}}'
|
||||||
|
scope: keyword.control.begin.klammertext # @@@name opening
|
||||||
|
- match: '@@@{{not_delim}}'
|
||||||
|
scope: keyword.control.end.klammertext # bare @@@ close
|
||||||
|
- match: '{{name}}@@@{{not_delim}}'
|
||||||
|
scope: keyword.control.end.klammertext # name@@@ named close
|
||||||
|
|
||||||
|
# --- klammer definitions @@ ---
|
||||||
|
- match: '@@{{name}}'
|
||||||
|
scope: storage.type.begin.klammertext # @@name opening
|
||||||
|
- match: '@@{{not_delim}}'
|
||||||
|
scope: storage.type.end.klammertext # bare @@ close
|
||||||
|
- match: '{{name}}@@{{not_delim}}'
|
||||||
|
scope: storage.type.end.klammertext # name@@ named close
|
||||||
|
|
||||||
|
# --- klammer applications @ ---
|
||||||
|
- match: '@{{name}}'
|
||||||
|
scope: entity.name.function.begin.klammertext # @name opening
|
||||||
|
- match: '@{{not_delim}}'
|
||||||
|
scope: entity.name.function.end.klammertext # bare @ close
|
||||||
|
- match: '{{name}}@{{not_delim}}'
|
||||||
|
scope: entity.name.function.end.klammertext # name@ named close
|
||||||
|
|
||||||
|
# rest of line is removed
|
||||||
|
removal_line:
|
||||||
|
- meta_scope: comment.line.klammertext
|
||||||
|
- match: '\n'
|
||||||
|
pop: true
|
||||||
|
|
||||||
|
# rest of file is removed (## never closes)
|
||||||
|
removal_file:
|
||||||
|
- meta_scope: comment.block.klammertext
|
||||||
|
|
||||||
|
# #[ ... ]# removed, nestable
|
||||||
|
removal_block:
|
||||||
|
- meta_scope: comment.block.klammertext
|
||||||
|
- match: '#\['
|
||||||
|
scope: punctuation.definition.comment.klammertext
|
||||||
|
push: removal_block
|
||||||
|
- match: '\]#'
|
||||||
|
scope: punctuation.definition.comment.klammertext
|
||||||
|
pop: true
|
||||||
|
|
||||||
|
# @code ... code@ — interior verbatim (unscoped), only the close ends it
|
||||||
|
literal_code:
|
||||||
|
- match: 'code@'
|
||||||
|
scope: entity.name.function.end.klammertext
|
||||||
|
pop: true
|
||||||
102
doc/edit/sublime/Klammertext_in_Sublime_Text.md
Normal file
102
doc/edit/sublime/Klammertext_in_Sublime_Text.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Klammertext for Sublime Text
|
||||||
|
|
||||||
|
A Sublime Text port of the Emacs major mode for Klammertext
|
||||||
|
(`doc/emacs/klammertext-mode.el`). It brings syntax highlighting, delimiter
|
||||||
|
matching, and comment toggling to `.kt` and `.k` files. Behavior mirrors the
|
||||||
|
Emacs mode closely; where the two intentionally differ, the file headers say so.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `Klammertext.sublime-syntax` | Syntax highlighting. Colors the text-removal constructs (`#`, `##`, `#[...]#`) and the three `@`-tiers — application `@`, definition `@@`, system `@@@` — each as an opening vs. a close, plus `^`-escapes and verbatim `@code ... code@` spans. |
|
||||||
|
| `Klammertext.py` | Plugin with two features that share one context-sensitive matcher: jump between an opening and its close, and live highlighting of the matching delimiter as the caret moves (mismatched or unbalanced delimiters flag in red). |
|
||||||
|
| `Default.sublime-keymap` | Binds jump-to-match to **Ctrl+M**, scoped to Klammertext files. |
|
||||||
|
| `Comments.tmPreferences` | Comment toggling: **Ctrl+/** inserts `# ` (line removal), **Ctrl+Shift+/** wraps in `#[ ... ]#` (block removal). |
|
||||||
|
| `Breakers` / `Celeste` / `Mariana` / `Monokai` / `Sixteen` `.sublime-color-scheme` | Color overrides for Sublime's five built-in schemes — one hue system, full intensity on the dark schemes, scaled down on the light ones. Additive: they recolor only the Klammertext delimiters and leave the rest of each scheme unchanged. |
|
||||||
|
| `Klammertext_in_Sublime_Text.md` | This file. |
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Put the files into a folder named `Klammertext` under Sublime's `Packages`
|
||||||
|
directory:
|
||||||
|
|
||||||
|
| Platform | Path |
|
||||||
|
|----------|------|
|
||||||
|
| Linux | `~/.config/sublime-text/Packages/Klammertext/` |
|
||||||
|
| macOS | `~/Library/Application Support/Sublime Text/Packages/Klammertext/` |
|
||||||
|
| Windows | `%AppData%\Sublime Text\Packages\Klammertext\` |
|
||||||
|
|
||||||
|
The quickest way to find it: **Preferences → Browse Packages…** opens the
|
||||||
|
`Packages` directory. Create the `Klammertext` folder there and copy the files
|
||||||
|
in. Sublime loads them live — no restart — and applies the syntax to `.kt` and
|
||||||
|
`.k` files automatically.
|
||||||
|
|
||||||
|
Use a dedicated folder (not `Packages/User/`) so the bundled keymap does not
|
||||||
|
merge into your personal one. If you want highlighting only, the
|
||||||
|
`.sublime-syntax` file alone works from `Packages/User/`.
|
||||||
|
|
||||||
|
The plugin targets **Sublime Text 4**: the live-highlight colors use Sublime's
|
||||||
|
adaptive `region.*` scopes, which were added in ST4.
|
||||||
|
|
||||||
|
## Features and keys
|
||||||
|
|
||||||
|
| Trigger | Action |
|
||||||
|
|---------|--------|
|
||||||
|
| open a `.kt` / `.k` file | Syntax highlighting (automatic) |
|
||||||
|
| **Ctrl+M** | Jump between a klammer application's opening and closing `@` (equivalent of the Emacs mode's `C-c C-j`) |
|
||||||
|
| caret on a klammer `@` | The matching delimiter boxes automatically; a name mismatch or unbalanced delimiter boxes in red with a status-bar message (equivalent of `show-paren-mode`) |
|
||||||
|
| **Ctrl+/** | Toggle line comment (`#`) |
|
||||||
|
| **Ctrl+Shift+/** | Toggle block comment (`#[ ... ]#`) |
|
||||||
|
|
||||||
|
Ctrl+M is Sublime's own "go to matching bracket" key, reused here because the
|
||||||
|
built-in cannot match Klammertext's context-dependent `@`. macOS users who
|
||||||
|
prefer `super+m` can change it in `Default.sublime-keymap`.
|
||||||
|
|
||||||
|
## Colors
|
||||||
|
|
||||||
|
Colors are installed automatically for all five of Sublime's built-in schemes.
|
||||||
|
Each `*.sublime-color-scheme` file (Breakers, Celeste, Mariana, Monokai,
|
||||||
|
Sixteen) is an *additive override*: Sublime merges it onto the matching scheme
|
||||||
|
by filename, recoloring only the Klammertext delimiters and leaving everything
|
||||||
|
else untouched. There is nothing to set up.
|
||||||
|
|
||||||
|
All five share one hue system — application blue, definition green, system
|
||||||
|
orange, each opening bright and its close the same hue darker — shown at full
|
||||||
|
intensity on the dark schemes (Monokai, Mariana) and scaled down for contrast on
|
||||||
|
the light schemes (Breakers, Celeste, Sixteen). Removed text uses each scheme's
|
||||||
|
own comment grey.
|
||||||
|
|
||||||
|
For any other scheme — a legacy `.tmTheme` such as Solarized, or a third-party
|
||||||
|
scheme — copy one of the included files to `<Scheme Name>.sublime-color-scheme`
|
||||||
|
in the package folder (its name is shown at **Preferences → Settings** under
|
||||||
|
`color_scheme`), choosing a light or dark source file to match the ground. The
|
||||||
|
exact values are in each file's header comment.
|
||||||
|
|
||||||
|
## Keeping literal klammers in sync
|
||||||
|
|
||||||
|
Klammers whose content is verbatim (`@code ... code@`) are listed in three
|
||||||
|
places that must agree — a Sublime syntax/plugin cannot read the Emacs
|
||||||
|
defcustom, so the list is duplicated:
|
||||||
|
|
||||||
|
- `klammertext-literal-klammers` in `doc/emacs/klammertext-mode.el` (the source of truth)
|
||||||
|
- `LITERAL_KLAMMERS` in `Klammertext.py`
|
||||||
|
- the `@code` rule and `literal_code` context in `Klammertext.sublime-syntax`
|
||||||
|
|
||||||
|
All three are seeded with just `code`. When you add or remove a literal
|
||||||
|
klammer, change all three.
|
||||||
|
|
||||||
|
## Not included
|
||||||
|
|
||||||
|
Whole-file semantic validation — persistent error underlines when the cursor is
|
||||||
|
elsewhere, klammer-name completion, go-to-definition — is not part of this
|
||||||
|
package. That would need a language server (used through the Sublime LSP
|
||||||
|
package), a separate program, and is unrelated to the highlighting and matching
|
||||||
|
provided here.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the plugin does not seem to load, open **View → Show Console** for any error
|
||||||
|
message. Check that the files sit directly inside `Packages/Klammertext/` (not
|
||||||
|
a nested subfolder) and that the current file's syntax reads "Klammertext" in
|
||||||
|
the status bar at the bottom-right of the window.
|
||||||
60
doc/edit/sublime/Mariana.sublime-color-scheme
Normal file
60
doc/edit/sublime/Mariana.sublime-color-scheme
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Klammertext colors for the "Mariana" scheme (dark ground).
|
||||||
|
// One hue system across all schemes: application = blue, definition =
|
||||||
|
// green, system = orange; each opens bright and its close is 80%% of the
|
||||||
|
// open (a klammer "begins bright and gets dark"). Shown at full intensity
|
||||||
|
// on dark grounds, at 60%% on light grounds for contrast. Delimiters are
|
||||||
|
// forced to normal style. Merged onto Mariana by filename; recolors only
|
||||||
|
// .klammertext scopes. (The highlighting was first developed as an Emacs
|
||||||
|
// major mode; see Klammertext_in_Sublime_Text.md.)
|
||||||
|
//
|
||||||
|
// #a6acb9 removed text (Mariana's comment grey)
|
||||||
|
// #ff6b6b removal markers
|
||||||
|
// #89ddff @name open blue
|
||||||
|
// #6eb1cc name@ close darker blue
|
||||||
|
// #c3e88d @@name open green
|
||||||
|
// #9cba71 name@@ close darker green
|
||||||
|
// #ffab70 @@@name open orange
|
||||||
|
// #cc895a name@@@ close darker orange
|
||||||
|
{
|
||||||
|
"name": "Mariana",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"scope": "comment.line.klammertext, comment.block.klammertext",
|
||||||
|
"foreground": "#a6acb9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "punctuation.definition.comment.klammertext",
|
||||||
|
"foreground": "#ff6b6b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "entity.name.function.begin.klammertext",
|
||||||
|
"foreground": "#89ddff",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "entity.name.function.end.klammertext",
|
||||||
|
"foreground": "#6eb1cc",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "storage.type.begin.klammertext",
|
||||||
|
"foreground": "#c3e88d",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "storage.type.end.klammertext",
|
||||||
|
"foreground": "#9cba71",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "keyword.control.begin.klammertext",
|
||||||
|
"foreground": "#ffab70",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "keyword.control.end.klammertext",
|
||||||
|
"foreground": "#cc895a",
|
||||||
|
"font_style": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
60
doc/edit/sublime/Monokai.sublime-color-scheme
Normal file
60
doc/edit/sublime/Monokai.sublime-color-scheme
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Klammertext colors for the "Monokai" scheme (dark ground).
|
||||||
|
// One hue system across all schemes: application = blue, definition =
|
||||||
|
// green, system = orange; each opens bright and its close is 80%% of the
|
||||||
|
// open (a klammer "begins bright and gets dark"). Shown at full intensity
|
||||||
|
// on dark grounds, at 60%% on light grounds for contrast. Delimiters are
|
||||||
|
// forced to normal style. Merged onto Monokai by filename; recolors only
|
||||||
|
// .klammertext scopes. (The highlighting was first developed as an Emacs
|
||||||
|
// major mode; see Klammertext_in_Sublime_Text.md.)
|
||||||
|
//
|
||||||
|
// #8a8272 removed text (Monokai's comment grey)
|
||||||
|
// #ff6b6b removal markers
|
||||||
|
// #89ddff @name open blue
|
||||||
|
// #6eb1cc name@ close darker blue
|
||||||
|
// #c3e88d @@name open green
|
||||||
|
// #9cba71 name@@ close darker green
|
||||||
|
// #ffab70 @@@name open orange
|
||||||
|
// #cc895a name@@@ close darker orange
|
||||||
|
{
|
||||||
|
"name": "Monokai",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"scope": "comment.line.klammertext, comment.block.klammertext",
|
||||||
|
"foreground": "#8a8272"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "punctuation.definition.comment.klammertext",
|
||||||
|
"foreground": "#ff6b6b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "entity.name.function.begin.klammertext",
|
||||||
|
"foreground": "#89ddff",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "entity.name.function.end.klammertext",
|
||||||
|
"foreground": "#6eb1cc",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "storage.type.begin.klammertext",
|
||||||
|
"foreground": "#c3e88d",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "storage.type.end.klammertext",
|
||||||
|
"foreground": "#9cba71",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "keyword.control.begin.klammertext",
|
||||||
|
"foreground": "#ffab70",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "keyword.control.end.klammertext",
|
||||||
|
"foreground": "#cc895a",
|
||||||
|
"font_style": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
60
doc/edit/sublime/Sixteen.sublime-color-scheme
Normal file
60
doc/edit/sublime/Sixteen.sublime-color-scheme
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Klammertext colors for the "Sixteen" scheme (light ground).
|
||||||
|
// One hue system across all schemes: application = blue, definition =
|
||||||
|
// green, system = orange; each opens bright and its close is 80%% of the
|
||||||
|
// open (a klammer "begins bright and gets dark"). Shown at full intensity
|
||||||
|
// on dark grounds, at 60%% on light grounds for contrast. Delimiters are
|
||||||
|
// forced to normal style. Merged onto Sixteen by filename; recolors only
|
||||||
|
// .klammertext scopes. (The highlighting was first developed as an Emacs
|
||||||
|
// major mode; see Klammertext_in_Sublime_Text.md.)
|
||||||
|
//
|
||||||
|
// #b8b8b8 removed text (Sixteen's comment grey)
|
||||||
|
// #994040 removal markers
|
||||||
|
// #528599 @name open blue
|
||||||
|
// #426a7a name@ close darker blue
|
||||||
|
// #758b55 @@name open green
|
||||||
|
// #5e7044 name@@ close darker green
|
||||||
|
// #996743 @@@name open orange
|
||||||
|
// #7a5236 name@@@ close darker orange
|
||||||
|
{
|
||||||
|
"name": "Sixteen",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"scope": "comment.line.klammertext, comment.block.klammertext",
|
||||||
|
"foreground": "#b8b8b8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "punctuation.definition.comment.klammertext",
|
||||||
|
"foreground": "#994040"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "entity.name.function.begin.klammertext",
|
||||||
|
"foreground": "#528599",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "entity.name.function.end.klammertext",
|
||||||
|
"foreground": "#426a7a",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "storage.type.begin.klammertext",
|
||||||
|
"foreground": "#758b55",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "storage.type.end.klammertext",
|
||||||
|
"foreground": "#5e7044",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "keyword.control.begin.klammertext",
|
||||||
|
"foreground": "#996743",
|
||||||
|
"font_style": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scope": "keyword.control.end.klammertext",
|
||||||
|
"foreground": "#7a5236",
|
||||||
|
"font_style": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
45
doc/edit/sublime/example.kt
Normal file
45
doc/edit/sublime/example.kt
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# This is a line comment — removed to end of line, in the "ignored" color.
|
||||||
|
# The # marker is a different color from the text it removes.
|
||||||
|
|
||||||
|
#[ This is a block comment. It can span lines,
|
||||||
|
and #[ nest ]# like this. ]#
|
||||||
|
|
||||||
|
# --- Klammer applications (@) : opening @name vs. closing name@ / bare @ ---
|
||||||
|
|
||||||
|
@i italic @ @b bold @ @tt monospace @
|
||||||
|
|
||||||
|
@sup 2 | 3 @ # positional arguments separated by |
|
||||||
|
@sup-2-3 # the abbreviated form colors only the name
|
||||||
|
|
||||||
|
@link https://example.com :text a labelled link @
|
||||||
|
|
||||||
|
A named close is handy for long arguments: @section a long body here section@
|
||||||
|
|
||||||
|
# --- Klammer definitions (@@) and system commands (@@@) ---
|
||||||
|
|
||||||
|
@@mdlh : @i Material Definition Language Handbook @ @@
|
||||||
|
@@heading.html : *arg* @@
|
||||||
|
@@@target html | HTML output | options @@@
|
||||||
|
|
||||||
|
# --- Escapes: a caret makes the next character literal (shown as plain text) ---
|
||||||
|
|
||||||
|
^@ and ^# and ^^ and ^| are literal, not delimiters.
|
||||||
|
|
||||||
|
# --- Literal klammer: @code ... code@ interior is verbatim ---
|
||||||
|
# The stray @ and # below are NOT delimiters inside a literal span:
|
||||||
|
|
||||||
|
@code
|
||||||
|
if (a @ b) { return "# not a comment"; }
|
||||||
|
code@
|
||||||
|
|
||||||
|
# --- Whitespace operators (#- #+ #/) are not removals; shown as plain text ---
|
||||||
|
|
||||||
|
tight#-spacing gap#+3here break#/2line
|
||||||
|
|
||||||
|
# --- A deliberate MISMATCH: put the cursor on @open or close@ to see it turn ---
|
||||||
|
# --- red with a message (the names disagree); a matched pair boxes normally. ---
|
||||||
|
|
||||||
|
@open some content close@
|
||||||
|
|
||||||
|
## Everything from this line to the end of the file is removed (## = to EOF).
|
||||||
|
this trailing line is greyed out as removed text
|
||||||
38
doc/install/klammertext.zsh
Normal file
38
doc/install/klammertext.zsh
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Klammertext via Apple's `container` — macOS (Apple Silicon) shell wrapper
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Lets you run Klammertext without typing the full `container run ...` command.
|
||||||
|
# Install: save this file (e.g. ~/klammertext.zsh) and add to your ~/.zshrc:
|
||||||
|
#
|
||||||
|
# source ~/klammertext.zsh
|
||||||
|
#
|
||||||
|
# Then open a new terminal and use `ktext`, `kdesc`, `kdiag` like normal
|
||||||
|
# commands. Requires Apple Silicon + macOS 26 or later, with Apple's
|
||||||
|
# `container` runtime installed and its service started (`container system
|
||||||
|
# start`). Install `container` from the signed .pkg at
|
||||||
|
# https://github.com/apple/container/releases (NOT Homebrew). See
|
||||||
|
# doc/install/macos_container_install.md for the full guide.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# The published image is multi-arch; on Apple Silicon `container` pulls the
|
||||||
|
# native arm64 build, so no --platform / --rosetta is needed.
|
||||||
|
KLAMMERTEXT_IMAGE="${KLAMMERTEXT_IMAGE:-akopra/klammertext:latest}"
|
||||||
|
|
||||||
|
_klammertext_run() {
|
||||||
|
local cmd="$1"; shift
|
||||||
|
container run --rm \
|
||||||
|
-v "$PWD:/work" -w /work \
|
||||||
|
"$KLAMMERTEXT_IMAGE" "$cmd" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The three Klammertext commands. Files are read from and written to the
|
||||||
|
# current directory (mounted into the container as /work).
|
||||||
|
ktext() { _klammertext_run ktext "$@"; }
|
||||||
|
kdesc() { _klammertext_run kdesc "$@"; }
|
||||||
|
kdiag() { _klammertext_run kdiag "$@"; }
|
||||||
|
|
||||||
|
# Download or update to the latest published image (delete first so the moving
|
||||||
|
# `latest` tag is definitely refreshed).
|
||||||
|
klammertext-update() {
|
||||||
|
container image delete "$KLAMMERTEXT_IMAGE" 2>/dev/null
|
||||||
|
container image pull "$KLAMMERTEXT_IMAGE"
|
||||||
|
}
|
||||||
133
doc/install/linux_container_install.md
Normal file
133
doc/install/linux_container_install.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# Running Klammertext on Linux with Docker
|
||||||
|
|
||||||
|
This guide runs Klammertext on a Linux system (Ubuntu or Pop!_OS — the steps are
|
||||||
|
identical) using the prebuilt Docker container. You do **not** need to install
|
||||||
|
TeX Live, Python, or any programming tools — everything, including the TeX Live
|
||||||
|
system that makes PDFs, is packaged inside a single downloadable image. You
|
||||||
|
install Docker once, then Klammertext works like a normal command.
|
||||||
|
|
||||||
|
The published image is multi-arch, so Docker pulls the build matching your CPU
|
||||||
|
(`amd64` on Intel/AMD, `arm64` on ARM machines) automatically.
|
||||||
|
|
||||||
|
For a source build instead (full `@eval` access, no Docker), see
|
||||||
|
`linux_source_install.md`.
|
||||||
|
|
||||||
|
## Step 1 — Install Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install docker.io
|
||||||
|
sudo usermod -aG docker $USER
|
||||||
|
```
|
||||||
|
|
||||||
|
Log out and back in for the group change to take effect (so you can run `docker`
|
||||||
|
without `sudo`). You only do this once.
|
||||||
|
|
||||||
|
## Step 2 — Download Klammertext
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull akopra/klammertext:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
This downloads Klammertext and its built-in TeX Live (a few hundred megabytes).
|
||||||
|
You won't need to do it again unless you're updating.
|
||||||
|
|
||||||
|
## Step 3 — Add the Klammertext commands
|
||||||
|
|
||||||
|
Add these aliases to `~/.bashrc` (or `~/.zshrc`) so `ktext`, `kdesc`, and
|
||||||
|
`kdiag` work as ordinary commands that read and write files in whatever folder
|
||||||
|
you run them from:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
alias ktext='docker run --rm -u $(id -u):$(id -g) -v "$PWD:/work" -w /work akopra/klammertext ktext'
|
||||||
|
alias kdesc='docker run --rm -v "$PWD:/work" -w /work akopra/klammertext kdesc'
|
||||||
|
alias kdiag='docker run --rm -v "$PWD:/work" -w /work akopra/klammertext kdiag'
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-u $(id -u):$(id -g)` on `ktext` makes output files owned by you rather than
|
||||||
|
root. `kdesc` and `kdiag` only read files, so they don't need it. The
|
||||||
|
`-v "$PWD:/work"` mounts your current directory into the container as `/work`,
|
||||||
|
which is required for the commands to see your files.
|
||||||
|
|
||||||
|
Reload your shell (open a new terminal, or `source ~/.bashrc`).
|
||||||
|
|
||||||
|
## Step 4 — Make your first document
|
||||||
|
|
||||||
|
In a folder you want to work in, create a test file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > hello.kt <<'EOF'
|
||||||
|
@document
|
||||||
|
:structure article
|
||||||
|
:title Hello
|
||||||
|
:text
|
||||||
|
@s1 Hello, Klammertext @
|
||||||
|
|
||||||
|
This document was produced with no TeX Live installed — just Docker and the
|
||||||
|
Klammertext image.
|
||||||
|
@
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Produce a web page and a PDF:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ktext hello.kt -t html # makes hello/index.html
|
||||||
|
ktext hello.kt -t pdf # makes hello.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it — you're running Klammertext.
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
To update to the latest published image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull akopra/klammertext:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Haskell support (`@eval :haskell`)
|
||||||
|
|
||||||
|
The standard image does not include Haskell. For `@eval :haskell`, pull the
|
||||||
|
Haskell image and use it in place of the standard one:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull akopra/klammertext:haskell
|
||||||
|
alias ktext='docker run --rm -u $(id -u):$(id -g) -v "$PWD:/work" -w /work akopra/klammertext:haskell ktext'
|
||||||
|
```
|
||||||
|
|
||||||
|
Test:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ktext -s '@eval :haskell main = putStrLn "hello" @' -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, a source install gives all `@eval` modes without a separate image
|
||||||
|
(see `linux_source_install.md`).
|
||||||
|
|
||||||
|
## Klammer set loading
|
||||||
|
|
||||||
|
The Standard Klammer Set is loaded by default. To load a different klammer set,
|
||||||
|
pass `-k PATH` (the klammer set's `.k` file). To run with only the three
|
||||||
|
primitive klammers (`@read`, `@eval`, `@cond`), use `-k none`.
|
||||||
|
|
||||||
|
## If something goes wrong
|
||||||
|
|
||||||
|
- **`Cannot connect to the Docker daemon`** — the Docker service isn't running:
|
||||||
|
`sudo systemctl start docker`, then retry.
|
||||||
|
- **`permission denied` running `docker`** — your user isn't in the `docker`
|
||||||
|
group yet: `sudo usermod -aG docker $USER`, then log out and back in.
|
||||||
|
- **`No such file or directory` for your input** — the file must be in the
|
||||||
|
directory you run the command from (that's what gets mounted). `cd` into the
|
||||||
|
folder with your `.kt` files first.
|
||||||
|
- **Output files owned by root** — add `-u $(id -u):$(id -g)` to the `ktext`
|
||||||
|
command/alias (as shown in Step 3).
|
||||||
|
|
||||||
|
## Freeing disk space
|
||||||
|
|
||||||
|
To remove the image (you can re-pull it later):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker rmi akopra/klammertext:latest
|
||||||
|
docker system prune # optional: remove all unused Docker data
|
||||||
|
```
|
||||||
277
doc/install/linux_source_install.md
Normal file
277
doc/install/linux_source_install.md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# Klammertext source installation on Linux (Ubuntu / Pop!_OS)
|
||||||
|
|
||||||
|
This document describes how to build and install Klammertext from source on a
|
||||||
|
Linux system — Ubuntu or Pop!_OS; the steps are identical — without using the
|
||||||
|
container. A source installation gives full access to all `@eval` modes,
|
||||||
|
including `:haskell` and `:shell` commands that depend on locally installed
|
||||||
|
software.
|
||||||
|
|
||||||
|
For the container installation on Linux, see `linux_container_install.md`.
|
||||||
|
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
The following packages are required to build Klammertext:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install g++ make python3-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The C++ compiler must support C++20. GCC 11 or later is required (Ubuntu 22.04
|
||||||
|
and later include GCC 12+).
|
||||||
|
|
||||||
|
The SKS `@image` klammer requires OpenImageIO Python bindings. These must match
|
||||||
|
the Python version that ktext is built against (check with
|
||||||
|
`python3.XX -c "import OpenImageIO"`). For example, if ktext links against
|
||||||
|
Python 3.12:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip3.12 install OpenImageIO
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
g++ --version
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Clone the repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.andykopra.com/ack/klammertext.git
|
||||||
|
cd klammertext
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
Klammertext's runtime environment is provided by a single self-configuring
|
||||||
|
file. Source it from your shell profile (e.g., `~/.bashrc` or `~/.zshrc`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /path/to/klammertext/mac/env/runtime.env
|
||||||
|
```
|
||||||
|
|
||||||
|
It self-locates `KLAMMERTEXT_HOME` from its own path, adds `bin/` and
|
||||||
|
`tst/` to `PATH` (plus the newest `~/external/texlive/<year>/bin/<arch>` if a
|
||||||
|
TeX Live is installed there), sets `LD_LIBRARY_PATH` so `libklammertext.so` is
|
||||||
|
found, and sets the LSan suppressions. There is no per-host or per-OS variable
|
||||||
|
to set. For a TeX Live or library in a non-standard location, add it to an
|
||||||
|
optional, gitignored `mac/env/runtime.env.local` (sourced at the end).
|
||||||
|
|
||||||
|
After editing your shell profile, reload it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Configure the build
|
||||||
|
|
||||||
|
No build configuration is needed. The single `mac/env/makefile.env` is
|
||||||
|
cross-platform: it reads `KLAMMERTEXT_HOME` from the environment (set by
|
||||||
|
`runtime.env` above), auto-detects the platform with `uname`, and auto-detects
|
||||||
|
Python with `python3-config` — no hardcoded version and no per-host file to
|
||||||
|
edit. Verify the Python development headers are present:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3-config --includes # prints -I.../python3.XX for your Python
|
||||||
|
```
|
||||||
|
|
||||||
|
If `python3-config` is missing, install your distribution's `python3-dev`
|
||||||
|
(Debian/Ubuntu) or `python3-devel` (Fedora/RHEL) package.
|
||||||
|
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Build the shared library, the SKS components, and the three commands with a
|
||||||
|
single command: `make -C com` builds its prerequisites in `mac/` and `sks/`
|
||||||
|
first, then the commands. `OPTIMIZE=1` selects an optimized `-O3` build (what
|
||||||
|
you want to install and run); without it you get a slower `-O0` debug build
|
||||||
|
with AddressSanitizer, intended for development:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make -C com -j OPTIMIZE=1 # lib/libklammertext.so + sks/*.so + bin/{ktext,kdesc,kdiag}
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify the build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ktext -s '@eval 1 + 1 @' -d
|
||||||
|
```
|
||||||
|
|
||||||
|
This should print `2`. For a quick document smoke test, create a small file and
|
||||||
|
render it to HTML:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > hello.kt <<'EOF'
|
||||||
|
@document
|
||||||
|
:structure article
|
||||||
|
:title Hello
|
||||||
|
:text
|
||||||
|
@s1 Hello, Klammertext @
|
||||||
|
|
||||||
|
This document was built from source.
|
||||||
|
@
|
||||||
|
EOF
|
||||||
|
ktext hello.kt -t html # writes hello/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## TeX Live (for PDF output)
|
||||||
|
|
||||||
|
The Standard Klammer Set uses **XeLaTeX** for the `pdf` target. Build a complete
|
||||||
|
Klammertext TeX Live tree with the bundled script, giving it a destination
|
||||||
|
directory under `~/external/texlive/<year>` — the location `runtime.env`
|
||||||
|
auto-detects. The script needs `perl`, `xz-utils`, `fontconfig`, and either
|
||||||
|
`wget` or `curl`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install perl wget xz-utils fontconfig
|
||||||
|
bash $KLAMMERTEXT_HOME/doc/install/texlive_additional_packages.sh ~/external/texlive/2026
|
||||||
|
```
|
||||||
|
|
||||||
|
This installs `scheme-small` plus the additional packages the SKS needs and
|
||||||
|
rebuilds all formats, fetching the binaries for your architecture. It writes a
|
||||||
|
`KLAMMERTEXT_BUILD_INFO.txt` provenance file (mirror, release, package list,
|
||||||
|
date) into the tree.
|
||||||
|
|
||||||
|
Because the tree lives under `~/external/texlive/2026`, `runtime.env` finds it
|
||||||
|
automatically — open a new shell (or re-source `runtime.env`) and `xelatex`
|
||||||
|
will be on `PATH`. No manual `KLAMMERTEXT_TEXLIVE_BIN` is needed.
|
||||||
|
|
||||||
|
If you would rather reuse a TeX Live you already have, point `runtime.env` at it
|
||||||
|
from the gitignored escape hatch instead, and install the SKS's extra packages
|
||||||
|
into it yourself (the package list is in `doc/install/texlive_additional_packages.sh`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat >> "$KLAMMERTEXT_HOME/mac/env/runtime.env.local" <<'EOF'
|
||||||
|
export KLAMMERTEXT_TEXLIVE_BIN=/path/to/texlive/bin/x86_64-linux
|
||||||
|
export PATH="$KLAMMERTEXT_TEXLIVE_BIN:$PATH"
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify and test (reusing the `hello.kt` from the Build section):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xelatex --version
|
||||||
|
ktext hello.kt -t pdf # writes hello.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Optional: Haskell (for @eval :haskell)
|
||||||
|
|
||||||
|
The `@eval :haskell` mode requires `runghc`, which is part of the Haskell
|
||||||
|
toolchain. Alternatively, the `akopra/klammertext:haskell` container image
|
||||||
|
includes GHC (see `linux_container_install.md`).
|
||||||
|
|
||||||
|
The recommended way to install Haskell on Ubuntu is via ghcup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Follow the prompts to install GHC, cabal, and related tools. After
|
||||||
|
installation, ensure the ghcup bin directory is in your `PATH`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PATH=$HOME/.ghcup/bin:$PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
runghc --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Test in Klammertext:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ktext -s '@eval :haskell main = putStr "Hello from Haskell" @' -d
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Directory layout after build
|
||||||
|
|
||||||
|
```
|
||||||
|
klammertext/
|
||||||
|
├── bin/ ktext, kdesc, kdiag executables (after build)
|
||||||
|
├── lib/ libklammertext.so shared library (after build)
|
||||||
|
├── mac/ Klammermachine C++ source
|
||||||
|
├── sks/ Standard Klammer Set (.k files and .so modules)
|
||||||
|
│ ├── document/ document.so
|
||||||
|
│ ├── kutil/ kutil.o
|
||||||
|
│ └── target/ html_util.o, latex_util.o
|
||||||
|
├── com/ command source (ktext, kdesc, kdiag) and Makefile
|
||||||
|
├── doc/ installation guides (doc/install) and editor support (doc/edit)
|
||||||
|
└── tst/ test suites
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Verifying the installation
|
||||||
|
|
||||||
|
Run the following commands to verify that everything works:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic evaluation (Python)
|
||||||
|
ktext -s '@eval 1 + 1 @' -d
|
||||||
|
|
||||||
|
# Shell evaluation
|
||||||
|
ktext -s '@eval :shell date @' -d
|
||||||
|
|
||||||
|
# Show machine state (SKS is loaded by default)
|
||||||
|
ktext -s '' -m
|
||||||
|
|
||||||
|
# HTML and PDF output (uses the hello.kt from the Build section)
|
||||||
|
ktext hello.kt -t html
|
||||||
|
ktext hello.kt -t pdf # requires TeX Live
|
||||||
|
|
||||||
|
# Haskell evaluation (requires ghcup)
|
||||||
|
ktext -s '@eval :haskell main = putStr "42" @' -d
|
||||||
|
|
||||||
|
# Run unit tests
|
||||||
|
make -C $KLAMMERTEXT_HOME/tst test
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
To update an existing source installation to the latest version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd $KLAMMERTEXT_HOME
|
||||||
|
git pull
|
||||||
|
make -C com -j OPTIMIZE=1 # rebuild library, SKS components, and commands
|
||||||
|
```
|
||||||
|
|
||||||
|
The TeX Live tree only needs rebuilding if the SKS's package requirements
|
||||||
|
changed (rare); when they do, re-run the script from the TeX Live section
|
||||||
|
above.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**"libklammertext.so: cannot open shared object file"**
|
||||||
|
Ensure `LD_LIBRARY_PATH` includes `$KLAMMERTEXT_HOME/lib`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LD_LIBRARY_PATH=$KLAMMERTEXT_HOME/lib:$LD_LIBRARY_PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
**"KLAMMERTEXT_HOME is not set"**
|
||||||
|
Set the environment variable as described in the Environment variables
|
||||||
|
section above.
|
||||||
|
|
||||||
|
**"python3.XX/Python.h: No such file or directory"**
|
||||||
|
Install the Python development headers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install python3-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**"xelatex: command not found" (when using -t pdf)**
|
||||||
|
Install TeX Live and ensure its bin directory is in `PATH`.
|
||||||
|
|
||||||
|
**"@eval :haskell requires runghc"**
|
||||||
|
Install Haskell via ghcup as described in the Optional: Haskell section.
|
||||||
161
doc/install/macos_container_install.md
Normal file
161
doc/install/macos_container_install.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# Running Klammertext on a Mac (Apple Silicon)
|
||||||
|
|
||||||
|
This guide gets Klammertext running on your Mac in a few minutes. You do
|
||||||
|
**not** need to install TeX Live, Python, or any programming tools —
|
||||||
|
everything, including the TeX Live system that makes PDFs, is packaged inside
|
||||||
|
a single downloadable image. You install Apple's `container` runtime once, and
|
||||||
|
then Klammertext works like a normal command.
|
||||||
|
|
||||||
|
This guide is for **Apple Silicon Macs (M1/M2/M3/M4/M5) running macOS 26 or
|
||||||
|
later**, which is what Apple's `container` runtime requires. (Support for older
|
||||||
|
Intel Macs can be provided separately if needed.)
|
||||||
|
|
||||||
|
## Step 1 — Install Apple's `container` runtime
|
||||||
|
|
||||||
|
`container` is Apple's own tool for running Linux container images natively on
|
||||||
|
Apple Silicon. It's free.
|
||||||
|
|
||||||
|
1. Go to <https://github.com/apple/container/releases> and download the latest
|
||||||
|
installer package (the `.pkg` file). **Do not** use Homebrew — the Homebrew
|
||||||
|
`container` formula is a different, unrelated tool.
|
||||||
|
2. Double-click the downloaded `.pkg` and follow the installer.
|
||||||
|
3. Open the **Terminal** app (Applications → Utilities → Terminal) and start the
|
||||||
|
`container` background service (accept the recommended default if prompted):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
container system start
|
||||||
|
```
|
||||||
|
|
||||||
|
You only do this once. You can check the service any time with
|
||||||
|
`container system status`.
|
||||||
|
|
||||||
|
## Step 2 — Download Klammertext
|
||||||
|
|
||||||
|
In Terminal, paste this and press Return:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
container image pull akopra/klammertext:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
This downloads Klammertext and its built-in TeX Live. It's a few hundred
|
||||||
|
megabytes, so it takes a minute the first time. Because the image is multi-arch,
|
||||||
|
`container` fetches the native Apple Silicon (arm64) build. You won't need to do
|
||||||
|
this again unless you're updating.
|
||||||
|
|
||||||
|
## Step 3 — Add the Klammertext commands
|
||||||
|
|
||||||
|
This step makes `ktext` (and its helpers) available as ordinary commands.
|
||||||
|
|
||||||
|
1. In Terminal, create the wrapper file by pasting this whole block and
|
||||||
|
pressing Return:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cat > ~/klammertext.zsh <<'EOF'
|
||||||
|
# Klammertext via Apple's `container` runtime (native arm64 image; no Rosetta).
|
||||||
|
KLAMMERTEXT_IMAGE="${KLAMMERTEXT_IMAGE:-akopra/klammertext:latest}"
|
||||||
|
_klammertext_run() {
|
||||||
|
local cmd="$1"; shift
|
||||||
|
container run --rm \
|
||||||
|
-v "$PWD:/work" -w /work \
|
||||||
|
"$KLAMMERTEXT_IMAGE" "$cmd" "$@"
|
||||||
|
}
|
||||||
|
ktext() { _klammertext_run ktext "$@"; }
|
||||||
|
kdesc() { _klammertext_run kdesc "$@"; }
|
||||||
|
kdiag() { _klammertext_run kdiag "$@"; }
|
||||||
|
klammertext-update() {
|
||||||
|
container image delete "$KLAMMERTEXT_IMAGE" 2>/dev/null
|
||||||
|
container image pull "$KLAMMERTEXT_IMAGE"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Tell your shell to load it, by pasting this and pressing Return:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo 'source ~/klammertext.zsh' >> ~/.zshrc
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Close Terminal and open a new window** so the change takes effect.
|
||||||
|
|
||||||
|
You now have three commands — `ktext`, `kdesc`, `kdiag` — that run Klammertext
|
||||||
|
inside a container while reading and writing files in whatever folder you're
|
||||||
|
working in.
|
||||||
|
|
||||||
|
## Step 4 — Make your first document
|
||||||
|
|
||||||
|
In Terminal, go to a folder you want to work in (for example your Desktop) and
|
||||||
|
create a test file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ~/Desktop
|
||||||
|
cat > hello.kt <<'EOF'
|
||||||
|
@document
|
||||||
|
:structure article
|
||||||
|
:title Hello
|
||||||
|
:text
|
||||||
|
@s1 Hello, Klammertext @
|
||||||
|
|
||||||
|
This document was produced on macOS with no TeX Live installed —
|
||||||
|
just Apple's `container` runtime and the Klammertext image.
|
||||||
|
@
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Now produce a web page and a PDF from it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ktext hello.kt -t html # makes hello/index.html
|
||||||
|
ktext hello.kt -t pdf # makes hello.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
Open the results:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
open hello.pdf
|
||||||
|
open hello/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it — you're running Klammertext.
|
||||||
|
|
||||||
|
## Good to know
|
||||||
|
|
||||||
|
- **Work inside one folder.** Klammertext can only see files in (or below) the
|
||||||
|
folder you run the command from. Keep a document and the files it uses
|
||||||
|
together, and run `ktext` from that folder.
|
||||||
|
- **Runs natively.** On Apple Silicon, `container` runs the native arm64 image
|
||||||
|
with no Rosetta translation.
|
||||||
|
- **Fonts.** The default fonts (Crimson Pro, Open Sans, Inconsolata) are built
|
||||||
|
in, so PDFs work with no internet connection. If you ask for a different font
|
||||||
|
by name, Klammertext downloads it from Google Fonts the first time, which
|
||||||
|
needs an internet connection.
|
||||||
|
- **Updating later.** When a new version is announced, run `klammertext-update`
|
||||||
|
in Terminal.
|
||||||
|
- **If you also build Klammertext from source on this Mac.** Most people don't —
|
||||||
|
the whole point of the container is that you don't need a source build. But if
|
||||||
|
this machine *also* has a native source build on its `PATH` (so `which ktext`
|
||||||
|
shows a path like `.../K/com/ktext`), the wrapper's `ktext` function would
|
||||||
|
shadow that native command. To keep both, give the container wrappers their
|
||||||
|
own names by using `ktextc` / `kdescc` / `kdiagc` (trailing `c` = container)
|
||||||
|
in place of `ktext` / `kdesc` / `kdiag` in the Step 3 file. Then plain `ktext`
|
||||||
|
still runs your source build and `ktextc` runs the container.
|
||||||
|
- **Quitting.** Klammertext only runs while you're using it; there's nothing
|
||||||
|
left running afterward. If you want to stop the `container` service entirely,
|
||||||
|
run `container system stop`; start it again with `container system start` next
|
||||||
|
time.
|
||||||
|
|
||||||
|
## If something goes wrong
|
||||||
|
|
||||||
|
- **`command not found: ktext`** — you didn't open a new Terminal window after
|
||||||
|
Step 3, or the `source` line didn't get added. Re-run the Step 3 commands and
|
||||||
|
open a fresh Terminal.
|
||||||
|
- **`container: command not found`** — the `container` runtime isn't installed
|
||||||
|
(Step 1), or the Terminal window predates the install (open a new one).
|
||||||
|
- **A command hangs or won't connect** — the `container` service isn't running.
|
||||||
|
Run `container system start` (check with `container system status`), then try
|
||||||
|
again.
|
||||||
|
- **A run aborted and now seems stuck** — `container run --rm` can leave the
|
||||||
|
container behind after an error. Clear leftovers with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
for id in $(container list -a -q); do container kill "$id"; container delete "$id"; done
|
||||||
|
```
|
||||||
171
doc/install/macos_source_install.md
Normal file
171
doc/install/macos_source_install.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
# Klammertext source installation on macOS (Apple Silicon)
|
||||||
|
|
||||||
|
Companion to `linux_source_install.md`. Verified on an Apple-Silicon Mac
|
||||||
|
(arm64, macOS 26 "Tahoe"). Klammertext's core (engine, SKS, HTML/LaTeX,
|
||||||
|
`@image`) builds and runs natively with Apple Clang; the cross-platform build
|
||||||
|
environment (`mac/env/makefile.env`) auto-detects the OS via `uname`.
|
||||||
|
|
||||||
|
## 1. Toolchain prerequisites
|
||||||
|
|
||||||
|
```sh
|
||||||
|
xcode-select --install # Command Line Tools (clang, headers) — if not already present
|
||||||
|
```
|
||||||
|
|
||||||
|
Then install Python with a linkable `libpython` and `python3-config`, using
|
||||||
|
whichever package manager you have — **Homebrew** or **MacPorts**. Both work;
|
||||||
|
only the install prefix differs, and the build auto-detects it.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Homebrew (https://brew.sh):
|
||||||
|
brew install python
|
||||||
|
brew install gcc # OPTIONAL: a second compiler for a standards check
|
||||||
|
|
||||||
|
# MacPorts (https://www.macports.org) — provisional, pending testing on a
|
||||||
|
# MacPorts system:
|
||||||
|
sudo port install python312
|
||||||
|
sudo port select --set python3 python312 # so python3 / python3-config resolve
|
||||||
|
sudo port install gcc14 # OPTIONAL: a second compiler for a standards check
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- The build embeds Python, which needs `python3-config` and a linkable
|
||||||
|
`libpython`. Apple's `/usr/bin/python3` does **not** ship a usable
|
||||||
|
`python3-config` and Apple discourages linking it — so a package-manager
|
||||||
|
Python (Homebrew or MacPorts) is required. It coexists with Apple's;
|
||||||
|
`python3-config` resolves to it when the manager's `bin` is early on `PATH`
|
||||||
|
(`/opt/homebrew/bin` for Homebrew, `/opt/local/bin` for MacPorts — both set up
|
||||||
|
by their installers). For MacPorts, `port select --set python3 python312`
|
||||||
|
makes `python3` and `python3-config` resolve.
|
||||||
|
- `makefile.env` gets all Python include/link flags from `python3-config` and
|
||||||
|
auto-detects the package-manager prefix (`/opt/homebrew` or `/opt/local`, via
|
||||||
|
`MACOS_PREFIX`), so either manager works without edits. Override with
|
||||||
|
`make MACOS_PREFIX=...` if yours is installed elsewhere.
|
||||||
|
|
||||||
|
## 2. Image support (the `@image` klammer): OpenImageIO
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Homebrew:
|
||||||
|
/opt/homebrew/bin/pip3.<N> install --break-system-packages OpenImageIO
|
||||||
|
# e.g. pip3.14 — match your Python's version
|
||||||
|
|
||||||
|
# MacPorts (matches the python312 installed above):
|
||||||
|
sudo port install py312-openimageio
|
||||||
|
```
|
||||||
|
|
||||||
|
This provides the OpenImageIO Python bindings the embedded interpreter uses.
|
||||||
|
With pip, the self-contained PyPI wheel goes into that Python's site-packages;
|
||||||
|
`--break-system-packages` is needed because the Python is PEP-668 "externally
|
||||||
|
managed". (Homebrew alternative: `brew install openimageio`, heavier — it pulls
|
||||||
|
ffmpeg/openexr/etc.)
|
||||||
|
|
||||||
|
## 3. Clone and configure the environment
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://git.andykopra.com/ack/klammertext.git ~/projects/klammertext
|
||||||
|
# Set up the runtime environment (KLAMMERTEXT_HOME, PATH); add to ~/.zprofile:
|
||||||
|
echo 'source "$HOME/projects/klammertext/mac/env/runtime.env"' >> ~/.zprofile
|
||||||
|
```
|
||||||
|
|
||||||
|
The single self-configuring `runtime.env` self-locates `KLAMMERTEXT_HOME` from
|
||||||
|
its own path. On macOS it sets no `LD_LIBRARY_PATH`/`DYLD_LIBRARY_PATH`
|
||||||
|
(`libklammertext.so` is found via the binaries' `@loader_path` rpath, and
|
||||||
|
`document.so` is dlopen'd by absolute path under `$KLAMMERTEXT_HOME`) and no
|
||||||
|
`LSAN_OPTIONS` (LeakSanitizer is unsupported on macOS).
|
||||||
|
|
||||||
|
## 4. Build (Apple Clang)
|
||||||
|
|
||||||
|
Clang is the compiler you run on macOS, and it is the **default** here
|
||||||
|
(`makefile.env` selects clang on Darwin), so no `COMPILER=` flag is needed.
|
||||||
|
A single `make -C com` builds its prerequisites in `mac/` and `sks/` first,
|
||||||
|
then the commands:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd "$KLAMMERTEXT_HOME"
|
||||||
|
make -C com -j OPTIMIZE=1 # libklammertext.so + sks/*.so + bin/{ktext,kdesc,kdiag}
|
||||||
|
ktext -s '@eval 1 + 1 @' -d # smoke test — prints 2
|
||||||
|
```
|
||||||
|
|
||||||
|
For a document smoke test, create a small file and render it to HTML:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cat > hello.kt <<'EOF'
|
||||||
|
@document
|
||||||
|
:structure article
|
||||||
|
:title Hello
|
||||||
|
:text
|
||||||
|
@s1 Hello, Klammertext @
|
||||||
|
|
||||||
|
This document was built from source.
|
||||||
|
@
|
||||||
|
EOF
|
||||||
|
ktext hello.kt -t html # writes hello/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
(A PDF render needs TeX Live — see section 5.)
|
||||||
|
|
||||||
|
## 5. PDF target: TeX Live
|
||||||
|
|
||||||
|
Build a complete Klammertext TeX Live tree with the bundled script, into
|
||||||
|
`~/external/texlive/<year>` — the location `runtime.env` auto-detects
|
||||||
|
(`bin/universal-darwin`). macOS already has `curl`, `perl`, and `tar`, and
|
||||||
|
`install-tl` self-provides `xz`, so nothing extra is needed:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash "$KLAMMERTEXT_HOME/doc/install/texlive_additional_packages.sh" ~/external/texlive/2026
|
||||||
|
```
|
||||||
|
|
||||||
|
This installs `scheme-small` plus the SKS's additional packages and rebuilds all
|
||||||
|
formats, fetching the `universal-darwin` binaries, and writes a
|
||||||
|
`KLAMMERTEXT_BUILD_INFO.txt` provenance file into the tree. **This is the same
|
||||||
|
command used on Linux**, so the TeX Live layout is identical across your
|
||||||
|
machines. `runtime.env` then finds the tree automatically — open a new shell (or
|
||||||
|
re-source it) and `xelatex` is on `PATH`; no manual `KLAMMERTEXT_TEXLIVE_BIN` is
|
||||||
|
needed.
|
||||||
|
|
||||||
|
If you already run BasicTeX/MacTeX and prefer to reuse it, point `runtime.env`
|
||||||
|
at its bin directory from the gitignored escape hatch instead (and install the
|
||||||
|
SKS's extra packages into it yourself — the list is in the script). Set the
|
||||||
|
variable **and** prepend it to `PATH`, since `runtime.env.local` is sourced
|
||||||
|
after the main `PATH` is built:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cat >> "$KLAMMERTEXT_HOME/mac/env/runtime.env.local" <<'EOF'
|
||||||
|
export KLAMMERTEXT_TEXLIVE_BIN=/usr/local/texlive/2025basic/bin/universal-darwin
|
||||||
|
export PATH="$KLAMMERTEXT_TEXLIVE_BIN:$PATH"
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify and test (reusing `hello.kt` from section 4):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
xelatex --version
|
||||||
|
ktext hello.kt -t pdf # writes hello.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Updating
|
||||||
|
|
||||||
|
To update an existing source installation to the latest version:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd "$KLAMMERTEXT_HOME"
|
||||||
|
git pull
|
||||||
|
make -C com -j OPTIMIZE=1 # rebuild library, SKS components, and commands
|
||||||
|
```
|
||||||
|
|
||||||
|
Rebuild the TeX Live tree only if the SKS's package requirements changed (rare);
|
||||||
|
re-run the script from section 5.
|
||||||
|
|
||||||
|
## Compiler notes (macOS)
|
||||||
|
|
||||||
|
- **Clang is the compiler you run, and the default here.** Apple Clang builds
|
||||||
|
run correctly; `makefile.env` selects clang on Darwin automatically.
|
||||||
|
- **Do not run gcc-built binaries on macOS.** GCC (Homebrew `g++-NN` or MacPorts
|
||||||
|
`g++-mp-NN`) is useful only as an optional compile-time standards check
|
||||||
|
(`make -C com COMPILER=gcc`); the resulting binaries **crash at runtime** on
|
||||||
|
macOS because of a gcc/macOS codegen issue (for example `std::source_location`
|
||||||
|
returning a bad pointer, so `Machine::Machine()` walks into `strlen` and
|
||||||
|
SIGSEGVs). Always run the clang-built binary. (gcc-built binaries run fine on
|
||||||
|
Linux.)
|
||||||
|
- **Switching compilers requires a full clean** — `g++` and `clang++` objects
|
||||||
|
must not be mixed (ABI). `make -C com redo` does a full clean rebuild across
|
||||||
|
`mac`, `sks`, and `com`; a partial `make -C mac clean` does not.
|
||||||
117
doc/install/texlive_additional_packages.sh
Normal file
117
doc/install/texlive_additional_packages.sh
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build a self-contained TeX Live tree for the Klammertext SKS "tex"/"pdf"
|
||||||
|
# targets: scheme-small plus the additional packages the SKS requires, with
|
||||||
|
# all formats rebuilt. This is the single source of truth for constructing a
|
||||||
|
# Klammertext TeX Live directory — used both by the Docker build (Dockerfile,
|
||||||
|
# per-arch) and for native installs.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# texlive_additional_packages.sh <texdir> [mirror]
|
||||||
|
#
|
||||||
|
# <texdir> Destination directory for the TeX Live tree (created by
|
||||||
|
# install-tl), e.g. /opt/texlive or ~/external/texlive/2026.
|
||||||
|
# Should not already exist.
|
||||||
|
# [mirror] A CONCRETE tlnet mirror URL. Must NOT be the mirror.ctan.org
|
||||||
|
# redirect: it resolves to a different mirror (possibly a different
|
||||||
|
# TeX Live revision) on each call, which makes tlmgr abort partway
|
||||||
|
# with "tlmgr itself needs to be updated". Defaults to a pinned
|
||||||
|
# CTAN mirror. Once the current TeX Live year is frozen (the next
|
||||||
|
# release ships), point this at the historic tlnet-final snapshot
|
||||||
|
# for exact reproducibility, e.g.
|
||||||
|
# https://ftp.math.utah.edu/pub/texlive/historic/2026/tlnet-final
|
||||||
|
#
|
||||||
|
# install-tl fetches the binaries for the ARCHITECTURE it runs on, so running
|
||||||
|
# this under arm64 produces an arm64 tree and under x86_64 an x86_64 tree.
|
||||||
|
#
|
||||||
|
# Prerequisites on the host: perl, tar, gzip, xz (xz-utils), and either wget or
|
||||||
|
# curl (macOS ships curl, not wget). fontconfig is recommended so fmtutil can
|
||||||
|
# build all formats cleanly.
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
TEXDIR="${1:?usage: $0 <texdir> [mirror]}"
|
||||||
|
MIRROR="${2:-https://ctan.math.illinois.edu/systems/texlive/tlnet}"
|
||||||
|
|
||||||
|
# --- Fetch the installer into a scratch dir --------------------------------
|
||||||
|
WORK="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$WORK"' EXIT
|
||||||
|
cd "$WORK"
|
||||||
|
# Fetch the installer with whichever downloader is present (macOS has curl, not
|
||||||
|
# wget; Debian build images have wget).
|
||||||
|
if command -v wget >/dev/null 2>&1; then
|
||||||
|
wget -q "$MIRROR/install-tl-unx.tar.gz"
|
||||||
|
else
|
||||||
|
curl -fsSL -O "$MIRROR/install-tl-unx.tar.gz"
|
||||||
|
fi
|
||||||
|
tar --strip-components=1 -xzf install-tl-unx.tar.gz
|
||||||
|
|
||||||
|
# --- Base install: scheme-small into $TEXDIR -------------------------------
|
||||||
|
cat > texlive.profile <<PROFILE
|
||||||
|
selected_scheme scheme-small
|
||||||
|
TEXDIR $TEXDIR
|
||||||
|
TEXMFLOCAL $TEXDIR/texmf-local
|
||||||
|
TEXMFSYSCONFIG $TEXDIR/texmf-config
|
||||||
|
TEXMFSYSVAR $TEXDIR/texmf-var
|
||||||
|
tlpdbopt_install_docfiles 0
|
||||||
|
tlpdbopt_install_srcfiles 0
|
||||||
|
tlpdbopt_autobackup 0
|
||||||
|
PROFILE
|
||||||
|
|
||||||
|
# install-tl's in-line fmtutil can exit 3 before all packages are present;
|
||||||
|
# that is non-fatal (install-tl continues) and the formats are rebuilt below.
|
||||||
|
./install-tl --profile=texlive.profile --repository="$MIRROR"
|
||||||
|
|
||||||
|
# --- Use the tlmgr from the tree we just built (absolute path), not whatever
|
||||||
|
# tlmgr may be on PATH. Pin its repo to the same concrete mirror and sync
|
||||||
|
# it to that repo's revision before installing more packages. -----------
|
||||||
|
ARCH="$(ls "$TEXDIR/bin")"
|
||||||
|
TLMGR="$TEXDIR/bin/$ARCH/tlmgr"
|
||||||
|
"$TLMGR" option repository "$MIRROR"
|
||||||
|
"$TLMGR" update --self
|
||||||
|
|
||||||
|
# --- Additional packages required by the SKS beyond scheme-small -----------
|
||||||
|
# Single list, used both for the install and for the provenance README below,
|
||||||
|
# so the two cannot drift apart.
|
||||||
|
PACKAGES="adjustbox collectbox collection-fontsrecommended enumitem fontaxes \
|
||||||
|
footmisc inconsolata layouts mdframed multirow needspace opensans pict2e \
|
||||||
|
textpos titlesec upquote zref"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086 # intentional word splitting into separate args
|
||||||
|
"$TLMGR" install $PACKAGES
|
||||||
|
|
||||||
|
# --- Rebuild every format now that the full package set is installed -------
|
||||||
|
"$TEXDIR/bin/$ARCH/fmtutil-sys" --all
|
||||||
|
|
||||||
|
# --- Provenance: record how this tree was constructed ----------------------
|
||||||
|
# Written into the tree itself so it is self-documenting when found later.
|
||||||
|
RELEASE="$(head -n1 "$TEXDIR/release-texlive.txt" 2>/dev/null || echo unknown)"
|
||||||
|
BUILT="$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||||
|
cat > "$TEXDIR/KLAMMERTEXT_BUILD_INFO.txt" <<INFO
|
||||||
|
Klammertext TeX Live tree
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Built for the Klammertext Standard Klammer Set (SKS) "tex"/"pdf" targets by
|
||||||
|
doc/install/texlive_additional_packages.sh.
|
||||||
|
|
||||||
|
Built: $BUILT
|
||||||
|
Mirror: $MIRROR
|
||||||
|
TeX Live: $RELEASE
|
||||||
|
Architecture: bin/$ARCH
|
||||||
|
Base scheme: scheme-small
|
||||||
|
Docfiles/srcfiles omitted; all formats rebuilt with fmtutil-sys --all.
|
||||||
|
|
||||||
|
Additional packages installed beyond scheme-small:
|
||||||
|
$(printf ' %s\n' $PACKAGES)
|
||||||
|
|
||||||
|
Reconstruct an equivalent tree with:
|
||||||
|
texlive_additional_packages.sh <texdir> $MIRROR
|
||||||
|
|
||||||
|
Note: the mirror above serves the CURRENT TeX Live release, which receives
|
||||||
|
package updates within its year, so a rebuild is not guaranteed byte-identical.
|
||||||
|
For exact reproducibility, rebuild from the frozen historic tlnet-final
|
||||||
|
snapshot once the release year is no longer current, e.g.
|
||||||
|
https://ftp.math.utah.edu/pub/texlive/historic/<year>/tlnet-final
|
||||||
|
INFO
|
||||||
|
|
||||||
|
echo "Klammertext TeX Live tree built in $TEXDIR (binaries in bin/$ARCH)"
|
||||||
|
echo "Provenance written to $TEXDIR/KLAMMERTEXT_BUILD_INFO.txt"
|
||||||
3
lib/.gitignore
vendored
Normal file
3
lib/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!README.md
|
||||||
5
lib/README.md
Normal file
5
lib/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# lib
|
||||||
|
|
||||||
|
This directory holds the compiled Klammermachine shared library
|
||||||
|
(`libklammertext.so`) after you build Klammertext with `make -C com`.
|
||||||
|
It is empty in the repository.
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
53
sks/Makefile
Normal file
53
sks/Makefile
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Klammertext sks/ Makefile
|
||||||
|
# Builds all .so files in sks/ subdirectories
|
||||||
|
|
||||||
|
K := $(KLAMMERTEXT_HOME)
|
||||||
|
KS := $(K)/sks
|
||||||
|
KM := $(K)/mac
|
||||||
|
|
||||||
|
# Shared library that all .so files depend on
|
||||||
|
LIBRARY := $(K)/lib/libklammertext.so
|
||||||
|
|
||||||
|
# Support directories (build .o files used by other sks/ components)
|
||||||
|
SUPPORT_DIRS := kutil target
|
||||||
|
|
||||||
|
# Excluded directories:
|
||||||
|
# kutil, target - support directories that build .o files, not .so
|
||||||
|
# book, phase - obsolete, not updated for libklammertext.so
|
||||||
|
|
||||||
|
# Directories that build .so files (excluding support and obsolete directories)
|
||||||
|
# Find all subdirectories with Makefiles that have .so build targets
|
||||||
|
# (look for lines like "xyz.so :" or "all : xyz.so")
|
||||||
|
SO_DIRS := $(shell for dir in $(KS)/*/; do \
|
||||||
|
if [ -f "$${dir}Makefile" ] && grep -qE '^[a-z]+\.so\s*:|all\s*:.*\.so' "$${dir}Makefile" 2>/dev/null; then \
|
||||||
|
basename "$$dir"; \
|
||||||
|
fi; done | grep -v -E '^(kutil|target|book|phase)$$')
|
||||||
|
|
||||||
|
.PHONY: all clean support $(SUPPORT_DIRS) $(SO_DIRS)
|
||||||
|
|
||||||
|
# Default target: build support first, then all .so files
|
||||||
|
all : support $(SO_DIRS)
|
||||||
|
|
||||||
|
# Build support directories
|
||||||
|
support : $(SUPPORT_DIRS)
|
||||||
|
|
||||||
|
kutil :
|
||||||
|
$(MAKE) -j -C $(KS)/kutil
|
||||||
|
|
||||||
|
target : kutil
|
||||||
|
$(MAKE) -j -C $(KS)/target
|
||||||
|
|
||||||
|
# Pattern rule for .so directories
|
||||||
|
# Each depends on the library and support directories
|
||||||
|
$(SO_DIRS) : support $(LIBRARY)
|
||||||
|
$(MAKE) -j -C $(KS)/$@
|
||||||
|
|
||||||
|
# Clean all subdirectories
|
||||||
|
clean :
|
||||||
|
@for dir in $(SUPPORT_DIRS) $(SO_DIRS); do \
|
||||||
|
echo "Cleaning $$dir..."; \
|
||||||
|
$(MAKE) -C $(KS)/$$dir clean; \
|
||||||
|
done
|
||||||
|
|
||||||
|
# Rebuild everything
|
||||||
|
redo : clean all
|
||||||
113
sks/block/block.k
Normal file
113
sks/block/block.k
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
@@sp.k : Non-breaking space character @@
|
||||||
|
@@sp.html :: &^#160; @@
|
||||||
|
@@sp.tex :: ~ @@
|
||||||
|
|
||||||
|
@@footnote.k s : Footnote (TBD) @@
|
||||||
|
@@footnote :: [*s*] @@
|
||||||
|
|
||||||
|
@@indent.k s :w.int 3 :linebreak.bool false : Indented block @@
|
||||||
|
@@indent :: @eval block.Indent(K) eval@ @@
|
||||||
|
|
||||||
|
@@quote.k s :w.int 1 :source : Quotation block @@
|
||||||
|
|
||||||
|
@@quote.html ::
|
||||||
|
<div class="quote">
|
||||||
|
*s*
|
||||||
|
</div>
|
||||||
|
@@
|
||||||
|
|
||||||
|
@@quote.tex ::
|
||||||
|
ANDY: QUOTE: *s*
|
||||||
|
#[
|
||||||
|
\hspace*{@{justify.length_mul("|margin|", 1, 'latex')}@}
|
||||||
|
\begin{minipage}{\textwidth- @{justify.length_mul("|margin|", 2, 'latex')}@ }
|
||||||
|
\raggedright
|
||||||
|
|text|
|
||||||
|
@? """|source|""" |?
|
||||||
|
\vspace*{6pt}
|
||||||
|
{\begin{spacing}{1.1}\footnotesize\raggedleft |source| \end{spacing}}
|
||||||
|
?@
|
||||||
|
\end{minipage}
|
||||||
|
]#
|
||||||
|
@@
|
||||||
|
|
||||||
|
@@quote.txt ::
|
||||||
|
@eval block.block_indent(K) eval@
|
||||||
|
@@
|
||||||
|
|
||||||
|
@@note.k s :label Note :color 1.0,1.0,0.9 :bordercolor 0.2,0.2,0.2 :level.int 0 :width
|
||||||
|
: Rectangular block for a special note @@
|
||||||
|
|
||||||
|
@@note :: @eval block.Note(K) eval@ @@
|
||||||
|
|
||||||
|
@@center.k s : Center text @@
|
||||||
|
|
||||||
|
@@center.tex ::
|
||||||
|
\begin{center}
|
||||||
|
*s*
|
||||||
|
\end{center}
|
||||||
|
@@
|
||||||
|
|
||||||
|
@@center.html ::
|
||||||
|
<div class="center">
|
||||||
|
*s*
|
||||||
|
</div>
|
||||||
|
@@
|
||||||
|
|
||||||
|
@@right.k s : Right-justified text @@
|
||||||
|
|
||||||
|
@@right.html ::
|
||||||
|
<b>TBD</b> *s*
|
||||||
|
@@
|
||||||
|
|
||||||
|
@@right.tex ::
|
||||||
|
\begin{flushright}
|
||||||
|
*s*
|
||||||
|
\end{flushright}
|
||||||
|
@@
|
||||||
|
|
||||||
|
@@nl.k : Newline character @@
|
||||||
|
@@nl.html :: <br> @@
|
||||||
|
@@nl.tex :: \newline @@
|
||||||
|
@@nl.txt :: \n @@
|
||||||
|
|
||||||
|
@@tnl.k : Table newline (deprecated; check) @@
|
||||||
|
@@tnl.html :: <br> @@
|
||||||
|
@@tnl.tex :: \\\\ @@
|
||||||
|
@@tnl.txt :: \n @@
|
||||||
|
|
||||||
|
@@newpage.k : Start new page @@
|
||||||
|
@@newpage.html :: @@
|
||||||
|
@@newpage.tex :: \newpage @@
|
||||||
|
@@newpage.txt :: @@
|
||||||
|
|
||||||
|
@@extendpage.k linecount : Extenad current page @@
|
||||||
|
@@extendpage.html :: @@
|
||||||
|
@@extendpage.tex :: \enlargethispage{*linecount*\baselineskip} @@
|
||||||
|
@@extendpage.txt :: @@
|
||||||
|
|
||||||
|
@@vspace.k length : Vertical space @@
|
||||||
|
@@vspace.tex :: \vspace*{*length*} @@
|
||||||
|
|
||||||
|
@@qa.k question | answer : Question and answer formatting @@
|
||||||
|
@@qa ::
|
||||||
|
@b Q: @ *question*
|
||||||
|
|
||||||
|
@b A: @ *answer*
|
||||||
|
@@
|
||||||
|
|
||||||
|
@@@argtype coords | x and y coordinates :pattern 'float'\s+'float' @@@
|
||||||
|
|
||||||
|
@@block.k : to.coords | content :width.float .5 :point.coords 0.0 0.0
|
||||||
|
: Absolute positioning of text block @@
|
||||||
|
|
||||||
|
@@block :: @eval block.Block(K) eval@ @@
|
||||||
|
|
||||||
|
@@lines.k s : Maintain line breaks @@
|
||||||
|
@@lines :: @eval block.Lines(K) eval@ @@
|
||||||
|
|
||||||
|
@@twocolumns.tex s :
|
||||||
|
\begin{multicols}{2}
|
||||||
|
*s*
|
||||||
|
\end{multicols}
|
||||||
|
@@
|
||||||
114
sks/block/block.py
Normal file
114
sks/block/block.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import re
|
||||||
|
import textwrap
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
import klammer_base
|
||||||
|
import kutil
|
||||||
|
import latex_util
|
||||||
|
|
||||||
|
class Indent(klammer_base.Klammer_base):
|
||||||
|
def __init__(self, K):
|
||||||
|
super().__init__(K)
|
||||||
|
self.s = kutil.escape(self.s)
|
||||||
|
|
||||||
|
def html(self):
|
||||||
|
return "FIX: INDENT " + self.s
|
||||||
|
|
||||||
|
def tex(self):
|
||||||
|
tab = f"\\hspace*{{{self.w}ex}}"
|
||||||
|
result = ""
|
||||||
|
|
||||||
|
if self.linebreak:
|
||||||
|
for e in self.s.split("\n"):
|
||||||
|
result += f"{tab}{e}\\\\\n"
|
||||||
|
result = result[:-3]
|
||||||
|
result = re.sub(r"\t", r"\\t", result)
|
||||||
|
else:
|
||||||
|
result = tab + latex_util.minipage(
|
||||||
|
"\\raggedright " + self.s, f"\\textwidth - {self.w}ex", center=False, vmargin="4pt")
|
||||||
|
#print(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def txt(self):
|
||||||
|
indent = " " * self.w
|
||||||
|
result = self.s
|
||||||
|
result = re.sub("KK0022", '"', result)
|
||||||
|
result = indent + f"\n{indent}".join(textwrap.wrap(result, width=70, break_on_hyphens=False))
|
||||||
|
return f"@lit\n{result}\nlit@"
|
||||||
|
|
||||||
|
class Note(klammer_base.Klammer_base):
|
||||||
|
def __init__(self, K):
|
||||||
|
super().__init__(K)
|
||||||
|
|
||||||
|
def html(self):
|
||||||
|
color = ",".join([f"{float(e)*100}%" for e in self.color.split(",")])
|
||||||
|
result = f'''<div class="box" style="background-color: rgb({color}); border-color: rgb({self.bordercolor}) ;">
|
||||||
|
<b>{self.label}:</b> {self.s}
|
||||||
|
</div>'''
|
||||||
|
return result
|
||||||
|
|
||||||
|
def tex(self):
|
||||||
|
if self.width:
|
||||||
|
width = r'{}\\textwidth'.format(self.width)
|
||||||
|
else:
|
||||||
|
#width = r'\\textwidth - 16pt - {}\\leftmargin'.format(self.level)
|
||||||
|
width = r'\\linewidth - \\leftmargin + 2pt'
|
||||||
|
|
||||||
|
result = '''
|
||||||
|
\\par\\begingroup
|
||||||
|
COLOR\\setlength{\\fboxsep}{8pt}
|
||||||
|
\\fcolorbox{bordercolor}{localcolor}{
|
||||||
|
\\parbox{WIDTH}{\\raggedright\\setlength{\\parskip}{8pt}
|
||||||
|
\\textbf{LABEL:} TEXT
|
||||||
|
}}\\endgroup\\par
|
||||||
|
'''
|
||||||
|
result = re.sub('LABEL', self.label, result)
|
||||||
|
result = re.sub('TEXT', re.sub(r'\\', r'\\\\', self.s), result)
|
||||||
|
result = re.sub('COLOR', r'\\definecolor{{localcolor}}{{rgb}}{{{}}}\nCOLOR'.format(self.color), result)
|
||||||
|
result = re.sub('COLOR', r'\\definecolor{{bordercolor}}{{rgb}}{{{}}}\n'.format(self.bordercolor), result)
|
||||||
|
result = re.sub('WIDTH', width, result)
|
||||||
|
print(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class Block(klammer_base.Klammer_base):
|
||||||
|
def __init__(self, K):
|
||||||
|
super().__init__(K)
|
||||||
|
#self.show()
|
||||||
|
|
||||||
|
def html(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def tex(self):
|
||||||
|
to_x, to_y = [float(e) for e in self.to.split()]
|
||||||
|
pt_x, pt_y = [float(e) for e in self.point.split()]
|
||||||
|
result = f"""
|
||||||
|
\\begin{{textblock}}{{{self.width}}}[{pt_x},{pt_y}]({to_x},{to_y})
|
||||||
|
\\vspace*{{-1\\parskip}}
|
||||||
|
{self.content.strip()}
|
||||||
|
\\end{{textblock}}
|
||||||
|
"""
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class Lines(klammer_base.Klammer_base):
|
||||||
|
def __init__(self, K):
|
||||||
|
super().__init__(K)
|
||||||
|
self.s = kutil.escape(self.s)
|
||||||
|
self.lines = self.s.split("\n")
|
||||||
|
|
||||||
|
def html(self):
|
||||||
|
result = ""
|
||||||
|
for line in self.lines:
|
||||||
|
result += line + "<br>\n"
|
||||||
|
return result
|
||||||
|
|
||||||
|
def tex(self):
|
||||||
|
return "\\\\\n".join(self.lines) + "\n"
|
||||||
|
result = ""
|
||||||
|
for line in self.lines:
|
||||||
|
if line:
|
||||||
|
line += r" \\"
|
||||||
|
result += line + "\n"
|
||||||
|
result = re.sub(r"\\\\\n\n", "\n\n", result)
|
||||||
|
return result
|
||||||
26
sks/block/css/block.css
Normal file
26
sks/block/css/block.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
p {
|
||||||
|
margin: .5rem 0 .5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote {
|
||||||
|
margin-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
border: solid black 1px;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
clear: both;
|
||||||
|
margin: 1.0em 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indent {
|
||||||
|
margin-left: 2rem;
|
||||||
|
}
|
||||||
1
sks/block/css/list.txt
Normal file
1
sks/block/css/list.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
block.css
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user