chappi-core 0.1.0

Typed cognitive graph, patch, reentry, projection, and measurement engine for Chappi
Documentation
use chappi_syntax::{Atom, Document, Form, FormKind, Span, canonicalize};

use crate::resume::{
    affect_text, attention_text, confidence_text, constraint_text, desire_text, goal_status,
    lifetime_text, metacognition_text, prediction_text, question_text, tension_text,
};
use crate::{CognitiveState, FocusFrame, Node, NodeKind, Relation, Source};

/// Encodes a validated semantic state as a v1 snapshot document.
pub fn snapshot_document(state: &CognitiveState) -> Document {
    let mut forms = vec![list(vec![symbol("chappi"), integer(state.version)])];
    forms.extend(state.nodes.iter().map(node_form));
    forms.extend(
        state
            .nodes
            .iter()
            .filter(|node| node.revision > 0)
            .map(|node| {
                list(vec![
                    symbol("revision"),
                    symbol(&node.id),
                    integer(node.revision),
                ])
            }),
    );
    forms.extend(state.relations.iter().map(relation_form));
    forms.extend(
        state
            .focus_streams
            .iter()
            .flat_map(|stream| stream.frames.iter())
            .map(focus_form),
    );
    Document { forms }
}

/// Encodes a validated semantic state as canonical v1 snapshot bytes.
pub fn snapshot_source(state: &CognitiveState) -> Vec<u8> {
    canonicalize(&snapshot_document(state))
}

fn node_form(node: &Node) -> Form {
    let mut items = vec![
        symbol(node_head(&node.kind)),
        symbol(&node.id),
        symbol(&node.owner),
        symbol(lifetime_text(node.lifetime)),
        integer(node.salience),
    ];
    match &node.kind {
        NodeKind::Entity { entity_kind } => items.push(symbol(entity_kind)),
        NodeKind::Claim { confidence } => items.push(symbol(confidence_text(*confidence))),
        NodeKind::Evidence {
            evidence_kind,
            source,
        } => {
            items.push(symbol(evidence_kind));
            items.push(match source {
                Source::Symbol(value) => symbol(value),
                Source::String(value) => string(value),
            });
        }
        NodeKind::Goal { status } => items.push(symbol(goal_status(*status))),
        NodeKind::Constraint { strength } => items.push(symbol(constraint_text(*strength))),
        NodeKind::Question { status } => items.push(symbol(question_text(*status))),
        NodeKind::Relationship { target, status } => {
            items.push(symbol(target));
            items.push(symbol(status));
        }
        NodeKind::Desire { force } => items.push(symbol(desire_text(*force))),
        NodeKind::Attention { mode } => items.push(symbol(attention_text(*mode))),
        NodeKind::Association => {}
        NodeKind::Tension { status } => items.push(symbol(tension_text(*status))),
        NodeKind::Prediction { confidence, status } => {
            items.push(symbol(confidence_text(*confidence)));
            items.push(symbol(prediction_text(*status)));
        }
        NodeKind::Perspective(value) => {
            items.push(symbol(&value.target));
            items.push(symbol(confidence_text(value.confidence)));
        }
        NodeKind::Affect {
            target,
            affect_kind,
            intensity,
        } => {
            items.push(symbol(target));
            items.push(symbol(affect_kind));
            items.push(symbol(affect_text(*intensity)));
        }
        NodeKind::Metacognition { status } => items.push(symbol(metacognition_text(*status))),
    }
    items.push(node.payload.clone());
    list(items)
}

fn node_head(kind: &NodeKind) -> &'static str {
    match kind {
        NodeKind::Entity { .. } => "entity",
        NodeKind::Claim { .. } => "claim",
        NodeKind::Evidence { .. } => "evidence",
        NodeKind::Goal { .. } => "goal",
        NodeKind::Constraint { .. } => "constraint",
        NodeKind::Question { .. } => "question",
        NodeKind::Relationship { .. } => "relationship",
        NodeKind::Desire { .. } => "desire",
        NodeKind::Attention { .. } => "attention",
        NodeKind::Association => "association",
        NodeKind::Tension { .. } => "tension",
        NodeKind::Prediction { .. } => "prediction",
        NodeKind::Perspective(_) => "perspective",
        NodeKind::Affect { .. } => "affect",
        NodeKind::Metacognition { .. } => "metacognition",
    }
}

fn relation_form(relation: &Relation) -> Form {
    list(vec![
        symbol("rel"),
        symbol(relation.kind.as_str()),
        symbol(&relation.source),
        symbol(&relation.target),
    ])
}

fn focus_form(frame: &FocusFrame) -> Form {
    list(vec![
        symbol("focus"),
        symbol(&frame.stream_id),
        integer(frame.sequence),
        symbol(&frame.owner),
        symbol(frame.transition.as_str()),
        frame
            .previous_sequence
            .map_or_else(|| symbol("none"), integer),
        bucket("fg", &frame.foreground),
        bucket("bg", &frame.background),
        bucket("residue", &frame.residue),
        frame.trigger.clone(),
    ])
}

fn bucket(head: &str, ids: &[String]) -> Form {
    let mut ids = ids.iter().collect::<Vec<_>>();
    ids.sort_unstable();
    list(
        std::iter::once(symbol(head))
            .chain(ids.into_iter().map(|id| symbol(id)))
            .collect(),
    )
}

fn list(items: Vec<Form>) -> Form {
    Form {
        span: empty_span(),
        kind: FormKind::List(items),
    }
}

fn symbol(value: &str) -> Form {
    Form {
        span: empty_span(),
        kind: FormKind::Atom(Atom::Symbol(value.to_owned())),
    }
}

fn string(value: &str) -> Form {
    Form {
        span: empty_span(),
        kind: FormKind::Atom(Atom::String(value.to_owned())),
    }
}

fn integer(value: impl ToString) -> Form {
    Form {
        span: empty_span(),
        kind: FormKind::Atom(Atom::Integer(value.to_string())),
    }
}

fn empty_span() -> Span {
    Span {
        start: 0,
        end: 0,
        start_line: 1,
        start_column: 1,
        end_line: 1,
        end_column: 1,
    }
}