tricu

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

tri-tools.md (20019B)


Compile, run, and format

Build and run sets up the included stable compiler and a first application. The commands below use its WORK variable. Language and Modules define source behavior.

Compile and evaluate

result/bin/tricu eval demos/source-patterns/app.tri \
  --store "$WORK/application" --format number
# 23

result/bin/tricu arboricx compile --file demos/source-patterns/app.tri \
  --store "$WORK/application" --name main --output "$WORK/app.arboricx"

Dependencies use sibling NAME.tri files plus the nearest tricu.workspace source-location declaration; see workspace rules. The module basename must be a valid workspace identifier. eval accepts one file and selects exported main. compile packages all published exports, or the subset selected by repeatable --name options. Missing or duplicate selections fail.

Without --toolchain, the installed stable compiler is authenticated and loaded indexed. Its bundle SHA256 identifies the compiler in receipts. The packaged bundle is a Cabal data file, retained in the Nix closure even when invoked outside the checkout. Relocatable installations can set tricu_datadir to their data root (containing bootstrap/build-stable.arboricx); missing/corrupt data fails closed. Updating the shipped compiler also requires updating its pinned host SHA256.

--toolchain is an explicit full manifest hash with a sourceBuildGraph export. --tool-store defaults to --store and requires --toolchain; separating them lets applications start with an empty store. Explicit overrides retain their verified executable-object hash as compiler identity in receipts.

When the file argument is omitted, eval requires a complete native module on stdin, including its exported entrypoint. Stdin uses an isolated temporary workspace: use manifest imports, or a file for workspace-relative dependencies. Multiple input files are rejected; use native imports instead. tree write also uses the packaged compiler and selects an exported definition.

--compile-timeout SECONDS bounds tool loading and module builds. Its default is 120 seconds, with a range of 1 to 86400. Completed immutable objects can remain after an interrupted build.

Stable backend and direct self-recursion

bootstrap/build-stable.arboricx selects stable lowering for every source module built by that artifact. It supports direct function self-recursion and uses the source-module.stable.v1 options identity. Imported trees keep their exact representations.

Compiler-generation backend selection is separate: generation runners use --backend stable. The retained optimized application artifact is build.arboricx. See Compiler architecture for its abstraction and demand behavior, and Compiler rebuilding for generation procedures.

Stored execution

result/bin/tricu arboricx run --module "$APP_MANIFEST" --name main \
  --store "$WORK/runtime" --format number

Arguments are applied in option order:

Option Argument
--arg TEXT A tricu string.
--arg-tree-file FILE An exact tree-term byte payload.
--arg-tree-pin HASH A verified flat tree-term object from the selected store.
--arg-export MANIFEST:EXPORT A verified flat or indexed executable export.

--format chooses display interpretation; --result displays a canonical Result with success/error labels. --repeat repeats an invocation after loading once; pure applications may reuse an already evaluated value, so this is unsuitable for independent fresh-run benchmarks.

For stored IO commands that print their own output, --io --no-result suppresses the trailing host-rendered return value. Program output and errors are unchanged; --no-result without --io, or combined with --result, fails before execution. Repeat/profiling markers retain their existing behavior. Observation phases still occur with --no-result, leaving the suppressed return value unrendered and lazy. Explicit --profile-full-result forcing remains independent of result rendering.

Read-only interaction-tree inspection

Inspect a materialized host IO tree as read-only data directly from a bundle:

result/bin/tricu arboricx inspect --file "$WORK/app.arboricx" --name main --as io
result/bin/tricu arboricx inspect --file "$WORK/app.arboricx" --name main --as io --json

--as io is required and selects the tricuIO version 1 envelope and host action protocol. Pure contract effects use a separate protocol. --name defaults to main. Supply an already materialized IO tree; apply any arguments needed to produce that tree before inspection. Only explicit child actions are followed; continuations and environment-transforming functions remain opaque, labeled by their roles. Names come from bundle export aliases, and the outline covers currently explicit control flow.

For a small example with a named continuation:

