Skip to main content

socketry_markdown/util/
mdx.rs

1// Released under the MIT License.
2// Copyright, 2022, by Bernhard Berger.
3// Copyright, 2022-2024, by Titus Wormer.
4// Copyright, 2026, by Samuel Williams.
5
6use alloc::{boxed::Box, string::String};
7
8/// Signal used as feedback when parsing MDX ESM/expressions.
9#[derive(Clone, Debug)]
10pub enum Signal {
11    /// A syntax error.
12    ///
13    /// `markdown-rs` will crash with error message `String`, and convert the
14    /// `usize` (byte offset into `&str` passed to `MdxExpressionParse` or
15    /// `MdxEsmParse`) to where it happened in the whole document.
16    ///
17    /// ## Examples
18    ///
19    /// ```rust ignore
20    /// Signal::Error("Unexpected `\"`, expected identifier".into(), 1)
21    /// ```
22    Error(String, usize, Box<String>, Box<String>),
23    /// An error at the end of the (partial?) expression.
24    ///
25    /// `markdown-rs` will either crash with error message `String` if it
26    /// doesn’t have any more text, or it will try again later when more text
27    /// is available.
28    ///
29    /// ## Examples
30    ///
31    /// ```rust ignore
32    /// Signal::Eof("Unexpected end of file in string literal".into())
33    /// ```
34    Eof(String, Box<String>, Box<String>),
35    /// Done, successfully.
36    ///
37    /// `markdown-rs` knows that this is the end of a valid expression/esm and
38    /// continues with markdown.
39    ///
40    /// ## Examples
41    ///
42    /// ```rust ignore
43    /// Signal::Ok
44    /// ```
45    Ok,
46}
47
48/// Signature of a function that parses MDX ESM.
49///
50/// Can be passed as `mdx_esm_parse` in
51/// [`ParseOptions`][crate::configuration::ParseOptions] to support
52/// ESM according to a certain grammar (typically, a programming language).
53pub type EsmParse = dyn Fn(&str) -> Signal;
54
55/// Expression kind.
56#[derive(Clone, Debug)]
57pub enum ExpressionKind {
58    /// Kind of expressions in prose.
59    ///
60    /// ```mdx
61    /// > | # {Math.PI}
62    ///       ^^^^^^^^^
63    ///   |
64    /// > | {Math.PI}
65    ///     ^^^^^^^^^
66    /// ```
67    Expression,
68    /// Kind of expressions as attributes.
69    ///
70    /// ```mdx
71    /// > | <a {...b}>
72    ///        ^^^^^^
73    /// ```
74    AttributeExpression,
75    /// Kind of expressions as attribute values.
76    ///
77    /// ```mdx
78    /// > | <a b={c}>
79    ///          ^^^
80    /// ```
81    AttributeValueExpression,
82}
83
84/// Signature of a function that parses MDX expressions.
85///
86/// Can be passed as `mdx_expression_parse` in
87/// [`ParseOptions`][crate::configuration::ParseOptions] to support
88/// expressions according to a certain grammar (typically, a programming
89/// language).
90///
91pub type ExpressionParse = dyn Fn(&str, &ExpressionKind) -> Signal;
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use alloc::boxed::Box;
97
98    #[test]
99    fn test_mdx_expression_parse() {
100        fn func(_value: &str, _kind: &ExpressionKind) -> Signal {
101            Signal::Ok
102        }
103
104        let func_accepting = |_a: Box<ExpressionParse>| true;
105
106        assert!(
107            matches!(func("a", &ExpressionKind::Expression), Signal::Ok),
108            "should expose an `ExpressionParse` type (1)"
109        );
110
111        assert!(
112            func_accepting(Box::new(func)),
113            "should expose an `ExpressionParse` type (2)"
114        );
115    }
116
117    #[test]
118    fn test_mdx_esm_parse() {
119        fn func(_value: &str) -> Signal {
120            Signal::Ok
121        }
122
123        let func_accepting = |_a: Box<EsmParse>| true;
124
125        assert!(
126            matches!(func("a"), Signal::Ok),
127            "should expose an `EsmParse` type (1)"
128        );
129
130        assert!(
131            func_accepting(Box::new(func)),
132            "should expose an `EsmParse` type (2)"
133        );
134    }
135}