sable-markdown/src/parser/blocks/code_block.rs@main
raw
1use nom::{
2 IResult, Parser,
3 branch::alt,
4 bytes::complete::tag,
5 character::complete::char,
6 combinator::{not, opt, peek, recognize, value},
7 multi::{many_m_n, many0, many1},
8 sequence::preceded,
9};
10
11use crate::{
12 ast::{CodeBlock, CodeBlockKind},
13 parser::util::{line_terminated, not_eof_or_eol0, not_eof_or_eol1},
14};
15
16pub(super) fn code_block<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, CodeBlock> {
17 move |input: &'a str| alt((code_block_indented(), code_block_fenced())).parse(input)
18}
19
20pub(super) fn code_block_indented<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, CodeBlock> {
21 move |input: &'a str| {
22 let line_parser = preceded(
23 alt((value((), many_m_n(4, 4, char(' '))), value((), char('\t')))),
24 line_terminated(not_eof_or_eol0),
25 );
26
27 let (input, lines) = many1(line_parser).parse(input)?;
28 let literal = lines.join("\n");
29
30 let code_block = CodeBlock {
31 kind: CodeBlockKind::Indented,
32 literal,
33 };
34
35 Ok((input, code_block))
36 }
37}
38
39pub(super) fn code_block_fenced<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, CodeBlock> {
40 move |input: &'a str| {
41 let (input, space_prefix) = many_m_n(0, 3, char(' ')).parse(input)?;
42 let prefix_length = space_prefix.len();
43
44 let (input, (fence, info)) = line_terminated((
45 recognize(alt((
46 many_m_n(3, usize::MAX, char('`')),
47 many_m_n(3, usize::MAX, char('~')),
48 ))),
49 opt(recognize(not_eof_or_eol1)),
50 ))
51 .parse(input)?;
52 let ending_fence = || {
53 line_terminated((
54 many_m_n(0, 3, char(' ')),
55 tag(fence),
56 many0(char(fence.chars().next().unwrap())),
57 ))
58 };
59
60 let (input, lines) = many0(preceded(
61 peek(not(ending_fence())),
62 preceded(
63 many_m_n(0, prefix_length, char(' ')),
64 line_terminated(not_eof_or_eol0),
65 ),
66 ))
67 .parse(input)?;
68 let (input, _) = ending_fence().parse(input)?;
69
70 let literal = lines.join("\n");
71 let code_block = CodeBlock {
72 kind: CodeBlockKind::Fenced {
73 info: info.map(ToOwned::to_owned),
74 },
75 literal,
76 };
77
78 Ok((input, code_block))
79 }
80}
81