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}