use crate::ast::parsers::hooks::{AstParser, BlockParseContext, InlineParseContext};
use crate::ast::parsers::visitor::{AstVisitor, InlineVisitControl, VisitControl};
use crate::ast::tokens::token::{Block, Inline};
use napi::bindgen_prelude::{Env, Function, FunctionRef};
use napi_derive::napi;
#[napi(object)]
#[derive(Debug, Default, Clone)]
pub struct VisitControlJs {
pub insert_before: Option<Vec<Block>>,
pub insert_after: Option<Vec<Block>>,
pub replace_with: Option<Vec<Block>>,
pub remove: Option<bool>,
pub recurse: Option<bool>,
}
impl From<VisitControlJs> for VisitControl {
fn from(js: VisitControlJs) -> Self {
VisitControl {
insert_before: js.insert_before.unwrap_or_default(),
insert_after: js.insert_after.unwrap_or_default(),
replace_with: js.replace_with,
remove: js.remove.unwrap_or(false),
recurse: js.recurse.unwrap_or(false),
}
}
}
#[napi(object)]
#[derive(Debug, Default, Clone)]
pub struct InlineVisitControlJs {
pub insert_before: Option<Vec<Inline>>,
pub insert_after: Option<Vec<Inline>>,
pub replace_with: Option<Vec<Inline>>,
pub remove: Option<bool>,
pub recurse: Option<bool>,
}
impl From<InlineVisitControlJs> for InlineVisitControl {
fn from(js: InlineVisitControlJs) -> Self {
InlineVisitControl {
insert_before: js.insert_before.unwrap_or_default(),
insert_after: js.insert_after.unwrap_or_default(),
replace_with: js.replace_with,
remove: js.remove.unwrap_or(false),
recurse: js.recurse.unwrap_or(false),
}
}
}
#[napi]
pub type BlockCallback = FunctionRef<(Block,), Option<VisitControlJs>>;
#[napi]
pub type InlineCallback = FunctionRef<(Inline,), Option<InlineVisitControlJs>>;
#[napi(object, object_to_js = false)]
#[derive(Default)]
pub struct Visitor {
pub visit_block: Option<BlockCallback>,
pub visit_inline: Option<InlineCallback>,
}
impl Visitor {
pub(crate) fn register(self, env: Env) -> RegisteredJsVisitor {
RegisteredJsVisitor {
visit_block: self.visit_block,
visit_inline: self.visit_inline,
env,
}
}
}
pub(crate) struct RegisteredJsVisitor {
visit_block: Option<BlockCallback>,
visit_inline: Option<InlineCallback>,
env: Env,
}
impl AstVisitor for RegisteredJsVisitor {
fn visit_block(&mut self, block: &mut Block) -> VisitControl {
let cb = match &self.visit_block {
Some(cb) => cb,
_ => return VisitControl::default(),
};
let func: Function<(Block,), Option<VisitControlJs>> = match cb.borrow_back(&self.env) {
Ok(f) => f,
Err(_) => return VisitControl::default(),
};
let snapshot = block.clone();
let js_ctrl = func.call((snapshot,)).unwrap_or(None).unwrap_or_default();
js_ctrl.into()
}
fn visit_inline(&mut self, inline: &mut Inline) -> InlineVisitControl {
let cb = match &self.visit_inline {
Some(cb) => cb,
_ => return InlineVisitControl::default(),
};
let func: Function<(Inline,), Option<InlineVisitControlJs>> =
match cb.borrow_back(&self.env) {
Ok(f) => f,
Err(_) => return InlineVisitControl::default(),
};
let snapshot = inline.clone();
let js_ctrl = func.call((snapshot,)).unwrap_or(None).unwrap_or_default();
js_ctrl.into()
}
}
#[napi(object)]
#[derive(Debug, Default, Clone)]
pub struct BlockParseResultJs {
pub block: Option<Block>,
pub consumed: Option<u32>,
}
#[napi(object)]
#[derive(Debug, Default, Clone)]
pub struct InlineParseResultJs {
pub inline: Option<Inline>,
pub consumed: Option<u32>,
}
#[napi]
pub type BlockParseCallback = FunctionRef<(BlockParseContext,), Option<BlockParseResultJs>>;
#[napi]
pub type InlineParseCallback = FunctionRef<(InlineParseContext,), Option<InlineParseResultJs>>;
#[napi(object, object_to_js = false)]
#[derive(Default)]
pub struct Parser {
pub parse_block: Option<BlockParseCallback>,
pub parse_inline: Option<InlineParseCallback>,
}
impl Parser {
pub(crate) fn register(self, env: Env) -> RegisteredJsParser {
RegisteredJsParser {
parse_block: self.parse_block,
parse_inline: self.parse_inline,
env,
}
}
}
pub(crate) struct RegisteredJsParser {
parse_block: Option<BlockParseCallback>,
parse_inline: Option<InlineParseCallback>,
env: Env,
}
impl AstParser for RegisteredJsParser {
fn try_parse_block(&self, ctx: &BlockParseContext) -> Option<(Block, usize)> {
let cb = self.parse_block.as_ref()?;
let func: Function<(BlockParseContext,), Option<BlockParseResultJs>> =
match cb.borrow_back(&self.env) {
Ok(f) => f,
Err(e) => {
eprintln!("peisar: parseBlock hook could not be invoked: {e}");
return None;
}
};
let result = match func.call((ctx.clone(),)) {
Ok(r) => r?,
Err(e) => {
eprintln!(
"peisar: parseBlock hook returned an invalid result: {e}\n \
note: the returned `block` must be a complete node — every \
field (including `pos` with line/column/offset) is required"
);
return None;
}
};
let block = result.block?;
let consumed = result.consumed.unwrap_or(1).max(1) as usize;
Some((block, consumed))
}
fn try_parse_inline(&self, ctx: &InlineParseContext) -> Option<(Inline, usize)> {
let cb = self.parse_inline.as_ref()?;
let func: Function<(InlineParseContext,), Option<InlineParseResultJs>> =
match cb.borrow_back(&self.env) {
Ok(f) => f,
Err(e) => {
eprintln!("peisar: parseInline hook could not be invoked: {e}");
return None;
}
};
let result = match func.call((ctx.clone(),)) {
Ok(r) => r?,
Err(e) => {
eprintln!(
"peisar: parseInline hook returned an invalid result: {e}\n \
note: the returned `inline` must be a complete node — every \
field (including `pos` with line/column/offset) is required"
);
return None;
}
};
let inline = result.inline?;
let consumed = result.consumed.unwrap_or(0) as usize;
Some((inline, consumed))
}
}