Markdown parser fork with extended syntax for personal use.
1//! MDX expression (flow) occurs in the [flow][] content type.
2//!
3//! ## Grammar
4//!
5//! MDX expression (flow) forms with the following BNF
6//! (<small>see [construct][crate::construct] for character groups</small>):
7//!
8//! ```bnf
9//! mdx_expression_flow ::= mdx_expression *space_or_tab
10//!
11//! ; See the `partial_mdx_expression` construct for the BNF of that part.
12//! ```
13//!
14//! As this construct occurs in flow, like all flow constructs, it must be
15//! followed by an eol (line ending) or eof (end of file).
16//!
17//! See [`mdx_expression`][mdx_expression] for more info.
18//!
19//! ## Tokens
20//!
21//! * [`MdxFlowExpression`][Name::MdxFlowExpression]
22//! * [`SpaceOrTab`][Name::SpaceOrTab]
23//! * see [`mdx_expression`][mdx_expression] for more
24//!
25//! ## Recommendation
26//!
27//! See [`mdx_expression`][mdx_expression] for recommendations.
28//!
29//! ## References
30//!
31//! * [`syntax.js` in `micromark-extension-mdx-expression`](https://github.com/micromark/micromark-extension-mdx-expression/blob/main/packages/micromark-extension-mdx-expression/dev/lib/syntax.js)
32//! * [`mdxjs.com`](https://mdxjs.com)
33//!
34//! [flow]: crate::construct::flow
35//! [mdx_expression]: crate::construct::partial_mdx_expression
36
37use crate::construct::partial_space_or_tab::{space_or_tab, space_or_tab_min_max};
38use crate::event::Name;
39use crate::state::{Name as StateName, State};
40use crate::tokenizer::Tokenizer;
41use crate::util::constant::TAB_SIZE;
42
43/// Start of an MDX expression (flow).
44///
45/// ```markdown
46/// > | {Math.PI}
47/// ^
48/// ```
49pub fn start(tokenizer: &mut Tokenizer) -> State {
50 if tokenizer.parse_state.options.constructs.mdx_expression_flow {
51 tokenizer.tokenize_state.token_1 = Name::MdxFlowExpression;
52 if matches!(tokenizer.current, Some(b'\t' | b' ')) {
53 tokenizer.attempt(State::Next(StateName::MdxExpressionFlowBefore), State::Nok);
54 State::Retry(space_or_tab_min_max(
55 tokenizer,
56 0,
57 if tokenizer.parse_state.options.constructs.code_indented {
58 TAB_SIZE - 1
59 } else {
60 usize::MAX
61 },
62 ))
63 } else {
64 State::Retry(StateName::MdxExpressionFlowBefore)
65 }
66 } else {
67 State::Nok
68 }
69}
70
71/// After optional whitespace, before expression.
72///
73/// ```markdown
74/// > | {Math.PI}
75/// ^
76/// ```
77pub fn before(tokenizer: &mut Tokenizer) -> State {
78 if Some(b'{') == tokenizer.current {
79 tokenizer.concrete = true;
80 tokenizer.attempt(State::Next(StateName::MdxExpressionFlowAfter), State::Nok);
81 State::Retry(StateName::MdxExpressionStart)
82 } else {
83 State::Nok
84 }
85}
86
87/// After expression.
88///
89/// ```markdown
90/// > | {Math.PI}
91/// ^
92/// ```
93pub fn after(tokenizer: &mut Tokenizer) -> State {
94 match tokenizer.current {
95 Some(b'\t' | b' ') => {
96 tokenizer.attempt(State::Next(StateName::MdxExpressionFlowEnd), State::Nok);
97 State::Retry(space_or_tab(tokenizer))
98 }
99 _ => State::Retry(StateName::MdxExpressionFlowEnd),
100 }
101}
102
103/// After expression, after optional whitespace.
104///
105/// ```markdown
106/// > | {Math.PI}␠␊
107/// ^
108/// ```
109pub fn end(tokenizer: &mut Tokenizer) -> State {
110 // We want to allow tags directly after expressions.
111 //
112 // This case is useful:
113 //
114 // ```mdx
115 // <a>{b}</a>
116 // ```
117 //
118 // This case is not (very?) useful:
119 //
120 // ```mdx
121 // {a}<b/>
122 // ```
123 //
124 // …but it would be tougher than needed to disallow.
125 //
126 // To allow that, here we call the MDX JSX flow construct, and there we
127 // call this one.
128 //
129 // It would introduce a cyclical interdependency if we test JSX and
130 // expressions here.
131 // Because the JSX extension already uses parts of this monorepo, we
132 // instead test it there.
133 //
134 // Note: in the JS version of micromark, arbitrary extensions could be
135 // loaded.
136 // Here we know that only our own construct `mdx_expression_flow` can be
137 // enabled.
138
139 // if matches!(tokenizer.current, None | Some(b'\n')) {
140 // State::Ok
141 // } else {
142 // State::Nok
143 // }
144 match tokenizer.current {
145 None | Some(b'\n') => {
146 reset(tokenizer);
147 State::Ok
148 }
149 // Tag.
150 Some(b'<') if tokenizer.parse_state.options.constructs.mdx_jsx_flow => {
151 // We can’t just say: fine.
152 // Lines of blocks have to be parsed until an eol/eof.
153 tokenizer.tokenize_state.token_1 = Name::MdxJsxFlowTag;
154 tokenizer.attempt(
155 State::Next(StateName::MdxJsxFlowAfter),
156 State::Next(StateName::MdxJsxFlowNok),
157 );
158 State::Retry(StateName::MdxJsxStart)
159 }
160 // // An expression.
161 // Some(b'{') if tokenizer.parse_state.options.constructs.mdx_expression_flow => {
162 // tokenizer.attempt(
163 // State::Next(StateName::MdxExpressionFlowAfter),
164 // State::Next(StateName::MdxExpressionFlowNok),
165 // );
166 // State::Retry(StateName::MdxExpressionFlowStart)
167 // }
168 _ => {
169 reset(tokenizer);
170 State::Nok
171 }
172 }
173}
174
175/// Reset state.
176fn reset(tokenizer: &mut Tokenizer) {
177 tokenizer.concrete = false;
178 tokenizer.tokenize_state.token_1 = Name::Data;
179}