use nebu::Goldilocks as G;
use nox::{JetRegistry, NullCalls, Order, Outcome, Reduction, VecTrace};
use super::{Bytes, Context, EDGES, Out, list};
use crate::text::{Data, Expect, Program, parse_program};
pub const ARENA: usize = 1 << 14;
pub type Arena = Reduction<ARENA>;
pub fn build(r: &mut Arena, data: &Data) -> Option<Order> {
match data {
Data::Atom(v) => r.atom(G::new(*v)),
Data::Pair(a, b) => {
let a = build(r, a)?;
let b = build(r, b)?;
r.pair(a, b)
}
}
}
pub fn read(r: &Arena, id: Order) -> Option<Data> {
if r.is_atom(id) {
return Some(Data::Atom(r.atom_value(id)?.as_u64()));
}
let (h, t) = (r.head(id)?, r.tail(id)?);
Some(Data::Pair(Box::new(read(r, h)?), Box::new(read(r, t)?)))
}
pub fn tree_bytes(r: &Arena, id: Order) -> Option<Vec<u8>> {
let mut b = Bytes::new();
for (pid, bytes) in nox::encode_tree(r, id)? {
b.raw(&pid).lv(&bytes);
}
Some(b.0)
}
pub fn reduce(program: &Program) -> Result<(Vec<u8>, Vec<u8>), String> {
let (outcome, trace, mismatch) = reduce_checked(program)?;
match mismatch {
Some(m) => Err(m),
None => Ok((outcome, trace)),
}
}
pub fn reduce_checked(program: &Program) -> Result<(Vec<u8>, Vec<u8>, Option<String>), String> {
let mut r = Box::new(Arena::new());
let object = build(&mut r, &program.object).ok_or("arena full building object")?;
let formula = build(&mut r, &program.formula).ok_or("arena full building formula")?;
let registry = JetRegistry::<ARENA>::genesis();
let mut trace = VecTrace::default();
let outcome = nox::reduce_with_registry(&mut r, object, formula, program.budget, &NullCalls, &mut trace, ®istry);
let actual = match &outcome {
Outcome::Ok(result, remaining) => format!("result {:?} cost {}", read(&r, *result), program.budget - remaining),
Outcome::Halt(_) => "halt".to_string(),
Outcome::Error(kind) => format!("error {kind:?}"),
};
let mismatch = match (&program.expect, &outcome) {
(None, _) => None,
(Some(Expect::Result(want, cost)), Outcome::Ok(result, remaining))
if read(&r, *result).as_ref() == Some(want) && cost.is_none_or(|c| c == program.budget - remaining) =>
{
None
}
(Some(Expect::Error(want)), Outcome::Error(kind)) if format!("{kind:?}") == *want => None,
(Some(Expect::Halt), Outcome::Halt(_)) => None,
(Some(want), _) => Some(format!("expected {want:?}, got {actual}")),
};
let mut b = Bytes::new();
b.raw(&nox::particle_id(&r, formula).ok_or("no formula particle")?);
b.raw(&nox::particle_id(&r, object).ok_or("no object particle")?);
b.u64(program.budget);
match outcome {
Outcome::Ok(result, remaining) => {
b.u8(0);
b.raw(&nox::particle_id(&r, result).ok_or("no result particle")?);
b.lv(&tree_bytes(&r, result).ok_or("result tree unencodable")?);
b.u64(program.budget - remaining);
}
Outcome::Halt(remaining) => {
b.u8(1).u64(remaining);
}
Outcome::Error(kind) => {
b.u8(2).u8(kind as u8);
}
}
Ok((b.0, trace_bytes(&trace), mismatch))
}
pub fn trace_bytes(trace: &VecTrace) -> Vec<u8> {
let mut b = Bytes::new();
b.u64(trace.0.len() as u64);
for row in &trace.0 {
for &c in row.r() {
b.u64(c);
}
}
b.0
}
pub fn programs(ctx: &Context) -> Result<Vec<(String, String)>, String> {
let mut out = Vec::new();
let own = ctx.fixture("nox");
for name in list(&own, ".nox")? {
let src = std::fs::read_to_string(own.join(&name)).map_err(|e| format!("{name}: {e}"))?;
out.push((name.trim_end_matches(".nox").to_string(), src));
}
let jets = ctx.stack.join("nox/jets");
for name in list(&jets, ".nox")? {
let src = std::fs::read_to_string(jets.join(&name)).map_err(|e| format!("jets/{name}: {e}"))?;
out.push((format!("jets/{}", name.trim_end_matches(".nox")), src));
}
Ok(out)
}
pub fn run(ctx: &Context, out: &mut Out) {
encodings(out);
let programs = match programs(ctx) {
Ok(p) => p,
Err(e) => return out.fail(format!("nox fixtures: {e}")),
};
for (scenario, src) in programs {
let program = match parse_program(&src) {
Ok(p) => p,
Err(e) => {
out.fail(format!("nox fixture {scenario}: {e}"));
continue;
}
};
match reduce(&program) {
Ok((outcome, trace)) => {
out.mechanism("nox::reduce@v1", &scenario, &outcome);
out.mechanism("nox::trace@v1", &scenario, &trace);
}
Err(e) => out.fail(format!("nox fixture {scenario}: {e}")),
}
}
}
fn encodings(out: &mut Out) {
let mut b = Bytes::new();
for &v in &EDGES {
b.raw(&nox::encode_atom(G::new(v)));
}
out.encoding("nox::atom@v1", &b.0);
let one = nox::particle_of(&nox::encode_atom(G::new(1))).expect("atom particle");
let two = nox::particle_of(&nox::encode_atom(G::new(2))).expect("atom particle");
let pair = nox::encode_pair(&one, &two);
let mut b = Bytes::new();
b.raw(&pair).raw(&nox::particle_of(&pair).expect("pair particle"));
out.encoding("nox::pair@v1", &b.0);
let formula = crate::text::parse_data("[4 [[9 [[0 2] [0 3]]] [[1 100] [1 200]]]]").expect("literal");
let mut r = Box::new(Arena::new());
let id = build(&mut r, &formula).expect("arena");
out.encoding("nox::tree@v1", &tree_bytes(&r, id).expect("tree"));
let entries = nox::encode_tree(&r, id).expect("tree");
let ids: Vec<nox::Particle> = entries.iter().map(|(p, _)| *p).collect();
out.encoding("nox::wire::push@v1", &nox::write_push(&entries).unwrap_or_default());
out.encoding("nox::wire::request@v1", &nox::write_request(&ids).unwrap_or_default());
out.encoding("nox::wire::response@v1", &nox::write_response(&entries).unwrap_or_default());
}