neural/rune/rs/prysm/lib.rs

//! rune ↔ prysm bridge.
//!
//! A rune program that renders UI evaluates to a **chunk-noun** β€” a noun tree
//! using the `rune_ast::tag` ABI. This crate decodes that noun into
//! [`tape::Chunk`]s, the exact wire format prysm's `dispatch` renders (to wgpu
//! cells, ansi, or html). It is the only coupling between rune and prysm: rune
//! emits nouns, prysm renders chunks, and `noun_to_chunks` is the seam.
//!
//! ```text
//!   rune program ──lower──> Nox formula ──eval──> chunk-noun
//!                                                     β”‚  noun_to_chunks
//!                                                     β–Ό
//!                                            Vec<tape::Chunk> ──StreamChannel──> prysm::dispatch
//! ```

use rune_ast::{Noun, tag};
use tape::{bytes::Bytes, encode_nested, render, sigil, Chunk};

/// Decode a `tape`/cord payload noun into raw bytes.
///
/// Two encodings are accepted:
/// - **tape**: a null-terminated cons list of byte atoms `[h [e … 0]]` (how
///   `rune-lower` emits text β€” handles arbitrary length over `u64` atoms).
/// - **cord**: a single atom, read as little-endian bytes with trailing NULs
///   trimmed (a convenience for short literals).
pub fn noun_to_bytes(n: &Noun) -> Vec<u8> {
    match n {
        Noun::Atom(0) => Vec::new(),
        Noun::Atom(a) => {
            let mut v = a.to_le_bytes().to_vec();
            while v.last() == Some(&0) {
                v.pop();
            }
            v
        }
        Noun::Cell(_, _) => {
            let mut out = Vec::new();
            let mut cur = n;
            loop {
                match cur {
                    Noun::Cell(h, t) => {
                        if let Noun::Atom(b) = h.as_ref() {
                            out.push(*b as u8);
                        }
                        cur = t.as_ref();
                    }
                    Noun::Atom(0) => break,
                    Noun::Atom(b) => {
                        out.push(*b as u8);
                        break;
                    }
                }
            }
            out
        }
    }
}

fn text_of(n: &Noun) -> String {
    String::from_utf8_lossy(&noun_to_bytes(n)).into_owned()
}

/// Decode a single prysm element noun `[tag payload]` into one [`Chunk`].
/// Returns `None` if the noun is not a recognised element.
pub fn noun_to_chunk(n: &Noun) -> Option<Chunk> {
    let Noun::Cell(head, payload) = n else { return None };
    let Noun::Atom(t) = head.as_ref() else { return None };
    let chunk = match *t {
        tag::TEXT  => Chunk::text(&text_of(payload)),
        tag::ANNO  => Chunk::annotation(&text_of(payload)),
        tag::ERROR => Chunk::error(&text_of(payload)),
        tag::LOG   => Chunk::new(sigil::DOT, render::LOG, Bytes::from(noun_to_bytes(payload))),
        tag::BUTTON => {
            // payload = [label-tape target-tape]
            let Noun::Cell(label, target) = payload.as_ref() else { return None };
            let label_c  = Chunk::annotation(&text_of(label));   // (~, t)
            let target_c = Chunk::text(&text_of(target));        // (#, t)
            Chunk::new(sigil::ZAP, render::COMPONENT, encode_nested(&[label_c, target_c]))
        }
        _ => return None,
    };
    Some(chunk)
}

