1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use alloc::string::String;

/// Signal used as feedback when parsing MDX ESM/expressions.
#[derive(Clone, Debug)]
pub enum Signal {
    /// A syntax error.
    ///
    /// `markdown-rs` will crash with error message `String`, and convert the
    /// `usize` (byte offset into `&str` passed to `MdxExpressionParse` or
    /// `MdxEsmParse`) to where it happened in the whole document.
    ///
    /// ## Examples
    ///
    /// ```rust ignore
    /// Signal::Error("Unexpected `\"`, expected identifier".into(), 1)
    /// ```
    Error(String, usize),
    /// An error at the end of the (partial?) expression.
    ///
    /// `markdown-rs` will either crash with error message `String` if it
    /// doesn’t have any more text, or it will try again later when more text
    /// is available.
    ///
    /// ## Examples
    ///
    /// ```rust ignore
    /// Signal::Eof("Unexpected end of file in string literal".into())
    /// ```
    Eof(String),
    /// Done, successfully.
    ///
    /// `markdown-rs` knows that this is the end of a valid expression/esm and
    /// continues with markdown.
    ///
    /// ## Examples
    ///
    /// ```rust ignore
    /// Signal::Ok
    /// ```
    Ok,
}

/// Signature of a function that parses MDX ESM.
///
/// Can be passed as `mdx_esm_parse` in
/// [`ParseOptions`][crate::configuration::ParseOptions] to support
/// ESM according to a certain grammar (typically, a programming language).
pub type EsmParse = dyn Fn(&str) -> Signal;

/// Expression kind.
#[derive(Clone, Debug)]
pub enum ExpressionKind {
    /// Kind of expressions in prose.
    ///
    /// ```mdx
    /// > | # {Math.PI}
    ///       ^^^^^^^^^
    ///   |
    /// > | {Math.PI}
    ///     ^^^^^^^^^
    /// ```
    Expression,
    /// Kind of expressions as attributes.
    ///
    /// ```mdx
    /// > | <a {...b}>
    ///        ^^^^^^
    /// ```
    AttributeExpression,
    /// Kind of expressions as attribute values.
    ///
    /// ```mdx
    /// > | <a b={c}>
    ///          ^^^
    /// ```
    AttributeValueExpression,
}

/// Signature of a function that parses MDX expressions.
///
/// Can be passed as `mdx_expression_parse` in
/// [`ParseOptions`][crate::configuration::ParseOptions] to support
/// expressions according to a certain grammar (typically, a programming
/// language).
///
pub type ExpressionParse = dyn Fn(&str, &ExpressionKind) -> Signal;

#[cfg(test)]
mod tests {
    use super::*;
    use alloc::boxed::Box;

    #[test]
    fn test_mdx_expression_parse() {
        fn func(_value: &str, _kind: &ExpressionKind) -> Signal {
            Signal::Ok
        }

        let func_accepting = |_a: Box<ExpressionParse>| true;

        assert!(
            matches!(func("a", &ExpressionKind::Expression), Signal::Ok),
            "should expose an `ExpressionParse` type (1)"
        );

        assert!(
            func_accepting(Box::new(func)),
            "should expose an `ExpressionParse` type (2)"
        );
    }

    #[test]
    fn test_mdx_esm_parse() {
        fn func(_value: &str) -> Signal {
            Signal::Ok
        }

        let func_accepting = |_a: Box<EsmParse>| true;

        assert!(
            matches!(func("a"), Signal::Ok),
            "should expose an `EsmParse` type (1)"
        );

        assert!(
            func_accepting(Box::new(func)),
            "should expose an `EsmParse` type (2)"
        );
    }
}