139 lines
3.7 KiB
PHP
139 lines
3.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Arboricx;
|
|
|
|
/**
|
|
* FFI wrapper around libarboricx.so.
|
|
*
|
|
* Loads the shared library and exposes typed wrappers for the C ABI.
|
|
*/
|
|
final class ArboricxFFI
|
|
{
|
|
private static ?\FFI $ffi = null;
|
|
|
|
public static function init(string $libPath): void
|
|
{
|
|
if (self::$ffi !== null) {
|
|
return;
|
|
}
|
|
|
|
// Nix output layout first, then repo layout.
|
|
$candidates = [
|
|
__DIR__ . '/../arboricx.h',
|
|
__DIR__ . '/../../zig/include/arboricx.h',
|
|
];
|
|
$headerRaw = false;
|
|
foreach ($candidates as $path) {
|
|
$headerRaw = file_get_contents($path);
|
|
if ($headerRaw !== false) break;
|
|
}
|
|
if ($headerRaw === false) {
|
|
throw new \RuntimeException('Cannot read arboricx.h');
|
|
}
|
|
|
|
// PHP FFI only parses plain C declarations.
|
|
$header = $headerRaw;
|
|
$header = preg_replace('/#.*\n/', "\n", $header);
|
|
$header = preg_replace('/extern\s+"C"\s*\{/', '', $header);
|
|
$header = str_replace('}', '', $header);
|
|
$header = preg_replace('/\n\s*\n+/', "\n", $header);
|
|
|
|
self::$ffi = \FFI::cdef($header, $libPath);
|
|
}
|
|
|
|
public static function ffi(): \FFI
|
|
{
|
|
if (self::$ffi === null) {
|
|
throw new \RuntimeException('ArboricxFFI not initialized. Call ArboricxFFI::init($libPath) first.');
|
|
}
|
|
return self::$ffi;
|
|
}
|
|
}
|
|
|
|
function ctx_init(string $libPath): \FFI\CData
|
|
{
|
|
ArboricxFFI::init($libPath);
|
|
$ctx = ArboricxFFI::ffi()->arboricx_init();
|
|
if ($ctx === null) {
|
|
throw new \RuntimeException('arboricx_init failed');
|
|
}
|
|
return $ctx;
|
|
}
|
|
|
|
function ctx_free(\FFI\CData $ctx): void
|
|
{
|
|
ArboricxFFI::ffi()->arboricx_free($ctx);
|
|
}
|
|
|
|
function app(\FFI\CData $ctx, int $func, int $arg): int
|
|
{
|
|
return ArboricxFFI::ffi()->arb_app($ctx, $func, $arg);
|
|
}
|
|
|
|
function reduce(\FFI\CData $ctx, int $root, int $fuel = 1_000_000_000): int
|
|
{
|
|
return ArboricxFFI::ffi()->arb_reduce($ctx, $root, $fuel);
|
|
}
|
|
|
|
function ofNumber(\FFI\CData $ctx, int $n): int
|
|
{
|
|
return ArboricxFFI::ffi()->arb_of_number($ctx, $n);
|
|
}
|
|
|
|
function ofString(\FFI\CData $ctx, string $s): int
|
|
{
|
|
return ArboricxFFI::ffi()->arb_of_string($ctx, $s);
|
|
}
|
|
|
|
function toNumber(\FFI\CData $ctx, int $root): int
|
|
{
|
|
$out = ArboricxFFI::ffi()->new('uint64_t');
|
|
$ok = ArboricxFFI::ffi()->arb_to_number($ctx, $root, \FFI::addr($out));
|
|
if (!$ok) {
|
|
throw new \RuntimeException('arb_to_number failed');
|
|
}
|
|
return (int) $out->cdata;
|
|
}
|
|
|
|
function toString(\FFI\CData $ctx, int $root): string
|
|
{
|
|
$ptr = ArboricxFFI::ffi()->new('uint8_t*');
|
|
$len = ArboricxFFI::ffi()->new('size_t');
|
|
$ok = ArboricxFFI::ffi()->arb_to_string($ctx, $root, \FFI::addr($ptr), \FFI::addr($len));
|
|
if (!$ok) {
|
|
throw new \RuntimeException('arb_to_string failed');
|
|
}
|
|
$length = (int) $len->cdata;
|
|
$result = '';
|
|
for ($i = 0; $i < $length; $i++) {
|
|
$result .= chr($ptr[$i]);
|
|
}
|
|
ArboricxFFI::ffi()->arboricx_free_buf($ctx, $ptr, $length);
|
|
return $result;
|
|
}
|
|
|
|
function toBool(\FFI\CData $ctx, int $root): bool
|
|
{
|
|
$out = ArboricxFFI::ffi()->new('int');
|
|
$ok = ArboricxFFI::ffi()->arb_to_bool($ctx, $root, \FFI::addr($out));
|
|
if (!$ok) {
|
|
throw new \RuntimeException('arb_to_bool failed');
|
|
}
|
|
return (bool) $out->cdata;
|
|
}
|
|
|
|
function loadBundleDefault(\FFI\CData $ctx, string $bytes): int
|
|
{
|
|
$cdata = ArboricxFFI::ffi()->new('uint8_t[' . strlen($bytes) . ']');
|
|
for ($i = 0; $i < strlen($bytes); $i++) {
|
|
$cdata[$i] = ord($bytes[$i]);
|
|
}
|
|
$result = ArboricxFFI::ffi()->arb_load_bundle_default($ctx, $cdata, strlen($bytes));
|
|
if ($result === 0) {
|
|
throw new \RuntimeException('arb_load_bundle_default failed');
|
|
}
|
|
return $result;
|
|
}
|