tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

commit 57400db3c69720ea842f5692e5463aec1667a18d
parent 6b44fc8e95bb37c3c689008b5d2c89f062c443f9
Author: James Eversole <james@eversole.co>
Date:   Sun, 18 Oct 2026 04:41:28 -0500

Classify ASCII digits and letters with fixed bit slices

Preserve canonical-natural Boolean results while avoiding repeated general range comparisons. The three classifier representations and out-of-contract malformed/partial-tree behavior deliberately change; other math exports remain identical. Independent classifier, digest, compiler/AST, HTTP and UTF-8 controls passed. Matched full builds improved 8.96% on Haskell (R C C R) and 8.84% in one Zig pair, with identical complete outputs. Add reproducible source/provenance and wrong-subject controls; correct the UTF-8 probe's obsolete flat-storage assumption without weakening authentication. Fresh self-reproduction/publication remains separate.

Diffstat:
Mdocs/validation.md | 22++++++++++++++++++++++
Mlib/math/README.md | 9++++++++-
Mlib/math/native.tri | 19++++++++++++++++---
Atest/AsciiClassifiers.hs | 46++++++++++++++++++++++++++++++++++++++++++++++
Atest/ascii_classifiers.py | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/native_source_utf8.py | 8+++++---
6 files changed, 170 insertions(+), 7 deletions(-)

