tricu

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

commit 42f5dbaff9d4ee36310cad7adeb67fa4b39d5d64
parent 47bdc3b4fa6df858d7d3e5024ed821783ff4be58
Author: James Eversole <james@eversole.co>
Date:   Mon, 11 May 2026 13:07:35 -0500

Simple PHP web demo

Diffstat:
Aext/php/public/eval.php | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aext/php/public/index.php | 30++++++++++++++++++++++++++++++
Mext/php/run.php | 87+++++++++----------------------------------------------------------------------
Aext/php/src/common.php | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mflake.nix | 3+--
Mnotes/php-cli-run-flags.md | 13+++++++++++--
Mnotes/recursive-consumers.md | 2+-
7 files changed, 186 insertions(+), 83 deletions(-)

diff --git a/ext/php/public/eval.php b/ext/php/public/eval.php @@ -0,0 +1,53 @@ +<?php + +declare(strict_types=1); + +error_reporting(E_ALL); +ini_set('display_errors', '1'); + +if (!extension_loaded('ffi')) { + http_response_code(500); + echo "Error: PHP FFI extension is not loaded.\n"; + echo "If you are using the Nix build, run the included server script:\n"; + echo " ./result/bin/tricu-php-server\n"; + exit; +} + +require __DIR__ . '/../src/common.php'; + +use function Arboricx\{ctx_init, ctx_free, loadBundleDefault, ofNumber, ofString, app, reduce, decode, findLib, readBundle}; + +header('Content-Type: text/plain; charset=utf-8'); + +try { + if (!isset($_FILES['bundle']) || $_FILES['bundle']['error'] !== UPLOAD_ERR_OK) { + throw new \RuntimeException('Bundle upload failed.'); + } + + $args = []; + for ($i = 0; $i < 5; $i++) { + $v = $_POST["arg$i"] ?? ''; + if ($v !== '') { + $args[] = $v; + } + } + + $libPath = findLib(); + $ctx = ctx_init($libPath); + try { + $term = loadBundleDefault($ctx, readBundle($_FILES['bundle']['tmp_name'])); + + foreach ($args as $arg) { + $argTree = preg_match('/^\d+$/', $arg) ? ofNumber($ctx, (int)$arg) : ofString($ctx, $arg); + $term = app($ctx, $term, $argTree); + } + + $result = reduce($ctx, $term, 1_000_000_000); + echo decode($ctx, $result); + } finally { + ctx_free($ctx); + } +} catch (\Throwable $e) { + http_response_code(500); + echo 'Error: ' . $e->getMessage(); +} diff --git a/ext/php/public/index.php b/ext/php/public/index.php @@ -0,0 +1,30 @@ +<?php +declare(strict_types=1); +?> +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Arboricx Web</title> + <script src="https://unpkg.com/htmx.org@2.0.4"></script> +</head> +<body> + <h1>Arboricx Bundle Runner</h1> + <form hx-post="eval.php" hx-target="#result" enctype="multipart/form-data"> + <p> + <label>Bundle (.arboricx)<br> + <input type="file" name="bundle" accept=".arboricx" required></label> + </p> + <?php for ($i = 0; $i < 5; $i++): ?> + <p> + <label>Arg <?= $i + 1 ?> <small>(ignored if empty)</small><br> + <input type="text" name="arg<?= $i ?>"></label> + </p> + <?php endfor; ?> + <p> + <button type="submit">Run</button> + </p> + </form> + <pre id="result"></pre> +</body> +</html> diff --git a/ext/php/run.php b/ext/php/run.php @@ -11,89 +11,16 @@ declare(strict_types=1); * php run.php inspect <bundle.arboricx> */ -require __DIR__ . '/src/ffi.php'; +require __DIR__ . '/src/common.php'; -use function Arboricx\{ctx_init, ctx_free, loadBundleDefault, ofNumber, ofString, app, reduce, toString, toBool, toNumber}; - -// ── Locate libarboricx.so ────────────────────────────────────────────────── - -function findLib(): string -{ - $env = getenv('ARBORICX_LIB'); - if ($env !== false && file_exists($env)) { - return $env; - } - - $paths = [ - __DIR__ . '/../../zig/zig-out/lib/libarboricx.so', - '/usr/local/lib/libarboricx.so', - '/usr/lib/libarboricx.so', - './libarboricx.so', - ]; - foreach ($paths as $p) { - if (file_exists($p)) { - return $p; - } - } - - fwrite(STDERR, "Error: libarboricx.so not found.\nSet ARBORICX_LIB to its full path.\n"); - exit(1); -} - -// ── Decode helpers ───────────────────────────────────────────────────────── - -function decode(\FFI\CData $ctx, int $root): string -{ - // Bool first: false is Leaf, which is also a valid empty string/list. - try { - return toBool($ctx, $root) ? 'true' : 'false'; - } catch (\Throwable $e) { - try { - return toString($ctx, $root); - } catch (\Throwable $e2) { - try { - return (string) toNumber($ctx, $root); - } catch (\Throwable $e3) { - throw new \RuntimeException('could not decode result'); - } - } - } -} - -function decodeType(\FFI\CData $ctx, int $root): string -{ - try { - toBool($ctx, $root); - return 'bool'; - } catch (\Throwable $e) { - try { - toString($ctx, $root); - return 'string'; - } catch (\Throwable $e2) { - try { - toNumber($ctx, $root); - return 'number'; - } catch (\Throwable $e3) { - return 'unknown (raw tree)'; - } - } - } -} +use function Arboricx\{ctx_init, ctx_free, loadBundleDefault, ofNumber, ofString, app, reduce, toString, toBool, toNumber, findLib, decode, decodeType, readBundle}; // ── Commands ───────────────────────────────────────────────────────────────── -function readBundle(string $path): string +function bail(string $msg): void { - if (!file_exists($path)) { - fwrite(STDERR, "Error: bundle not found: $path\n"); - exit(1); - } - $bytes = file_get_contents($path); - if ($bytes === false) { - fwrite(STDERR, "Error: could not read bundle: $path\n"); - exit(1); - } - return $bytes; + fwrite(STDERR, "Error: $msg\n"); + exit(1); } function cmdRun(string $libPath, string $bundlePath, array $args): void @@ -109,6 +36,8 @@ function cmdRun(string $libPath, string $bundlePath, array $args): void $result = reduce($ctx, $term, 1_000_000_000); echo decode($ctx, $result) . "\n"; + } catch (\Throwable $e) { + bail($e->getMessage()); } finally { ctx_free($ctx); } @@ -131,6 +60,8 @@ function cmdInspect(string $libPath, string $bundlePath): void $value = '(raw tree)'; } echo " Type: $type\n Value: $value\n"; + } catch (\Throwable $e) { + bail($e->getMessage()); } finally { ctx_free($ctx); } diff --git a/ext/php/src/common.php b/ext/php/src/common.php @@ -0,0 +1,81 @@ +<?php + +declare(strict_types=1); + +namespace Arboricx; + +require __DIR__ . '/ffi.php'; + +use function Arboricx\{ctx_init, ctx_free, loadBundleDefault, ofNumber, ofString, app, reduce, toString, toBool, toNumber}; + +function findLib(): string +{ + $env = getenv('ARBORICX_LIB'); + if ($env !== false && file_exists($env)) { + return $env; + } + + $paths = [ + __DIR__ . '/../../zig/zig-out/lib/libarboricx.so', + __DIR__ . '/../libarboricx.so', + '/usr/local/lib/libarboricx.so', + '/usr/lib/libarboricx.so', + './libarboricx.so', + ]; + foreach ($paths as $p) { + if (file_exists($p)) { + return $p; + } + } + + throw new \RuntimeException('libarboricx.so not found. Set ARBORICX_LIB to its full path.'); +} + +function decode(\FFI\CData $ctx, int $root): string +{ + try { + return toBool($ctx, $root) ? 'true' : 'false'; + } catch (\Throwable $e) { + try { + return toString($ctx, $root); + } catch (\Throwable $e2) { + try { + return (string) toNumber($ctx, $root); + } catch (\Throwable $e3) { + throw new \RuntimeException('could not decode result'); + } + } + } +} + +function decodeType(\FFI\CData $ctx, int $root): string +{ + try { + toBool($ctx, $root); + return 'bool'; + } catch (\Throwable $e) { + try { + toString($ctx, $root); + return 'string'; + } catch (\Throwable $e2) { + try { + toNumber($ctx, $root); + return 'number'; + } catch (\Throwable $e3) { + return 'unknown (raw tree)'; + } + } + } +} + +function readBundle(string $path): string +{ + if (!file_exists($path)) { + throw new \RuntimeException("bundle not found: $path"); + } + $bytes = file_get_contents($path); + if ($bytes === false) { + throw new \RuntimeException("could not read bundle: $path"); + } + return $bytes; +} diff --git a/flake.nix b/flake.nix @@ -111,7 +111,7 @@ buildPhase = "true"; installPhase = '' mkdir -p $out/share/tricu-php $out/lib $out/bin - cp -r src run.php $out/share/tricu-php/ + cp -r src public run.php $out/share/tricu-php/ cp ${tricuZig}/lib/libarboricx.so $out/lib/ cp ${tricuZig}/include/arboricx.h $out/share/tricu-php/ @@ -168,7 +168,6 @@ haskellPackages.ghcid customGHC upx - zig gcc python3 ]; diff --git a/notes/php-cli-run-flags.md b/notes/php-cli-run-flags.md @@ -1,9 +1,18 @@ # PHP Recommended Run Flags ```php -php -d memory_limit=4G \ - -d opcache.enable_cli=1 \ +php -d opcache.enable_cli=1 \ -d opcache.jit_buffer_size=256M \ -d opcache.jit=tracing \ ext/php/run.php run $PATH_TO_ARBORIX_BUNDLE $ARGS ``` + +For bundle execution test server: + +```php +nix build .#tricu-php +ARBORICX_LIB=../../../lib/libarboricx.so php \ + -S localhost:8081 \ + -t ./result/share/tricu-php/public \ + -d ffi.enable=true +``` diff --git a/notes/recursive-consumers.md b/notes/recursive-consumers.md @@ -2,7 +2,7 @@ ## Rule -Put consumed data first in recursive workers. +Put consumed data first in recursive workers in `tricu` code. *AVOID* this shape: