tricu

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

host-abi.md (8273B)


tricu Host ABI

The portable Arboricx runners in lib/arboricx/arboricx.tri wrap successful results in a tagged value envelope. Hosts decode the selected canonical payload. This result envelope is separate from the IO action protocol interpreted by src/IODriver.hs and from the browser's explicit result codecs.

Outer result protocol

Host ABI runners return the existing tricu result shape from lib/binary.tri:

ok value rest = pair true (pair value rest)
err code rest = pair false (pair code rest)

On success, value is a host ABI value.

On failure, code is a canonical Tree Calculus number. The host may report the numeric code and optionally inspect rest for debugging.

Host ABI value shape

A host ABI value is:

pair tag payload

The tag says how the host should interpret payload.

The payload is always the canonical/raw Tree Calculus representation for that type. The ABI envelope tags the payload; it does not replace or recursively wrap canonical Tree Calculus data.

Tags

Tags:

hostTreeTag   = 0
hostStringTag = 1
hostNumberTag = 2
hostBoolTag   = 3
hostListTag   = 4
hostBytesTag  = 5

Constructors

The ABI constructors are:

hostTree value
hostString characters
hostNumber n
hostBool b
hostList xs
hostBytes bytes

Each constructor returns:

pair tag payload

Examples:

hostString "hello"
hostNumber 42
hostBool true
hostList [1 2 3]
hostTree (t t t)

Payload conventions

Payloads use existing canonical tricu encodings:

ABI value Payload
hostTree arbitrary raw Tree Calculus value
hostString proper list of canonical Unicode scalar values (0..0x10ffff, excluding 0xd800..0xdfff)
hostNumber canonical tricu number
hostBool canonical tricu bool (false = t, true = t t)
hostList canonical tricu list (t empty, pair head tail cons)
hostBytes proper list of canonical naturals in 0..255

hostList payloads are raw canonical lists, not lists of host ABI values.

Accessors / matching

Accessors:

hostValueTag hostValue
hostValuePayload hostValue

A host can decode the envelope by destructuring the pair directly, but these helpers make the ABI explicit and testable.

Validation predicates

Typed runners validate that the raw application result can be interpreted as the requested type before wrapping it.

Predicates:

hostNumber? value
hostBool? value
hostList? value
hostString? value
hostBytes? value

These predicates validate canonical tree encodings.

Important ambiguity note:

Tree Calculus encodings are not globally disjoint. For example, t is also false, 0, and []. Typed runners intentionally interpret values according to the requested type.

Error behavior

Typed ABI runners return an error if the application result does not match the requested type.

Codec error:

errHostCodecFailed = 14

Example:

runArboricxToString bundle args

returns:

ok (hostString resultCharacters) rest

if resultCharacters is string-like, otherwise:

err errHostCodecFailed rest

The typed runner preserves the execution remainder on codec failure; it does not replace it with the rejected payload. Invalid dispatch tags and improper argument-list spines are rejected before bundle parsing/application, with code 14 and an empty remainder. The raw tree constructor remains unchecked; typed execution checks the selected payload interpretation.

String payloads are not UTF-8 byte streams. Wire manifest text uses validated UTF-8 octets, whereas the host string envelope carries Unicode scalar values.

Execution wrappers

The base self-hosted Arboricx runners are defined in lib/arboricx/arboricx.tri:

runArboricxArgs bundleBytes args
runArboricxArgsByName nameBytes bundleBytes args

Host ABI wrappers layer typed output envelopes on top:

runArboricxToTree bundleBytes args
runArboricxToString bundleBytes args
runArboricxToNumber bundleBytes args
runArboricxToBool bundleBytes args
runArboricxToList bundleBytes args
runArboricxToBytes bundleBytes args

Named-export variants:

runArboricxByNameToTree nameBytes bundleBytes args
runArboricxByNameToString nameBytes bundleBytes args
runArboricxByNameToNumber nameBytes bundleBytes args
runArboricxByNameToBool nameBytes bundleBytes args
runArboricxByNameToList nameBytes bundleBytes args
runArboricxByNameToBytes nameBytes bundleBytes args

Host usage

For a bundle whose default export is an unapplied function:

append "hello "

A host that expects a string result evaluates:

runArboricxToString bundleBytes ["james"]

On success, the result is:

ok (hostString "hello james") rest

The host then:

  1. unwraps ok,
  2. checks hostStringTag,
  3. decodes the canonical string payload.

Implementation reference

Invariants

The reference tests cover these invariants:

  1. Each constructor stores the correct tag and payload.
  2. hostValueTag and hostValuePayload destructure values correctly.
  3. runArboricxToTree always wraps successful raw results as hostTree.
  4. runArboricxToString wraps canonical string results as hostString.
  5. runArboricxToNumber wraps canonical natural-number results as hostNumber.
  6. runArboricxToBool wraps canonical booleans as hostBool.
  7. A typed runner returns errHostCodecFailed when validation fails.
  8. Named-export typed runners select the requested export before wrapping.

Native source IO boundary

These are IO action tags, separate from the result-envelope tags above:

Constructor IO tag Result payload
readSourceFile path 37 Pair of exact file byte list and decoded Unicode scalar list.
encodeSourceText characters 38 UTF-8 byte list; noncanonical naturals, non-scalars and improper lists fail.

Both return ordinary IO Results with an empty remainder. Reading uses the same read permission checks as readFile (tag 20), which remains an octet reader. Source decoding is strict UTF-8 and rejects an initial BOM with a path-bearing diagnostic. Embedded U+FEFF is an ordinary scalar, not stripped. Encoding does not normalize text or translate newlines; it performs no filesystem IO.

These actions support the current-source native compiler candidate. Retained compiler bundles keep their recorded behavior until integrated publication.

Haskell filesystem permission boundary

Read/write grants are canonical path prefixes with directory-component boundaries. Checks resolve existing symlinked ancestors even when the final filename does not yet exist. Empty grants do not authorize paths; resolution errors deny access. Unrestricted read/write flags explicitly bypass the corresponding checks.

This is not a race-free filesystem sandbox: checking a pathname and subsequently opening it are separate operations. Use trusted directories and do not allow untrusted concurrent mutation of directory entries or symlinks.

Haskell IO run ownership

A host IO run owns its asynchronous OS workers and open socket registry. Root completion and abortIO end the run; they do not leave detached workers running. Normal return, failure and external cancellation cancel and join outstanding OS workers before closing remaining registered sockets. Explicit socket close still invalidates that handle immediately. Task handles and socket handles are local to the run and cannot transfer resource ownership to another invocation.

Cancellation does not execute pending tricu continuations or language-level cleanup actions, and it cannot roll back completed effects or partial writes. Worker cancellation waits for host cleanup; an uninterruptible OS operation can delay termination beyond a requested timeout. This is not a hard process-kill deadline. Socket operations report operational IO errors as Results, but host asynchronous cancellation is not converted into an ordinary Result.