#include #include #include #include "util.h" #include "check.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 {}; // Optional: "kdiag --machine" with no input shows the Machine's initial // state, which is a question about the machine rather than about any // document -- and the one thing a programmer wants before feeding it // anything. args.req("input", "Klammertext input text", "'text'", false); 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 @"); args.flag("eval", "Process eval: @eval @"); args.flag("cond", "Process cond: @cond | | @"); 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", "Register klammer definitions: @@ ... @@. Their katoms " "are consumed, so they appear only with --replaced."); args.flag("system", "Register machine definitions: @@@target, @@@argtype, @@@state, " "@@@klammerset. Their katoms are consumed, so they appear only " "with --replaced. The two tiers are ORDERED: a klammer names a " "target, so --klammer alone cannot register a klammer whose " "target the input itself declares. That is not an error here: " "the definition is skipped and its katoms stay visible, which is " "the report."); args.flag("process", "Process all, both tiers of definition included"); args.flag("check", "Check every klammer application in the input and in the " "body of every defined klammer"); args.flag("machine", "Show the state of the Klammermachine after all processing"); args.opt("v", "'verbosity'", "degree", "0", "'verbosity'"); if (show_usage(argc, argv)) { // A bare command is a REQUEST for information, not a failure: // usage goes to stdout (it is the result being asked for) and the // exit status is 0, so "kdiag && echo ok" reports what happened. args.usage(file_basename(argv[0])); exit(0); } auto p = [&](const 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; } // kdiag deliberately has no --klammersets: a klammerset is reached here // the way the input reaches anything else, with "@read sks/sks.k @". // The symbol mechanism is a higher-level convenience, and a debugger // must not depend on the machinery it debugs -- if symbol resolution // breaks, kdiag must still run, and the two paths to the same file can // then be compared. Machine machine; // kdiag has no target argument, and evaluating needs one: Eval::eval // re-reads its result under K_target, so with the variable unset every // @eval died with "Variable "K_target" not defined" -- an error naming // something the user never wrote. A command that does not specify a // target evaluates under the GENERAL target (Andy, 2026-08-15), which // is also what kdiag means: it loads no klammerset, so nothing // target-specific is in scope to begin with. machine.m_state.set("K_target", Target_registry::general_name); machine.m_state.set("K_input_dir", fs::current_path().string()); std::string input = args.as_string("input"); std::string command = construct_command_pathname(argv[0]); // ONE authoritative katom list. process() returns the katoms but // registers nothing -- read() is what appends to m_katoms and extracts // the definitions -- so kdiag does that itself, in the order read() // uses, and then displays m_katoms. Displaying its own copy instead // would show the definition katoms unconsumed, because extraction // marks them "replaced" in the Machine's list, not in a copy. bool all = p("process"); katom_list processed = all ? machine.process(input, command) : machine.process(input, command, p("nonascii"), p("literal"), p("ignore"), p("ws"), p("klammer"), p("eval"), p("cond"), p("read")); machine.m_katoms.insert( machine.m_katoms.end(), processed.begin(), processed.end()); // Machine definitions first: a klammer definition names a target, so // the target must be registered before the klammer that uses it. // Tolerant: a definition that cannot be registered because the other // tier was not run is skipped, not fatal -- its katoms stay visible. // kdiag dissects structure and typing, which means allowing modes of // processing that would be errors in ktext. if (all || p("system")) machine.extract_machine_definitions(true); if (all || p("klammer")) machine.extract_klammer_definitions(true); katom_list& katoms = machine.m_katoms; 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; if (!p("check")) { 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::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"; } } int check_errors = 0; if (p("check")) { check_errors = report_diagnostics(check_machine(machine, katoms, "*"), std::cout); // The commonest cause of a wall of "not defined" is that nothing // was registered. The advice belongs here rather than in the // catch block: --check REPORTS, it does not throw, so an exception // handler never sees this case. The condition is the CAUSE, not a // proxy for it. if (check_errors > 0 && machine.m_klammers.m_klammers.empty()) { std::cout << "\nNo klammers are registered, so every application is " "undefined.\nkdiag loads no klammerset: read one in the input, " "as \"@read sks/sks.k @\",\nand add --process (or --system " "--klammer --read) to register what it defines.\n"; } } // Last, and after everything else: the state shown is the result of // whatever the other arguments did. This is why --check defers its // exit status rather than returning as soon as it finds errors. if (p("machine")) { std::cout << machine; } if (check_errors > 0) { std::cout << black; return 1; } } catch (Error& e) { // The advice must name a way in that kdiag actually has. It named // "--klammersets sks" until 2026-08-15, a flag the 2026-08-14 redesign // deliberately removed from this command -- a debugger must not depend // on the machinery it debugs -- so the one remedy offered was one that // errors. Same wording as the --check hint above, for the same reason. std::string advice = ""; if (e.m_type == "target" || e.m_type == "definition") { advice = "kdiag loads no klammerset: read one in the input, as " "\"@read sks/sks.k @\", and add --process to register what it " "defines."; } e.print_message(advice); // Nonzero, as ktext does: a command that prints an error and exits 0 // reports success, and a script cannot tell the difference. std::cout << black; return 1; } std::cout << black; return 0; }