language.md (12197B)
SCP semantic boundary
SCP is a small tree-native language. This is the contract for validated
authored IR and its source spelling. consumers.structural supplies the
independent reference oracle; consumers.lexical-worker is the supported
executable compiler. Validation is required before publishing or compiling
authored IR. Execution is partial: recurrence and dynamic calls can diverge, and
matching can fail.
Values and patterns
Every value is a tricu tree: Leaf (t/leaf), Stem (t child/stem child),
or Fork (t left right/(left, right)). Patterns are Any, Leaf, Stem,
Fork, Capture, opaque Literal, and As. A literal matches by exact tree
equal?; its payload stays opaque. Numeric source literals are tricu's compact
LSB natural-number trees in every SCP source position, including state
patterns and outputs. succ and add in lib/consumer/ are ordinary SCP
workers over those trees. As binds the whole value before matching its child.
Fork matches its left side, then its right side. An unmatched pattern does not
bind anything. The same patterns apply to rule heads and computed-value match
arms.
Rules run in source order. For each rule, match input first and state
second; the state pattern sees input captures. A miss in either proceeds to the
next rule without executing that rule's output. The first fully matching rule
evaluates only its own output; failure during that output does not resume rule
selection. Exhausting rules fails with no matching consumer rule. Initial
state is an opaque tree supplied at preparation or invocation, interpreted as
runtime data.
Within a rule, a capture slot is bound at most once, including across input and
state patterns. call binds its returned value only in its receiver; match
starts each arm with the surrounding bindings and extends them only in that
arm's body. Duplicate slots and unknown output captures are validation errors.
The reference interpreter may be used independently to probe malformed IR or
shadowing; validated IR defines the compiler contract.
Qualified source bindings (unit frontend)
The multi-consumer body API (consumers.unit-source / unit-compile) accepts
several named definitions with explicit qualified output references. Self.name
denotes the reusable open entry of a local consumer; Alias.name denotes a tree
explicitly supplied by the build/import boundary. These references elaborate to
opaque literals before authored-IR validation. They do not invoke code, infer
initial state, or convert entries to workers. Captures are unqualified in this
frontend and cannot shadow qualified bindings. Duplicate definitions/import
bindings, unknown references, and unsupported static local code-reference cycles
are errors. Definition order does not select evaluation order; rule order inside
each consumer is unchanged. Runtime recurrence and explicit worker selection
remain ordinary SCP operations.
consumers.unit-build stages native !import NAME, !import REF as Alias, and
!export headers through the shared graph and module publisher. It preserves
original body positions; Self is reserved and imports require simple
qualifiers. Local imports default to their module name; full manifest hashes
require an explicit alias. Imports and re-exports establish their own dependency
edges; !source NAME is build-only and redundant edges are rejected. Explicit !import REF * is currently rejected by this SCP profile;
wildcard local exports and whole-manifest re-exports are supported. Header-only
re-export units are allowed; empty public interfaces are rejected. The
single-consumer frontend accepts one matching export annotation, retaining
original rule/output positions; full module headers use the unit frontend. Only
explicitly selected configuration entries are invoked by a build driver;
exporting code never invokes it automatically. The existing single-consumer API
remains separate and retains its legacy capture identifier spelling.
Output and control
Literal returns its payload unchanged; Capture returns a bound value; Stem
and Fork construct trees. For Fork, equal, recur, call, and become,
evaluate their suboutputs left to right, propagating the first failure. recur input @ state continues the same consumer with both explicit values. call target input as slot in receiver invokes the target with its own initial
state, then binds its result inside the receiver. become target input @ state tail-delegates with the supplied state instead of the target's initial
state; it does not return to a receiver. Effect resumptions likewise use the
driver's explicit retained state rather than resetting the worker's initial
state. A returned runtime worker must have the executable worker descriptor
shape; the caller supplies trusted compatible code.
match source of (pattern => body | ...) evaluates source once, then
tries arms in order using the same pattern semantics as rule heads. A miss tries
the next arm without running the missed body. Only the first matching body runs.
Scrutinee failure propagates; no matching arm fails with no matching output arm. That failure is distinct from no matching consumer rule and from a
called worker's failure.
SCP choose condition yes no is only shorthand for match condition of (leaf => no | stem leaf => yes). False is Leaf; true is Stem Leaf. A Stem with
another child or a Fork fails as an unmatched output arm. The tricu-wide
lib/base.tri matchBool instead treats every non-Leaf as truthy; SCP uses the
exact two-case encoding above.
Experimental library effectful returns
consumers.effect-compose interprets ordinary command trees produced by SCP
workers:
- Return:
(0, result). - Request:
(1, (operation, (argument, (receiver, retainedState)))). - Invoke helper:
(2, (helper, (input, (initialState, (receiver, retainedState))))).
Operation receivers, helper return receivers, and final completion use the
standard tricu Result: (stem leaf, (value, metadata)) for success and
(leaf, (problem, metadata)) for failure. The dispatcher checks these exact
boolean tags and the payload pair. It forwards the complete Result unchanged,
including metadata. A helper may add stage information to its error payload;
a receiver may recover by returning a successful Result. Compact numeric tags
identify commands; Leaf and Stem Leaf identify Results.
The driver runs a package: an ordinary tagged tree
("scp-effect-package.v1", (entryName, (receivers, helpers))). Receivers are
(localName, consumer) rows; helpers bind local names to package descriptors or
selfReference. A library owns its entry, private receiver table, and dependency
bindings. A caller binds the package under its chosen helper name.
makePackage entry receivers helpers returns a Result, checking proper tables,
row/value shapes, duplicate names within each table, and entry resolution.
Preparation supplies validated reference consumers or trusted compatible
executable workers. Dependency descriptors receive finite shape checks;
preparation verifies imported package contents. Receiver and helper names occupy
separate namespaces; each package resolves its own bindings.
run step operations package input state selects reference interpretation or
the existing lexical explicit-state worker ABI. Requests resolve receivers in
the current package. Helper invocations resolve their return receiver in the
caller package and their dependency in its helper table, then enter the callee's
entry with explicit initial state. A dependency bound to selfReference selects
the current package, enabling recursive effectful helper calls through a finite
configuration tree. The operation registry is driver-owned and shared.
L.lEffectStep checks executable descriptor shape at each resumption.
L.lPrepareEffectReceivers validates a proper receiver table, its row/consumer
pairs, and descriptor shapes, returning the preserved table in a Result.
L.lPreparedEffectStep invokes those prepared descriptors' open entries directly
with explicit state and the same success/failure Result ABI. Preparation uses
compiler-produced or trusted linked executable entries. Structural adaptation
of receiver tables is followed by preparation before prepared invocation.
The library authoring constructors in consumers.effect-packages select receiver
names from explicit ownership declarations and compiled rows. Injected pure
workers are selected separately; helper bindings supply package dependencies.
Their default runner uses prepared invocation. Missing owned names, duplicate
bindings, and omitted entries fail construction with Results.
Invocation pushes
(callerPackage, (returnReceiver, (helperAlias, (resolvedWorker, retainedState))))
onto the dispatcher-owned frame list. Requests preserve package and stack.
A return pops a frame, restores caller scope, and invokes the resolved receiver
with the returned Result and retained state. An empty stack completes with that
same Result. Pure call/become preserve package scope; injected effect-producing
workers use that package's receiver and dependency names.
Worker failures, missing names, and malformed commands/Results end dispatch
through a runner-owned diagnostic handler. runWithFault faultK step operations package input state supplies that handler explicitly. Its diagnostic is
(kind, (detail, ((entryName, receiverName), returns))). returns lists pending
(callerEntry, (helperAlias, returnReceiver)) relationships, nearest first.
Kinds are worker, resolution, protocol, and preparation. Error detail
remains a tree.
The standard handler passes ("scp-execution-fault.v1", diagnostic) to
abortIO. The Haskell IO interpreter ends scheduling before pending Bind
continuations and returns the attributed diagnostic to its runner. Source and
pinned IO runners report location and pending returns with nonzero exit status.
Application error Results retain ordinary Result presentation and exit status
zero.
prepare preparedResult use routes configuration failures to the same handler
while preserving their detail and metadata; successful preparation calls use.
Tools can select prepareWithFault and runWithFault handlers for inspection.
Resolution precedes effects and helper entry. Package construction rejects local duplicate bindings. Operation lookup uses the driver-owned first-match table. Programs and tools can inspect package tables, frames, Results, and diagnostics.
Elaboration and failure boundaries
A registry route's known literal prefix is elaborated before IR
validation: a proper list [a, b, ...] becomes Fork(Literal a, Fork(Literal b, ... Capture suffix)). Empty prefix becomes Capture suffix. A short,
nonmatching, or non-Fork input misses the pattern and permits next-rule
fallthrough. The public expandPrefix builder returns Result Pattern and
rejects improper prefix lists with improper literal prefix, instead of
manufacturing an invalid IR node. The registry source frontend converts that
error into a parser diagnostic. Ordinary stripPrefix consumers handle
runtime-supplied prefixes. The lexical compiler may recognize the structural
literal spine as an optimization, never as another pattern meaning.
Malformed syntax fails at source parsing or elaboration; malformed authored IR
(shape, unknown tag, duplicate/unknown slots, empty match, or an improper
match arm list) fails validation with a structured malformed rule/path
diagnostic. An accepted IR form that a backend cannot compile is separately
unsupported. Runtime failures include unmatched rules/arms and worker errors.
Output literal payloads and initial-state trees remain opaque. Linking and
worker preparation establish compatible dynamic executable values. Build
receipts record compiler selection and executable identity.
Any proposed language form should first be tested as elaboration into these patterns and outputs on real programs. Measure compilation and deployed execution separately; compiler recognition may optimize an existing shape without expanding SCP's semantic vocabulary.