raw
1use nom::{
2 IResult, Parser,
3 branch::alt,
4 bytes::complete::tag,
5 character::complete::{anychar, char, none_of, one_of, satisfy},
6 combinator::{map, not, peek, recognize, value, verify},
7 multi::{fold_many0, many0, many1},
8 sequence::{delimited, preceded},
9};
10
11pub(super) fn link_label<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, Vec<crate::ast::Inline>> {
12 move |input: &'a str| delimited(tag("["), link_label_inner(), tag("]")).parse(input)
13}
14
15fn link_label_inner<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, Vec<crate::ast::Inline>> {
16 move |input: &'a str| {
17 let (input, label_chars) = verify(
18 many1(preceded(
19 peek(not(char(']'))),
20 alt((value(']', tag("\\]")), anychar)),
21 )),
22 |chars: &[char]| chars.iter().any(|&c| c != ' ' && c != '\n') && chars.len() < 1000,
23 )
24 .parse(input)?;
25
26 let label = label_chars.iter().collect::<String>();
27
28 let (_, label) = crate::parser::inline::inline_many1()
29 .parse(label.as_str())
30 .map_err(|err| err.map_input(|_| input))?;
31
32 Ok((input, label))
33 }
34}
35
36pub(super) fn link_title(input: &str) -> IResult<&str, String> {
37 alt((
38 link_title_double_quoted,
39 link_title_single_quoted,
40 link_title_parenthesized,
41 ))
42 .parse(input)
43}
44
45fn link_title_parenthesized(input: &str) -> IResult<&str, String> {
46 delimited(char('('), link_title_inner(')'), char(')')).parse(input)
47}
48
49fn link_title_single_quoted(input: &str) -> IResult<&str, String> {
50 delimited(char('\''), link_title_inner('\''), char('\'')).parse(input)
51}
52
53fn link_title_double_quoted(input: &str) -> IResult<&str, String> {
54 delimited(tag("\""), link_title_inner('"'), tag("\"")).parse(input)
55}
56
57fn link_title_inner(end_delim: char) -> impl FnMut(&str) -> IResult<&str, String> {
58 move |input: &str| {
59 fold_many0(
60 alt((
61 map(escaped_char, |c| c.to_string()),
62 map(none_of(&[end_delim, '\\'][..]), |c| c.to_string()),
63 )),
64 String::new,
65 |mut acc, s| {
66 acc.push_str(&s);
67 acc
68 },
69 )
70 .parse(input)
71 }
72}
73
74fn escaped_char(input: &str) -> IResult<&str, char> {
75 preceded(tag("\\"), anychar).parse(input)
76}
77
78pub(super) fn link_destination(input: &str) -> IResult<&str, String> {
79 alt((link_destination1, link_destination2)).parse(input)
80}
81
82fn link_destination1(input: &str) -> IResult<&str, String> {
83 let (input, _) = char('<').parse(input)?;
84
85 let (input, chars) = many0(alt((
86 preceded(char('\\'), one_of("<>")),
87 preceded(peek(not(one_of("\n<>"))), anychar),
88 )))
89 .parse(input)?;
90 let (input, _) = char('>').parse(input)?;
91
92 let v: String = chars.iter().collect();
93
94 Ok((input, v))
95}
96
97fn link_destination2(input: &str) -> IResult<&str, String> {
98 let (input, _) = peek(satisfy(|c| is_valid_char(c) && c != '<')).parse(input)?;
99
100 map(
101 recognize(many1(alt((
102 value((), escaped_char),
103 value((), balanced_parens),
104 value((), satisfy(|c| is_valid_char(c) && c != '(' && c != ')')),
105 )))),
106 |s: &str| s.to_string(),
107 )
108 .parse(input)
109}
110
111fn balanced_parens(input: &str) -> IResult<&str, String> {
112 delimited(
113 tag("("),
114 map(
115 fold_many0(
116 alt((
117 map(escaped_char, |c| c.to_string()),
118 map(balanced_parens, |s| format!("({s})")),
119 map(satisfy(|c| is_valid_char(c) && c != '(' && c != ')'), |c| {
120 c.to_string()
121 }),
122 )),
123 String::new,
124 |mut acc, item| {
125 acc.push_str(&item);
126 acc
127 },
128 ),
129 |s| s,
130 ),
131 tag(")"),
132 )
133 .parse(input)
134}
135
136const fn is_valid_char(c: char) -> bool {
137 !c.is_ascii_control() && c != ' ' && c != '<'
138}
139