ffi.php (3837B)
1 <?php 2 3 declare(strict_types=1); 4 5 namespace Arboricx; 6 7 /** 8 * FFI wrapper around libarboricx.so. 9 * 10 * Loads the shared library and exposes typed wrappers for the C ABI. 11 */ 12 final class ArboricxFFI 13 { 14 private static ?\FFI $ffi = null; 15 16 public static function init(string $libPath): void 17 { 18 if (self::$ffi !== null) { 19 return; 20 } 21 22 // Nix output layout first, then repo layout. 23 $candidates = [ 24 __DIR__ . '/../arboricx.h', 25 __DIR__ . '/../../zig/include/arboricx.h', 26 ]; 27 $headerRaw = false; 28 foreach ($candidates as $path) { 29 $headerRaw = file_get_contents($path); 30 if ($headerRaw !== false) break; 31 } 32 if ($headerRaw === false) { 33 throw new \RuntimeException('Cannot read arboricx.h'); 34 } 35 36 // PHP FFI only parses plain C declarations. 37 $header = $headerRaw; 38 $header = preg_replace('/#.*\n/', "\n", $header); 39 $header = preg_replace('/extern\s+"C"\s*\{/', '', $header); 40 $header = str_replace('}', '', $header); 41 $header = preg_replace('/\n\s*\n+/', "\n", $header); 42 43 self::$ffi = \FFI::cdef($header, $libPath); 44 } 45 46 public static function ffi(): \FFI 47 { 48 if (self::$ffi === null) { 49 throw new \RuntimeException('ArboricxFFI not initialized. Call ArboricxFFI::init($libPath) first.'); 50 } 51 return self::$ffi; 52 } 53 } 54 55 function ctx_init(string $libPath): \FFI\CData 56 { 57 ArboricxFFI::init($libPath); 58 $ctx = ArboricxFFI::ffi()->arboricx_init(); 59 if ($ctx === null) { 60 throw new \RuntimeException('arboricx_init failed'); 61 } 62 return $ctx; 63 } 64 65 function ctx_free(\FFI\CData $ctx): void 66 { 67 ArboricxFFI::ffi()->arboricx_free($ctx); 68 } 69 70 function app(\FFI\CData $ctx, int $func, int $arg): int 71 { 72 return ArboricxFFI::ffi()->arb_app($ctx, $func, $arg); 73 } 74 75 function reduce(\FFI\CData $ctx, int $root, int $fuel = 1_000_000_000): int 76 { 77 return ArboricxFFI::ffi()->arb_reduce($ctx, $root, $fuel); 78 } 79 80 function ofNumber(\FFI\CData $ctx, int $n): int 81 { 82 return ArboricxFFI::ffi()->arb_of_number($ctx, $n); 83 } 84 85 function ofString(\FFI\CData $ctx, string $s): int 86 { 87 return ArboricxFFI::ffi()->arb_of_string($ctx, $s); 88 } 89 90 function toNumber(\FFI\CData $ctx, int $root): int 91 { 92 $out = ArboricxFFI::ffi()->new('uint64_t'); 93 $ok = ArboricxFFI::ffi()->arb_to_number($ctx, $root, \FFI::addr($out)); 94 if (!$ok) { 95 throw new \RuntimeException('arb_to_number failed'); 96 } 97 return (int) $out->cdata; 98 } 99 100 function toString(\FFI\CData $ctx, int $root): string 101 { 102 $ptr = ArboricxFFI::ffi()->new('uint8_t*'); 103 $len = ArboricxFFI::ffi()->new('size_t'); 104 $ok = ArboricxFFI::ffi()->arb_to_string($ctx, $root, \FFI::addr($ptr), \FFI::addr($len)); 105 if (!$ok) { 106 throw new \RuntimeException('arb_to_string failed'); 107 } 108 $length = (int) $len->cdata; 109 $result = ''; 110 for ($i = 0; $i < $length; $i++) { 111 $result .= chr($ptr[$i]); 112 } 113 ArboricxFFI::ffi()->arboricx_free_buf($ctx, $ptr, $length); 114 return $result; 115 } 116 117 function toBool(\FFI\CData $ctx, int $root): bool 118 { 119 $out = ArboricxFFI::ffi()->new('int'); 120 $ok = ArboricxFFI::ffi()->arb_to_bool($ctx, $root, \FFI::addr($out)); 121 if (!$ok) { 122 throw new \RuntimeException('arb_to_bool failed'); 123 } 124 return (bool) $out->cdata; 125 } 126 127 function loadBundleDefault(\FFI\CData $ctx, string $bytes): int 128 { 129 $cdata = ArboricxFFI::ffi()->new('uint8_t[' . strlen($bytes) . ']'); 130 for ($i = 0; $i < strlen($bytes); $i++) { 131 $cdata[$i] = ord($bytes[$i]); 132 } 133 $result = ArboricxFFI::ffi()->arb_load_bundle_default($ctx, $cdata, strlen($bytes)); 134 if ($result === 0) { 135 throw new \RuntimeException('arb_load_bundle_default failed'); 136 } 137 return $result; 138 }