result/bin/tricu arboricx compile --file demos/interaction-inspection.tri \
  --store "$WORK/application" --output "$WORK/inspection.arboricx"
result/bin/tricu arboricx inspect --file "$WORK/inspection.arboricx" --as io

The outline includes these roles (node indices omitted here):

root: tricuIO v1                         aliases: "main"
  root.action: Bind
    root.action.action: ReadFile
      root.action.action.path: "settings.txt"
    root.action.resume: continuation (not applied)  aliases: "afterRead"

The demo spells out the IO constructors directly. Reading settings.txt occurs only when the IO tree is executed; compilation and inspection treat its path as data.

Inspect the actual Arboricx server

demos/arboricx-inspection/main.tri reexports the application's exact main tree and adds named inspection views in a separate companion module:

result/bin/tricu arboricx compile --file demos/arboricx-inspection/main.tri \
  --store "$WORK/application" --output "$WORK/server-inspection.arboricx" \
  --compile-timeout 900
result/bin/tricu arboricx inspect --file "$WORK/server-inspection.arboricx" --as io
result/bin/tricu arboricx inspect --file "$WORK/server-inspection.arboricx" \
  --name serveView --as io

The main outline names every currently visible occurrence:

main: tricuIO v1
  startupAction: Bind
    initializeThenServe: Bind
      initializeStore: Bind
        ensureRootDirectory: Bind
          createRootDirectory: CreateDirectory
            storeRoot: "./store"
          afterCreateDirectory: continuation (not applied)
        afterRootDirectory: continuation (not applied)
      afterInitialization: continuation (not applied)
    finishStartup: continuation (not applied)

The outline uses export aliases in place of occurrence paths for readability. Four configured values match directly: storeRoot, createRootDirectory, ensureRootDirectory, and initializeStore. The remaining labels are author-named structural projections of the materialized action. Projection extracts existing subtrees as data; all four continuations remain opaque and unapplied. Aliases establish exact tree identity; their names convey the author's interpretation. Matching requires exact structural equality with the specialized tree. General library helpers and observationally equivalent expressions can have different trees.

The independently materialized serveView exposes serveRegistry, its createSocket action, and the opaque afterSocketResult continuation. Bind/listen/accept remain behind that continuation and depend on the returned socket. initializeView offers a shorter view of store initialization. Inspection reads these trees; running the view exports with --io performs their effects.

Run the comparison in a new directory:

python3 test/arboricx_server_inspection.py result/bin/tricu \
  --directory /tmp/server-inspection

The harness checks exact main identity, compares ordinary exports with structural projections, and saves JSON reports and outlines for the separate views. It reports bundle bytes, stored nodes, alias coverage, and compilation timings for the selected compiler. Timings include sequential cache reuse. Validation consists of compilation, artifact import, and read-only inspection; application IO remains unexecuted.

Identities, output, and limits

The bundle’s whole-file SHA-256 fingerprints the inspected bytes. Authenticate that fingerprint against an independently selected pin and verify provenance separately. The complete indexed graph is structurally validated, including unused nodes. Export aliases are exact matches to displayed trees within that bundle. Several names may match one tree, and names lost through specialization cannot be recovered. n123 identifies a bundle-local tree node. CAS objects have separate addresses. A generated path such as root.action.resume identifies an occurrence; multiple occurrences of one tree can execute effects independently.

--json emits tricu.interaction-inspection.v1: bundle fingerprint, selected export, supported protocol version, limits, stored-node count, and a depth-first occurrences array. Each occurrence includes its path, depth, role, tree ID, export aliases, omitted-alias count, display summary, status, and optional reference to a previously described action occurrence. Summaries are display text whose wording may change; retain the artifact for exact payload data. Status is decoded, opaque, invalid, unknown, truncated, or reference. opaque marks a value whose internals remain unexamined. Callability and type validity require separate checks.