diff --git a/docs/validation.md b/docs/validation.md @@ -1223,3 +1223,25 @@ compared completely, not by a prefix or hash alone. These finite controls are not compiler acceptance or a general demand-equivalence proof. Publication still requires the reproduction, cross-host, site-refresh and retention workflow in [compiler rebuilding](compiler-rebuilding.md.html). + +## ASCII classifiers (opt-in source check) + +`native_math`'s `digit`, `letter`, and `lowerHex` operate on canonical naturals, +not arbitrary trees. The focused check compiles current source using the +packaged stable compiler, audits its native receipt, and compares complete +Boolean trees against independent Integer predicates: + +```sh +nix develop -c python3 test/ascii_classifiers.py result/bin/tricu \ + --directory "$WORK/ascii-classifiers" +``` + +Use a fresh directory. Coverage includes every byte and BMP code point, Unicode +boundaries, naturals up to 1025 bits with ASCII low bits, deterministic larger +samples, and a wrong-subject rejection control. Malformed/partial-tree results +and demand are outside this numeric fast path's contract. The digest consumer's +canonical guard and arbitrary-tree fallback require the separate digest-byte +checks above. Changes also require compiler source/diagnostic, HTTP header and +UTF-8 boundary checks; classifier tests alone are not consumer or generation +acceptance. Performance measurements must use matched complete workloads, not +only individual predicates. diff --git a/lib/math/README.md b/lib/math/README.md @@ -64,7 +64,14 @@ Comparison walks binary tails, allowing the more significant differing bit to win. Equality stops at the first differing low bit. Addition uses carry bits; subtraction first checks ordering, then propagates borrow and removes high zero bits. Multiplication uses binary shift/add; decimal accumulation uses shifts and -addition. Classification composes binary comparisons. +addition. `digit`, `letter`, and `lowerHex` classify fixed bit slices instead of composing +full range comparisons. Digit recognition checks a high part of 3 and a low +nibble in 0..9. Letter recognition ignores the ASCII case bit and checks a low +five-bit value in 1..26; lowercase hex letters have a high part of 12 and low +three bits in 1..6. High-part equality rejects larger naturals, including Unicode +characters outside ASCII. `ascii` and `printable` retain binary comparisons. +These shortcuts rely on the canonical-natural contract; they need not preserve +malformed-tree results or demand of irrelevant bits. Comparison, addition, and subtraction walk binary digits rather than counting to the numeric value. Multiplication uses shift/add; division performs binary long diff --git a/lib/math/native.tri b/lib/math/native.tri @@ -143,7 +143,20 @@ power base exponent = (let rec decimal value digit = add (add (double (double (double value))) (double value)) digit ascii value = lte value 127 -digit value = inRange value 48 57 -letter value = either (inRange value 65 90) (inRange value 97 122) -lowerHex value = either (digit value) (inRange value 97 102) +-- 48..57: high part is 3; low nibble is 0..9. Bit zero is irrelevant. +digit (t _ (t b1 (t b2 (t b3 high)))) = + both (equal high 3) (not (both b3 (either b2 b1))) +digit _ = t +-- A..Z/a..z: ignore the case bit; low five bits must be 1..26. +letter (t b0 (t b1 (t b2 (t b3 (t b4 (t _ high)))))) = + (both (equal high 1) + (both (either b0 (either b1 (either b2 (either b3 b4)))) + (not (both (both b4 b3) (either b2 (both b1 b0)))))) +letter _ = t +-- a..f: high part is 12; low three bits are neither zero nor seven. +asciiLowerHexLetter (t b0 (t b1 (t b2 high))) = + (both (equal high 12) + (both (either b0 (either b1 b2)) (not (both b0 (both b1 b2))))) +asciiLowerHexLetter _ = t +lowerHex value = either (digit value) (asciiLowerHexLetter value) printable value = inRange value 32 126 diff --git a/test/AsciiClassifiers.hs b/test/AsciiClassifiers.hs @@ -0,0 +1,46 @@ +module Main where +import Runtime +import Wire +import qualified Data.ByteString as B +import qualified Data.Text as T +import qualified Data.Vector as V +import qualified Data.Map.Strict as M +import Control.Monad +import System.Environment + +-- Independent Integer predicates: no implementation bit tests or tree slicing. +expected :: String -> Integer -> Bool +expected "digit" n = n >= 48 && n <= 57 +expected "letter" n = n >= 65 && n <= 90 || n >= 97 && n <= 122 +expected "lowerHex" n = expected "digit" n || n >= 97 && n <= 102 +expected name _ = error ("unknown classifier: " ++ name) +truth :: Bool -> T +truth True = Stem Leaf +truth False = Leaf + +-- Include all bytes, BMP code points (including non-scalar naturals), Unicode +-- boundaries, large values retaining ASCII low bits, and deterministic samples. +-- Arbitrary/partial trees are explicitly outside native_math's input contract. +values :: [Integer] +values = [0..65535] + ++ [0xD7FF,0xD800,0xDFFF,0xE000,0xFFFF,0x10000,0x10FFFF,0x110000] + ++ [2^k+d | k <- [16,17,20,21,31,32,63,64,127,128,255,256,1024 :: Int], + d <- [-1,0,1,48,65,97]] + ++ take 1024 (iterate (\n -> (n*6364136223846793005+1442695040888963407) + `mod` (2^(256 :: Int))) 42) + +main :: IO () +main = do + [file] <- getArgs + bundle <- B.readFile file >>= either fail pure . decodeBundle + either fail pure (verifyBundle bundle) + let nodes = reconstructBundleTerms (bundleNodes bundle) + workers = M.fromList [(T.unpack (exportName e), + nodes V.! fromIntegral (exportRoot e)) + | e <- manifestExports (bundleManifest bundle)] + forM_ ["digit", "letter", "lowerHex"] $ \name -> do + let worker = workers M.! name + forM_ values $ \n -> + unless (apply worker (ofNumber n) == truth (expected name n)) + (fail ("classifier mismatch: " ++ show (name,n))) + putStrLn ("PASS " ++ show (length values*3) ++ " complete Boolean observations") diff --git a/test/ascii_classifiers.py b/test/ascii_classifiers.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Compile current native_math and independently check complete ASCII predicates. + +No reused library dependencies; the packaged stable compiler is authenticated. +This focused source check does not publish or accept a compiler generation. +""" +import argparse +import hashlib +import json +from pathlib import Path +import subprocess + +from bundle_dag import merge_bundles +from pattern_lowering import audit +from shipped_compiler import verify as verify_compiler + +ROOT = Path(__file__).resolve().parent.parent + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('tricu', type=Path) + parser.add_argument('--directory', type=Path, required=True) + args = parser.parse_args() + root = args.directory.resolve() + root.mkdir(parents=True, exist_ok=False) + workspace = root / 'workspace' + workspace.mkdir() + (workspace / 'tricu.workspace').write_text('tricu.workspace.v1\n') + host = args.tricu.resolve() + compiler = verify_compiler() + source = (ROOT / 'lib/math/native.tri').read_bytes() + (workspace / 'native_math.tri').write_bytes(source) + report = dict(complete=False, compiler_sha256=compiler['sha256'], + runtime_sha256=hashlib.sha256(host.read_bytes()).hexdigest(), + source_sha256=hashlib.sha256(source).hexdigest(), commands=[]) + + def command(argv, timeout=180, rejection=False): + cmd = list(map(str, argv)) + out = subprocess.run(cmd, cwd=ROOT, capture_output=True, text=True, timeout=timeout) + report['commands'].append(dict(argv=cmd, code=out.returncode, + stdout=out.stdout, stderr=out.stderr)) + (root / 'report.json').write_text(json.dumps(report, indent=2) + '\n') + if rejection: + assert out.returncode != 0 and 'classifier mismatch' in out.stderr, out + elif out.returncode: + raise RuntimeError(out.stdout[-4000:] + out.stderr[-4000:]) + + candidate = root / 'native_math.arboricx' + command([host, 'arboricx', 'compile', '--file', workspace / 'native_math.tri', + '--store', root / 'store', '--output', candidate, '--compile-timeout', '150', + '+RTS', '-N1', '-M4096m', '-RTS']) + report['modules'] = audit(workspace, root / 'store', compiler['sha256'], {}) + expr = ('let f=builtins.getFlake "git+file://' + str(ROOT) + '";' + 'p=f.inputs.nixpkgs.legacyPackages.x86_64-linux;' + 'in p.haskellPackages.ghcWithPackages(h:[h.cryptonite h.memory h.base16-bytestring h.vector])') + command(['nix', 'shell', '--impure', '--expr', expr, '--command', 'ghc', '-O2', '-rtsopts', + '-XOverloadedStrings', '-XLambdaCase', '-XMultiWayIf', '-XScopedTypeVariables', + '-isrc', '-outputdir', root / 'objects', ROOT / 'test/AsciiClassifiers.hs', + '-o', root / 'controls'], 300) + command([root / 'controls', candidate, '+RTS', '-M2048m', '-RTS']) + wrong = root / 'wrong.arboricx' + merge_bundles(wrong, [(candidate, [('letter', 'digit'), ('letter', 'letter'), + ('lowerHex', 'lowerHex')])], prune_unreachable=True) + command([root / 'controls', wrong, '+RTS', '-M2048m', '-RTS'], rejection=True) + report.update(complete=True, wrong_subject_rejected=True, + controls_sha256=hashlib.sha256((ROOT / 'test/AsciiClassifiers.hs').read_bytes()).hexdigest()) + (root / 'report.json').write_text(json.dumps(report, indent=2) + '\n') + print('PASS ASCII classifiers: canonical naturals, complete Booleans, wrong-subject and provenance controls') + + +if __name__ == '__main__': + main() diff --git a/test/native_source_utf8.py b/test/native_source_utf8.py @@ -70,16 +70,18 @@ lexProbe path = io (onOk_ (readSourceFile path) (source : pure (lexGateSource (s for node in nodes: sizes.append(1 + sum(sizes[child] for child in node[1:])) expanded_bytes = sum(sizes[index] for index in exports.values()) - support_reply = runner.run('support-import', ['import', '--file', root / 'support.arboricx', '--store', runner.store, '--flat'], 180) + support_reply = runner.run('support-import', ['import', '--file', root / 'support.arboricx', '--store', runner.store, '--indexed'], 180) support = re.search(r'Created module alias .* -> ([0-9a-f]{64})', support_reply)[1] manifest = (runner.store / 'objects' / support[:3] / support).read_bytes() assert hashlib.sha256(b'arboricx.module-manifest.v1\0' + manifest).hexdigest() == support entries = [line.split('\t') for line in manifest.decode().splitlines()[1:]] assert {fields[1]: fields[3] for fields in entries} == selected assert len(entries) == len(selected) - for tree in set(selected.values()): + from executable_object import verify_payload + for _, name, kind, tree, abi in entries: raw = (runner.store / 'objects' / tree[:3] / tree).read_bytes() - assert hashlib.sha256(b'arboricx.tree-term.v1\0' + raw).hexdigest() == tree + verify_payload(kind, raw) + assert hashlib.sha256(kind.encode() + b'\0' + raw).hexdigest() == tree (root / 'support.tri').write_text(f'!export * from {support}\n') source = '!import support *\n!export readProbe\n!export mapProbe\n!export lexProbe\n' + body + wrapper (root / 'driver.tri').write_text(source)