feat(php): Simple web demo
This commit is contained in:
53
ext/php/public/eval.php
Normal file
53
ext/php/public/eval.php
Normal file
@@ -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();
|
||||||
|
}
|
||||||
30
ext/php/public/index.php
Normal file
30
ext/php/public/index.php
Normal file
@@ -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>
|
||||||
@@ -11,89 +11,16 @@ declare(strict_types=1);
|
|||||||
* php run.php inspect <bundle.arboricx>
|
* 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};
|
use function Arboricx\{ctx_init, ctx_free, loadBundleDefault, ofNumber, ofString, app, reduce, toString, toBool, toNumber, findLib, decode, decodeType, readBundle};
|
||||||
|
|
||||||
// ── 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 ─────────────────────────────────────────────────────────────────
|
// ── Commands ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function readBundle(string $path): string
|
function bail(string $msg): void
|
||||||
{
|
{
|
||||||
if (!file_exists($path)) {
|
fwrite(STDERR, "Error: $msg\n");
|
||||||
fwrite(STDERR, "Error: bundle not found: $path\n");
|
exit(1);
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
$bytes = file_get_contents($path);
|
|
||||||
if ($bytes === false) {
|
|
||||||
fwrite(STDERR, "Error: could not read bundle: $path\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
return $bytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmdRun(string $libPath, string $bundlePath, array $args): void
|
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);
|
$result = reduce($ctx, $term, 1_000_000_000);
|
||||||
echo decode($ctx, $result) . "\n";
|
echo decode($ctx, $result) . "\n";
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
bail($e->getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
ctx_free($ctx);
|
ctx_free($ctx);
|
||||||
}
|
}
|
||||||
@@ -131,6 +60,8 @@ function cmdInspect(string $libPath, string $bundlePath): void
|
|||||||
$value = '(raw tree)';
|
$value = '(raw tree)';
|
||||||
}
|
}
|
||||||
echo " Type: $type\n Value: $value\n";
|
echo " Type: $type\n Value: $value\n";
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
bail($e->getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
ctx_free($ctx);
|
ctx_free($ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
81
ext/php/src/common.php
Normal file
81
ext/php/src/common.php
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
buildPhase = "true";
|
buildPhase = "true";
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/share/tricu-php $out/lib $out/bin
|
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}/lib/libarboricx.so $out/lib/
|
||||||
cp ${tricuZig}/include/arboricx.h $out/share/tricu-php/
|
cp ${tricuZig}/include/arboricx.h $out/share/tricu-php/
|
||||||
|
|
||||||
@@ -168,7 +168,6 @@
|
|||||||
haskellPackages.ghcid
|
haskellPackages.ghcid
|
||||||
customGHC
|
customGHC
|
||||||
upx
|
upx
|
||||||
zig
|
|
||||||
gcc
|
gcc
|
||||||
python3
|
python3
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
# PHP Recommended Run Flags
|
# PHP Recommended Run Flags
|
||||||
|
|
||||||
```php
|
```php
|
||||||
php -d memory_limit=4G \
|
php -d opcache.enable_cli=1 \
|
||||||
-d opcache.enable_cli=1 \
|
|
||||||
-d opcache.jit_buffer_size=256M \
|
-d opcache.jit_buffer_size=256M \
|
||||||
-d opcache.jit=tracing \
|
-d opcache.jit=tracing \
|
||||||
ext/php/run.php run $PATH_TO_ARBORIX_BUNDLE $ARGS
|
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
|
||||||
|
```
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Rule
|
## Rule
|
||||||
|
|
||||||
Put consumed data first in recursive workers.
|
Put consumed data first in recursive workers in `tricu` code.
|
||||||
|
|
||||||
*AVOID* this shape:
|
*AVOID* this shape:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user