sim-codec-doc 0.1.4

Markup document codec backends for SIM.
Documentation
use sim_kernel::{Args, Expr, NumberLiteral, Symbol};
pub(super) use sim_value::access::field as map_field;

use crate::{
    BackendId, MarkupBackend, MarkupBlock, MarkupDecodeOptions, MarkupDoc, MarkupEncodeOptions,
    MarkupError, MarkupFidelity, decode_markup_doc, install_doc_codec,
};

pub(super) fn cx() -> sim_kernel::Cx {
    let mut cx = sim_test_support::core_cx();
    sim_test_support::register_f64_number_domain(&mut cx);
    install_doc_codec(&mut cx).unwrap();
    cx
}

pub(super) fn cx_with_general_codecs() -> sim_kernel::Cx {
    let mut cx = cx();
    let json = sim_codec_json::JsonCodecLib::new(cx.registry_mut().fresh_codec_id());
    cx.load_lib(&json).unwrap();
    let lisp = sim_codec_lisp::LispCodecLib::new(cx.registry_mut().fresh_codec_id()).unwrap();
    cx.load_lib(&lisp).unwrap();
    cx
}

pub(super) fn call_report(cx: &mut sim_kernel::Cx, symbol: Symbol) -> Expr {
    let value = cx.registry().function_by_symbol(&symbol).unwrap().clone();
    let callable = value.object().as_callable().unwrap();
    let value = callable.call(cx, Args::new(Vec::new())).unwrap();
    value.object().as_expr(cx).unwrap()
}

pub(super) fn map_symbol(expr: &Expr, field: &str) -> Option<Symbol> {
    match map_field(expr, field)? {
        Expr::Symbol(symbol) => Some(symbol.clone()),
        _ => None,
    }
}

pub(super) fn map_string<'a>(expr: &'a Expr, field: &str) -> Option<&'a str> {
    match map_field(expr, field)? {
        Expr::String(text) => Some(text),
        _ => None,
    }
}

pub(super) fn blocks_without_spans(blocks: &[MarkupBlock]) -> Vec<MarkupBlock> {
    blocks.iter().cloned().map(block_without_span).collect()
}

fn block_without_span(block: MarkupBlock) -> MarkupBlock {
    match block {
        MarkupBlock::Heading {
            level, text, id, ..
        } => MarkupBlock::Heading {
            level,
            text,
            id,
            span: None,
        },
        MarkupBlock::Paragraph { content, .. } => MarkupBlock::Paragraph {
            content,
            span: None,
        },
        MarkupBlock::CodeBlock { lang, code, .. } => MarkupBlock::CodeBlock {
            lang,
            code,
            span: None,
        },
        MarkupBlock::MathBlock { source, .. } => MarkupBlock::MathBlock { source, span: None },
        MarkupBlock::Quote { blocks, .. } => MarkupBlock::Quote {
            blocks: blocks_without_spans(&blocks),
            span: None,
        },
        MarkupBlock::List { ordered, items, .. } => MarkupBlock::List {
            ordered,
            items: items
                .into_iter()
                .map(|item| blocks_without_spans(&item))
                .collect(),
            span: None,
        },
        MarkupBlock::Table { header, rows, .. } => MarkupBlock::Table {
            header,
            rows,
            span: None,
        },
        MarkupBlock::Figure { src, caption, .. } => MarkupBlock::Figure {
            src,
            caption,
            span: None,
        },
        MarkupBlock::Raw { backend, text, .. } => MarkupBlock::Raw {
            backend,
            text,
            span: None,
        },
    }
}

#[derive(Clone)]
pub(super) struct TestBackend {
    id: BackendId,
}

impl TestBackend {
    pub(super) fn new(id: &str) -> Self {
        Self {
            id: BackendId::new(id),
        }
    }
}

impl MarkupBackend for TestBackend {
    fn id(&self) -> BackendId {
        self.id.clone()
    }

    fn decode(
        &self,
        input: &str,
        _opts: &MarkupDecodeOptions,
    ) -> Result<(MarkupDoc, MarkupFidelity), MarkupError> {
        Ok((decode_markup_doc(input), MarkupFidelity::exact(self.id())))
    }

    fn encode(
        &self,
        doc: &MarkupDoc,
        _opts: &MarkupEncodeOptions,
    ) -> Result<(String, MarkupFidelity), MarkupError> {
        Ok((doc.to_source_text(), MarkupFidelity::exact(self.id())))
    }
}

#[allow(dead_code)]
pub(super) fn _number(value: usize) -> Expr {
    Expr::Number(NumberLiteral {
        domain: Symbol::qualified("numbers", "f64"),
        canonical: value.to_string(),
    })
}