#pragma once #include #include #include class Machine; // Target coverage: which targets a klammer can actually render to. // // Coverage is a FACT about a klammer, distinct from a klammer set's claim // about what it supports and from what happens when a document meets a // target. This module computes the fact, and computes only what can be // computed -- it changes nothing about how the Machine behaves. See // notes/target_coverage.md for why the fact has to come first. // // Three rules, and the whole of the analysis is deciding which one applies: // // DERIVED where coverage is structurally determined. A general // definition (no target suffix) whose body is text and nothing // else covers every target. One whose body calls other klammers // covers the INTERSECTION of what those klammers cover -- a // klammer can only render where everything it is made of renders. // // DECLARED where coverage depends on something the engine cannot // interpret. A general body holding an @eval is the main case: // deciding which targets a Python function answers for is // undecidable, so the targets have to be written down (which is // what the comma-separated target list is for). A general body // holding a ^'...'^ literal span is the same problem wearing // different clothes -- the span exists precisely to carry raw // target markup past the escaping pass, so a body containing one // is target-specific with nothing for the intersection rule to // see. @read is included: its content is not known statically. // // DECLARED-ALL a definition written ".*" asserts that the klammer works for // EVERY target, including targets that do not exist yet. A list // of the targets defined today cannot say that. It is the one // target declaration a machine could later falsify: a ".*" // klammer whose implementation branches per target is // contradicting itself, which is a structural property. // // UNKNOWN where neither applies -- no definition at all. Absence means // "not decided yet", never "deliberately unavailable": the SKS is // incomplete on schedule rather than by design, so nothing may // read a missing definition as a statement of intent. // // The intersection is computed as a GREATEST FIXPOINT rather than by // recursion: general klammers may call each other, and a cycle would not // terminate. Every general klammer starts at "all targets" and the rule is // applied until nothing shrinks, which terminates because the sets only ever // lose members. The pass runs over the whole registry AFTER loading, not at // definition time -- definitions load in file order, so a body may call a // klammer defined later. enum class coverage_t { all_declared, // written ".*": every target, including ones not yet defined all, // general body, no klammer calls: every target derived, // general body of klammer calls: their intersection declared, // written per target, no general body to derive from undecidable, // general body holding @eval, @read, or a literal span none, // declared (.k) but never defined }; struct Klammer_coverage { std::string m_name {}; coverage_t m_kind { coverage_t::none }; // The targets this klammer can render to, as computed. std::vector m_targets {}; // Targets the Machine currently offers it for. These differ exactly // where a general body is copied to targets it cannot really serve, which // is the hazard the report exists to surface. std::vector m_effective {}; // Targets named in a written definition (the comma-list, or one per // definition), empty for a purely general klammer. std::vector m_written {}; // For `derived`: the klammers the body calls. For `undecidable`: why. std::vector m_from {}; std::string m_reason {}; // Whether a ".k" declaration exists. A klammer without one still works // -- its parameters can be declared on the definition itself -- but it // has no DESCRIPTION, so kdesc can say nothing about what it does and // "kdesc -k " can only find it by name. bool m_declared { false }; // The file(s) the klammer is written in, in definition order. Usually // one -- a klammer's targets are declared together -- but a klammer whose // definitions are spread over several files lists them all. Shown by // "--coverage -v", and only on a row that names ONE klammer. std::vector m_files {}; }; // Compute the coverage of every klammer the machine has loaded. Analysis // only: nothing in the Machine is modified. std::vector klammer_coverage(const Machine& machine); // The report behind "kdesc --coverage". `full` ("--coverage all") adds the // source-file column and shows every "Needs attention" category, including // the empty ones; without it those categories appear only when they have // entries, so a klammer set with nothing wrong produces a short report. // // It is an argument rather than a verbosity level because the two are // independent: -v says how much to show about the command's PROCESSING, and // this says what the command's RESULT contains. // // Audiences, in the project's terms: an AUTHOR runs it to see what is // available for a target; a DESIGNER runs "--coverage all" to be reminded of // the full set of categories while building a klammer set. void report_coverage(const Machine& machine, const std::vector& coverage, bool full, std::ostream& os);