Inspecting environments

An evaluated Environment contains the values and structure produced by DIPL, along with available metadata and provenance. Inspector provides a read-only way to explore that environment: find paths, examine values and tables, locate their source, and trace recorded dependencies. Create one inspector for an environment and reuse it for related queries. The environment must outlive the inspector.

Use open_artifact to load a DIPfile, DIPL source, or DIPH5 snapshot. Use DIP when you need to assemble inputs before parsing. Comparison and preview work across separate evaluations, so they are independent of Inspector. The tables below show which operation to use; the following sections give short C++ examples.

Inspection quick reference

Start with an evaluated environment. Enable the two optional inspection records only if you need them:

using namespace snt::dip;
auto env = open_artifact("DIPfile", true, true);
Inspector view{env};

The first true records dependencies; the second retains original array and table blocks. The examples use illustrative paths. other means a second environment; source, overrides, and error stand for caller inputs.

Find a path

Finding information in an environment

C++ call

What it provides and when to use it

detect_artifact("DIPfile")

Identifies an input by its filename. Use it before opening an unfamiliar project, DIPL file, or DIPH5 snapshot.

open_artifact("DIPfile") / reload_artifact(env, "DIPfile")

Loads an environment, or refreshes one after a file changes. A failed reload leaves the current environment intact.

view.select("?physics.")

Finds matching evaluated values. Use a path query or tag filter to narrow a search.

view.cursor("physics").children()

Walks direct children. Use elements() for lists and items() for maps when building a tree.

view.capabilities("physics.speed")

Reports which views a path supports: value, children, source, array, table, or graph. Use it to choose relevant actions in a UI.

Read values and data

Reading evaluated data

C++ call

What it provides and when to use it

view.value("physics.speed")

Returns an owned value with its type, shape, units, metadata, and provenance. Use for a detailed value view. view.values() returns all values in environment order.

view.value_summary("samples")

Returns type, shape, size, units, and metadata without copying the array. Use it to decide how much data to show.

view.value_slice("samples", {{0, 9}})

Reads elements 0 through 9 of an evaluated array. Use for paging or plots; the range is inclusive and does not trigger lazy disk reads.

view.table("measurements")

Returns row count and ordered column descriptions without copying cells. Read a column with view.value_slice; view.tables() lists all tables.

view.block_input("samples")

Returns the original array or table text when a live parse retained it. Use to show input beside evaluated data; view.block_inputs lists all retained blocks.

Explain a value

Meaning and provenance

C++ call

What it provides and when to use it

view.describe("physics.speed")

Produces a bounded description of a value, group, collection, or table. Use in search results or agent tools. view.list_descriptions describes a limited set of query matches.

view.override_contract("physics.speed")

Classifies a proposed override path and reports its type, current shape, units, and enforced rules. Use it to prepare an override before preview.

view.schema_hierarchy()

Lists reusable schema definitions, evaluated application sites, and the schemas associated with each value. Use it to browse a model by schema.

view.source_locations(source)

Finds declarations, modifications, and overrides for a SourceEntity. Use for source navigation; entities can also be named sources, schemas, units, or project entries.

view.dependency_neighborhood("physics.speed")

Shows direct inputs, readers, and current value and condition events. Use for a selected node’s graph view; graph recording must be enabled.

view.graph().dependencies("?physics.speed")

Accesses recorded reads. The graph also exposes reverse references, evaluation history, and expression trees for calculation tracing.

Browse reusable schemas

A definition describes members that a schema can create. An application records where evaluation used the schema. A value association identifies the schema that supplied a particular evaluated value. These are separate facts: one definition may be applied at many paths, and an override does not change the definition.

auto hierarchy = view.schema_hierarchy();
for (const auto& definition : hierarchy.definitions)
    std::cout << definition.id << ": " << definition.members.size() << " top-level members\n";
for (const auto& application : hierarchy.applications)
    std::cout << application.path << ": " << application.schema_ids.front() << '\n';

Members have nested members in declaration order. schema_refs names other definitions without copying their trees. Applications retain evaluation order and distinguish schemas inherited by collection items from explicit ones. Loaded DIPH5 snapshots have definitions_available == false and applications_complete == false: only retained effective associations can be reconstructed, without application origins or inheritance flags.

Check a proposed change

Comparing and reporting changes

C++ call

What it provides and when to use it

Comparison comparison{env, other}

Finds added, removed, and changed values between two environments. Pass two DIPH5 paths to load snapshots instead. comparison.render() gives a bounded text summary.

preview("DIPfile", overrides)

Evaluates a baseline and a candidate with proposed overrides. Use its diagnostics and comparison before applying a change.

diagnostic_from_exception(error)

Converts a caught exception into a structured message with available source locations. Use it to report parse or reload failures.

Using the inspection APIs

The examples in this section assume the env and view created above and #include <snt/dip/inspect/inspector.h>. Paths are illustrative; use paths defined by your own DIPL input.

Open and reload an artifact

