feat(php): use new FFI for Arboricx
This commit is contained in:
218
ext/php/run.php
218
ext/php/run.php
@@ -4,32 +4,81 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* run.php — Self-hosted Arboricx PHP host shell.
|
||||
* run.php — Arboricx PHP host shell via libarboricx C ABI.
|
||||
*
|
||||
* Usage:
|
||||
* php run.php run <bundle.arboricx> <arg> [arg ...]
|
||||
* php run.php run <bundle.arboricx> [args...]
|
||||
* php run.php inspect <bundle.arboricx>
|
||||
*
|
||||
* The "run" command:
|
||||
* 1. Reads the .arboricx bundle as raw bytes
|
||||
* 2. Encodes bundle bytes as a Tree Calculus byte list
|
||||
* 3. Encodes each host argument (string or number)
|
||||
* 4. Calls runArboricxToString via the hardcoded kernel
|
||||
* 5. Unwraps ok/err, then Host ABI envelope
|
||||
* 6. Decodes the string payload
|
||||
*
|
||||
* This is a minimal host shell — it does NOT parse Arboricx bundles itself.
|
||||
* The kernel handles bundle parsing internally.
|
||||
*/
|
||||
|
||||
require __DIR__ . '/src/functions.php';
|
||||
require __DIR__ . '/src/codecs.php';
|
||||
require __DIR__ . '/src/kernel.php';
|
||||
require __DIR__ . '/src/ffi.php';
|
||||
|
||||
use Arboricx\Node;
|
||||
use function Arboricx\{app, reduce, ofString, ofNumber, ofBytes, ofList,
|
||||
formatTree, unwrapResult, unwrapHostValue, decodeHostPayload,
|
||||
getRunArboricxToString};
|
||||
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)';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Commands ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -47,94 +96,43 @@ function readBundle(string $path): string
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
function cmdRun(string $bundlePath, array $args): void
|
||||
function cmdRun(string $libPath, string $bundlePath, array $args): void
|
||||
{
|
||||
$bundleBytes = readBundle($bundlePath);
|
||||
|
||||
$kernel = getRunArboricxToString();
|
||||
if ($kernel === null) {
|
||||
fwrite(STDERR, "Error: runArboricxToString kernel not configured\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$bundleTree = ofBytes($bundleBytes);
|
||||
|
||||
$argTrees = [];
|
||||
foreach ($args as $arg) {
|
||||
$argTrees[] = encodeArg($arg);
|
||||
}
|
||||
$argsTree = ofList($argTrees);
|
||||
|
||||
// Kernel application: runArboricxToString bundle args
|
||||
$expr = app(app($kernel, $bundleTree), $argsTree);
|
||||
fwrite(STDERR, "Reducing kernel application...\n");
|
||||
$result = reduce($expr, 1_000_000_000_000);
|
||||
|
||||
// The kernel returns an ok/err pair. On ok, the value is a
|
||||
// Host ABI envelope: Fork(tag_number, payload_tree).
|
||||
[$kind, $value, $rest] = unwrapResult($result);
|
||||
if ($kind === 'error') {
|
||||
fwrite(STDERR, "Error detail: $rest\n");
|
||||
exit(1);
|
||||
}
|
||||
if ($kind === 'err') {
|
||||
[$ok2, $code] = Arboricx\toNumber($value);
|
||||
$codeStr = $ok2 ? (string)$code : formatTree($value);
|
||||
fwrite(STDERR, "Arboricx error code: $codeStr\n");
|
||||
fwrite(STDERR, "Rest: " . formatTree($rest) . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
[$tag, $payload] = unwrapHostValue($value);
|
||||
|
||||
$ctx = ctx_init($libPath);
|
||||
try {
|
||||
$decoded = decodeHostPayload($tag, $payload);
|
||||
echo $decoded['value'] . "\n";
|
||||
} catch (\Throwable $e) {
|
||||
fwrite(STDERR, "Host ABI decode error: " . $e->getMessage() . "\n");
|
||||
fwrite(STDERR, "Raw tag: $tag, payload: " . formatTree($payload) . "\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
$term = loadBundleDefault($ctx, readBundle($bundlePath));
|
||||
|
||||
function encodeArg(string $arg): Node
|
||||
{
|
||||
return ctype_digit($arg) ? ofNumber((int)$arg) : ofString($arg);
|
||||
}
|
||||
|
||||
function cmdInspect(string $bundlePath): void
|
||||
{
|
||||
$bundleBytes = readBundle($bundlePath);
|
||||
$bytes = strlen($bundleBytes);
|
||||
echo "Bundle: $bundlePath\n";
|
||||
echo "Size: $bytes bytes\n";
|
||||
|
||||
// Run with no arguments to see the default export
|
||||
$kernel = getRunArboricxToString();
|
||||
if ($kernel === null) {
|
||||
fwrite(STDERR, "Warning: kernel not configured, skipping execution\n");
|
||||
return;
|
||||
}
|
||||
|
||||
$bundleTree = ofBytes($bundleBytes);
|
||||
$emptyArgs = ofList([]);
|
||||
$result = reduce(app(app($kernel, $bundleTree), $emptyArgs), 10_000);
|
||||
[$kind, $value, $rest] = unwrapResult($result);
|
||||
|
||||
if ($kind === 'ok') {
|
||||
echo "\nResult (ok):\n";
|
||||
try {
|
||||
[$tag, $payload] = unwrapHostValue($value);
|
||||
$decoded = decodeHostPayload($tag, $payload);
|
||||
echo " Tag: $tag (type: " . $decoded['type'] . ")\n";
|
||||
echo " Value: " . $decoded['value'] . "\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo " Raw: " . formatTree($value) . "\n";
|
||||
foreach ($args as $arg) {
|
||||
$argTree = preg_match('/^\d+$/', $arg) ? ofNumber($ctx, (int)$arg) : ofString($ctx, $arg);
|
||||
$term = app($ctx, $term, $argTree);
|
||||
}
|
||||
} else {
|
||||
echo "\nResult (err):\n";
|
||||
[$ok, $code] = Arboricx\toNumber($value);
|
||||
echo " Code: " . ($ok ? (string)$code : formatTree($value)) . "\n";
|
||||
|
||||
$result = reduce($ctx, $term, 1_000_000_000);
|
||||
echo decode($ctx, $result) . "\n";
|
||||
} finally {
|
||||
ctx_free($ctx);
|
||||
}
|
||||
}
|
||||
|
||||
function cmdInspect(string $libPath, string $bundlePath): void
|
||||
{
|
||||
$ctx = ctx_init($libPath);
|
||||
try {
|
||||
$bundle = readBundle($bundlePath);
|
||||
echo "Bundle: $bundlePath\nSize: " . strlen($bundle) . " bytes\n\nResult:\n";
|
||||
|
||||
$term = loadBundleDefault($ctx, $bundle);
|
||||
$result = reduce($ctx, $term, 1_000_000_000);
|
||||
|
||||
$type = decodeType($ctx, $result);
|
||||
try {
|
||||
$value = decode($ctx, $result);
|
||||
} catch (\RuntimeException $e) {
|
||||
$value = '(raw tree)';
|
||||
}
|
||||
echo " Type: $type\n Value: $value\n";
|
||||
} finally {
|
||||
ctx_free($ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,31 +142,31 @@ $argv = $_SERVER['argv'] ?? [];
|
||||
$argc = $_SERVER['argc'] ?? 0;
|
||||
|
||||
if ($argc < 2) {
|
||||
echo "Arboricx PHP Host Shell\n";
|
||||
echo "\nUsage:\n";
|
||||
echo "Arboricx PHP Host Shell (via libarboricx C ABI)\n\nUsage:\n";
|
||||
echo " php run.php run <bundle.arboricx> [args...]\n";
|
||||
echo " php run.php inspect <bundle.arboricx>\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$libPath = findLib();
|
||||
$command = $argv[1];
|
||||
|
||||
switch ($command) {
|
||||
case 'run':
|
||||
if ($argc < 3) {
|
||||
fwrite(STDERR, "Usage: php run.php run <bundle.arboricx> [args...]\n");
|
||||
exit(1);
|
||||
}
|
||||
cmdRun($argv[2], array_slice($argv, 3));
|
||||
cmdRun($libPath, $argv[2], array_slice($argv, 3));
|
||||
break;
|
||||
case 'inspect':
|
||||
if ($argc < 3) {
|
||||
fwrite(STDERR, "Usage: php run.php inspect <bundle.arboricx>\n");
|
||||
exit(1);
|
||||
}
|
||||
cmdInspect($argv[2]);
|
||||
cmdInspect($libPath, $argv[2]);
|
||||
break;
|
||||
default:
|
||||
echo "Unknown command: $command\n";
|
||||
echo "Usage: php run.php run|inspect ...\n";
|
||||
fwrite(STDERR, "Unknown command: $command\nUsage: php run.php run|inspect ...\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user