Option Default Meaning
--max-bytes 67108864 Input byte cap, adjustable downward to 1 byte.
--max-depth 24 Occurrence depth, 0–256; root depth is 0.
--max-nodes 500 Displayed occurrences, 1–100000; stored-node count is separate.
--preview-items 80 Characters, bytes, or aliases per occurrence, 1–4096.

Numbers are inspected up to 64 encoded bits. String/byte previews validate only the visited prefix; suffixes remain unexamined. Depth and preview truncation appear on the affected occurrence; occurrenceLimitReached reports an omitted traversal remainder. Alias omission is reported separately. Payload previews may reveal secrets already present in the artifact; treat the unredacted inspection output accordingly.

Input must be a regular file. Validation and name indexing process the stored DAG directly. Display limits bound report size, and the input cap bounds file bytes. Validation heap and elapsed time require external resource limits when inspecting untrusted artifacts.

Unknown versions/tags and encountered malformed protocol shapes produce a report and a nonzero exit. Invalid bundles, missing exports, and input/option errors fail before a report. Deliberate opacity and budget truncation are successful reports with explicitly partial protocol coverage. Complete-program validity and execution safety require separate analysis. Inspection uses the bundle alone and ignores --store.

IO and permissions

Select --io for interaction-tree execution. Grant file access with repeatable --allow-read PATH and --allow-write PATH options. --unsafe-io grants broad access and belongs only in trusted, deliberately selected runs.

Compilation grants reads to the selected source directory and application store, and writes to the application store. Application execution uses its own grants. Path grants authorize access within trusted programs and store directories. Hostile-filesystem containment requires separate isolation.

Host artifact checks

Imports require normal-order Tree Calculus semantics and term exports with arboricx.abi.tree.v1. Export names must be unique. Aliases are single filename components: qualified and symbolic names are valid, while empty names, ./.., separators, colons, and control characters are rejected. The complete alias set is validated before alias publication; subsequent IO failures can leave a partial update.

Typed object reads verify domain-separated digests before decoding. Use the whole-bundle SHA-256 for registry downloads, and module manifest hashes for CLI execution. Storage specifies these identities and trust boundaries.

Indexed and flat objects

Import defaults to indexed storage; --indexed makes that selection explicit. This preserves sharing for native library imports, stored execution, and compiler loading. Bundle export accepts indexed or flat objects. Use --flat only for explicit flat-tree consumers or retained older native compilers.

Flat import checks aggregate expanded bytes before writing. Its default budget is 64 MiB; set a positive --max-flat-bytes BYTES to change it. This is an output budget, distinct from input size, heap use, and execution time. The included compiler should be imported indexed.

Profiling

result/bin/tricu eval demos/source-patterns/app.tri \
  --store "$WORK/application" --format number --compile-profile \
  +RTS -T -s -RTS 2>"$WORK/profile.log"

JSON samples report monotonic wall_ns, allocation, live heap, and GC counters to stderr. Differences between timestamps give stage timings. Samples read existing counters; allocation/live values reflect the most recent collection. -s adds process totals. Stored execution provides --profile with +RTS -T. Report preparation, loading, compilation, execution, publication, and result observation separately when comparing workloads.

Stored --profile follows the ordinary renderer's demand. invoke-end-N marks completion of the IO driver or creation of a still-lazy pure result. Pure evaluation can continue during observation. observe-begin-N / observe-end-N bracket rendering and output; observe-end-N follows flushing stdout. Measure invoke-begin-N through observe-end-N for invocation plus selected output, including rendering/IO costs. Merged logs receive a separating stderr newline before the final JSON sample.

For an explicitly different workload, add --profile-full-result (requires --profile). Separate force-begin-N / force-end-N samples bracket full-result forcing before rendering. This traversal memoizes completed physical nodes within one invocation, preserving sharing during traversal. It still demands fields that --result discards: infinite or partial results can diverge or exhaust resources. Samples read existing GC counters; reduction counts and peak memory require separate measurement and resource controls.

Previously, --profile implicitly forced the complete expanded result before invoke-end. Those historical intervals measured a different workload; compare samples within the same profiling mode. Active workload probes label invocation-through-output measurements explicitly; use fresh processes for independent application evaluation. --repeat permits reuse of an evaluated application.

