tricu

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

usage.md (20621B)


SCP projects: archived build/run reference

SCP is an archived, separate language experiment. This guide records historical usage; these steps are not tricu compiler acceptance or recovery prerequisites. Sources, historical launcher/tests and original mixed fixtures are preserved in archive/scp/. The active executable no longer has a scp command, and the former SCP Nix targets no longer exist. Commands and paths below describe historical checkouts, not the current checkout.

SCP compiles to reusable tree-calculus entries; configuration is ordinary SCP computation returning named workers. See the language contract for semantics. tricu scp owns typed project parsing, bounded phase orchestration, and pinned execution in the Haskell application. Compilation, configuration, and publication actions remain in consumers.command.

Preparation loads full verified compiler/SCP artifacts, never compiler-library source through Haskell. The checkout's .tri libraries and consumer runners are native modules. Create an explicit bootstrap from the checked immutable fixtures:

mkdir -p .bootstrap-run/scp-bootstrap
cp test/fixtures/compiler-library.arboricx .bootstrap-run/scp-bootstrap/compiler-bootstrap.arboricx
cp test/fixtures/support-library.arboricx .bootstrap-run/scp-bootstrap/scp-bootstrap.arboricx
export SCP_BOOTSTRAP="$PWD/.bootstrap-run/scp-bootstrap"

These are retained artifacts, not automatic rebuilding of edits to library source. --bootstrap selects this directory; absence/corruption fails, without recovery fallback. Project workspace names select qualified artifact exports; explicit workspace manifest pins select verified flat/indexed trees from the deployment CAS. Compiler identity includes executable and full artifact bytes. Compilation, configuration and publication still execute the tricu-owned command trees.

Normal project workflow

After nix build .#, prepare once and run the resulting deployment record:

result/bin/tricu scp prepare demos/consumers/prefix-lines/prefix-lines.scp-project \
  --bootstrap "$SCP_BOOTSTRAP" --store /tmp/tricu-logs/cas --output /tmp/tricu-logs/logs.prepared --seconds 240
result/bin/tricu scp run /tmp/tricu-logs/logs.prepared \
  --input-file demos/consumers/prefix-lines/tagged-log.fixture --seconds 60
# matches=3, first=disk

Use data-lines.scp-project for the unchanged-line filter. Declarations select workspace/directory, configuration entry/input/state and a presentation profile once. Paths are relative to the declaration; input/state explicitly choose leaf:true, UTF-8 text, or raw-byte file. Change ERROR to WARN and prepare another record to select warning payloads. The declaration selects entries explicitly; input and state remain data.

For a direct tree program, select scp-invocation.result.v1 and an invocation entry in presentation (no label or file adapter). For example:

result/bin/tricu scp prepare demos/consumers/fizzbuzz/fizzbuzz.scp-project \
  --bootstrap "$SCP_BOOTSTRAP" --store /tmp/fizzbuzz/cas --output /tmp/fizzbuzz/program.prepared --seconds 240
result/bin/tricu scp run /tmp/fizzbuzz/program.prepared \
  --input-tree-file demos/consumers/fizzbuzz/fizzbuzz-input.tree --seconds 60

Its project configuration binds the pinned fizzbuzz entry to initial state (2, 4) from fizzbuzz-phases.tree:

"configuration": {
  "bind": "fizzbuzz",
  "state": {"tree-file": "fizzbuzz-phases.tree"}
}

This shared operation publishes the worker under presentation.entry. For computed configuration, use the existing entry, input, state form to run an ordinary SCP configuration consumer. Both use the same worker, invocation, and receipt publication authority; the receipt records which profile was used. The runtime input is the tree list 1 through 15. The FizzBuzz mini-course derives the clocks from first principles and follows this real build/run boundary.

Author tree data

Define ordinary values in a .tri file, then select each definition explicitly:

result/bin/tricu tree write demos/consumers/fizzbuzz/fizzbuzz-data.tri \
  --name phases --output demos/consumers/fizzbuzz/fizzbuzz-phases.tree --store /tmp/fizzbuzz/authoring