snt::dip::detect_artifact() recognizes conventional filenames: DIPfile, .dip or .dipl, .dipt, and .diph5. It does not validate contents. snt::dip::open_artifact() loads a project, DIPL file, or DIPH5 snapshot. A .dipt file is table input within DIPL and cannot be opened as an independent environment.

#include <snt/dip/artifact.h>

auto kind = snt::dip::detect_artifact("DIPfile");
auto env = snt::dip::open_artifact("DIPfile");
snt::dip::reload_artifact(env, "DIPfile");

snt::dip::reload_artifact() replaces env only after the new input loads successfully. Pass recording options again when reloading a live parse: reload_artifact(env, "DIPfile", true, true) retains its dependency graph and original input blocks. A loaded DIPH5 snapshot uses the graph saved in the file.

Find values and traverse groups

Inspector::select finds evaluated value nodes by query, optionally filtered by tags. A Cursor traverses a known path. children() is for groups, elements() for lists, and items() for maps.

for (const auto& node : view.select("?physics.")) {
    // node->path.name is the full path of a matching value.
}

auto group = env["physics"];
for (const auto& [name, child] : group.children()) {
    // child.get_path() identifies a direct child.
}

A ? query selects all values; ?physics. selects a subtree. Selection returns independent node snapshots, while cursors read the environment. See Basic DIP usage for tag-filter and collection examples.

Inspect owned values

Inspector::value returns an owned typed value together with shape, units, metadata, tags, source provenance, declaration and override locations, and schema information. Use Inspector::values to obtain every evaluated value in environment order, with its full path.

auto speed = view.value("physics.speed");
auto shape = speed.shape;
auto units = speed.units;
auto source = speed.declaration_location;
if (speed.override_location) {
    auto replacement = *speed.override_location;
}

speed.changes lists the declaration, applied modifications, and effective override in evaluation order. Modifications ignored because of an override are omitted. speed.contributing_schema identifies the schema that supplied a value when known. A DIPH5 environment exposes only provenance retained in its snapshot.

Locate source declarations

Inspector::source_locations accepts a SourceEntity for an evaluated path, a path in a named source, a named source, schema, unit, or DIPfile entry. For values it returns the effective override first, then recent modifications, then the declaration.

auto locations = view.source_locations(
    {snt::dip::SourceEntityKind::Path, "physics.speed", {}});
for (const auto& location : locations) {
    // location.source.path and location.line identify the physical source.
}

Explicit groups and collection items have declaration locations. An inferred parent without its own declaration has no invented location. Embedded text points to its physical registration while logical_source_name and logical_line retain the original logical source. Distinct semantic locations can share a physical line. Check source_text_available before opening source text: DIPH5 may retain provenance without the complete text.

Inspect original array and table input

A live parse can retain the original string blocks used for arrays and tables. Enable this when opening or parsing, then inspect one path or list all blocks:

auto env = snt::dip::open_artifact("DIPfile", false, true);
snt::dip::Inspector view{env};
if (const auto* block = view.block_input("samples")) {
    auto original_text = block->code;
    auto source_line = block->source_line;
}
const auto& blocks = view.block_inputs();

Each block records its array or table kind, path, original text, logical source, and line. Ordinary parses do not keep these copies. Use parser.parse(false, true) for a parser and pass the same option to reload_artifact after refresh. DIPH5 snapshots contain evaluated values, not original block text.

Summarize and slice arrays

Inspector::value_summary reports type, shape, element count, units, and metadata without cloning the value. Use Inspector::value_slice for a bounded typed VAL value after checking the shape:

auto summary = view.value_summary("samples");
if (summary.elements >= 20) {
    auto page = view.value_slice("samples", {{10, 19}});
}

Slice ranges are zero-based and inclusive. Both calls accept resolved paths and named-source paths such as reference?calibration_curve. They read already evaluated in-memory values; DIPH5 loading remains eager and does not provide disk-backed lazy reads.

Read table metadata and cells

Inspector::table reports row count and ordered column metadata, including paths, types, and units, without copying cells. A table’s columns are evaluated arrays, so use Inspector::value_slice on each column path. Inspector::tables lists all tables.

auto table = view.table("measurements");
if (table.rows > 0) {
    for (const auto& column : table.columns) {
        auto first_cell = view.value_slice(column.path, {{0, 0}});
    }
}

Table identity is retained in DIPH5 files produced by this version.

Check available views

Inspector::capabilities reports which facts and operations are available for a value, group, collection, table, or named-source path. Use it before showing a viewer action:

auto capabilities = view.capabilities("measurements");
if (capabilities.hasTabularData) {
    auto table = view.table("measurements");
}

hasArrayData identifies array-valued nodes. hasReferenceGraph is true when a value or its condition has recorded reads or an expression tree, or another value references it. sourceEditable and directlyWritable remain false.

Describe one path or search for descriptions

Inspector::describe gives a bounded description of a value, group, collection, or table. It shares value and source facts with Inspector::value, but omits large content. Scalars and small arrays appear as DIPL text; value_unavailable_reason explains omitted content. Inspector::list_descriptions applies the same description to a ? query and tag filters, returning a limited list and total match count.

#include <snt/dip/inspect/semantic.h>

