soft3/nox/cli/main.rs

// ---
// tags: nox, rust
// crystal-type: source
// crystal-domain: comp
// ---
//! nox โ€” run data formulas from stdin or file
//!
//! Usage:
//!   nox <file.nox>              # evaluate formula from file
//!   nox -e '[5 [[1 3] [1 5]]]' # evaluate inline formula
//!   echo '[1 42]' | nox        # evaluate from stdin
//!
//! Object defaults to atom 0. Override with --object.
//! Budget defaults to 1_000_000. Override with --budget.

use std::io::Read;

use nebu::Goldilocks;
use nox::data::{Reduction, Order, Data};
use nox::reduce::{reduce_with_registry, Outcome};
use nox::call::NullCalls;
use nox::trace::NoTrace;
use nox::jets::registry::JetRegistry;

const ORDER_SIZE: usize = 1 << 16; // 64K data

// โ”€โ”€โ”€ data parser โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Parse a textual data `[a b]` or `42` into the order.
fn parse_data(reduction: &mut Reduction<ORDER_SIZE>, input: &str) -> Result<Order, String> {
    let tokens = tokenize(input)?;
    let mut pos = 0;
    let result = parse_expr(reduction, &tokens, &mut pos, 0)?;
    Ok(result)
}

/// Cap parse_expr recursion. Adversarial input like `[[[[โ€ฆ` would otherwise
/// blow the 32 MB worker stack at ~250k levels in debug.
const MAX_PARSE_DEPTH: usize = 4096;

#[derive(Debug)]
enum Token {
    Open,
    Close,
    Num(u64),
}

// u64::MAX in decimal has 20 digits. Anything longer cannot parse as u64
// and serves only to inflate allocation from adversarial input.
const MAX_NUM_DIGITS: usize = 20;

fn tokenize(input: &str) -> Result<Vec<Token>, String> {
    let mut tokens = Vec::new();
    let mut chars = input.chars().peekable();
    while let Some(&ch) = chars.peek() {
        match ch {
            '[' => { tokens.push(Token::Open); chars.next(); }
            ']' => { tokens.push(Token::Close); chars.next(); }
            ' ' ๏ฟฟ '\t' ๏ฟฟ '\n' ๏ฟฟ '\r' => { chars.next(); }
            '0'..='9' => {
                let mut num = String::new();
                while let Some(&c) = chars.peek() {
                    if c.is_ascii_digit() ๏ฟฟ๏ฟฟ c == '_' {
                        if c != '_' {
                            if num.len() >= MAX_NUM_DIGITS {
                                return Err(format!(
                                    "numeric literal exceeds {} digits",
                                    MAX_NUM_DIGITS
                                ));
                            }
                            num.push(c);
                        }
                        chars.next();
                    } else {
                        break;
                    }
                }
                let v: u64 = num.parse().map_err(๏ฟฟe๏ฟฟ format!("bad number '{}': {}", num, e))?;
                tokens.push(Token::Num(v));
            }
            _ => return Err(format!("unexpected character: {:?}", ch)),
        }
    }
    Ok(tokens)
}

