tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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:

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.