feat(php): use new FFI for Arboricx
This commit is contained in:
@@ -1,179 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Arboricx;
|
||||
|
||||
use function Arboricx\{leaf, stem, fork, isLeaf, isStem, isFork, reduce, stemChild, forkLeft, forkRight, same_tree, formatTree};
|
||||
|
||||
$GLOBALS['ARB_NUM_CACHE'] = [];
|
||||
|
||||
function ofNumber(int $n): Node
|
||||
{
|
||||
if ($n < 0) throw new \InvalidArgumentException('ofNumber: negative values not supported');
|
||||
if ($n === 0) return leaf();
|
||||
if ($n < 256 && isset($GLOBALS['ARB_NUM_CACHE'][$n])) {
|
||||
return $GLOBALS['ARB_NUM_CACHE'][$n];
|
||||
}
|
||||
$bitTree = ($n % 2) === 1 ? stem(leaf()) : leaf();
|
||||
$result = fork($bitTree, ofNumber(intdiv($n, 2)));
|
||||
if ($n < 256) {
|
||||
$GLOBALS['ARB_NUM_CACHE'][$n] = $result;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function toNumber(Node $tree): array
|
||||
{
|
||||
$tree = reduce($tree);
|
||||
if (isLeaf($tree)) return [true, 0];
|
||||
if (!isFork($tree)) return [false, 'Invalid Tree Calculus number'];
|
||||
|
||||
$bitTree = reduce(forkLeft($tree));
|
||||
if (isLeaf($bitTree)) {
|
||||
$bit = 0;
|
||||
} elseif (isStem($bitTree) && isLeaf(stemChild($bitTree))) {
|
||||
$bit = 1;
|
||||
} else {
|
||||
return [false, 'Invalid bit in Tree Calculus number'];
|
||||
}
|
||||
|
||||
[$ok, $rest] = toNumber(forkRight($tree));
|
||||
return $ok ? [true, $bit + 2 * $rest] : [false, $rest];
|
||||
}
|
||||
|
||||
function ofList(array $elements): Node
|
||||
{
|
||||
$result = leaf();
|
||||
for ($i = count($elements) - 1; $i >= 0; $i--) {
|
||||
$result = fork($elements[$i], $result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function toList(Node $tree): array
|
||||
{
|
||||
$tree = reduce($tree);
|
||||
if (isLeaf($tree)) return [true, []];
|
||||
if (!isFork($tree)) return [false, 'Invalid Tree Calculus list'];
|
||||
|
||||
[$ok, $rest] = toList(forkRight($tree));
|
||||
if (!$ok) return [false, $rest];
|
||||
array_unshift($rest, forkLeft($tree));
|
||||
return [true, $rest];
|
||||
}
|
||||
|
||||
/**
|
||||
* Strings are lists of byte values (each character encoded as a number tree).
|
||||
*/
|
||||
function ofString(string $s): Node
|
||||
{
|
||||
$bytes = [];
|
||||
for ($i = 0, $len = strlen($s); $i < $len; $i++) {
|
||||
$bytes[] = ofNumber(ord($s[$i]));
|
||||
}
|
||||
return ofList($bytes);
|
||||
}
|
||||
|
||||
function toString(Node $tree): array
|
||||
{
|
||||
[$ok, $elements] = toList($tree);
|
||||
if (!$ok) return [false, $elements];
|
||||
|
||||
$result = '';
|
||||
foreach ($elements as $elem) {
|
||||
[$ok2, $num] = toNumber($elem);
|
||||
if (!$ok2 || $num < 0 || $num > 255) {
|
||||
return [false, 'Invalid character code in Tree Calculus string'];
|
||||
}
|
||||
$result .= chr($num);
|
||||
}
|
||||
return [true, $result];
|
||||
}
|
||||
|
||||
function ofBytes(string $s): Node
|
||||
{
|
||||
return ofString($s);
|
||||
}
|
||||
|
||||
function toBytes(Node $tree): array
|
||||
{
|
||||
return toString($tree);
|
||||
}
|
||||
|
||||
function unwrapResult(Node $tree): array
|
||||
{
|
||||
$tree = reduce($tree);
|
||||
if (!isFork($tree)) return ['error', null, 'Result is not a valid ok/err pair'];
|
||||
|
||||
$tag = reduce(forkLeft($tree));
|
||||
$restPair = reduce(forkRight($tree));
|
||||
if (!isFork($restPair)) return ['error', null, 'Result payload is not a valid pair'];
|
||||
|
||||
$value = forkLeft($restPair);
|
||||
$rest = forkRight($restPair);
|
||||
$isTrue = same_tree($tag, stem(leaf()));
|
||||
return $isTrue ? ['ok', $value, $rest] : ['err', $value, $rest];
|
||||
}
|
||||
|
||||
/**
|
||||
* Host ABI tag constants.
|
||||
*
|
||||
* The kernel wraps successful results in a host value envelope:
|
||||
* Fork(tag_number, payload_tree)
|
||||
*/
|
||||
const HOST_TREE_TAG = 0;
|
||||
const HOST_STRING_TAG = 1;
|
||||
const HOST_NUMBER_TAG = 2;
|
||||
const HOST_BOOL_TAG = 3;
|
||||
const HOST_LIST_TAG = 4;
|
||||
const HOST_BYTES_TAG = 5;
|
||||
|
||||
function unwrapHostValue(Node $tree): array
|
||||
{
|
||||
$tree = reduce($tree);
|
||||
if (!isFork($tree)) throw new \InvalidArgumentException('Host ABI value must be a pair');
|
||||
|
||||
$tag = reduce(forkLeft($tree));
|
||||
$payload = forkRight($tree);
|
||||
[$ok, $tagNum] = toNumber($tag);
|
||||
if (!$ok) throw new \InvalidArgumentException('Host ABI tag must be a number');
|
||||
return [(int)$tagNum, $payload];
|
||||
}
|
||||
|
||||
function isBool(Node $tree): bool
|
||||
{
|
||||
$tree = reduce($tree);
|
||||
return isLeaf($tree) || (isStem($tree) && isLeaf(stemChild($tree)));
|
||||
}
|
||||
|
||||
function decodeHostPayload(int $tag, Node $payload): array
|
||||
{
|
||||
switch ($tag) {
|
||||
case HOST_TREE_TAG:
|
||||
return ['type' => 'tree', 'value' => formatTree($payload)];
|
||||
case HOST_STRING_TAG:
|
||||
[$ok, $str] = toString($payload);
|
||||
if (!$ok) throw new \InvalidArgumentException('Host ABI string decode failed: ' . $str);
|
||||
return ['type' => 'string', 'value' => $str];
|
||||
case HOST_NUMBER_TAG:
|
||||
[$ok, $num] = toNumber($payload);
|
||||
if (!$ok) throw new \InvalidArgumentException('Host ABI number decode failed: ' . $num);
|
||||
return ['type' => 'number', 'value' => $num];
|
||||
case HOST_BOOL_TAG:
|
||||
if (!isBool($payload)) throw new \InvalidArgumentException('Host ABI bool decode failed');
|
||||
return ['type' => 'bool', 'value' => isLeaf(reduce($payload)) ? false : true];
|
||||
case HOST_LIST_TAG:
|
||||
[$ok, $xs] = toList($payload);
|
||||
if (!$ok) throw new \InvalidArgumentException('Host ABI list decode failed: ' . $xs);
|
||||
return ['type' => 'list', 'value' => $xs];
|
||||
case HOST_BYTES_TAG:
|
||||
[$ok, $bytes] = toBytes($payload);
|
||||
if (!$ok) throw new \InvalidArgumentException('Host ABI bytes decode failed: ' . $bytes);
|
||||
return ['type' => 'bytes', 'value' => $bytes];
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unknown Host ABI tag: ' . $tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
138
ext/php/src/ffi.php
Normal file
138
ext/php/src/ffi.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Arboricx;
|
||||
|
||||
/**
|
||||
* Node-based Tree Calculus graph.
|
||||
*
|
||||
* Nodes are plain PHP objects so the runtime's refcounting GC
|
||||
* can reclaim unreachable subtrees automatically.
|
||||
*
|
||||
* tag 0 = Leaf
|
||||
* tag 1 = Stem(child)
|
||||
* tag 2 = Fork(left, right)
|
||||
* tag 3 = App(function, argument) -- evaluator thunk
|
||||
*/
|
||||
final class Node
|
||||
{
|
||||
public int $tag;
|
||||
public ?Node $a;
|
||||
public ?Node $b;
|
||||
|
||||
public function __construct(int $tag = 0, ?Node $a = null, ?Node $b = null)
|
||||
{
|
||||
$this->tag = $tag;
|
||||
$this->a = $a;
|
||||
$this->b = $b;
|
||||
}
|
||||
}
|
||||
|
||||
$GLOBALS['ARB_LEAF'] = new Node();
|
||||
$GLOBALS['ARB_CONS'] = [];
|
||||
|
||||
function newNode(int $tag, ?Node $a = null, ?Node $b = null): Node
|
||||
{
|
||||
if ($tag !== 3) {
|
||||
$key = $tag === 1
|
||||
? '1:' . spl_object_id($a)
|
||||
: '2:' . spl_object_id($a) . ':' . spl_object_id($b);
|
||||
if (isset($GLOBALS['ARB_CONS'][$key])) {
|
||||
return $GLOBALS['ARB_CONS'][$key];
|
||||
}
|
||||
$n = new Node($tag, $a, $b);
|
||||
$GLOBALS['ARB_CONS'][$key] = $n;
|
||||
return $n;
|
||||
}
|
||||
|
||||
return new Node($tag, $a, $b);
|
||||
}
|
||||
|
||||
function leaf(): Node
|
||||
{
|
||||
return $GLOBALS['ARB_LEAF'];
|
||||
}
|
||||
|
||||
function stem(Node $c): Node
|
||||
{
|
||||
return newNode(1, $c);
|
||||
}
|
||||
|
||||
function fork(Node $l, Node $r): Node
|
||||
{
|
||||
return newNode(2, $l, $r);
|
||||
}
|
||||
|
||||
function app(Node $f, Node $x): Node
|
||||
{
|
||||
return newNode(3, $f, $x);
|
||||
}
|
||||
|
||||
function tag(Node $t): int
|
||||
{
|
||||
return $t->tag;
|
||||
}
|
||||
|
||||
function isLeaf(Node $t): bool
|
||||
{
|
||||
return $t->tag === 0;
|
||||
}
|
||||
|
||||
function isStem(Node $t): bool
|
||||
{
|
||||
return $t->tag === 1;
|
||||
}
|
||||
|
||||
function isFork(Node $t): bool
|
||||
{
|
||||
return $t->tag === 2;
|
||||
}
|
||||
|
||||
function isApp(Node $t): bool
|
||||
{
|
||||
return $t->tag === 3;
|
||||
}
|
||||
|
||||
function stemChild(Node $t): Node
|
||||
{
|
||||
return $t->a;
|
||||
}
|
||||
|
||||
function forkLeft(Node $t): Node
|
||||
{
|
||||
return $t->a;
|
||||
}
|
||||
|
||||
function forkRight(Node $t): Node
|
||||
{
|
||||
return $t->b;
|
||||
}
|
||||
|
||||
function appFunc(Node $t): Node
|
||||
{
|
||||
return $t->a;
|
||||
}
|
||||
|
||||
function appArg(Node $t): Node
|
||||
{
|
||||
return $t->b;
|
||||
}
|
||||
|
||||
function reduce(Node $term, int $fuel = 100_000_000_000_000_000): Node
|
||||
{
|
||||
return whnf($term, $fuel);
|
||||
}
|
||||
|
||||
function whnf(Node $term, int &$fuel): Node
|
||||
{
|
||||
while (true) {
|
||||
if ($term->tag !== 3) {
|
||||
return $term;
|
||||
}
|
||||
|
||||
$orig = $term;
|
||||
$f = whnf($term->a, $fuel);
|
||||
$x = $term->b;
|
||||
$ftag = $f->tag;
|
||||
|
||||
// apply Leaf b = Stem b
|
||||
if ($ftag === 0) {
|
||||
$orig->tag = 1;
|
||||
$orig->a = $x;
|
||||
$orig->b = null;
|
||||
return $orig;
|
||||
}
|
||||
|
||||
// apply (Stem a) b = Fork a b
|
||||
if ($ftag === 1) {
|
||||
$orig->tag = 2;
|
||||
$orig->a = $f->a;
|
||||
$orig->b = $x;
|
||||
return $orig;
|
||||
}
|
||||
|
||||
if ($ftag !== 2) {
|
||||
throw new \RuntimeException('apply: function did not reduce to tree');
|
||||
}
|
||||
|
||||
$left = whnf($f->a, $fuel);
|
||||
$right = $f->b;
|
||||
$ltag = $left->tag;
|
||||
|
||||
// apply (Fork Leaf a) _ = a
|
||||
if ($ltag === 0) {
|
||||
$result = whnf($right, $fuel);
|
||||
if ($orig !== $result) {
|
||||
$orig->tag = $result->tag;
|
||||
$orig->a = $result->a;
|
||||
$orig->b = $result->b;
|
||||
}
|
||||
if ($fuel <= 0) throw new \RuntimeException('fuel exhausted');
|
||||
$fuel--;
|
||||
return $orig;
|
||||
}
|
||||
|
||||
// apply (Fork (Stem a) b) c = (a c) (b c)
|
||||
if ($ltag === 1) {
|
||||
$inner1 = newNode(3, $left->a, $x);
|
||||
$inner2 = newNode(3, $right, $x);
|
||||
$orig->tag = 3;
|
||||
$orig->a = $inner1;
|
||||
$orig->b = $inner2;
|
||||
$term = $orig;
|
||||
if ($fuel <= 0) throw new \RuntimeException('fuel exhausted');
|
||||
$fuel--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($ltag !== 2) {
|
||||
throw new \RuntimeException('apply: invalid Fork left child');
|
||||
}
|
||||
|
||||
$arg = whnf($x, $fuel);
|
||||
$atag = $arg->tag;
|
||||
|
||||
// apply (Fork (Fork a b) c) Leaf = a
|
||||
if ($atag === 0) {
|
||||
$result = whnf($left->a, $fuel);
|
||||
if ($orig !== $result) {
|
||||
$orig->tag = $result->tag;
|
||||
$orig->a = $result->a;
|
||||
$orig->b = $result->b;
|
||||
}
|
||||
if ($fuel <= 0) throw new \RuntimeException('fuel exhausted');
|
||||
$fuel--;
|
||||
return $orig;
|
||||
}
|
||||
|
||||
// apply (Fork (Fork a b) c) (Stem u) = b u
|
||||
if ($atag === 1) {
|
||||
$orig->tag = 3;
|
||||
$orig->a = $left->b;
|
||||
$orig->b = $arg->a;
|
||||
$term = $orig;
|
||||
if ($fuel <= 0) throw new \RuntimeException('fuel exhausted');
|
||||
$fuel--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// apply (Fork (Fork a b) c) (Fork u v) = (c u) v
|
||||
if ($atag === 2) {
|
||||
$inner = newNode(3, $right, $arg->a);
|
||||
$orig->tag = 3;
|
||||
$orig->a = $inner;
|
||||
$orig->b = $arg->b;
|
||||
$term = $orig;
|
||||
if ($fuel <= 0) throw new \RuntimeException('fuel exhausted');
|
||||
$fuel--;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new \RuntimeException('apply: argument did not reduce to tree');
|
||||
}
|
||||
}
|
||||
|
||||
function same_tree(Node $a, Node $b): bool
|
||||
{
|
||||
if ($a === $b) return true;
|
||||
if ($a->tag !== $b->tag) return false;
|
||||
if ($a->tag === 0) return true;
|
||||
if ($a->tag === 1) return same_tree($a->a, $b->a);
|
||||
if ($a->tag === 2) return same_tree($a->a, $b->a) && same_tree($a->b, $b->b);
|
||||
if ($a->tag === 3) return same_tree($a->a, $b->a) && same_tree($a->b, $b->b);
|
||||
return false;
|
||||
}
|
||||
|
||||
function formatTree(Node $t, int $depth = 0): string
|
||||
{
|
||||
if ($depth > 200) return '...';
|
||||
if ($t->tag === 0) return 'Leaf';
|
||||
if ($t->tag === 1) return 'Stem(' . formatTree($t->a, $depth + 1) . ')';
|
||||
if ($t->tag === 2) return 'Fork(' . formatTree($t->a, $depth + 1) . ', ' . formatTree($t->b, $depth + 1) . ')';
|
||||
if ($t->tag === 3) return 'App(' . formatTree($t->a, $depth + 1) . ', ' . formatTree($t->b, $depth + 1) . ')';
|
||||
return 'Unknown';
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Arboricx;
|
||||
|
||||
/**
|
||||
* Kernel loader.
|
||||
*
|
||||
* The kernel is the self-hosted Arboricx runtime compiled to a raw Tree
|
||||
* Calculus term. It is shipped as a ternary string and parsed on demand.
|
||||
*
|
||||
* Ternary string format (matches Research.hs toTernaryString):
|
||||
* '0' → Leaf
|
||||
* '1' rest → Stem(parse(rest))
|
||||
* '2' l r → Fork(parse(l), parse(r))
|
||||
*/
|
||||
|
||||
use function Arboricx\{leaf, stem, fork};
|
||||
|
||||
function loadKernelTernary(string $name): string
|
||||
{
|
||||
$file = __DIR__ . '/' . $name . '.ternary';
|
||||
if (!file_exists($file)) return '';
|
||||
$content = file_get_contents($file);
|
||||
return $content !== false ? trim($content) : '';
|
||||
}
|
||||
|
||||
function parseTernary(string $s, int &$pos = 0): Node
|
||||
{
|
||||
if ($pos >= strlen($s)) {
|
||||
throw new \RuntimeException('parseTernary: unexpected end of string');
|
||||
}
|
||||
$char = $s[$pos++];
|
||||
return match ($char) {
|
||||
'0' => leaf(),
|
||||
'1' => stem(parseTernary($s, $pos)),
|
||||
'2' => fork(parseTernary($s, $pos), parseTernary($s, $pos)),
|
||||
default => throw new \RuntimeException("parseTernary: unexpected char '$char' at position $pos"),
|
||||
};
|
||||
}
|
||||
|
||||
function getRunArboricxToString(): ?Node
|
||||
{
|
||||
$term = loadKernelTernary('kernel_run_arboricx_to_string');
|
||||
if ($term === '') return null;
|
||||
$pos = 0;
|
||||
$tree = parseTernary($term, $pos);
|
||||
if ($pos !== strlen($term)) {
|
||||
throw new \RuntimeException("kernel ternary has trailing data: parsed $pos of " . strlen($term) . ' bytes');
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user