Initial PHP host implementation
This commit is contained in:
274
ext/php/run.php
Normal file
274
ext/php/run.php
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* run.php — Self-hosted Arboricx PHP host shell.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* php run.php run <bundle.arboricx> <arg> [arg ...]
|
||||||
|
* php run.php inspect <bundle.arboricx>
|
||||||
|
* php run.php repl
|
||||||
|
*
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
use function Arboricx\{app, apply_, reduce, ofString, ofNumber, ofBytes, ofList,
|
||||||
|
formatTree, decodeResult, unwrapResult,
|
||||||
|
unwrapHostValue, decodeHostPayload,
|
||||||
|
isLeaf, isStem, isFork,
|
||||||
|
HOST_STRING_TAG, HOST_NUMBER_TAG, HOST_BOOL_TAG,
|
||||||
|
HOST_LIST_TAG, HOST_BYTES_TAG, HOST_TREE_TAG,
|
||||||
|
getRunArboricxToString};
|
||||||
|
|
||||||
|
// ── Commands ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function debugTime(string $label): void
|
||||||
|
{
|
||||||
|
static $start = null;
|
||||||
|
static $last = null;
|
||||||
|
$now = microtime(true);
|
||||||
|
if ($start === null) {
|
||||||
|
$start = $now;
|
||||||
|
$last = $now;
|
||||||
|
}
|
||||||
|
fwrite(STDERR, sprintf("[%.3fs +%.3fs] %s\n", $now - $start, $now - $last, $label));
|
||||||
|
$last = $now;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmdRun(string $bundlePath, array $args): void
|
||||||
|
{
|
||||||
|
debugTime('start');
|
||||||
|
|
||||||
|
if (!file_exists($bundlePath)) {
|
||||||
|
fwrite(STDERR, "Error: bundle file not found: $bundlePath\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$bundleBytes = file_get_contents($bundlePath);
|
||||||
|
debugTime('read bundle bytes');
|
||||||
|
if ($bundleBytes === false) {
|
||||||
|
fwrite(STDERR, "Error: could not read bundle file: $bundlePath\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$kernel = getRunArboricxToString();
|
||||||
|
debugTime('loaded kernel');
|
||||||
|
if ($kernel === null) {
|
||||||
|
fwrite(STDERR, "Error: runArboricxToString kernel not configured (empty ternary string)\n");
|
||||||
|
fwrite(STDERR, "Set KERNEL_RUN_ARBORICX_TO_STRING constant in kernel.php\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bundleTree = ofBytes($bundleBytes);
|
||||||
|
debugTime('encoded bundle bytes');
|
||||||
|
|
||||||
|
$argTrees = [];
|
||||||
|
foreach ($args as $arg) {
|
||||||
|
$argTrees[] = encodeArg($arg);
|
||||||
|
}
|
||||||
|
$argsTree = ofList($argTrees);
|
||||||
|
debugTime('encoded args');
|
||||||
|
|
||||||
|
$expr = app(app($kernel, $bundleTree), $argsTree);
|
||||||
|
debugTime('built expression');
|
||||||
|
|
||||||
|
fwrite(STDERR, "Reducing kernel application...\n");
|
||||||
|
$result = reduce($expr, 1_000_000);
|
||||||
|
debugTime('reduced kernel application');
|
||||||
|
|
||||||
|
[$kind, $value, $rest] = unwrapResult($result);
|
||||||
|
debugTime('unwrapped 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);
|
||||||
|
debugTime('unwrapped host value');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$decoded = decodeHostPayload($tag, $payload);
|
||||||
|
debugTime('decoded 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a command-line argument into a Tree Calculus value.
|
||||||
|
*
|
||||||
|
* - Numeric strings (digits only) → tree number
|
||||||
|
* - Everything else → tree string
|
||||||
|
*/
|
||||||
|
function encodeArg(string $arg): int
|
||||||
|
{
|
||||||
|
if (ctype_digit($arg) || ($arg !== '0' && preg_match('/^-?\d+$/', $arg))) {
|
||||||
|
return ofNumber((int)$arg);
|
||||||
|
}
|
||||||
|
return ofString($arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmdInspect(string $bundlePath): void
|
||||||
|
{
|
||||||
|
// Minimal inspection: just read the bundle as bytes and print its size.
|
||||||
|
// Full parsing is done by the kernel, not the host shell.
|
||||||
|
if (!file_exists($bundlePath)) {
|
||||||
|
fwrite(STDERR, "Error: bundle file not found: $bundlePath\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$bundleBytes = file_get_contents($bundlePath);
|
||||||
|
if ($bundleBytes === false) {
|
||||||
|
fwrite(STDERR, "Error: could not read bundle file: $bundlePath\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "\nResult (err):\n";
|
||||||
|
[$ok, $code] = Arboricx\toNumber($value);
|
||||||
|
echo " Code: " . ($ok ? (string)$code : formatTree($value)) . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmdRepl(): void
|
||||||
|
{
|
||||||
|
echo "Arboricx PHP REPL\n";
|
||||||
|
echo "Commands:\n";
|
||||||
|
echo " run <bundle> [args...] — Run a bundle\n";
|
||||||
|
echo " inspect <bundle> — Inspect a bundle\n";
|
||||||
|
echo " exit — Exit\n";
|
||||||
|
echo "\n";
|
||||||
|
|
||||||
|
$kernel = getRunArboricxToString();
|
||||||
|
if ($kernel === null) {
|
||||||
|
fwrite(STDERR, "Warning: kernel not configured\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
$prompt = "arboricx> ";
|
||||||
|
fwrite(STDOUT, $prompt);
|
||||||
|
$line = trim(fgets(STDIN));
|
||||||
|
if ($line === '' || $line === 'exit' || $line === 'quit') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = preg_split('/\s+/', $line, 2);
|
||||||
|
if (!$parts) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = $parts[0];
|
||||||
|
switch ($cmd) {
|
||||||
|
case 'run':
|
||||||
|
if (!isset($parts[1])) {
|
||||||
|
fwrite(STDERR, "Usage: run <bundle> [args...]\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$cmdRun($parts[1], array_slice($parts, 2));
|
||||||
|
break;
|
||||||
|
case 'inspect':
|
||||||
|
if (!isset($parts[1])) {
|
||||||
|
fwrite(STDERR, "Usage: inspect <bundle>\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cmdInspect($parts[1]);
|
||||||
|
break;
|
||||||
|
case 'help':
|
||||||
|
echo "Commands:\n";
|
||||||
|
echo " run <bundle> [args...] — Run a bundle\n";
|
||||||
|
echo " inspect <bundle> — Inspect a bundle\n";
|
||||||
|
echo " exit — Exit\n";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fwrite(STDERR, "Unknown command: $cmd\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
$argv = $_SERVER['argv'] ?? [];
|
||||||
|
$argc = $_SERVER['argc'] ?? 0;
|
||||||
|
|
||||||
|
if ($argc < 2) {
|
||||||
|
echo "Arboricx PHP Host Shell\n";
|
||||||
|
echo "\nUsage:\n";
|
||||||
|
echo " php run.php run <bundle.arboricx> [args...]\n";
|
||||||
|
echo " php run.php inspect <bundle.arboricx>\n";
|
||||||
|
echo " php run.php repl\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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));
|
||||||
|
break;
|
||||||
|
case 'inspect':
|
||||||
|
if ($argc < 3) {
|
||||||
|
fwrite(STDERR, "Usage: php run.php inspect <bundle.arboricx>\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
cmdInspect($argv[2]);
|
||||||
|
break;
|
||||||
|
case 'repl':
|
||||||
|
cmdRepl();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
echo "Unknown command: $command\n";
|
||||||
|
echo "Usage: php run.php run|inspect|repl ...\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
203
ext/php/src/codecs.php
Normal file
203
ext/php/src/codecs.php
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Arboricx;
|
||||||
|
|
||||||
|
use function Arboricx\{leaf, stem, fork, isLeaf, isStem, isFork, reduce, stemChild, forkLeft, forkRight, same_tree, formatTree};
|
||||||
|
|
||||||
|
function ofNumber(int $n): int
|
||||||
|
{
|
||||||
|
if ($n < 0) throw new \InvalidArgumentException('ofNumber: negative values not supported');
|
||||||
|
if ($n === 0) return leaf();
|
||||||
|
$bitTree = ($n % 2) === 1 ? stem(leaf()) : leaf();
|
||||||
|
return fork($bitTree, ofNumber(intdiv($n, 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNumber(int $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): int
|
||||||
|
{
|
||||||
|
$result = leaf();
|
||||||
|
for ($i = count($elements) - 1; $i >= 0; $i--) {
|
||||||
|
$result = fork($elements[$i], $result);
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toList(int $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];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ofString(string $s): int
|
||||||
|
{
|
||||||
|
$bytes = [];
|
||||||
|
for ($i = 0, $len = strlen($s); $i < $len; $i++) {
|
||||||
|
$bytes[] = ofNumber(ord($s[$i]));
|
||||||
|
}
|
||||||
|
return ofList($bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toString(int $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): int
|
||||||
|
{
|
||||||
|
return ofString($s);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBytes(int $tree): array
|
||||||
|
{
|
||||||
|
return toString($tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unwrapResult(int $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];
|
||||||
|
}
|
||||||
|
|
||||||
|
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(int $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(int $tree): bool
|
||||||
|
{
|
||||||
|
$tree = reduce($tree);
|
||||||
|
return isLeaf($tree) || (isStem($tree) && isLeaf(stemChild($tree)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNumber(int $tree): bool
|
||||||
|
{
|
||||||
|
[$ok, $_] = toNumber($tree);
|
||||||
|
return $ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isList(int $tree): bool
|
||||||
|
{
|
||||||
|
[$ok, $_] = toList($tree);
|
||||||
|
return $ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isString(int $tree): bool
|
||||||
|
{
|
||||||
|
[$ok, $elements] = toList($tree);
|
||||||
|
if (!$ok) return false;
|
||||||
|
foreach ($elements as $elem) if (!isNumber($elem)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeHostPayload(int $tag, int $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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeResult(int $tree): string
|
||||||
|
{
|
||||||
|
$tree = reduce($tree);
|
||||||
|
if (isLeaf($tree)) return 't';
|
||||||
|
|
||||||
|
[$ok, $str] = toString($tree);
|
||||||
|
if ($ok && $str !== '') {
|
||||||
|
$valid = true;
|
||||||
|
for ($i = 0; $i < strlen($str); $i++) {
|
||||||
|
$n = ord($str[$i]);
|
||||||
|
if ($n < 32 || $n > 126) { $valid = false; break; }
|
||||||
|
}
|
||||||
|
if ($valid) return '"' . $str . '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
[$ok, $num] = toNumber($tree);
|
||||||
|
if ($ok) return (string)$num;
|
||||||
|
|
||||||
|
[$ok, $xs] = toList($tree);
|
||||||
|
if ($ok) return '[' . implode(', ', array_map(fn($x) => decodeResult($x), $xs)) . ']';
|
||||||
|
|
||||||
|
return formatTree($tree);
|
||||||
|
}
|
||||||
259
ext/php/src/functions.php
Normal file
259
ext/php/src/functions.php
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Arboricx;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arena-backed Tree Calculus representation.
|
||||||
|
*
|
||||||
|
* Node refs are ints:
|
||||||
|
* 0 = Leaf
|
||||||
|
* tag 1 = Stem(child)
|
||||||
|
* tag 2 = Fork(left, right)
|
||||||
|
* tag 3 = App(function, argument) -- evaluator-internal thunk
|
||||||
|
* tag 4 = Ind(target) -- update-in-place indirection
|
||||||
|
*/
|
||||||
|
|
||||||
|
$GLOBALS['ARB_TAG'] = [0 => 0];
|
||||||
|
$GLOBALS['ARB_A'] = [0 => 0];
|
||||||
|
$GLOBALS['ARB_B'] = [0 => 0];
|
||||||
|
$GLOBALS['ARB_NEXT'] = 1;
|
||||||
|
$GLOBALS['ARB_CONS_CACHE'] = [];
|
||||||
|
|
||||||
|
function newNode(int $tag, int $a, int $b = 0): int
|
||||||
|
{
|
||||||
|
$id = $GLOBALS['ARB_NEXT']++;
|
||||||
|
$GLOBALS['ARB_TAG'][$id] = $tag;
|
||||||
|
$GLOBALS['ARB_A'][$id] = $a;
|
||||||
|
$GLOBALS['ARB_B'][$id] = $b;
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateInd(int $node, int $target): void
|
||||||
|
{
|
||||||
|
if ($node === 0 || $node === $target) return;
|
||||||
|
$GLOBALS['ARB_TAG'][$node] = 4;
|
||||||
|
$GLOBALS['ARB_A'][$node] = $target;
|
||||||
|
$GLOBALS['ARB_B'][$node] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deref(int $t): int
|
||||||
|
{
|
||||||
|
while ($t !== 0 && ($GLOBALS['ARB_TAG'][$t] ?? -1) === 4) {
|
||||||
|
$next = $GLOBALS['ARB_A'][$t];
|
||||||
|
if ($next === $t) break;
|
||||||
|
$t = $next;
|
||||||
|
}
|
||||||
|
return $t;
|
||||||
|
}
|
||||||
|
|
||||||
|
function leaf(): int { return 0; }
|
||||||
|
function stem(int $child): int { return newNode(1, $child); }
|
||||||
|
function fork(int $left, int $right): int { return newNode(2, $left, $right); }
|
||||||
|
function appNode(int $f, int $x): int { return newNode(3, $f, $x); }
|
||||||
|
function tree(): int { return 0; }
|
||||||
|
|
||||||
|
function tag(int $t): int { $t = deref($t); return $GLOBALS['ARB_TAG'][$t] ?? -1; }
|
||||||
|
function argA(int $t): int { $t = deref($t); return $GLOBALS['ARB_A'][$t]; }
|
||||||
|
function argB(int $t): int { $t = deref($t); return $GLOBALS['ARB_B'][$t]; }
|
||||||
|
|
||||||
|
function isLeaf(int $t): bool { return deref($t) === 0; }
|
||||||
|
function isStem(int $t): bool { return tag($t) === 1; }
|
||||||
|
function isFork(int $t): bool { return tag($t) === 2; }
|
||||||
|
function isApp(int $t): bool { return tag($t) === 3; }
|
||||||
|
function isTree(int $t): bool { $tag = tag($t); return $tag === 0 || $tag === 1 || $tag === 2; }
|
||||||
|
function isTerm(int $t): bool { $tag = tag($t); return $tag === 0 || $tag === 1 || $tag === 2 || $tag === 3; }
|
||||||
|
|
||||||
|
function stemChild(int $t): int { return argA($t); }
|
||||||
|
function forkLeft(int $t): int { return argA($t); }
|
||||||
|
function forkRight(int $t): int { return argB($t); }
|
||||||
|
function appFunc(int $t): int { return argA($t); }
|
||||||
|
function appArg(int $t): int { return argB($t); }
|
||||||
|
|
||||||
|
function app(int $f, int $x): int { return appNode($f, $x); }
|
||||||
|
|
||||||
|
function newStemFast(int $child, array &$TAG, array &$A, array &$B): int
|
||||||
|
{
|
||||||
|
$id = $GLOBALS['ARB_NEXT']++;
|
||||||
|
$TAG[$id] = 1;
|
||||||
|
$A[$id] = $child;
|
||||||
|
$B[$id] = 0;
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newForkFast(int $left, int $right, array &$TAG, array &$A, array &$B): int
|
||||||
|
{
|
||||||
|
$id = $GLOBALS['ARB_NEXT']++;
|
||||||
|
$TAG[$id] = 2;
|
||||||
|
$A[$id] = $left;
|
||||||
|
$B[$id] = $right;
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newAppFast(int $f, int $x, array &$TAG, array &$A, array &$B): int
|
||||||
|
{
|
||||||
|
$id = $GLOBALS['ARB_NEXT']++;
|
||||||
|
$TAG[$id] = 3;
|
||||||
|
$A[$id] = $f;
|
||||||
|
$B[$id] = $x;
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function apply_(int $a, int $b, int $fuel = 1_000_000): int
|
||||||
|
{
|
||||||
|
return reduce(app($a, $b), $fuel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reduce(int $term, int $fuel = 1_000_000): int
|
||||||
|
{
|
||||||
|
return whnf($term, $fuel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function whnf(int $term, int &$fuel): int
|
||||||
|
{
|
||||||
|
$TAG =& $GLOBALS['ARB_TAG'];
|
||||||
|
$A =& $GLOBALS['ARB_A'];
|
||||||
|
$B =& $GLOBALS['ARB_B'];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// deref term
|
||||||
|
while ($term !== 0 && $TAG[$term] === 4) {
|
||||||
|
$term = $A[$term];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($TAG[$term] !== 3) {
|
||||||
|
return $term;
|
||||||
|
}
|
||||||
|
|
||||||
|
$orig = $term;
|
||||||
|
$f = whnf($A[$term], $fuel);
|
||||||
|
$x = $B[$term];
|
||||||
|
|
||||||
|
// deref function result
|
||||||
|
while ($f !== 0 && $TAG[$f] === 4) {
|
||||||
|
$f = $A[$f];
|
||||||
|
}
|
||||||
|
$ftag = $TAG[$f];
|
||||||
|
|
||||||
|
// apply Leaf b = Stem b
|
||||||
|
if ($f === 0) {
|
||||||
|
$result = newStemFast($x, $TAG, $A, $B);
|
||||||
|
if ($orig !== $result) { $TAG[$orig] = 4; $A[$orig] = $result; $B[$orig] = 0; }
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply (Stem a) b = Fork a b
|
||||||
|
if ($ftag === 1) {
|
||||||
|
$result = newForkFast($A[$f], $x, $TAG, $A, $B);
|
||||||
|
if ($orig !== $result) { $TAG[$orig] = 4; $A[$orig] = $result; $B[$orig] = 0; }
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ftag !== 2) {
|
||||||
|
throw new \RuntimeException('apply: function did not reduce to tree');
|
||||||
|
}
|
||||||
|
|
||||||
|
$left = whnf($A[$f], $fuel);
|
||||||
|
$right = $B[$f];
|
||||||
|
|
||||||
|
// deref left
|
||||||
|
while ($left !== 0 && $TAG[$left] === 4) {
|
||||||
|
$left = $A[$left];
|
||||||
|
}
|
||||||
|
$ltag = $TAG[$left];
|
||||||
|
|
||||||
|
// apply (Fork Leaf a) _ = a
|
||||||
|
if ($left === 0) {
|
||||||
|
$term = $right;
|
||||||
|
if ($orig !== $term) { $TAG[$orig] = 4; $A[$orig] = $term; $B[$orig] = 0; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply (Fork (Stem a) b) c = (a c) (b c)
|
||||||
|
if ($ltag === 1) {
|
||||||
|
$term = newAppFast(
|
||||||
|
newAppFast($A[$left], $x, $TAG, $A, $B),
|
||||||
|
newAppFast($right, $x, $TAG, $A, $B),
|
||||||
|
$TAG, $A, $B
|
||||||
|
);
|
||||||
|
if ($orig !== $term) { $TAG[$orig] = 4; $A[$orig] = $term; $B[$orig] = 0; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ltag !== 2) {
|
||||||
|
throw new \RuntimeException('apply: invalid Fork left child');
|
||||||
|
}
|
||||||
|
|
||||||
|
$arg = whnf($x, $fuel);
|
||||||
|
while ($arg !== 0 && $TAG[$arg] === 4) {
|
||||||
|
$arg = $A[$arg];
|
||||||
|
}
|
||||||
|
$atag = $TAG[$arg];
|
||||||
|
|
||||||
|
// apply (Fork (Fork a b) c) Leaf = a
|
||||||
|
if ($arg === 0) {
|
||||||
|
$term = $A[$left];
|
||||||
|
if ($orig !== $term) { $TAG[$orig] = 4; $A[$orig] = $term; $B[$orig] = 0; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply (Fork (Fork a b) c) (Stem u) = b u
|
||||||
|
if ($atag === 1) {
|
||||||
|
$term = newAppFast($B[$left], $A[$arg], $TAG, $A, $B);
|
||||||
|
if ($orig !== $term) { $TAG[$orig] = 4; $A[$orig] = $term; $B[$orig] = 0; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply (Fork (Fork a b) c) (Fork u v) = (c u) v
|
||||||
|
if ($atag === 2) {
|
||||||
|
$term = newAppFast(
|
||||||
|
newAppFast($right, $A[$arg], $TAG, $A, $B),
|
||||||
|
$B[$arg],
|
||||||
|
$TAG, $A, $B
|
||||||
|
);
|
||||||
|
if ($orig !== $term) { $TAG[$orig] = 4; $A[$orig] = $term; $B[$orig] = 0; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException('apply: argument did not reduce to tree');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize(int $term, int &$fuel): int
|
||||||
|
{
|
||||||
|
$term = whnf($term, $fuel);
|
||||||
|
if (isStem($term)) return stem(normalize(stemChild($term), $fuel));
|
||||||
|
if (isFork($term)) return fork(normalize(forkLeft($term), $fuel), normalize(forkRight($term), $fuel));
|
||||||
|
return $term;
|
||||||
|
}
|
||||||
|
|
||||||
|
function same_tree(int $a, int $b): bool
|
||||||
|
{
|
||||||
|
$a = deref($a); $b = deref($b);
|
||||||
|
if ($a === $b) return true;
|
||||||
|
if (tag($a) !== tag($b)) return false;
|
||||||
|
if (isLeaf($a)) return true;
|
||||||
|
if (isStem($a)) return same_tree(stemChild($a), stemChild($b));
|
||||||
|
if (isFork($a)) return same_tree(forkLeft($a), forkLeft($b)) && same_tree(forkRight($a), forkRight($b));
|
||||||
|
if (isApp($a)) return same_tree(appFunc($a), appFunc($b)) && same_tree(appArg($a), appArg($b));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function typeTag(int $t): string
|
||||||
|
{
|
||||||
|
return match (tag($t)) {
|
||||||
|
0 => 'leaf', 1 => 'stem', 2 => 'fork', 3 => 'app', 4 => 'ind', default => 'unknown'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTree(int $t, int $depth = 0): string
|
||||||
|
{
|
||||||
|
$t = deref($t);
|
||||||
|
if ($depth > 200) return '...';
|
||||||
|
if (isLeaf($t)) return 'Leaf';
|
||||||
|
if (isStem($t)) return 'Stem(' . formatTree(stemChild($t), $depth + 1) . ')';
|
||||||
|
if (isFork($t)) return 'Fork(' . formatTree(forkLeft($t), $depth + 1) . ', ' . formatTree(forkRight($t), $depth + 1) . ')';
|
||||||
|
if (isApp($t)) return 'App(' . formatTree(appFunc($t), $depth + 1) . ', ' . formatTree(appArg($t), $depth + 1) . ')';
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
164
ext/php/src/kernel.php
Normal file
164
ext/php/src/kernel.php
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Arboricx;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kernel.php — Hardcoded tricu kernel entrypoints.
|
||||||
|
*
|
||||||
|
* The kernel is the self-hosted Arboricx runtime written in Tree Calculus,
|
||||||
|
* compiled to a raw Tree Calculus term. We hardcode it as a **ternary string**
|
||||||
|
* and parse it on demand.
|
||||||
|
*
|
||||||
|
* Ternary string format (from Research.hs toTernaryString):
|
||||||
|
* '0' → Leaf
|
||||||
|
* '1' ++ rest → Stem(parse(rest))
|
||||||
|
* '2' ++ rest → Fork(parse(rest), parse(rest))
|
||||||
|
*
|
||||||
|
* The Kernel class holds these constants and exposes them as Tree values.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use function Arboricx\{leaf, stem, fork, isLeaf, isStem, isFork};
|
||||||
|
|
||||||
|
// ── Kernel constants ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// ── File loading ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a kernel ternary string from a .ternary file.
|
||||||
|
*
|
||||||
|
* Convention: ext/php/src/kernel_<name>.ternary
|
||||||
|
*
|
||||||
|
* @param string $name e.g. 'run_arboricx_to_string'
|
||||||
|
* @return string The ternary string, or '' if file not found
|
||||||
|
*/
|
||||||
|
function loadKernelTernary(string $name): string
|
||||||
|
{
|
||||||
|
// Determine the base directory: go up from this file to ext/php/src/
|
||||||
|
$dir = dirname(__FILE__);
|
||||||
|
$file = $dir . '/' . $name . '.ternary';
|
||||||
|
if (file_exists($file)) {
|
||||||
|
$content = file_get_contents($file);
|
||||||
|
if ($content !== false) {
|
||||||
|
return trim($content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Kernel constants (fallbacks — actual values loaded from .ternary files) ─
|
||||||
|
|
||||||
|
/**
|
||||||
|
* runArboricxToString — the primary Host ABI entrypoint.
|
||||||
|
*
|
||||||
|
* This is the compiled Tree Calculus term for:
|
||||||
|
*
|
||||||
|
* runArboricxToString = (bs args :
|
||||||
|
* bindResult (runArboricxArgsByName [] bs args)
|
||||||
|
* (value rest :
|
||||||
|
* wrapHostValue hostString? hostString value rest))
|
||||||
|
*
|
||||||
|
* Loaded from ext/php/src/kernel_run_arboricx_to_string.ternary
|
||||||
|
* if it exists, otherwise falls back to the constant below.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const KERNEL_RUN_ARBORICX_TO_STRING = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* runArboricxByNameToString — named-export variant.
|
||||||
|
*
|
||||||
|
* runArboricxByNameToString = (nameBytes bs args : ...)
|
||||||
|
*
|
||||||
|
* Loaded from ext/php/src/kernel_run_arboricx_by_name_to_string.ternary
|
||||||
|
* if it exists, otherwise falls back to the constant below.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const KERNEL_RUN_ARBORICX_BY_NAME_TO_STRING = '';
|
||||||
|
|
||||||
|
// ── Parser ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a ternary string into a Tree Calculus value.
|
||||||
|
*
|
||||||
|
* Grammar:
|
||||||
|
* term → '0' → Leaf
|
||||||
|
* → '1' term → Stem(term)
|
||||||
|
* → '2' term term → Fork(term, term)
|
||||||
|
*
|
||||||
|
* @param string $s The ternary string to parse
|
||||||
|
* @param int &$pos Current position (passed by reference)
|
||||||
|
* @return int The parsed Tree value node id
|
||||||
|
*/
|
||||||
|
function parseTernary(string $s, int &$pos = 0): int
|
||||||
|
{
|
||||||
|
if ($pos >= strlen($s)) {
|
||||||
|
throw new \RuntimeException('parseTernary: unexpected end of string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$char = $s[$pos];
|
||||||
|
$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"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Kernel accessors ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the runArboricxToString kernel tree.
|
||||||
|
*
|
||||||
|
* Tries loading from ext/php/src/kernel_run_arboricx_to_string.ternary first,
|
||||||
|
* then falls back to the KERNEL_RUN_ARBORICX_TO_STRING constant.
|
||||||
|
*
|
||||||
|
* Returns the parsed Tree Calculus value, or null if not configured.
|
||||||
|
*/
|
||||||
|
function getRunArboricxToString(): ?int
|
||||||
|
{
|
||||||
|
// Try file first
|
||||||
|
$term = loadKernelTernary('kernel_run_arboricx_to_string');
|
||||||
|
if ($term === '') {
|
||||||
|
$term = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the runArboricxByNameToString kernel tree.
|
||||||
|
*
|
||||||
|
* Tries loading from ext/php/src/kernel_run_arboricx_by_name_to_string.ternary
|
||||||
|
* first, then falls back to the KERNEL_RUN_ARBORICX_BY_NAME_TO_STRING constant.
|
||||||
|
*/
|
||||||
|
function getRunArboricxByNameToString(): ?int
|
||||||
|
{
|
||||||
|
// Try file first
|
||||||
|
$term = loadKernelTernary('kernel_run_arboricx_by_name_to_string');
|
||||||
|
if ($term === '') {
|
||||||
|
$term = KERNEL_RUN_ARBORICX_BY_NAME_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;
|
||||||
|
}
|
||||||
1
ext/php/src/kernel_run_arboricx_to_string.ternary
Normal file
1
ext/php/src/kernel_run_arboricx_to_string.ternary
Normal file
File diff suppressed because one or more lines are too long
9
notes/php-cli-run-flags.md
Normal file
9
notes/php-cli-run-flags.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# PHP Recommended Run Flags
|
||||||
|
|
||||||
|
```php
|
||||||
|
php -d memory_limit=4G \
|
||||||
|
-d opcache.enable_cli=1 \
|
||||||
|
-d opcache.jit_buffer_size=256M \
|
||||||
|
-d opcache.jit=tracing \
|
||||||
|
ext/php/run.php run $PATH_TO_ARBORIX_BUNDLE $ARGS
|
||||||
|
```
|
||||||
14
src/Main.hs
14
src/Main.hs
@@ -26,7 +26,7 @@ import qualified Data.Map as Map
|
|||||||
|
|
||||||
data TricuArgs
|
data TricuArgs
|
||||||
= Repl
|
= Repl
|
||||||
| Evaluate { file :: [FilePath], form :: EvaluatedForm }
|
| Evaluate { file :: [FilePath], form :: EvaluatedForm, outFile :: FilePath }
|
||||||
| TDecode { file :: [FilePath] }
|
| TDecode { file :: [FilePath] }
|
||||||
| Compile { inputFile :: FilePath, outFile :: FilePath, names :: [String] }
|
| Compile { inputFile :: FilePath, outFile :: FilePath, names :: [String] }
|
||||||
| Export { hash :: String, exportNameOpt :: String, outFile :: FilePath, names :: [String] }
|
| Export { hash :: String, exportNameOpt :: String, outFile :: FilePath, names :: [String] }
|
||||||
@@ -49,6 +49,8 @@ evaluateMode = Evaluate
|
|||||||
&= help "Optional output form: (tree|fsl|ast|ternary|ascii|decode).\n \
|
&= help "Optional output form: (tree|fsl|ast|ternary|ascii|decode).\n \
|
||||||
\ Defaults to tricu-compatible `t` tree form."
|
\ Defaults to tricu-compatible `t` tree form."
|
||||||
&= name "t"
|
&= name "t"
|
||||||
|
, outFile = def &= help "Optional output file path. Defaults to stdout."
|
||||||
|
&= name "o" &= typ "FILE"
|
||||||
}
|
}
|
||||||
&= help "Evaluate tricu and return the result of the final expression."
|
&= help "Evaluate tricu and return the result of the final expression."
|
||||||
&= explicit
|
&= explicit
|
||||||
@@ -123,7 +125,7 @@ main = do
|
|||||||
putStrLn "Welcome to the tricu REPL"
|
putStrLn "Welcome to the tricu REPL"
|
||||||
putStrLn "You may exit with `CTRL+D` or the `!exit` command."
|
putStrLn "You may exit with `CTRL+D` or the `!exit` command."
|
||||||
repl
|
repl
|
||||||
Evaluate { file = filePaths, form = outputForm } -> do
|
Evaluate { file = filePaths, form = outputForm, outFile = evalOutFile } -> do
|
||||||
maybeDbPath <- lookupEnv "TRICU_DB_PATH"
|
maybeDbPath <- lookupEnv "TRICU_DB_PATH"
|
||||||
evalResult <- case filePaths of
|
evalResult <- case filePaths of
|
||||||
[] -> do
|
[] -> do
|
||||||
@@ -136,7 +138,7 @@ main = do
|
|||||||
Nothing -> return Map.empty
|
Nothing -> return Map.empty
|
||||||
input <- getContents
|
input <- getContents
|
||||||
pure $ runTricuTEnv initialEnv input
|
pure $ runTricuTEnv initialEnv input
|
||||||
(_:restFilePaths) -> do
|
filePaths@(_:_) -> do
|
||||||
initialEnv <- case maybeDbPath of
|
initialEnv <- case maybeDbPath of
|
||||||
Just _ -> do
|
Just _ -> do
|
||||||
conn <- initContentStore
|
conn <- initContentStore
|
||||||
@@ -144,10 +146,12 @@ main = do
|
|||||||
close conn
|
close conn
|
||||||
return env
|
return env
|
||||||
Nothing -> return Map.empty
|
Nothing -> return Map.empty
|
||||||
finalEnv <- foldM evaluateFileWithContext initialEnv restFilePaths
|
finalEnv <- foldM evaluateFileWithContext initialEnv filePaths
|
||||||
pure $ mainResult finalEnv
|
pure $ mainResult finalEnv
|
||||||
let fRes = formatT outputForm evalResult
|
let fRes = formatT outputForm evalResult
|
||||||
putStr fRes
|
if null evalOutFile
|
||||||
|
then putStr fRes
|
||||||
|
else writeFile evalOutFile fRes
|
||||||
TDecode { file = filePaths } -> do
|
TDecode { file = filePaths } -> do
|
||||||
value <- case filePaths of
|
value <- case filePaths of
|
||||||
[] -> getContents
|
[] -> getContents
|
||||||
|
|||||||
Reference in New Issue
Block a user