Markdown parser fork with extended syntax for personal use.
at hack 179 lines 5.4 kB view raw
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}