update card_rendering/parser.rs

This commit is contained in:
llama 2025-06-20 07:45:41 +08:00
parent ef0686b679
commit 001aea4eca
No known key found for this signature in database
GPG key ID: 0B7543854B9413C3

View file

@ -21,7 +21,8 @@ use nom::sequence::pair;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::separated_pair; use nom::sequence::separated_pair;
use nom::sequence::terminated; use nom::sequence::terminated;
use nom::sequence::tuple; use nom::Input;
use nom::Parser;
use super::CardNodes; use super::CardNodes;
use super::Directive; use super::Directive;
@ -86,9 +87,12 @@ impl<'a> Directive<'a> {
} }
/// Consume 0 or more of anything in " \t\r\n" after `parser`. /// Consume 0 or more of anything in " \t\r\n" after `parser`.
fn trailing_whitespace0<'parser, 's, P, O>(parser: P) -> impl FnMut(&'s str) -> IResult<'s, O> fn trailing_whitespace0<I, O, E, P>(parser: P) -> impl Parser<I, Output = O, Error = E>
where where
P: FnMut(&'s str) -> IResult<'s, O> + 'parser, I: Input,
<I as Input>::Item: nom::AsChar,
E: nom::error::ParseError<I>,
P: Parser<I, Output = O, Error = E>,
{ {
terminated(parser, multispace0) terminated(parser, multispace0)
} }
@ -97,11 +101,11 @@ where
fn is_not0<'parser, 'arr: 'parser, 's: 'parser>( fn is_not0<'parser, 'arr: 'parser, 's: 'parser>(
arr: &'arr str, arr: &'arr str,
) -> impl FnMut(&'s str) -> IResult<'s, &'s str> + 'parser { ) -> impl FnMut(&'s str) -> IResult<'s, &'s str> + 'parser {
alt((is_not(arr), success(""))) move |s| alt((is_not(arr), success(""))).parse(s)
} }
fn node(s: &str) -> IResult<Node> { fn node(s: &str) -> IResult<Node> {
alt((sound_node, tag_node, text_node))(s) alt((sound_node, tag_node, text_node)).parse(s)
} }
/// A sound tag `[sound:resource]`, where `resource` is pointing to a sound or /// A sound tag `[sound:resource]`, where `resource` is pointing to a sound or
@ -110,11 +114,11 @@ fn sound_node(s: &str) -> IResult<Node> {
map( map(
delimited(tag("[sound:"), is_not("]"), tag("]")), delimited(tag("[sound:"), is_not("]"), tag("]")),
Node::SoundOrVideo, Node::SoundOrVideo,
)(s) )
.parse(s)
} }
fn take_till_potential_tag_start(s: &str) -> IResult<&str> { fn take_till_potential_tag_start(s: &str) -> IResult<&str> {
use nom::InputTake;
// first char could be '[', but wasn't part of a node, so skip (eof ends parse) // first char could be '[', but wasn't part of a node, so skip (eof ends parse)
let (after, offset) = anychar(s).map(|(s, c)| (s, c.len_utf8()))?; let (after, offset) = anychar(s).map(|(s, c)| (s, c.len_utf8()))?;
Ok(match after.find('[') { Ok(match after.find('[') {
@ -127,7 +131,7 @@ fn take_till_potential_tag_start(s: &str) -> IResult<&str> {
fn tag_node(s: &str) -> IResult<Node> { fn tag_node(s: &str) -> IResult<Node> {
/// Match the start of an opening tag and return its name. /// Match the start of an opening tag and return its name.
fn name(s: &str) -> IResult<&str> { fn name(s: &str) -> IResult<&str> {
preceded(tag("[anki:"), is_not("] \t\r\n"))(s) preceded(tag("[anki:"), is_not("] \t\r\n")).parse(s)
} }
/// Return a parser to match an opening `name` tag and return its options. /// Return a parser to match an opening `name` tag and return its options.
@ -138,31 +142,35 @@ fn tag_node(s: &str) -> IResult<Node> {
/// empty. /// empty.
fn options(s: &str) -> IResult<Vec<(&str, &str)>> { fn options(s: &str) -> IResult<Vec<(&str, &str)>> {
fn key(s: &str) -> IResult<&str> { fn key(s: &str) -> IResult<&str> {
is_not("] \t\r\n=")(s) is_not("] \t\r\n=").parse(s)
} }
fn val(s: &str) -> IResult<&str> { fn val(s: &str) -> IResult<&str> {
alt(( alt((
delimited(tag("\""), is_not0("\""), tag("\"")), delimited(tag("\""), is_not0("\""), tag("\"")),
is_not0("] \t\r\n\""), is_not0("] \t\r\n\""),
))(s) ))
.parse(s)
} }
many0(trailing_whitespace0(separated_pair(key, tag("="), val)))(s) many0(trailing_whitespace0(separated_pair(key, tag("="), val))).parse(s)
} }
delimited( move |s| {
pair(tag("[anki:"), trailing_whitespace0(tag(name))), delimited(
options, pair(tag("[anki:"), trailing_whitespace0(tag(name))),
tag("]"), options,
) tag("]"),
)
.parse(s)
}
} }
/// Return a parser to match a closing `name` tag. /// Return a parser to match a closing `name` tag.
fn closing_parser<'parser, 'name: 'parser, 's: 'parser>( fn closing_parser<'parser, 'name: 'parser, 's: 'parser>(
name: &'name str, name: &'name str,
) -> impl FnMut(&'s str) -> IResult<'s, ()> + 'parser { ) -> impl FnMut(&'s str) -> IResult<'s, ()> + 'parser {
value((), tuple((tag("[/anki:"), tag(name), tag("]")))) move |s| value((), (tag("[/anki:"), tag(name), tag("]"))).parse(s)
} }
/// Return a parser to match and return anything until a closing `name` tag /// Return a parser to match and return anything until a closing `name` tag
@ -170,12 +178,15 @@ fn tag_node(s: &str) -> IResult<Node> {
fn content_parser<'parser, 'name: 'parser, 's: 'parser>( fn content_parser<'parser, 'name: 'parser, 's: 'parser>(
name: &'name str, name: &'name str,
) -> impl FnMut(&'s str) -> IResult<'s, &'s str> + 'parser { ) -> impl FnMut(&'s str) -> IResult<'s, &'s str> + 'parser {
recognize(fold_many0( move |s| {
pair(not(closing_parser(name)), take_till_potential_tag_start), recognize(fold_many0(
// we don't need to accumulate anything pair(not(closing_parser(name)), take_till_potential_tag_start),
|| (), // we don't need to accumulate anything
|_, _| (), || (),
)) |_, _| (),
))
.parse(s)
}
} }
let (_, tag_name) = name(s)?; let (_, tag_name) = name(s)?;
@ -185,11 +196,12 @@ fn tag_node(s: &str) -> IResult<Node> {
closing_parser(tag_name), closing_parser(tag_name),
), ),
|(options, content)| Node::Directive(Directive::new(tag_name, options, content)), |(options, content)| Node::Directive(Directive::new(tag_name, options, content)),
)(s) )
.parse(s)
} }
fn text_node(s: &str) -> IResult<Node> { fn text_node(s: &str) -> IResult<Node> {
map(take_till_potential_tag_start, Node::Text)(s) map(take_till_potential_tag_start, Node::Text).parse(s)
} }
#[cfg(test)] #[cfg(test)]