Expand description
SHCL reference implementation: parser, accessor, writer/formatter. Single file on purpose - the drop-in story is “copy this file into your tree”. The language spec lives in project/spec.md; the conformance corpus in project/conformance/ pins every behavior here. Every other binding mirrors this file’s structure on purpose (parity over idiom - see style-guide.md), so restructuring here means restructuring all.
Structs§
- Diagnostic
- One parser or validator finding, tied to a source line.
- Document
- A parsed SHCL document: the tree, its diagnostics, and its strictness level.
- Load
Error - A failed strict load: the diagnostics that failed it, plus the recovered tree.
- Read
- Full-tier read result: value plus status plus the original raw text (when the
path resolved), so a caller can always recover what was actually in the file.
Array reads also carry one status per slot (element, or wildcard instance) in
slots;statusis then the worst slot. Scalar reads leaveslotsempty.lineis the 1-based source line of the resolved binding (0 when the path did not resolve to one node, or the node was writer-built), so a consumer check the schema cannot express can still cite the line.quotedis true when the read’s single scalar element was quoted in the source - the escape hatch that lets a downstream language reserve@nullwhile"@null"stays a plain string. Arrays, raw blocks, and empties leave it false. - Shcl
Date Time - Local (floating) date/time unless a zone suffix was present. Fields mirror what was written: a date-only value has no time, and vice versa.
Enums§
- File
Status - What load_file found: the four cases a consumer’s own load path otherwise confuses. Clean and HadErrors both carry a usable document; NotFound and Unreadable come back with an empty one.
- Save
Error - Why a save did not happen. The two cases need different handling, so they
are separate values rather than two spellings of one message:
Refusedis the lost-content gate, which the caller can reverse withsave_file_lossy, andIois the disk’s answer, which they cannot. - Severity
- Only
Errorfails a strict load;Hintflags legal-but-lookalike input. - Status
- Read status sentinels.
Emptyis informational - the empty value is still returned. Ordered by severity so a worst-of aggregate is justmax. - Strictness
- Per-document forgiveness knob. Set once at load; composes with per-call onBad.
- Write
Reason - Why a write would fail (
write_reason()): the distinctions behind a setter’s barefalse.Writable= the path passes the writer’s validation; the rest name the five ways it cannot. - Zone
Spec - A datetime’s zone suffix as written.
Constants§
- MAX_
DEPTH - Maximum nesting depth (levels below the document root), enforced at load
and by the Writer. Deeper lines are skipped with an
E016error. The cap is what keeps the recursive tree walks (emit, merge, clone) safely inside every binding’s stack, so a hostile or machine-generated document can make a load fail but never crash the consumer.
Functions§
- format_
f64 - Render a float the way the writer and the CLI do: shortest round-trip
decimal, never scientific notation,
inf/-inf/NaNspelled out. Rust’s own Display already does exactly that, which is why this is a wrapper - the other three bindings have to implement it, and a consumer building canonical text by hand should not have to know which of the four they are reading. - generate
- Emit a commented, typed starter config from a schema (
shcl init --schema). Paths that must exist (required, or a repeat lower bound of 1+) are live (theirdefault, or an empty value); optional paths are commented out so the file is valid and minimal as-is. A must-exist wildcard path whose parent gets materialized by another live line is generated too, in dotted form - otherwise the file would fail the very schema that produced it - and remaining wildcard or[#N]paths (which cannot be materialized) are listed in a trailing comment block. The output always loads clean and validates clean against its schema, except a repeat lower bound of 2+ (identical generated lines would merge, so the shortfall is reported). A footer naming the format and pointing at the spec is written last unlessno_banner; the flag is negative so leaving it alone writes the footer. Err = schema faults (V09x), same asvalidate/check --schema. - parse_
datetime - Whole-value date/time parse per the whitelist. None = BadType.
- quote_
segment - Quote one path segment so it can be spliced into a lookup path: a bare name
passes through, anything else comes back quoted and escaped in the form the
path scanner accepts. Splicing user-typed text into a path without this is
path injection - a dotted name silently reads as nesting. Same spelling
paths()and the canonical emitter produce. - suppress_
declared_ reopens - Drop the H002 hints a schema disavows: a section whose entry declares
reopen: trueis MEANT to be written in parts, so the merge hint is structurally a false positive there. Matching is by leaf name, same as the H001 suppressor, and it errs toward quiet, for a hint. Used bycheck --schemaand load_and_validate; call it wherever doc diagnostics and a schema meet. - suppress_
declared_ repeats - Drop the H001 hints a schema disavows: a field whose declared repeat upper
bound is above 1 repeats BY DESIGN (repetition is its instance mechanism),
so the repeated-bare-leaf hint is structurally a false positive there and
trains users to ignore hints. Matching is by leaf name - the filter
consumers were hand-rolling - which errs toward quiet, for a hint. Used by
check --schemaand load_and_validate; call it wherever doc diagnostics and a schema meet. - write_
file_ atomic - The file tier’s write mechanism (also what the CLI’s
--writeuses): a temp file in the same dir, then a rename over the target, so an interrupted write can never truncate the config it rewrites. The data is synced before the rename so a crash cannot publish an empty file.