result/bin/tricu tree write demos/consumers/fizzbuzz/fizzbuzz-data.tri \
  --name input --output demos/consumers/fizzbuzz/fizzbuzz-input.tree --store /tmp/fizzbuzz/authoring

tree write evaluates source at this authoring boundary and writes the selected value through the existing tree-term codec. It runs no IO action. Output replaces atomically after successful evaluation/encoding, preserves an existing file's mode, and refuses source overwrite and symbolic-link destinations. Its store supports normal imports during authoring. Runtime arguments remain serialized data and source-free invocation never consults this authoring file.

Configuration input/state also accept tree-file (relative payload path) or tree-pin (hash in the selected CAS). Unlike file, these decode an exact tree rather than constructing a byte-list tree. The consumer's contract interprets the resulting tree, including Leaf, lists, strings, numbers and workers.

Both transports reuse arboricx.tree-term.v1: byte 0 is Leaf, byte 1 precedes a Stem child, byte 2 precedes Fork left/right children. Truncated, unknown-tag and trailing-byte payloads fail decoding; CAS inputs additionally verify the digest under that domain. Raw files contain complete tree-term payloads. Decoding constructs the argument tree. Normal consumer computation may inspect or explicitly invoke values according to its contract.

Native CAS execution accepts ordered, repeatable --arg TEXT, --arg-tree-file FILE, and --arg-tree-pin HASH arguments. Prepared direct programs accept one tree through --input-tree-file or --input-tree-pin; file presentation uses --input-file. Direct runs default to --format result: ok: VALUE or error: DETAIL. Successful Leaf payloads display as ok: []; other payloads use the decoder's conventions (Leaf also encodes zero). Use --format tree to display the complete exact Result, or --format decode for the earlier whole-tree decoder view. The native equivalent of Result display is arboricx run ... --result --format decode. Native CAS runs accept --profile +RTS -T -RTS: JSON samples on stderr delimit loading, each invocation, and numbered IO yields. Each sample performs a major GC and reports monotonic time, cumulative allocation, GC time, and live managed-heap bytes. Yield samples also report native Bind/Local frame counts and scheduler task count. Invocation completion forces the entire result tree; output rendering follows its sample. Loading includes manifest, code, argument decoding, and root evaluation; remaining lazy work occurs during invocation. Samples include collection/recording overhead. A malformed Result envelope is a presentation/protocol error; canonical failure Results remain data with exit status zero. Arbitrary error trees survive and fall back to the selected view. This is display only; file presentation owns its own output. The direct prepared record selects the existing invocation manifest/export.

prepare uses separate bounded build, configuration and publication processes. Build/configuration commands load trusted workspace exports directly, without writing generated source into the checkout. --bootstrap DIRECTORY selects the workspace for these phase exports; by default it uses the current workspace. Prepared run loads no workspace or library source. GNU timeout supplies the process deadline and five-second TERM/KILL escalation. It generates a conservative runner-declared compiler identity; --compiler overrides it explicitly. Only successful completion replaces the output record via one atomic rename. Failed preparation may leave CAS objects, but preserves an existing record. The record selects the existing CAS manifests/receipts.

run reads only that record and invokes pinned native CAS code. It never loads the project declaration, rebuilds or reconfigures. Store paths are relative to the record: move the record and CAS together, preserving their layout. Treat records as trusted selections. An edited declaration has no effect on previously prepared runs until you explicitly prepare again.

Advanced phase commands

For separate phase inspection/control, from the repository root:

# Runner-declared compiler identity.
compiler=$({ sha256sum result/bin/tricu tricu.workspace;
  find lib -name '*.tri' -type f -print0 | sort -z | xargs -0 sha256sum;
} | sha256sum | cut -d ' ' -f 1)

result/bin/tricu scp build --store /tmp/tricu-logs --seconds 240 \
  --workspace demos.consumer-prefix-workspace --directory "$PWD" --compiler "$compiler"