/// Decode a rune result noun into a stream of [`Chunk`]s.
///
/// A `[LIST e1 [e2 … 0]]` noun yields one chunk per element; any other
/// recognised element noun yields a single chunk; anything else yields none.
pub fn noun_to_chunks(n: &Noun) -> Vec<Chunk> {
    if let Noun::Cell(head, tail) = n {
        if matches!(head.as_ref(), Noun::Atom(t) if *t == tag::LIST) {
            let mut out = Vec::new();
            let mut cur = tail.as_ref();
            while let Noun::Cell(elem, rest) = cur {
                if let Some(c) = noun_to_chunk(elem) {
                    out.push(c);
                }
                cur = rest.as_ref();
            }
            return out;
        }
    }
    noun_to_chunk(n).into_iter().collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Build a tape noun the way rune-lower does, for fixtures.
    fn tape(s: &str) -> Noun {
        let mut n = Noun::Atom(0);
        for &b in s.as_bytes().iter().rev() {
            n = Noun::cell(Noun::Atom(b as u64), n);
        }
        n
    }

    #[test]
    fn tape_roundtrips_arbitrary_length() {
        // 8+ bytes β€” the case a single u64 atom cannot hold.
        let s = "hello, cyber neuron";
        assert_eq!(noun_to_bytes(&tape(s)), s.as_bytes());
    }

    #[test]
    fn cord_atom_decodes_le() {
        // "hi" packed LSB-first into one atom = 0x6968.
        assert_eq!(noun_to_bytes(&Noun::Atom(0x6968)), b"hi");
        assert_eq!(noun_to_bytes(&Noun::Atom(0)), b"");
    }

    #[test]
    fn text_element_is_hax_text() {
        let n = Noun::cell(Noun::Atom(tag::TEXT), tape("hi"));
        let c = noun_to_chunk(&n).unwrap();
        assert_eq!((c.sigil, c.render), (sigil::HAX, render::TEXT));
        assert_eq!(&c.payload[..], b"hi");
    }

    #[test]
    fn error_element_is_zap_error() {
        let n = Noun::cell(Noun::Atom(tag::ERROR), tape("boom"));
        let c = noun_to_chunk(&n).unwrap();
        assert_eq!((c.sigil, c.render), (sigil::ZAP, render::ERROR));
        assert!(std::str::from_utf8(&c.payload).unwrap().contains("boom"));
    }

    #[test]
    fn button_element_is_zap_component_with_label_and_target() {
        // [BUTTON [label target]]
        let n = Noun::cell(
            Noun::Atom(tag::BUTTON),
            Noun::cell(tape("ok"), tape("@master")),
        );
        let c = noun_to_chunk(&n).unwrap();
        assert_eq!((c.sigil, c.render), (sigil::ZAP, render::COMPONENT));
        // Inner: annotation(label) + text(target) β€” matches prysm action::spawn.
        let inner = tape::decode_nested(&c.payload);
        assert_eq!(inner.len(), 2);
        assert_eq!((inner[0].sigil, inner[0].render), (sigil::SIG, render::TEXT));
        assert_eq!(&inner[0].payload[..], b"ok");
        assert_eq!((inner[1].sigil, inner[1].render), (sigil::HAX, render::TEXT));
        assert_eq!(&inner[1].payload[..], b"@master");
    }

    #[test]
    fn list_yields_one_chunk_per_element() {
        // [LIST text("a") [error("b") 0]]
        let elems = Noun::cell(
            Noun::cell(Noun::Atom(tag::TEXT), tape("a")),
            Noun::cell(
                Noun::cell(Noun::Atom(tag::ERROR), tape("b")),
                Noun::Atom(0),
            ),
        );
        let n = Noun::cell(Noun::Atom(tag::LIST), elems);
        let chunks = noun_to_chunks(&n);
        assert_eq!(chunks.len(), 2);
        assert_eq!(chunks[0].render, render::TEXT);
        assert_eq!(chunks[1].render, render::ERROR);
    }
}

Homonyms

