A snapshot of the development tree. The substantial changes since the last one:
COMMAND OUTPUT POLICY. The three commands display text in exactly three cases,
and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before
termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext
that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging
used to share the stream and land inside the document. A bare command prints
its usage and succeeds rather than failing. Colour is emitted only to a
terminal, per stream, and NO_COLOR is honoured.
"-v 1" reports every decision whose outcome you could not have read off your own
input: the klammerset that was loaded and from which file, a font's directory, a
":files" name's file, how "-o" was expanded. Higher levels are the trace.
The commands no longer warn and continue: an anomaly is an error, described with
its location. Two exceptions remain, each for a stated reason -- a condition
that is expected and temporary by design, and a judgment that is a heuristic
rather than exact.
@cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when
the file is read. Two consequences for a writer:
* a state variable reaches the predicate. "@@@state Flag :value true @@@
@cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and
silently take the false branch. The document now behaves like a klammer
body, whose arguments are bound before its conditionals are decided.
* nothing in a discarded branch happens -- it is not read, not evaluated, not
expanded. An @eval in the branch not taken used to run anyway.
Its predicate relation is total and strict: true, True, 1; false, False, 0, and
empty; anything else is an error at the @cond rather than silently false.
@eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard
error out of the document (it appears under "-v 1") and treat a nonzero exit as
an error naming what the command reported. A command that exits nonzero on
purpose -- "grep" finding no match -- says so with "|| true".
KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the
order given, sharing one namespace, with the definition modes deciding
collisions. "none" means none and may not be combined with other symbols. A
klammerset with symbol X is declared in a file X/X.k, which is what lets two
sets require the same third set without loading it twice.
TESTS. Four new suites: the kdiag command's interface, the @eval primitive's
contract with the outside world, and verbosity at both tiers. Three suites
that could not run on macOS at all now do.
Assembled from dev commit 6c8ee6c22fca.
119 lines
6.2 KiB
C++
119 lines
6.2 KiB
C++
#pragma once
|
|
|
|
#include <iosfwd>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "util.h" // strings_t
|
|
|
|
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<std::string> 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<std::string> m_effective {};
|
|
// Targets named in a written definition (the comma-list, or one per
|
|
// definition), empty for a purely general klammer.
|
|
std::vector<std::string> m_written {};
|
|
// For `derived`: the klammers the body calls. For `undecidable`: why.
|
|
std::vector<std::string> 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 <text>" 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<std::string> m_files {};
|
|
};
|
|
|
|
// Compute the coverage of every klammer the machine has loaded. Analysis
|
|
// only: nothing in the Machine is modified.
|
|
// `defined_outside` restricts the RESULT to klammers with a definition from a
|
|
// file not in the list. The ANALYSIS still sees every klammer: a general
|
|
// body's coverage is the intersection of what the klammers it calls cover, and
|
|
// most of those come from the klammerset. So the klammersets are loaded, used
|
|
// to derive, and left out of the report -- which is what "kdesc -i" wants.
|
|
std::vector<Klammer_coverage> klammer_coverage(const Machine& machine,
|
|
const strings_t& defined_outside = {});
|
|
|
|
// 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<Klammer_coverage>& coverage,
|
|
bool full, std::ostream& os);
|