printf 'ERROR ' > /tmp/log-prefix
result/bin/tricu scp configure --store /tmp/tricu-logs --seconds 240 \
  --code CODE_MANIFEST_HASH --entry configurePrefix \
  --input-file /tmp/log-prefix --state-leaf

result/bin/tricu scp file --store /tmp/tricu-logs --seconds 240 \
  --invocations INVOCATIONS_HASH --entry prefixLines --label matches

result/bin/tricu scp run-file --store /tmp/tricu-logs --seconds 60 \
  --manifest FILE_MANIFEST_HASH --input-file "$PWD/demos/consumers/prefix-lines/tagged-log.fixture"
# matches=3, first=disk; Result payload is ["disk", t, "final"]

Each command prints the pins used by the next step. Build code once, then configure different prefixes from that same code manifest. Prefix files are exact byte data: printf, not echo, avoids an unintended newline. Neither input nor state has an implicit default: choose --input-leaf/--input-file and --state-leaf/--state-file, or the explicit tree-file/tree-pin choices. Unqualified --input-file/--state-file values are byte lists. Use tree-file/tree-pin for exact serialized trees. Arbitrary tree requests remain available through the portable build-driver API.

For simple entry/state binding without a configuration consumer:

result/bin/tricu scp configure-entry --store /tmp/fizzbuzz/cas --seconds 240 \
  --code CODE_MANIFEST_HASH --entry fizzbuzz --name fizzbuzz \
  --state-tree-file demos/consumers/fizzbuzz/fizzbuzz-phases.tree

Its portable request is entryConfigurationRequest manifest entry name state; bindPinned verifies the pinned export, constructs its worker, and publishes through the standard configuration authority. The receipt binds the entry, publication name (input object), state, manifests, and explicit configuration profile scp-configuration.entry-state.v1. Configuration input is otherwise reserved for the ordinary computation profile.

file explicitly selects the bytes → Result [line] presentation profile; callable compatibility is trusted, not inferred from tree shape. run-file loads its pinned processFile export, grants read access to the supplied path, and invokes native CAS execution without source, bootstrap aliases or a compiler. Read/consumer failures print failure-stage and readable failure-detail; non-text errors stay in the returned Result. Application failures return Result data with exit status zero. Native/process/protocol failures and external deadlines produce nonzero command status (timeout: 124).

Build/configure/publication execute trusted local IO. --seconds bounds the child process, including bootstrap and publication. Argument preparation occurs before that deadline. Publication is atomic per object; timeout/failure may leave intermediate CAS objects. Commands default to their own executable; --tricu explicitly overrides the child runner. GNU timeout is required. Paths travel as argument values; configuration files are snapshotted before execution and data is applied as trees, with no source interpolation. Input/store paths may contain spaces or quotes. The demo shell scripts also accept TRICU to select which CLI executable they invoke.

Author a program

These examples consume whole byte lists. Source, code, configured outputs and receipts have distinct identities. Dynamic executable values require trusted, ABI-compatible code. The demos' shell scripts remain advanced examples composing the phase commands: build-prefix-lines.sh, build-data-lines.sh, and build-configurations.sh. Examples are indexed in demos/consumers/README.md.

Format source

The Haskell command runs the tricu library consumers.format; it does not compile or resolve the SCP program being formatted. Run from this workspace, using a fresh store while changing the formatting library:

result/bin/tricu scp format lib/consumer/add.scp --bootstrap "$SCP_BOOTSTRAP"
result/bin/tricu scp format path/to/program.scp --bootstrap "$SCP_BOOTSTRAP" --write
result/bin/tricu scp format path/to/program.scp --bootstrap "$SCP_BOOTSTRAP" --check
python3 test/scp_format_check.py result/bin/tricu --bootstrap "$SCP_BOOTSTRAP"

Without a mode flag, formatted UTF-8 source goes to stdout. --check exits nonzero when the source needs formatting; --write replaces a changed file atomically, preserving its POSIX mode bits. The flags are mutually exclusive; write mode refuses symbolic links. Syntax/encoding failures do not replace the file. An installed consumers.format store alias can also supply the library outside the checkout; normal workspace/store import rules apply.