.tri equation alignment

tricu format runs a pinned formatTri program. Prepare the current formatter using the included compiler and a new output directory:

FORMAT_DIRECTORY="$WORK/formatter"
python3 test/prepare_formatter.py result/bin/tricu --directory "$FORMAT_DIRECTORY"
FORMATTER_MANIFEST=$(python3 -c \
  'import json,sys; print(json.load(open(sys.argv[1]))["formatter_manifest"])' \
  "$FORMAT_DIRECTORY/report.json")
result/bin/tricu format path/to/source.tri \
  --formatter "$FORMATTER_MANIFEST" --tool-store "$FORMAT_DIRECTORY/store"

Preparation builds native dependencies, checks the formatting corpus and CLI controls, and records source hashes and compiler identity. Its bounds are 8 GiB child heap, 900 seconds compiling the formatter, and 1200 seconds overall.

Default output is UTF-8 source on stdout. --check fails when formatting would change bytes. --write replaces the file atomically, preserving permissions and refusing symbolic links. Select one of those modes at a time. --seconds bounds loading and formatting, defaulting to 60. Invalid UTF-8, bad artifacts, and cancellation fail before replacement. See the formatting contract for which groups are aligned.

Library workspaces and demos

The repository tricu.workspace maps native library aliases to source paths; ordinary CLI compilation stages mapped sources automatically. Bootstrap source maps remain a separate compiler-rebuilding protocol. To reuse already compiled dependency manifests from a completed generation in a new DEMO_DIRECTORY:

python3 test/native_library_migration.py result/bin/tricu "$GENERATION" \
  "$DEMO_DIRECTORY" --backend stable --library demos.equality

The helper preserves native source and binds complete dependency manifests. Supply additional prepared dependencies with repeatable --dependencies, or select libraries in dependency order. Import the resulting bundle indexed and run its selected export as above. Command and batch budgets default to 20 minutes; full-cohort work uses explicitly selected limits from Testing.

Interactive sessions

The default REPL and tricu repl use the portable compiler. The repl command accepts the same compiler override/deadline options as eval. Import the prelude explicitly when needed. Simple x = 7 definitions persist; x displays a result without changing session source. Redefinition replaces the entire previous equation group and recompiles all session definitions, so dependents see the new definition: after x = 7, f ignored = x, and x = 8, f t returns 8.

For multiline definitions, enter !def NAME, the complete definition/equation group, then !end. The compiled local export set must match the named session groups exactly: a block for x defining only y, or also defining an unrelated name, is rejected without changing the session. For multiline or ambiguous expressions, use !expr through !end. EOF cancels an unfinished block. replResult is reserved for the session. The single-line classifier chooses a submission form; the portable compiler parses and validates every submission.

!import accepts native import syntax, with workspace dependencies resolved from the current directory. !load FILE compiles the file and opens its exported interface by immutable manifest. !use MANIFEST [NAMESPACE] imports a pinned interface; a module alias is resolved to its current pin when submitted. Import collisions fail, preserving existing definitions. Use qualified imports when names overlap. Temporary session modules are removed after each submission; the current directory must be writable. CAS objects and receipts may remain.

Compilation, import and result-rendering errors preserve prior successful source and completion state. Immutable CAS publications and external effects persist. Expression results are displayed as values; IO execution requires a separate run, and retaining a result requires an explicit definition. !reset clears source/imports; !env lists local exported definitions. !format, !output, !store, !clear, !help, and !exit remain available. After changing !store, ensure the new store contains the pinned dependencies before building. The legacy !name tree injection operation is retired. Import explicit module interfaces to introduce existing trees into the session. CLI operations and source tests use the portable compiler.

Focused CLI and REPL validation, using the selected compiler and existing fixtures:

python3 test/default_frontend.py result/bin/tricu

This opts into bounded CLI and interactive probes, including package failures, overrides, working-directory independence, current syntax and error rollback.