use std::sync::Arc;
use sim_codec::{
CodecDefaultDecode, CodecRuntime, DecodeBudget, Decoder, Encoder, Input, Output, ReadCx,
codec_value,
};
use sim_kernel::{
AbiVersion, Cx, DefaultFactory, Dependency, Error, Export, Lib, LibManifest, LibTarget, Linker,
Result, Symbol, Version, WriteCx,
};
use crate::backend::{
BackendRegistry, MarkupBackend, MarkupDecodeOptions, MarkupEncodeOptions, MarkupError,
default_backend_registry,
};
use crate::document::DocValue;
use crate::functions::{CHUNK_FUNCTIONS, DocChunkFunction};
use crate::markdown::MarkdownBackend;
use crate::markup::MarkupDoc;
pub const MARKUP_CODEC_PREFIX: &str = "codec/markup/";
pub fn markup_codec_symbol(id: &crate::BackendId) -> Symbol {
Symbol::qualified("codec", format!("markup/{}", id.as_str()))
}
pub struct MarkupCodec {
backend: Arc<dyn MarkupBackend>,
}
impl MarkupCodec {
pub fn new(backend: Arc<dyn MarkupBackend>) -> Self {
Self { backend }
}
pub fn backend_id(&self) -> crate::BackendId {
self.backend.id()
}
}
impl Decoder for MarkupCodec {
fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<sim_kernel::Expr> {
let source = input.into_string()?;
let budget = DecodeBudget::new(cx.limits);
budget.check_input_bytes(cx.codec, source.len())?;
let (doc, _fidelity) = self
.backend
.decode(&source, &MarkupDecodeOptions::default())
.map_err(|err| err.into_kernel_error(cx.codec))?;
Ok(doc.as_expr())
}
}
impl Encoder for MarkupCodec {
fn encode(&self, cx: &mut WriteCx<'_>, expr: &sim_kernel::Expr) -> Result<Output> {
let doc = MarkupDoc::from_expr(expr).map_err(|err| {
MarkupError::InvalidDocument(err.to_string()).into_kernel_error(cx.codec)
})?;
let (source, fidelity) = self
.backend
.encode(&doc, &MarkupEncodeOptions::default())
.map_err(|err| err.into_kernel_error(cx.codec))?;
if !fidelity.dropped.is_empty() {
return Err(MarkupError::Encode(format!(
"backend {} reported {} dropped part(s)",
fidelity.backend,
fidelity.dropped.len()
))
.into_kernel_error(cx.codec));
}
Ok(Output::Text(source))
}
}
pub struct DocCodec;
impl Decoder for DocCodec {
fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<sim_kernel::Expr> {
let source = input.into_string()?;
let budget = DecodeBudget::new(cx.limits);
budget.check_input_bytes(cx.codec, source.len())?;
let (doc, _fidelity) = MarkdownBackend
.decode(&source, &MarkupDecodeOptions::default())
.map_err(|err| err.into_kernel_error(cx.codec))?;
Ok(doc.as_expr())
}
}
impl Encoder for DocCodec {
fn encode(&self, cx: &mut WriteCx<'_>, expr: &sim_kernel::Expr) -> Result<Output> {
match expr {
sim_kernel::Expr::String(text) => Ok(Output::Text(text.clone())),
sim_kernel::Expr::Map(_) => Ok(Output::Text(encode_doc_expr(expr).map_err(|err| {
Error::CodecError {
codec: cx.codec,
message: err.to_string(),
}
})?)),
_ => Err(Error::CodecError {
codec: cx.codec,
message: "codec:doc encodes document values or strings".to_owned(),
}),
}
}
}
fn encode_doc_expr(expr: &sim_kernel::Expr) -> Result<String> {
match MarkupDoc::from_expr(expr) {
Ok(doc) => Ok(doc.to_source_text()),
Err(markup_error) => match DocValue::from_expr(expr) {
Ok(doc) => Ok(doc.text),
Err(_) => Err(markup_error),
},
}
}
struct RegisteredBackend {
backend: Arc<dyn MarkupBackend>,
codec_id: sim_kernel::CodecId,
}
struct MarkupCodecLib {
backends: Vec<RegisteredBackend>,
}
impl MarkupCodecLib {
fn new(cx: &mut Cx, registry: BackendRegistry) -> Self {
let backends = registry
.iter()
.map(|(_, backend)| RegisteredBackend {
backend: backend.clone(),
codec_id: cx.registry_mut().fresh_codec_id(),
})
.collect();
Self { backends }
}
}
impl Lib for MarkupCodecLib {
fn manifest(&self) -> LibManifest {
LibManifest {
id: Symbol::qualified("codec", "markup"),
version: Version(env!("CARGO_PKG_VERSION").to_owned()),
abi: AbiVersion { major: 0, minor: 1 },
target: LibTarget::HostRegistered,
requires: Vec::<Dependency>::new(),
capabilities: Vec::new(),
exports: self
.backends
.iter()
.map(|registered| Export::Codec {
symbol: markup_codec_symbol(®istered.backend.id()),
codec_id: Some(registered.codec_id),
})
.collect(),
}
}
fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
let expr_shape =
sim_codec::resolve_expr_shape(linker, &Symbol::qualified("codec", "Document"))?;
let options_shape = sim_codec::resolve_options_shape(linker)?;
for registered in &self.backends {
let symbol = markup_codec_symbol(®istered.backend.id());
linker.codec_value(
symbol.clone(),
codec_value(CodecRuntime {
id: registered.codec_id,
symbol,
decoder: Some(Arc::new(MarkupCodec::new(registered.backend.clone()))),
located_decoder: None,
tree_decoder: None,
encoder: Some(Arc::new(MarkupCodec::new(registered.backend.clone()))),
located_encoder: None,
tree_encoder: None,
expr_shape: expr_shape.clone(),
options_shape: options_shape.clone(),
default_decode: CodecDefaultDecode::Datum,
}),
)?;
}
Ok(())
}
}
pub struct DocCodecLib {
symbol: Symbol,
codec_id: sim_kernel::CodecId,
}
impl DocCodecLib {
pub fn new(id: sim_kernel::CodecId) -> Self {
Self {
symbol: Symbol::qualified("codec", "doc"),
codec_id: id,
}
}
}
impl Lib for DocCodecLib {
fn manifest(&self) -> LibManifest {
let mut exports = vec![Export::Codec {
symbol: self.symbol.clone(),
codec_id: Some(self.codec_id),
}];
exports.extend(CHUNK_FUNCTIONS.iter().map(|kind| Export::Function {
symbol: kind.symbol(),
function_id: None,
}));
LibManifest {
id: self.symbol.clone(),
version: Version(env!("CARGO_PKG_VERSION").to_owned()),
abi: AbiVersion { major: 0, minor: 1 },
target: LibTarget::HostRegistered,
requires: Vec::<Dependency>::new(),
capabilities: Vec::new(),
exports,
}
}
fn load(&self, cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
let _factory = DefaultFactory;
let expr_shape =
sim_codec::resolve_expr_shape(linker, &Symbol::qualified("codec", "Document"))?;
let options_shape = sim_codec::resolve_options_shape(linker)?;
linker.codec_value(
self.symbol.clone(),
codec_value(CodecRuntime {
id: self.codec_id,
symbol: self.symbol.clone(),
decoder: Some(Arc::new(DocCodec)),
located_decoder: None,
tree_decoder: None,
encoder: Some(Arc::new(DocCodec)),
located_encoder: None,
tree_encoder: None,
expr_shape,
options_shape,
default_decode: CodecDefaultDecode::Datum,
}),
)?;
for kind in CHUNK_FUNCTIONS {
linker.function_value(
kind.symbol(),
cx.factory()
.opaque(Arc::new(DocChunkFunction::new(*kind)))?,
)?;
}
Ok(())
}
}
pub fn install_doc_codec(cx: &mut Cx) -> Result<()> {
install_markup_codecs(cx, default_backend_registry())?;
let lib = DocCodecLib::new(cx.registry_mut().fresh_codec_id());
cx.load_lib(&lib)?;
Ok(())
}
pub fn install_markup_codecs(cx: &mut Cx, registry: BackendRegistry) -> Result<()> {
if registry.is_empty() {
return Err(Error::Lib("markup backend registry is empty".to_owned()));
}
let lib = MarkupCodecLib::new(cx, registry);
cx.load_lib(&lib)?;
Ok(())
}