Formatting aligns rule-head @ and =>, plus corresponding call ... as, recur ... @, become ... @, and route-resumption @ separators. Consumer boundaries and blank lines separate column groups. Structural syntax paths keep nested separators distinct. Rule indentation is two spaces. Headers, comments, literal spellings, existing line breaks (including CRLF), and final newline presence are preserved. Other expression spacing and line wrapping are retained. Widths count characters, not terminal display cells; internal tabs and wide Unicode glyphs can therefore look misaligned. The syntax walker recognizes general SCP and the bounded registry-route spelling; it does not perform binding validation or validate/resolve module headers.

The focused formatter test checks token/comment preservation, idempotence, add.scp columns, nested/multiline cases, and CLI write/check/error behavior. Run it as an opt-in focused check.

Library surface and ownership

The normal entry point is a project declaration and its prepared record. Library files are organized by responsibility, independent of source language:

Semantic consumers.* module names are independent of these physical paths; tricu.workspace is the explicit alias-to-source map. Native SCP workspace rows likewise map logical names to exact paths; a subdirectory is not an implicit namespace or dependency search path.

For library work, the import surface is organized by responsibility:

Responsibility Modules / intended entry points
Native consumer code lib/consumer/*.scp and text/*.scp; each declares its exports. base.scp explicitly re-exports the full collection. Names denote entries, not configured workers.
Portable phase API consumers.build-driver: workspaceBuildRequest, configurationRequest, buildCode, configurePinned, and result accessors. consumers.configure validates/publishes returned worker rows.
Compilation consumers.source (lexer), base-source (single consumer), unit-source (definitions), unit-compile (static references), unit-build (native headers/graph profile), lexical-worker (validated worker lowering). pattern-slots is a small compiler-internal ordering helper.
Semantics and checks consumers.structural owns authored IR and the independent oracle; validate checks IR; linked-reference matches exact registered entries without executing compiled code.
Execution ABI consumers.lexical-worker: lCompile, lCompileOpen, lRunChecked, lRunOpenChecked, worker construction/shape helpers. Open calls receive explicit state.
IO / tools consumers.file owns file reads and Result presentation; command owns phase actions/output markers; format owns source alignment; effect dispatches explicit operation/receiver registries. effect-compose experimentally interprets explicit helper-return frames; file-effects supplies shared ReadBytes/WriteBytes handlers; copy-bytes owns the reusable conversion helper's package factory. source-worker supplies explicit compileSource bytes initial / loadSource path initial bootstrap for single-source lessons.
Reusable exploratory adapters consumers.layout constructs authored layout transitions; registry, registry-base, registry-path, registry-source, and text-summary provide application/effect boundaries and bounded route syntax used by experiments.
Comparison implementations consumers.comparison.registry-lambda and consumers.comparison.text-summary-lambda share reusable baseline code under lib/consumer/comparison/.
Alternative lowering experiments consumers.lowerings.{specialize,project,success,tail-fold,cps-worker} live under lib/consumer/experiments/lowerings/ as comparison/research modules. fizzbuzz is an authored-IR fixture.
Exploratory pinned source linking consumers.module and link retain the earlier explicit hash-pinned linker/preparation probes. Native project builds use build-driver and the shared graph instead.

Names abbreviated in the table share the consumers. prefix. The neutral source.build, source.module, source.object, lock/receipt and CAS layers retain publication/verification authority. source.workspace-build deliberately assembles the mixed .tri/SCP profiles.

.tri workspace aliases expose definitions; they do not enforce a private helper boundary. Treat the entry points above as the intended interfaces, not every bs*/us*/uc*/ub*/l* helper as a compatibility promise. Native .scp exports and selected CAS publication lists are actual export boundaries.

Application-specific workspace maps/configurations belong in demos/. Shared loaders, counters, handlers and renderers live in the library. Lambda baselines use consumers.comparison.*; alternative lowerings remain explicit experiments. Their progression and historical measurements are collected in the lowering experiments.

Focused checks are in validation. Historical lowering and effect experiments are not part of ordinary project preparation.