Something went wrong. Try again.
Markdown parser fork with extended syntax for personal use.
Something went wrong. Try again.
6.8 kB · 238 lines
Rust
at hack
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239//! MDX ESM occurs in the [flow][] content type.//!//! ## Grammar//!//! MDX expression (flow) forms with the following BNF//! (<small>see [construct][crate::construct] for character groups</small>)://!//! ```bnf//! mdx_esm ::= word *line *(eol *line)//!//! word ::= 'e' 'x' 'p' 'o' 'r' 't' | 'i' 'm' 'p' 'o' 'r' 't'//! ```//!//! This construct must be followed by a blank line or eof (end of file).//! It can include blank lines if [`MdxEsmParse`][crate::MdxEsmParse] passed in//! [`ParseOptions`][parse_options] allows it.//!//! ## Tokens//!//! * [`LineEnding`][Name::LineEnding]//! * [`MdxEsm`][Name::MdxEsm]//! * [`MdxEsmData`][Name::MdxEsmData]//!//! ## References//!//! * [`syntax.js` in `micromark-extension-mdxjs-esm`](https://github.com/micromark/micromark-extension-mdxjs-esm/blob/main/dev/lib/syntax.js)//! * [`mdxjs.com`](https://mdxjs.com)//!//! [flow]: crate::construct::flow//! [parse_options]: crate::ParseOptions
use crate::event::Name;use crate::message;use crate::state::{Name as StateName, State};use crate::tokenizer::Tokenizer;use crate::util::{mdx_collect::collect, slice::Slice};use crate::MdxSignal;use alloc::boxed::Box;
/// Start of MDX ESM.////// ```markdown/// > | import a from 'b'/// ^/// ```pub fn start(tokenizer: &mut Tokenizer) -> State { // If it’s turned on. if tokenizer.parse_state.options.constructs.mdx_esm // If there is a gnostic parser. && tokenizer.parse_state.options.mdx_esm_parse.is_some() // When not interrupting. && !tokenizer.interrupt // Only at the start of a line, not at whitespace or in a container. && tokenizer.point.column == 1 && matches!(tokenizer.current, Some(b'e' | b'i')) { // Place where keyword starts. tokenizer.tokenize_state.start = tokenizer.point.index; tokenizer.enter(Name::MdxEsm); tokenizer.enter(Name::MdxEsmData); tokenizer.consume(); State::Next(StateName::MdxEsmWord) } else { State::Nok }}
/// In keyword.////// ```markdown/// > | import a from 'b'/// ^^^^^^/// ```pub fn word(tokenizer: &mut Tokenizer) -> State { if matches!(tokenizer.current, Some(b'a'..=b'z')) { tokenizer.consume(); State::Next(StateName::MdxEsmWord) } else { let slice = Slice::from_indices( tokenizer.parse_state.bytes, tokenizer.tokenize_state.start, tokenizer.point.index, );
if matches!(slice.as_str(), "export" | "import") && tokenizer.current == Some(b' ') { tokenizer.concrete = true; tokenizer.tokenize_state.start = tokenizer.events.len() - 1; tokenizer.consume(); State::Next(StateName::MdxEsmInside) } else { tokenizer.tokenize_state.start = 0; State::Nok } }}
/// In data.////// ```markdown/// > | import a from 'b'/// ^/// ```pub fn inside(tokenizer: &mut Tokenizer) -> State { match tokenizer.current { None | Some(b'\n') => { tokenizer.exit(Name::MdxEsmData); State::Retry(StateName::MdxEsmLineStart) } _ => { tokenizer.consume(); State::Next(StateName::MdxEsmInside) } }}
/// At start of line.////// ```markdown/// | import a from 'b'/// > | export {a}/// ^/// ```pub fn line_start(tokenizer: &mut Tokenizer) -> State { match tokenizer.current { None => State::Retry(StateName::MdxEsmAtEnd), Some(b'\n') => { tokenizer.check( State::Next(StateName::MdxEsmAtEnd), State::Next(StateName::MdxEsmContinuationStart), ); State::Retry(StateName::MdxEsmBlankLineBefore) } _ => { tokenizer.enter(Name::MdxEsmData); tokenizer.consume(); State::Next(StateName::MdxEsmInside) } }}
/// At start of line that continues.////// ```markdown/// | import a from 'b'/// > | export {a}/// ^/// ```pub fn continuation_start(tokenizer: &mut Tokenizer) -> State { tokenizer.enter(Name::LineEnding); tokenizer.consume(); tokenizer.exit(Name::LineEnding); State::Next(StateName::MdxEsmLineStart)}
/// At start of a potentially blank line.////// ```markdown/// | import a from 'b'/// > | export {a}/// ^/// ```pub fn blank_line_before(tokenizer: &mut Tokenizer) -> State { tokenizer.enter(Name::LineEnding); tokenizer.consume(); tokenizer.exit(Name::LineEnding); State::Next(StateName::BlankLineStart)}
/// At end of line (blank or eof).////// ```markdown/// > | import a from 'b'/// ^/// ```pub fn at_end(tokenizer: &mut Tokenizer) -> State { let result = parse_esm(tokenizer);
// Done!. if matches!(result, State::Ok) { tokenizer.concrete = false; tokenizer.exit(Name::MdxEsm); }
result}
/// Parse ESM with a given function.fn parse_esm(tokenizer: &mut Tokenizer) -> State { // We can `unwrap` because we don’t parse if this is `None`. let parse = tokenizer .parse_state .options .mdx_esm_parse .as_ref() .unwrap();
// Collect the body of the ESM and positional info for each run of it. let result = collect( &tokenizer.events, tokenizer.parse_state.bytes, tokenizer.tokenize_state.start, &[Name::MdxEsmData, Name::LineEnding], &[], );
// Parse and handle what was signaled back. match parse(&result.value) { MdxSignal::Ok => State::Ok, MdxSignal::Error(message, relative, source, rule_id) => { let point = tokenizer .parse_state .location .as_ref() .expect("expected location index if aware mdx is on") .relative_to_point(&result.stops, relative) .expect("expected non-empty string"); State::Error(message::Message { place: Some(Box::new(message::Place::Point(point))), reason: message, source, rule_id, }) } MdxSignal::Eof(message, source, rule_id) => { if tokenizer.current.is_none() { State::Error(message::Message { place: Some(Box::new(message::Place::Point(tokenizer.point.to_unist()))), reason: message, source, rule_id, }) } else { tokenizer.tokenize_state.mdx_last_parse_error = Some((message, *source, *rule_id)); State::Retry(StateName::MdxEsmContinuationStart) } } }}