2026-09-25 00:32:55 +02:00
|
|
|
#ifdef _WIN32
|
2026-09-25 18:43:22 +02:00
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
|
#define NOMINMAX
|
|
|
|
|
#include <windows.h>
|
2026-09-25 00:32:55 +02:00
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <io.h>
|
|
|
|
|
#endif
|
2026-09-25 18:43:22 +02:00
|
|
|
#include <cstdlib>
|
2026-07-18 18:48:23 +02:00
|
|
|
#include <tuple>
|
|
|
|
|
|
|
|
|
|
#include "argv.h"
|
|
|
|
|
#include "file.h"
|
|
|
|
|
#include "log.h"
|
2026-08-16 01:37:59 +02:00
|
|
|
#include "machine.h"
|
2026-07-18 18:48:23 +02:00
|
|
|
|
|
|
|
|
using namespace std::string_literals;
|
|
|
|
|
|
|
|
|
|
fs::path construct_command_pathname(char* command)
|
|
|
|
|
{
|
2026-08-23 20:48:26 +02:00
|
|
|
// The pseudo source file for command-line string input (-s, kdiag's
|
|
|
|
|
// input): a file IN THE CWD named after the command, so that a relative
|
|
|
|
|
// @read in string input resolves against the directory the user ran the
|
|
|
|
|
// command from -- the string-input analog of "a document's relative
|
|
|
|
|
// names resolve against the document". The basename matters: the
|
|
|
|
|
// former "<cwd>/<argv[0]>" string concatenation anchored resolution at
|
|
|
|
|
// the BINARY's directory whenever argv[0] was absolute
|
|
|
|
|
// (/usr/local/bin/ktext, a wrapper script), so every relative @read in
|
|
|
|
|
// -s input searched bin/ instead of the cwd.
|
|
|
|
|
return fs::current_path() / fs::path(command).filename();
|
2026-07-18 18:48:23 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-25 00:32:55 +02:00
|
|
|
void set_command_identity(const std::string& name, const std::string& pathname)
|
|
|
|
|
{
|
|
|
|
|
command_name = name;
|
|
|
|
|
command_pathname = pathname;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 18:48:23 +02:00
|
|
|
void set_verbose_level(int argc, char* argv[])
|
|
|
|
|
{
|
2026-09-25 00:32:55 +02:00
|
|
|
#ifdef _WIN32
|
|
|
|
|
// Klammertext writes LF on every system: a document a Windows user pipes
|
|
|
|
|
// or redirects must not gain CR, and the suites read the streams byte for
|
|
|
|
|
// byte. The C runtime's text mode would append it to every line.
|
|
|
|
|
_setmode(_fileno(stdout), _O_BINARY);
|
|
|
|
|
_setmode(_fileno(stderr), _O_BINARY);
|
2026-09-25 18:43:22 +02:00
|
|
|
// A console decodes what a program writes in the CONSOLE's output code
|
|
|
|
|
// page, which on a localized Windows is an OEM page (850 on a German one)
|
|
|
|
|
// and never UTF-8, whatever the process's own code page (the manifest
|
|
|
|
|
// makes that UTF-8). The commands write UTF-8 -- kdesc --katoms's
|
|
|
|
|
// ⟨name⟩, an error report's ⟨katom⟩ -- so a console showed three cp850
|
|
|
|
|
// characters per symbol while a file or a pipe received the right bytes
|
|
|
|
|
// (which is why no suite saw it; Andy did, 2026-09-25). Switch the
|
|
|
|
|
// console to UTF-8 while a command runs, when a stream is a console, and
|
|
|
|
|
// put it back at exit: the page belongs to the console, not the process,
|
|
|
|
|
// and would outlive it.
|
|
|
|
|
static UINT console_output_cp = 0;
|
|
|
|
|
static UINT console_output_cp_now = 0;
|
|
|
|
|
if (_isatty(_fileno(stdout)) || _isatty(_fileno(stderr))) {
|
|
|
|
|
console_output_cp = GetConsoleOutputCP();
|
|
|
|
|
if (console_output_cp != 0 && console_output_cp != CP_UTF8) {
|
|
|
|
|
SetConsoleOutputCP(CP_UTF8);
|
|
|
|
|
console_output_cp_now = GetConsoleOutputCP();
|
|
|
|
|
std::atexit([] { SetConsoleOutputCP(console_output_cp); });
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-25 00:32:55 +02:00
|
|
|
#endif
|
2026-07-18 18:48:23 +02:00
|
|
|
//command_name = absolute_pathname(argv[0]);
|
2026-09-25 00:32:55 +02:00
|
|
|
command_name = fs::path(argv[0]).generic_string();
|
|
|
|
|
command_pathname = construct_command_pathname(argv[0]).generic_string();
|
2026-07-18 18:48:23 +02:00
|
|
|
//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");
|
2026-09-25 18:43:22 +02:00
|
|
|
#ifdef _WIN32
|
|
|
|
|
if (console_output_cp != 0 && console_output_cp != CP_UTF8) {
|
|
|
|
|
K::log(1, "Console output code page set to UTF-8 for this run (was "
|
|
|
|
|
+ std::to_string(console_output_cp) + ", now "
|
|
|
|
|
+ std::to_string(console_output_cp_now) + ")");
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2026-07-18 18:48:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()) {
|
2026-07-26 23:22:59 +02:00
|
|
|
// No -o: output goes next to the input file ("" -- an input with no
|
|
|
|
|
// directory component -- resolves to cwd below).
|
|
|
|
|
output_dir = file_directory(input_filenames[0]);
|
2026-07-18 18:48:23 +02:00
|
|
|
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};
|
|
|
|
|
}
|
2026-08-16 01:37:59 +02:00
|
|
|
|
|
|
|
|
|
2026-09-08 16:54:21 +02:00
|
|
|
void set_command_state(
|
|
|
|
|
Machine& machine, const std::string& frame_name, const std::string& target,
|
|
|
|
|
const std::vector<std::string>& input_filenames, const std::string& output_dir,
|
|
|
|
|
const std::string& output_basename, bool display_only)
|
|
|
|
|
{
|
|
|
|
|
State& S = machine.m_state;
|
|
|
|
|
S.open_frame(frame_name);
|
|
|
|
|
S.set("K_target", target);
|
|
|
|
|
S.set("K_output_dir", output_dir);
|
|
|
|
|
S.set("K_output_basename", output_basename);
|
|
|
|
|
S.set("K_stdout_only", display_only ? "true" : "false");
|
|
|
|
|
// The list form uses the standalone-"/" separator (filenames may
|
|
|
|
|
// contain spaces); K_input_filename is the single root input file.
|
|
|
|
|
S.set("K_input_filenames", join(input_filenames, " / "));
|
|
|
|
|
S.set("K_input_filename", input_filenames.empty() ? "" : input_filenames[0]);
|
|
|
|
|
S.set("K_verbose_level", std::to_string(verbose_level));
|
|
|
|
|
if (!input_filenames.empty()) {
|
|
|
|
|
S.set("K_input_dir", absolute_pathname(file_directory(input_filenames[0])));
|
|
|
|
|
} else {
|
2026-09-25 00:32:55 +02:00
|
|
|
S.set("K_input_dir", fs::current_path().generic_string());
|
2026-09-08 16:54:21 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-16 01:37:59 +02:00
|
|
|
void load_klammersets(Machine& machine, const strings_t& symbols)
|
|
|
|
|
{
|
|
|
|
|
// "none" is the absence of a klammerset, not the name of one, so it says
|
|
|
|
|
// nothing about what should be loaded ALONGSIDE it: written first it would
|
|
|
|
|
// silently discard the rest of the list, written last it would be looked up
|
|
|
|
|
// as a symbol and reported missing. Both readings are wrong, so neither is
|
|
|
|
|
// guessed at -- "none" is accepted only alone.
|
|
|
|
|
if (is_in("none"s, symbols) && symbols.size() > 1) {
|
|
|
|
|
throw Klammerset_error(
|
|
|
|
|
"The klammerset \"none\" cannot be combined with other klammersets: "
|
|
|
|
|
"it means that none is loaded. Give \"none\" alone, or name only the "
|
|
|
|
|
"klammersets to load.",
|
2026-08-23 20:48:26 +02:00
|
|
|
Locator::none());
|
2026-08-16 01:37:59 +02:00
|
|
|
}
|
|
|
|
|
if (symbols.empty()) {
|
|
|
|
|
// Which klammerset was loaded, and from where, is a DERIVED value: the
|
|
|
|
|
// user wrote no --klammersets at all and got the SKS from
|
|
|
|
|
// $KLAMMERTEXT_HOME. Only the symbol branch below reported its
|
|
|
|
|
// resolution, so the commonest case -- the default -- said nothing.
|
|
|
|
|
std::string sks = machine.m_state.value("KLAMMERTEXT_HOME") + "/sks/sks.k";
|
|
|
|
|
(void)K::log(1, "Klammerset (default): " + sks);
|
|
|
|
|
machine.read(fs::path(sks));
|
|
|
|
|
} else if (symbols[0] == "none") {
|
|
|
|
|
(void)K::log(1, "Klammersets: none");
|
|
|
|
|
} else {
|
|
|
|
|
// The whitespace-delimited words in symbols must be klammerset symbols:
|
|
|
|
|
for (const auto& symbol : symbols) {
|
|
|
|
|
// Same guard as ":requires" (Machine::load_klammerset_files): a set
|
|
|
|
|
// named twice on the command line is loaded once.
|
|
|
|
|
if (machine.m_klammersets.has(symbol)) {
|
|
|
|
|
(void)K::log(1, "Klammerset \"" + symbol + "\": already loaded, skipped");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
std::string klammerset_filename = resolve_klammerset_symbol(
|
2026-09-25 00:32:55 +02:00
|
|
|
symbol, machine.m_state.value("K_input_dir"), Locator::none()).generic_string();
|
2026-08-16 01:37:59 +02:00
|
|
|
// Symbol -> file is the search path's answer, and the search
|
|
|
|
|
// path has three stages with shadowing: the file it landed on is
|
|
|
|
|
// exactly what a user cannot read off "--klammersets x".
|
|
|
|
|
(void)K::log(1, "Klammerset \"" + symbol + "\": " + klammerset_filename);
|
|
|
|
|
machine.read(fs::path(klammerset_filename));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|