cyb/optica/src/lib.rs
soft3/strata/src/lib.rs
cyb/honeycrisp/src/lib.rs
warriors/trisha/honeycrisp/lib.rs
warriors/trisha/wgpu/lib.rs
soft3/glia/import/lib.rs
soft3/foculus/src/lib.rs
soft3/nox/rs/lib.rs
soft3/cybergraph/src/lib.rs
soft3/tru/rs/lib.rs
soft3/mudra/src/lib.rs
soft3/glia/run/lib.rs
cyb/prysm/rs/lib.rs
warriors/trisha/rs/lib.rs
cyb/src-tauri/src/lib.rs
soft3/mir/src/lib.rs
soft3/lens/src/lib.rs
neural/trident/src/lib.rs
neural/rune/rs/subject/lib.rs
cyb/cyb/cyb-services/src/lib.rs
soft3/strata/nebu/rs/lib.rs
soft3/lens/core/src/lib.rs
neural/rs/mir-format/src/lib.rs
soft3/zheng/rs/src/lib.rs
neural/rune/rs/interp/lib.rs
soft3/radio/iroh-willow/src/lib.rs
neural/rune/rs/parse/lib.rs
neural/eidos/rs/src/lib.rs
neural/rs/darwin-sys/src/lib.rs
soft3/radio/iroh-gossip/src/lib.rs
soft3/radio/iroh-ffi/src/lib.rs
soft3/radio/iroh-car/src/lib.rs
soft3/radio/iroh-relay/src/lib.rs
soft3/bbg/rs/src/lib.rs
soft3/radio/iroh-docs/src/lib.rs
soft3/lens/ikat/src/lib.rs
neural/rune/rs/lex/lib.rs
cyb/honeycrisp/aruminium/src/lib.rs
soft3/hemera/rs/src/lib.rs
neural/rune/rs/ast/lib.rs
soft3/radio/iroh-blobs/src/lib.rs
cyb/honeycrisp/acpu/src/lib.rs
soft3/lens/porphyry/src/lib.rs
cyb/honeycrisp/rane/src/lib.rs
neural/rune/rs/compile/lib.rs
neural/rune/rs/parse-pure/lib.rs
neural/rs/codegen/src/lib.rs
soft3/lens/binius/src/lib.rs
neural/rs/link/src/lib.rs
neural/rune/rs/mold/lib.rs
soft3/strata/proof/src/lib.rs
soft3/lens/brakedown/src/lib.rs
soft3/strata/kuro/rs/lib.rs
soft3/lens/assayer/src/lib.rs
neural/rs/core/src/lib.rs
neural/rs/macros/src/lib.rs
soft3/radio/cyber-bao/src/lib.rs
soft3/strata/compute/src/lib.rs
soft3/radio/iroh-base/src/lib.rs
soft3/radio/iroh-dns-server/src/lib.rs
neural/rune/rs/lower/lib.rs
soft3/strata/ext/src/lib.rs
soft3/strata/core/src/lib.rs
soft3/hemera/wgsl/src/lib.rs
soft3/radio/iroh/src/lib.rs
cyb/honeycrisp/unimem/src/lib.rs
cyb/evy/crates/evy_engine_tasks/src/lib.rs
cyb/evy/crates/evy_dialect/src/lib.rs
cyb/wysm/crates/wasi/src/lib.rs
cyb/wysm/crates/fuzz/src/lib.rs
soft3/strata/genies/rs/src/lib.rs
cyb/evy/crates/evy_platform_caps/src/lib.rs
neural/inf/rs/oracle/src/lib.rs
soft3/strata/jali/wgsl/src/lib.rs
cyb/evy/forks/bevy_transform/src/lib.rs
soft3/tape/impl/rust/src/lib.rs
cyb/wysm/crates/wasmi/src/lib.rs
cyb/evy/forks/bevy_render/src/lib.rs
cyb/evy/crates/evy_ecs_storage/src/lib.rs
cyb/evy/forks/naga/src/lib.rs
soft3/strata/trop/wgsl/src/lib.rs
cyb/wysm/crates/c_api/artifact/lib.rs
cyb/evy/forks/bevy_ecs/src/lib.rs
cyb/wysm/crates/ir/src/lib.rs
cyb/evy/forks/bevy_animation/src/lib.rs
cyb/evy/forks/bevy_sprite_render/src/lib.rs
cyb/wysm/crates/c_api/src/lib.rs
neural/inf/rs/parse/src/lib.rs
soft3/strata/trop/rs/src/lib.rs
soft3/strata/kuro/wgsl/src/lib.rs
neural/trident/editor/zed/src/lib.rs
cyb/evy/forks/bevy_mesh/src/lib.rs
cyb/evy/crates/evy_radio/src/lib.rs
cyb/evy/forks/bevy_anti_alias/src/lib.rs
soft3/strata/jali/rs/src/lib.rs
cyb/wysm/crates/wast/src/lib.rs
neural/inf/rs/plan/src/lib.rs
neural/rs/tests/macro-integration/src/lib.rs
soft3/radio/iroh-ffi/iroh-js/src/lib.rs
cyb/evy/forks/bevy_image/src/lib.rs
cyb/evy/forks/bevy_post_process/src/lib.rs
neural/inf/rs/source/src/lib.rs
cyb/wysm/crates/core/src/lib.rs
cyb/evy/crates/evy_diagnostic/src/lib.rs
cyb/evy/crates/evy_engine_dispatch/src/lib.rs
cyb/evy/forks/bevy_pbr/src/lib.rs
cyb/evy/forks/bevy_gizmos/src/lib.rs
cyb/evy/forks/bevy_gizmos_render/src/lib.rs
soft3/radio/iroh/bench/src/lib.rs
neural/inf/rs/lex/src/lib.rs
neural/inf/rs/ast/src/lib.rs
soft3/strata/genies/wgsl/src/lib.rs
soft3/strata/nebu/wgsl/src/lib.rs
cyb/wysm/crates/collections/src/lib.rs
neural/inf/rs/lower/src/lib.rs
cyb/evy/forks/bevy_sprite/src/lib.rs
cyb/evy/forks/bevy_diagnostic/src/lib.rs
neural/inf/rs/eval/src/lib.rs
cyb/wysm/crates/c_api/macro/lib.rs
cyb/evy/forks/bevy_tasks/src/lib.rs
cyb/evy/forks/bevy_core_pipeline/src/lib.rs
cyb/evy/crates/evy_prysm_core/src/lib.rs
neural/inf/rs/value/src/lib.rs
cyb/evy/crates/evy_engine_core/src/lib.rs
soft3/radio/tests/integration/src/lib.rs
bootloader/go-cyber/cw/packages/cyber-std-test/src/lib.rs
bootloader/go-cyber/cw/contracts/std-test/src/lib.rs
bootloader/go-cyber/cw/contracts/graph-filter/src/lib.rs
bootloader/go-cyber/cw/packages/cyber-std/src/lib.rs

Graph