fn parse_expr(
    reduction: &mut Reduction<ORDER_SIZE>,
    tokens: &[Token],
    pos: &mut usize,
    depth: usize,
) -> Result<Order, String> {
    if depth > MAX_PARSE_DEPTH {
        return Err("expression too deeply nested".into());
    }
    if *pos >= tokens.len() {
        return Err("unexpected end of input".to_string());
    }
    match &tokens[*pos] {
        Token::Num(v) => {
            *pos += 1;
            reduction.atom(Goldilocks::new(*v))
                .ok_or_else(๏ฟฟ๏ฟฟ "reduction full".to_string())
        }
        Token::Open => {
            *pos += 1; // skip [
            // Parse all elements inside brackets, right-nest them.
            // [a b c d] โ†’ Pair(a, Pair(b, Pair(c, d)))
            let mut elems = Vec::new();
            while *pos < tokens.len() && !matches!(tokens[*pos], Token::Close) {
                elems.push(parse_expr(reduction, tokens, pos, depth + 1)?);
            }
            if *pos < tokens.len() && matches!(tokens[*pos], Token::Close) {
                *pos += 1;
            } else {
                return Err("expected ']'".to_string());
            }
            if elems.is_empty() {
                return Err("empty brackets".to_string());
            }
            if elems.len() == 1 {
                // `[x]` is not a data: a pair needs โ‰ฅ 2 elements. Reject
                // explicitly so `1 42` doesn't silently parse as `[1 42]`.
                return Err("single-element brackets are not a valid pair".to_string());
            }
            // Right-nest: [a b c] โ†’ Pair(a, Pair(b, c))
            let mut result = elems.pop().unwrap();
            while let Some(head) = elems.pop() {
                result = reduction.pair(head, result)
                    .ok_or_else(|| "reduction full".to_string())?;
            }
            Ok(result)
        }
        Token::Close => {
            Err("unexpected ']'".to_string())
        }
    }
}

// โ”€โ”€โ”€ data printer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn print_data(reduction: &Reduction<ORDER_SIZE>, r: Order) -> String {
    match reduction.get(r).map(|e| e.inner) {
        Some(Data::Atom { value, .. }) => format!("{}", value.as_u64()),
        Some(Data::Pair { left, right }) => {
            format!("[{} {}]", print_data(reduction, left), print_data(reduction, right))
        }
        None => "<invalid>".to_string(),
    }
}

// โ”€โ”€โ”€ main โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn main() {
    // Reduction<65536> is ~6 MB โ€” too large for the default 8 MB main thread stack.
    // Spawn a thread with an explicit 32 MB stack so the heap-allocated order is safe.
    let exit_code = std::thread::Builder::new()
        .stack_size(32 * 1024 * 1024)
        .spawn(run)
        .expect("failed to spawn nox thread")
        .join()
        .expect("nox thread panicked");
    std::process::exit(exit_code);
}

fn run() -> i32 {
    let args: Vec<String> = std::env::args().collect();

    let mut formula_text = String::new();
    let mut object_text = String::from("0");
    let mut budget: u64 = 1_000_000;

    let mut i = 1;
    while i < args.len() {
        match args[i].as_str() {
            "-e" => {
                i += 1;
                if i < args.len() {
                    formula_text = args[i].clone();
                } else {
                    eprintln!("error: -e requires an argument");
                    return 1;
                }
            }
            "--object" | "-o" => {
                i += 1;
                if i < args.len() {
                    object_text = args[i].clone();
                } else {
                    eprintln!("error: --object requires an argument");
                    return 1;
                }
            }
            "--budget" | "-b" => {
                i += 1;
                if i < args.len() {
                    match args[i].parse::<u64>() {
                        Ok(b) => budget = b,
                        Err(_) => { eprintln!("error: invalid budget"); return 1; }
                    }
                } else {
                    eprintln!("error: --budget requires an argument");
                    return 1;
                }
            }
            "--help" | "-h" => {
                print_usage();
                return 0;
            }
            other => {
                match std::fs::read_to_string(other) {
                    Ok(s) => formula_text = s,
                    Err(e) => { eprintln!("error reading '{}': {}", other, e); return 1; }
                }
            }
        }
        i += 1;
    }

    if formula_text.is_empty() {
        use std::io::IsTerminal;
        if std::io::stdin().is_terminal() {
            print_usage();
            return 1;
        }
        if let Err(e) = std::io::stdin().read_to_string(&mut formula_text) {
            eprintln!("error reading stdin: {}", e);
            return 1;
        }
    }

    let formula_text = formula_text.trim();
    if formula_text.is_empty() {
        eprintln!("error: no formula provided");
        return 1;
    }

    let mut reduction = Reduction::<ORDER_SIZE>::new();
    let hints = NullCalls;
    let registry = JetRegistry::<ORDER_SIZE>::genesis();

    let object = match parse_data(&mut reduction, &object_text) {
        Ok(n) => n,
        Err(e) => { eprintln!("error parsing object: {}", e); return 1; }
    };

    let formula = match parse_data(&mut reduction, &formula_text) {
        Ok(n) => n,
        Err(e) => { eprintln!("error parsing formula: {}", e); return 1; }
    };

    match reduce_with_registry(&mut reduction, object, formula, budget, &hints, &mut NoTrace, &registry) {
        Outcome::Ok(result, remaining) => {
            println!("{}", print_data(&reduction, result));
            eprintln!("cost: {} (budget remaining: {})", budget - remaining, remaining);
            0
        }
        Outcome::Halt(remaining) => {
            eprintln!("halted: budget exhausted (remaining: {})", remaining);
            1
        }
        Outcome::Error(kind) => {
            eprintln!("error: {:?}", kind);
            1
        }
    }
}

