Host ABI definition and ergonomics in TC

This commit is contained in:
2026-05-09 18:33:03 -05:00
parent d0886ad886
commit 2e8a0a4c46
4 changed files with 542 additions and 52 deletions

View File

@@ -4,6 +4,8 @@ This document describes how to build a minimal host-language shell that can exec
The intended reader is an implementation agent building a first prototype in a host language such as PHP. The same approach should generalize to any language with a small Tree Calculus evaluator.
See also: [`docs/host-abi.md`](./host-abi.md) for the precise host-facing ABI value tags and typed runner contract.
## Goal
Build a tiny host program that can:
@@ -14,7 +16,8 @@ Build a tiny host program that can:
4. Read an application `.arboricx` bundle from disk.
5. Convert host inputs into canonical Tree Calculus values.
6. Apply the kernel to the application bundle and arguments.
7. Decode the result back into host values.
7. Unwrap a standardized host ABI result.
8. Decode the host ABI payload back into host values.
A concrete target example:
@@ -29,13 +32,19 @@ The host should be able to call that bundle with the host string `"james"` and r
hello james
```
Conceptually the host evaluates:
With the Host ABI layer, the preferred conceptual call is:
```tricu
runArboricxArgs <applicationBundleBytes> ["james"]
runArboricxToString <applicationBundleBytes> ["james"]
```
where `runArboricxArgs` comes from the self-hosted Arboricx runtime kernel.
This returns:
```tricu
ok (hostString "hello james") rest
```
where `runArboricxToString` comes from the self-hosted Arboricx runtime kernel.
## Architectural overview
@@ -43,7 +52,7 @@ There are two Arboricx bundles involved:
1. **Kernel bundle**
- Contains the self-hosted Arboricx parser/executor written in tricu.
- Exposes ergonomic runtime entrypoints such as `runArboricxArgs`.
- Exposes ergonomic runtime entrypoints such as `runArboricxArgs` and Host ABI entrypoints such as `runArboricxToString`.
- This can be hardcoded as a Tree Calculus value in the host, or loaded by a minimal host-side Arboricx parser.
2. **Application bundle**
@@ -149,7 +158,9 @@ This logic is exactly what the tricu self-hosted kernel does, so the hardcoded-k
The ergonomic runtime API currently lives in `lib/arboricx.tri`.
Primary entrypoints:
### Raw execution entrypoints
These return raw application results inside the existing `ok` / `err` result protocol:
```tricu
readArboricxExecutableByName nameBytes bundleBytes
@@ -160,52 +171,70 @@ runArboricxArgsByName nameBytes bundleBytes args
runArboricxArgs bundleBytes args
```
Recommended host entrypoint:
```tricu
runArboricxArgs
```
It accepts:
`runArboricxArgs` accepts:
1. Raw application bundle bytes as a Tree Calculus byte list.
2. A Tree Calculus list of arguments.
It returns a result-wrapped value.
For named exports, use:
```tricu
runArboricxArgsByName
```
It accepts:
For named exports, use `runArboricxArgsByName`, which accepts:
1. Export name as bytes.
2. Application bundle bytes as bytes.
3. Argument list.
### Applying the kernel in the host evaluator
### Host ABI typed entrypoints
If the host has the Tree Calculus value for `runArboricxArgs`, call it by constructing nested application trees.
For host-language ports, prefer the Host ABI typed runners. These wrap successful outputs in a tagged host ABI value so every host can decode the same envelope shape.
Default export variants:
```tricu
runArboricxToTree bundleBytes args
runArboricxToString bundleBytes args
runArboricxToNumber bundleBytes args
runArboricxToBool bundleBytes args
runArboricxToList bundleBytes args
runArboricxToBytes bundleBytes args
```
Named export variants:
```tricu
runArboricxByNameToTree nameBytes bundleBytes args
runArboricxByNameToString nameBytes bundleBytes args
runArboricxByNameToNumber nameBytes bundleBytes args
runArboricxByNameToBool nameBytes bundleBytes args
runArboricxByNameToList nameBytes bundleBytes args
runArboricxByNameToBytes nameBytes bundleBytes args
```
Recommended first host entrypoint for the `append "hello "` example:
```tricu
runArboricxToString
```
## Applying the kernel in the host evaluator
If the host has the Tree Calculus value for `runArboricxToString`, call it by constructing nested application trees.
In Tree Calculus application form:
```text
((runArboricxArgs bundleBytesTree) argsTree)
((runArboricxToString bundleBytesTree) argsTree)
```
Structurally, if `app(f, x)` constructs `Fork(f, x)`, then:
```php
$expr = app(app($kernelRunArboricxArgs, $bundleBytesTree), $argsTree);
$expr = app(app($kernelRunArboricxToString, $bundleBytesTree), $argsTree);
$result = normalize($expr);
```
For named export execution:
```text
(((runArboricxArgsByName nameBytesTree) bundleBytesTree) argsTree)
(((runArboricxByNameToString nameBytesTree) bundleBytesTree) argsTree)
```
Structurally:
@@ -213,7 +242,7 @@ Structurally:
```php
$expr = app(
app(
app($kernelRunArboricxArgsByName, $nameBytesTree),
app($kernelRunArboricxByNameToString, $nameBytesTree),
$bundleBytesTree
),
$argsTree
@@ -221,24 +250,73 @@ $expr = app(
$result = normalize($expr);
```
## Result convention
## Result convention and Host ABI envelope
The runtime API returns results using the tricu `ok` / `err` convention from `lib/binary.tri`:
All runtime APIs return the existing tricu `ok` / `err` convention from `lib/binary.tri`:
```tricu
ok value rest = pair true (pair value rest)
err code rest = pair false (pair code rest)
```
The host should unwrap this result before decoding the final value.
The host should always unwrap this outer result first.
Expected success shape:
### Raw runners
Raw runners such as `runArboricxArgs` return:
```tricu
ok value rest
ok rawApplicationValue rest
```
For typical execution, `value` is the application result. `rest` is usually not important to the host shell unless debugging parser behavior.
The host must know how to interpret `rawApplicationValue`.
### Host ABI typed runners
Typed runners such as `runArboricxToString` return:
```tricu
ok hostAbiValue rest
```
A host ABI value has shape:
```tricu
pair tag payload
```
The payload is still the canonical/raw Tree Calculus representation for that type.
Initial tags are specified in [`docs/host-abi.md`](./host-abi.md):
```tricu
hostTreeTag = 0
hostStringTag = 1
hostNumberTag = 2
hostBoolTag = 3
hostListTag = 4
hostBytesTag = 5
```
For example:
```tricu
runArboricxToString bundleBytes ["james"]
```
returns:
```tricu
ok (hostString "hello james") rest
```
which is structurally:
```tricu
ok (pair hostStringTag "hello james") rest
```
### Error shape
Expected error shape:
@@ -250,8 +328,11 @@ The error code is a Tree Calculus number. Error constants are defined in:
- `lib/binary.tri`
- `lib/arboricx-common.tri`
- `lib/arboricx.tri` for Host ABI codec errors, currently `errHostCodecFailed = 14`
A prototype host can simply report the numeric error code and optionally dump a compact representation of `rest`.
Typed runners return `errHostCodecFailed` if the application result cannot be interpreted as the requested type.
A prototype host can report the numeric error code and optionally dump a compact representation of `rest`.
## Example execution flow
@@ -268,7 +349,7 @@ Host flow:
1. Load kernel entrypoint tree:
```php
$runArboricxArgs = loadHardcodedKernelEntrypoint('runArboricxArgs');
$runArboricxToString = loadHardcodedKernelEntrypoint('runArboricxToString');
```
2. Read application bundle bytes:
@@ -293,7 +374,7 @@ Host flow:
5. Build application expression:
```php
$expr = app(app($runArboricxArgs, $bundleBytesTree), $args);
$expr = app(app($runArboricxToString, $bundleBytesTree), $args);
```
6. Evaluate:
@@ -305,19 +386,26 @@ Host flow:
7. Unwrap `ok` result:
```php
[$ok, $value, $rest] = unwrapResult($result);
[$ok, $hostValue, $rest] = unwrapResult($result);
if (!$ok) { throw new RuntimeException('Arboricx error'); }
```
8. Decode the value:
8. Unwrap Host ABI envelope:
```php
echo decodeString($value); // hello james
[$tag, $payload] = unwrapHostValue($hostValue);
if ($tag !== HOST_STRING_TAG) { throw new RuntimeException('Expected string'); }
```
9. Decode the payload:
```php
echo decodeString($payload); // hello james
```
## What the kernel does internally
`runArboricxArgs` performs the following steps inside Tree Calculus:
`runArboricxToString` performs the following steps inside Tree Calculus:
1. Parse and validate the raw Arboricx bundle bytes.
2. Parse the manifest.
@@ -328,9 +416,12 @@ Host flow:
4. Read the nodes section.
5. Reconstruct the selected root tree from the Merkle DAG.
6. Apply each host-provided argument in order.
7. Return `ok result rest` or an `err`.
7. Validate that the raw result is string-like.
8. Return `ok (hostString result) rest`, or an `err`.
`runArboricxArgsByName` is identical except that it selects a named export.
`runArboricxByNameToString` is identical except that it selects a named export.
Other typed runners follow the same pattern for their requested output type.
## Tests proving the expected behavior
@@ -342,12 +433,18 @@ Important cases:
- `readArboricxExecutableByName: selects named export`
- `runArboricx: applies host-provided argument to default export`
- `runArboricxArgs: applies host-provided argument list in order`
- `host ABI: constructors expose tag and payload`
- `runArboricxToTree: wraps raw result as hostTree`
- `runArboricxToString: wraps string result as hostString`
- `runArboricxToNumber: wraps number result as hostNumber`
- `runArboricxToBool: rejects non-bool result`
These tests demonstrate the host-shell contract:
- application bundle bytes are supplied as a Tree Calculus byte list,
- host arguments are supplied as canonical Tree Calculus values,
- execution returns a result-wrapped Tree Calculus value.
- execution returns an outer result-wrapped value,
- Host ABI typed runners return a tagged ABI envelope inside `ok`.
## Minimal PHP prototype checklist
@@ -357,13 +454,14 @@ A PHP prototype should implement:
- [ ] Application helper: `app($f, $x) = Fork($f, $x)`.
- [ ] Normal-order Tree Calculus reducer.
- [ ] Fuel/step limit for debugging.
- [ ] Hardcoded kernel entrypoint tree for `runArboricxArgs`.
- [ ] Hardcoded kernel entrypoint tree for `runArboricxToString` for the first string-output prototype.
- [ ] Encode application bundle file bytes into a Tree Calculus byte list.
- [ ] Encode host argument values into Tree Calculus values.
- [ ] Build expression: `((runArboricxArgs bundleBytes) args)`.
- [ ] Build expression: `((runArboricxToString bundleBytes) args)`.
- [ ] Normalize expression.
- [ ] Unwrap `ok` / `err` result.
- [ ] Decode result value into host type.
- [ ] Unwrap outer `ok` / `err` result.
- [ ] Unwrap Host ABI `pair tag payload` envelope.
- [ ] Decode payload according to tag.
For exact codec details, reference the Haskell implementation in `src/Research.hs` and the existing JS runtime if available.
@@ -371,14 +469,15 @@ For exact codec details, reference the Haskell implementation in `src/Research.h
For the first PHP implementation:
1. Hardcode only the `runArboricxArgs` kernel entrypoint as a Tree Calculus value.
1. Hardcode only the `runArboricxToString` kernel entrypoint as a Tree Calculus value.
2. Do not implement host-side Arboricx parsing yet.
3. Implement only enough codecs for:
- bytes,
- strings,
- lists,
- result unwrapping.
- result unwrapping,
- Host ABI envelope unwrapping.
4. Use one test fixture: an Arboricx bundle whose root is `append "hello "`.
5. Assert that calling it with `"james"` returns `"hello james"`.
5. Assert that calling it with `"james"` returns an outer `ok`, then a `hostString`, then payload `"hello james"`.
Once that works, add named export support via `runArboricxArgsByName` and expand codecs as needed.
Once that works, add named export support via `runArboricxByNameToString` and expand Host ABI tags/codecs as needed.