auto one = view.describe("simulation.steps");
auto matches = view.list_descriptions("?simulation.", {}, 50);
// one.value_text is optional; matches.total includes all matches.

Descriptions separate enforced options and conditions from advisory metadata. They report source-text and dependency-graph availability. List results omit value content by default. A default is not separately evaluated; use a preview for a baseline comparison.

Check an override target

Inspector::override_contract checks an exact path against the evaluated model before you construct an override. It identifies an existing value, an existing collection item, or a schema backed item that can be created:

auto value = view.override_contract("simulation.steps");
auto item = view.override_contract("materials[copper]");
if (item.kind == snt::dip::OverrideTargetKind::NewItem) {
    // An item group can be written at item.path; preview checks its children.
}

For a value, declared_type, current_shape, units, enforced_options, and enforced_condition describe known constraints. For a collection item, item_schemas and resolved_path describe the item at this evaluation: items[] resolves to the next list index. A missing schema or mismatched selector yields Unavailable and a stable reason. New child values cannot be targeted directly; create their item group first. Contracts are static and may become stale if the input changes. Use preview to validate proposed values, item children, and dependencies.

Preview an override

snt::dip::preview() parses a project or DIPL file independently for the baseline and candidate, applying ordered override entries to the candidate. Inspect validation diagnostics, accepted targets, and the comparison before using an override:

#include <snt/dip/preview.h>

std::vector<snt::dip::PreviewOverride> overrides{
    {snt::dip::PreviewOverride::Kind::Text, "simulation.steps = 200"}
};
auto result = snt::dip::preview("DIPfile", overrides);
if (result.candidate_valid) {
    auto changed = result.comparison.changed;
} else {
    auto diagnostics = result.candidate_diagnostics;
}

Preview writes no project or generated output files. A DIPH5 snapshot can be described, but cannot be a preview input. Results require stable source files and pure host callbacks across the two evaluations.

Compare evaluated environments

snt::dip::Comparison compares two loaded environments or two DIPH5 snapshots. Its result can be inspected as structured differences or rendered as bounded text.

#include <snt/dip/comparison.h>

auto before = snt::dip::open_artifact("before.diph5");
auto after = snt::dip::open_artifact("after.diph5");
snt::dip::Comparison comparison{before, after};
auto summary = comparison.render(20);

See DIPH5 comparison for comparison scopes and array difference semantics.

Capture dependency events

Dependency recording is opt-in for live parses: use parser.parse(true) or open_artifact(path, true). Normal parsing skips graph allocation. Pass true again to reload_artifact after refreshing a live graph.

auto env = snt::dip::open_artifact("DIPfile", true);
snt::dip::Inspector view{env};
const auto& graph = view.graph();
if (graph.recorded) {
    for (const auto& read : graph.dependencies("?physics.speed")) {
        // read.target is a resolved DIP node or source ID.
    }
    auto readers = graph.referenced_by("?physics.speed");
}

graph.events retains evaluation order and source locations. A value event records reads made during assignment; condition and branch-decision events are separate. dependencies uses the latest value event, excluding superseded expressions; referenced_by performs the reverse lookup. Each read retains its resolved target, original request, and EXS operand text when present. Node IDs use ?path for the current environment and source?path for imports; branch decisions use #case:N. A value event’s controlled_by identifies active decisions that selected it. Imported values retain a link to their source-qualified original.

Inspect a dependency neighborhood

Inspector::dependency_neighborhood provides a one-hop view for a selected path: distinct value and condition dependencies, values whose latest evaluations read it, and separate latest value and condition events. It also retains edge request and operand text.

auto neighborhood = view.dependency_neighborhood("physics.speed");
if (neighborhood.recorded) {
    for (const auto& dependency : neighborhood.dependencies) {
        // dependency.id is a resolved node or source ID.
    }
    if (neighborhood.value_event &&
        neighborhood.value_event->composition) {
        const auto& operations = neighborhood.value_event->composition->nodes;
    }
}

The input can be a resolved path, ?path, source?path, or source?. When graph capture is unavailable, recorded is false and no links are inferred. Numerical and logical expressions include an EXS composition tree with operands, operators, and groups. Direct references and string templates have reads without an EXS tree. Units stay on inspected values; their internal expressions are not expanded into this graph.

Graph data persists in DIPH5 2.7 snapshots when it was recorded. Loading a snapshot uses the saved graph state regardless of the recording option passed to open_artifact or reload_artifact. Older snapshots have no recorded graph. Function callbacks contribute links only for DIP values requested through the environment. A read means “read during evaluation,” not “necessary for the final result.”

Convert exceptions to diagnostics

snt::dip::diagnostic_from_exception() converts a caught exception to snt::core::Diagnostic without parsing formatted what() text. It retains the category, message, details, suggestion, and available source locations. Codes classify exception types, not individual error cases.

#include <snt/dip/inspect/diagnostic.h>

try {
    snt::dip::reload_artifact(env, "DIPfile");
} catch (const std::exception& error) {
    auto diagnostic = snt::dip::diagnostic_from_exception(error);
    // Show diagnostic.message and diagnostic.location to the user.
}