fn print_usage() {
    eprintln!(
        "\
\x1b[31m
    โ–ˆโ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ•—
\x1b[33m    โ–ˆโ–ˆโ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•
\x1b[32m    โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ•”โ•
\x1b[36m    โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•—
\x1b[34m    โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ•—
\x1b[35m    โ•šโ•โ•  โ•šโ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•  โ•šโ•โ•
\x1b[0m\x1b[37m    the VM for superintelligence\x1b[0m
\x1b[90m
    18 patterns ยท 36 genesis jets ยท 5 algebras
    Goldilocks field ยท p = 2^64 - 2^32 + 1
    computation IS linking ยท proof-native
\x1b[0m
  nox <file.nox>                evaluate formula from file
  nox -e '[5 [[1 3] [1 5]]]'    evaluate inline formula
  echo '[1 42]' | nox            evaluate from stdin

  -o, --object <data>    object (default: 0)
  -b, --budget <n>        budget (default: 1000000)
  -e <formula>            inline formula
"
    );
}

Homonyms

cyberia/src/main.rs
cyb/src-tauri/src/main.rs
soft3/tru/cli/main.rs
neural/rune/cli/main.rs
cyb/cyb-boot/src/main.rs
soft3/glia/import/main.rs
warriors/trisha/cli/main.rs
neural/trident/src/main.rs
cyb/optica/src/main.rs
neural/rs/link/src/main.rs
neural/rs/macho-linker/src/main.rs
soft3/zheng/cli/src/main.rs
cyb/cyb/cyb-portal/src/main.rs
neural/rs/pure-rust-check/src/main.rs
cyb/cyb/cyb-ui/src/main.rs
soft3/foculus/src/bin/main.rs
neural/eidos/cli/src/main.rs
soft3/glia/run/cli/main.rs
soft3/radio/iroh-relay/src/main.rs
cyb/cyb/cyb-shell/src/main.rs
neural/rs/rsc/src/main.rs
soft3/cybergraph/cli/src/main.rs
soft3/bbg/cli/src/main.rs
soft3/radio/radio-cli/src/main.rs
soft3/radio/particle/src/main.rs
soft3/hemera/cli/src/main.rs
soft3/radio/iroh-dns-server/src/main.rs
soft3/strata/trop/cli/src/main.rs
cyb/honeycrisp/rane/src/probe/main.rs
soft3/strata/kuro/cli/src/main.rs
soft3/strata/genies/cli/src/main.rs
bootloader/go-cyber/mcp/rust/src/main.rs
cyb/wysm/crates/cli/src/main.rs
soft3/strata/nebu/cli/src/main.rs
cyb/honeycrisp/acpu/src/probe/main.rs
neural/inf/rs/cli/src/main.rs
soft3/strata/jali/cli/src/main.rs
cyb/honeycrisp/unimem/experiments/hyp_probe/src/main.rs
cyb/honeycrisp/unimem/experiments/iosurface_probe/src/main.rs
cyb/honeycrisp/unimem/experiments/dext_iosurface_pa/client/src/main.rs
cyb/honeycrisp/unimem/experiments/dext_contiguous_alloc/client/src/main.rs

Graph