diff options
author | Titus Wormer <tituswormer@gmail.com> | 2022-09-09 13:17:59 +0200 |
---|---|---|
committer | Titus Wormer <tituswormer@gmail.com> | 2022-09-09 13:17:59 +0200 |
commit | 118cc91fd56a9b4c93bec5b1cb4c5f25924d353e (patch) | |
tree | 3a33911c5b3f7da33919dfb48f1a0b8f8b46bb1b /src | |
parent | ffef323d3c927f84e94cae21afeb541be7320f1c (diff) | |
download | markdown-rs-118cc91fd56a9b4c93bec5b1cb4c5f25924d353e.tar.gz markdown-rs-118cc91fd56a9b4c93bec5b1cb4c5f25924d353e.tar.bz2 markdown-rs-118cc91fd56a9b4c93bec5b1cb4c5f25924d353e.zip |
Add mdx expression (flow, text)
Diffstat (limited to '')
-rw-r--r-- | src/compiler.rs | 21 | ||||
-rw-r--r-- | src/construct/flow.rs | 24 | ||||
-rw-r--r-- | src/construct/mdx_expression_flow.rs | 84 | ||||
-rw-r--r-- | src/construct/mdx_expression_text.rs | 34 | ||||
-rw-r--r-- | src/construct/mod.rs | 8 | ||||
-rw-r--r-- | src/construct/partial_mdx_expression.rs | 83 | ||||
-rw-r--r-- | src/construct/partial_mdx_jsx.rs | 2 | ||||
-rw-r--r-- | src/construct/text.rs | 11 | ||||
-rw-r--r-- | src/event.rs | 5 | ||||
-rw-r--r-- | src/lib.rs | 18 | ||||
-rw-r--r-- | src/state.rs | 28 |
11 files changed, 306 insertions, 12 deletions
diff --git a/src/compiler.rs b/src/compiler.rs index e878c09..1f029f5 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -364,8 +364,10 @@ fn enter(context: &mut CompileContext) { | Name::HeadingAtxText | Name::HeadingSetextText | Name::Label - | Name::MdxJsxTextTag + | Name::MdxFlowExpression + | Name::MdxTextExpression | Name::MdxJsxFlowTag + | Name::MdxJsxTextTag | Name::ReferenceString | Name::ResourceTitleString => on_enter_buffer(context), @@ -406,9 +408,11 @@ fn exit(context: &mut CompileContext) { Name::CodeFencedFenceMeta | Name::MathFlowFenceMeta | Name::MdxJsxTextTag + | Name::MdxTextExpression | Name::Resource => { on_exit_drop(context); } + Name::MdxFlowExpression | Name::MdxJsxFlowTag => on_exit_drop_slurp(context), Name::CharacterEscapeValue | Name::CodeTextData | Name::Data | Name::MathTextData => { on_exit_data(context); } @@ -469,7 +473,6 @@ fn exit(context: &mut CompileContext) { Name::ListOrdered | Name::ListUnordered => on_exit_list(context), Name::ListItem => on_exit_list_item(context), Name::ListItemValue => on_exit_list_item_value(context), - Name::MdxJsxFlowTag => on_exit_mdx_jsx_flow_tag(context), Name::Paragraph => on_exit_paragraph(context), Name::ReferenceString => on_exit_reference_string(context), Name::ResourceDestinationString => on_exit_resource_destination_string(context), @@ -1093,6 +1096,14 @@ fn on_exit_drop(context: &mut CompileContext) { context.resume(); } +/// Handle [`Exit`][Kind::Exit]:*. +/// +/// Resumes, ignores what was resumed, and slurps the following line ending. +fn on_exit_drop_slurp(context: &mut CompileContext) { + context.resume(); + context.slurp_one_line_ending = true; +} + /// Handle [`Exit`][Kind::Exit]:{[`CodeTextData`][Name::CodeTextData],[`Data`][Name::Data],[`CharacterEscapeValue`][Name::CharacterEscapeValue]}. fn on_exit_data(context: &mut CompileContext) { context.push(&encode( @@ -1676,12 +1687,6 @@ fn on_exit_media(context: &mut CompileContext) { } } -/// Handle [`Exit`][Kind::Exit]:[`MdxJsxFlowTag`][Name::MdxJsxFlowTag]. -fn on_exit_mdx_jsx_flow_tag(context: &mut CompileContext) { - context.resume(); - context.slurp_one_line_ending = true; -} - /// Handle [`Exit`][Kind::Exit]:[`Paragraph`][Name::Paragraph]. fn on_exit_paragraph(context: &mut CompileContext) { let tight = context.tight_stack.last().unwrap_or(&false); diff --git a/src/construct/flow.rs b/src/construct/flow.rs index 5b2cbfe..e97ee63 100644 --- a/src/construct/flow.rs +++ b/src/construct/flow.rs @@ -16,6 +16,7 @@ //! * [Heading (atx)][crate::construct::heading_atx] //! * [Heading (setext)][crate::construct::heading_setext] //! * [HTML (flow)][crate::construct::html_flow] +//! * [MDX expression (flow)][crate::construct::mdx_expression_flow] //! * [MDX JSX (flow)][crate::construct::mdx_jsx_flow] //! * [Raw (flow)][crate::construct::raw_flow] (code (fenced), math (flow)) //! * [Thematic break][crate::construct::thematic_break] @@ -66,6 +67,13 @@ pub fn start(tokenizer: &mut Tokenizer) -> State { ); State::Retry(StateName::HtmlFlowStart) } + Some(b'{') => { + tokenizer.attempt( + State::Next(StateName::FlowAfter), + State::Next(StateName::FlowBeforeParagraph), + ); + State::Retry(StateName::MdxExpressionFlowStart) + } // Actual parsing: blank line? Indented code? Indented anything? // Tables, setext heading underlines, definitions, and paragraphs are // particularly weird. @@ -181,11 +189,25 @@ pub fn before_heading_setext(tokenizer: &mut Tokenizer) -> State { pub fn before_thematic_break(tokenizer: &mut Tokenizer) -> State { tokenizer.attempt( State::Next(StateName::FlowAfter), - State::Next(StateName::FlowBeforeGfmTable), + State::Next(StateName::FlowBeforeMdxExpression), ); State::Retry(StateName::ThematicBreakStart) } +/// At MDX expression (flow). +/// +/// ```markdown +/// > | {Math.PI} +/// ^ +/// ``` +pub fn before_mdx_expression(tokenizer: &mut Tokenizer) -> State { + tokenizer.attempt( + State::Next(StateName::FlowAfter), + State::Next(StateName::FlowBeforeGfmTable), + ); + State::Retry(StateName::MdxExpressionFlowStart) +} + /// At GFM table. /// /// ```markdown diff --git a/src/construct/mdx_expression_flow.rs b/src/construct/mdx_expression_flow.rs new file mode 100644 index 0000000..0faea31 --- /dev/null +++ b/src/construct/mdx_expression_flow.rs @@ -0,0 +1,84 @@ +//! To do. + +use crate::construct::partial_space_or_tab::{space_or_tab, space_or_tab_min_max}; +use crate::event::Name; +use crate::state::{Name as StateName, State}; +use crate::tokenizer::Tokenizer; +use crate::util::constant::TAB_SIZE; + +/// Start of MDX: expression (flow). +/// +/// ```markdown +/// > | {Math.PI} +/// ^ +/// ``` +pub fn start(tokenizer: &mut Tokenizer) -> State { + if tokenizer.parse_state.options.constructs.mdx_expression_flow { + tokenizer.tokenize_state.token_1 = Name::MdxFlowExpression; + tokenizer.concrete = true; + if matches!(tokenizer.current, Some(b'\t' | b' ')) { + tokenizer.attempt(State::Next(StateName::MdxExpressionFlowBefore), State::Nok); + State::Retry(space_or_tab_min_max( + tokenizer, + 0, + if tokenizer.parse_state.options.constructs.code_indented { + TAB_SIZE - 1 + } else { + usize::MAX + }, + )) + } else { + State::Retry(StateName::MdxExpressionFlowBefore) + } + } else { + State::Nok + } +} + +/// After optional whitespace, before of MDX expression (flow). +/// +/// ```markdown +/// > | {Math.PI} +/// ^ +/// ``` +pub fn before(tokenizer: &mut Tokenizer) -> State { + if Some(b'{') == tokenizer.current { + tokenizer.attempt(State::Next(StateName::MdxExpressionFlowAfter), State::Nok); + State::Retry(StateName::MdxExpressionStart) + } else { + State::Nok + } +} + +/// After an MDX expression (flow). +/// +/// ```markdown +/// > | {Math.PI} +/// ^ +/// ``` +pub fn after(tokenizer: &mut Tokenizer) -> State { + match tokenizer.current { + Some(b'\t' | b' ') => { + tokenizer.attempt(State::Next(StateName::MdxExpressionFlowEnd), State::Nok); + State::Retry(space_or_tab(tokenizer)) + } + _ => State::Retry(StateName::MdxExpressionFlowEnd), + } +} + +/// After an MDX expression (flow), after optional whitespace. +/// +/// ```markdown +/// > | {Math.PI}␠␊ +/// ^ +/// ``` +pub fn end(tokenizer: &mut Tokenizer) -> State { + tokenizer.concrete = false; + tokenizer.tokenize_state.token_1 = Name::Data; + + if matches!(tokenizer.current, None | Some(b'\n')) { + State::Ok + } else { + State::Nok + } +} diff --git a/src/construct/mdx_expression_text.rs b/src/construct/mdx_expression_text.rs new file mode 100644 index 0000000..8d061eb --- /dev/null +++ b/src/construct/mdx_expression_text.rs @@ -0,0 +1,34 @@ +//! To do. + +use crate::event::Name; +use crate::state::{Name as StateName, State}; +use crate::tokenizer::Tokenizer; + +/// Start of MDX: expression (text). +/// +/// ```markdown +/// > | a {Math.PI} c +/// ^ +/// ``` +pub fn start(tokenizer: &mut Tokenizer) -> State { + if Some(b'{') == tokenizer.current + && tokenizer.parse_state.options.constructs.mdx_expression_text + { + tokenizer.tokenize_state.token_1 = Name::MdxTextExpression; + tokenizer.attempt(State::Next(StateName::MdxExpressionTextAfter), State::Nok); + State::Retry(StateName::MdxExpressionStart) + } else { + State::Nok + } +} + +/// After an MDX expression (text) tag. +/// +/// ```markdown +/// > | a {Math.PI} c +/// ^ +/// ``` +pub fn after(tokenizer: &mut Tokenizer) -> State { + tokenizer.tokenize_state.token_1 = Name::Data; + State::Ok +} diff --git a/src/construct/mod.rs b/src/construct/mod.rs index 09ec976..1afa105 100644 --- a/src/construct/mod.rs +++ b/src/construct/mod.rs @@ -63,8 +63,10 @@ //! * [gfm label start footnote][gfm_label_start_footnote] //! * [gfm table][gfm_table] //! * [gfm task list item check][gfm_task_list_item_check] -//! * [mdx jsx (text)][mdx_jsx_text] +//! * [mdx expression (flow)][mdx_expression_flow] +//! * [mdx expression (text)][mdx_expression_text] //! * [mdx jsx (flow)][mdx_jsx_flow] +//! * [mdx jsx (text)][mdx_jsx_text] //! //! There are also several small subroutines typically used in different places: //! @@ -72,6 +74,7 @@ //! * [data][partial_data] //! * [destination][partial_destination] //! * [label][partial_label] +//! * [mdx expression][partial_mdx_expression] //! * [mdx jsx][partial_mdx_jsx] //! * [non lazy continuation][partial_non_lazy_continuation] //! * [space or tab][partial_space_or_tab] @@ -164,6 +167,8 @@ pub mod label_end; pub mod label_start_image; pub mod label_start_link; pub mod list_item; +pub mod mdx_expression_flow; +pub mod mdx_expression_text; pub mod mdx_jsx_flow; pub mod mdx_jsx_text; pub mod paragraph; @@ -171,6 +176,7 @@ pub mod partial_bom; pub mod partial_data; pub mod partial_destination; pub mod partial_label; +pub mod partial_mdx_expression; pub mod partial_mdx_jsx; pub mod partial_non_lazy_continuation; pub mod partial_space_or_tab; diff --git a/src/construct/partial_mdx_expression.rs b/src/construct/partial_mdx_expression.rs new file mode 100644 index 0000000..a949347 --- /dev/null +++ b/src/construct/partial_mdx_expression.rs @@ -0,0 +1,83 @@ +//! To do. + +use crate::construct::partial_space_or_tab::space_or_tab_min_max; +use crate::event::Name; +use crate::state::{Name as StateName, State}; +use crate::tokenizer::Tokenizer; +use alloc::format; + +/// Start of MDX: expression. +/// +/// ```markdown +/// > | a {Math.PI} c +/// ^ +/// ``` +pub fn start(tokenizer: &mut Tokenizer) -> State { + debug_assert_eq!(tokenizer.current, Some(b'{')); + tokenizer.enter(tokenizer.tokenize_state.token_1.clone()); + tokenizer.enter(Name::MdxExpressionMarker); + tokenizer.consume(); + tokenizer.exit(Name::MdxExpressionMarker); + State::Next(StateName::MdxExpressionBefore) +} + +pub fn before(tokenizer: &mut Tokenizer) -> State { + match tokenizer.current { + None => { + State::Error(format!( + "{}:{}: Unexpected end of file in expression, expected a corresponding closing brace for `{{`", + tokenizer.point.line, tokenizer.point.column + )) + } + Some(b'\n') => { + tokenizer.enter(Name::LineEnding); + tokenizer.consume(); + tokenizer.exit(Name::LineEnding); + State::Next(StateName::MdxExpressionEolAfter) + }, + Some(b'}') if tokenizer.tokenize_state.size == 0 => { + tokenizer.enter(Name::MdxExpressionMarker); + tokenizer.consume(); + tokenizer.exit(Name::MdxExpressionMarker); + tokenizer.exit(tokenizer.tokenize_state.token_1.clone()); + State::Ok + }, + Some(_) => { + tokenizer.enter(Name::MdxExpressionData); + State::Retry(StateName::MdxExpressionInside) + } + } +} + +pub fn inside(tokenizer: &mut Tokenizer) -> State { + if matches!(tokenizer.current, None | Some(b'\n')) + || (tokenizer.current == Some(b'}') && tokenizer.tokenize_state.size == 0) + { + tokenizer.exit(Name::MdxExpressionData); + State::Retry(StateName::MdxExpressionBefore) + } else { + // To do: only count if agnostic. + if tokenizer.current == Some(b'{') { + tokenizer.tokenize_state.size += 1; + } + + tokenizer.consume(); + State::Next(StateName::MdxExpressionInside) + } +} + +pub fn eol_after(tokenizer: &mut Tokenizer) -> State { + // Lazy continuation in a flow expression is a syntax error. + if tokenizer.tokenize_state.token_1 == Name::MdxFlowExpression && tokenizer.lazy { + State::Error(format!( + "{}:{}: Unexpected lazy line in expression in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc", + tokenizer.point.line, tokenizer.point.column + )) + } else if matches!(tokenizer.current, Some(b'\t' | b' ')) { + tokenizer.attempt(State::Next(StateName::MdxExpressionBefore), State::Nok); + // To do: use `start_column` + constants.tabSize for max space to eat. + State::Next(space_or_tab_min_max(tokenizer, 0, usize::MAX)) + } else { + State::Retry(StateName::MdxExpressionBefore) + } +} diff --git a/src/construct/partial_mdx_jsx.rs b/src/construct/partial_mdx_jsx.rs index 583241a..05a2c0c 100644 --- a/src/construct/partial_mdx_jsx.rs +++ b/src/construct/partial_mdx_jsx.rs @@ -1049,7 +1049,7 @@ pub fn es_whitespace_eol_after(tokenizer: &mut Tokenizer) -> State { // Lazy continuation in a flow tag is a syntax error. if tokenizer.tokenize_state.token_1 == Name::MdxJsxFlowTag && tokenizer.lazy { State::Error(format!( - "{}:{}: Unexpected lazy line in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc", + "{}:{}: Unexpected lazy line in jsx in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc", tokenizer.point.line, tokenizer.point.column )) } else { diff --git a/src/construct/text.rs b/src/construct/text.rs index b59fe65..34ea071 100644 --- a/src/construct/text.rs +++ b/src/construct/text.rs @@ -18,6 +18,7 @@ //! * [Label start (image)][crate::construct::label_start_image] //! * [Label start (link)][crate::construct::label_start_link] //! * [Label end][crate::construct::label_end] +//! * [MDX: expression (text)][crate::construct::mdx_expression_text] //! * [MDX: JSX (text)][crate::construct::mdx_jsx_text] //! //! > 👉 **Note**: for performance reasons, hard break (trailing) is formed by @@ -30,7 +31,7 @@ use crate::state::{Name as StateName, State}; use crate::tokenizer::Tokenizer; /// Characters that can start something in text. -const MARKERS: [u8; 15] = [ +const MARKERS: [u8; 16] = [ b'!', // `label_start_image` b'$', // `raw_text` (math (text)) b'&', // `character_reference` @@ -45,6 +46,7 @@ const MARKERS: [u8; 15] = [ b'`', // `raw_text` (code (text)) b'h', // `gfm_autolink_literal` (`protocol` kind) b'w', // `gfm_autolink_literal` (`www.` kind) + b'{', // `mdx_expression_text` b'~', // `attention` (gfm strikethrough) ]; @@ -153,6 +155,13 @@ pub fn before(tokenizer: &mut Tokenizer) -> State { ); State::Retry(StateName::LabelEndStart) } + Some(b'{') => { + tokenizer.attempt( + State::Next(StateName::TextBefore), + State::Next(StateName::TextBeforeData), + ); + State::Retry(StateName::MdxExpressionTextStart) + } _ => State::Retry(StateName::TextBeforeData), } } diff --git a/src/event.rs b/src/event.rs index 5827207..b18835e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -3166,6 +3166,11 @@ pub enum Name { /// ^ ^ ^ /// ``` ThematicBreakSequence, + + MdxFlowExpression, + MdxTextExpression, + MdxExpressionMarker, // void + MdxExpressionData, // void } /// List of void events, used to make sure everything is working well. @@ -301,6 +301,20 @@ pub struct Constructs { /// ^^^ /// ``` pub math_text: bool, + /// MDX: expression (flow). + /// + /// ```markdown + /// > | {Math.PI} + /// ^^^^^^^^^ + /// ``` + pub mdx_expression_flow: bool, + /// MDX: expression (text). + /// + /// ```markdown + /// > | a {Math.PI} c + /// ^^^^^^^^^ + /// ``` + pub mdx_expression_text: bool, /// MDX: JSX (flow). /// /// ```markdown @@ -356,6 +370,8 @@ impl Default for Constructs { list_item: true, math_flow: false, math_text: false, + mdx_expression_flow: false, + mdx_expression_text: false, mdx_jsx_flow: false, mdx_jsx_text: false, thematic_break: true, @@ -396,6 +412,8 @@ impl Constructs { code_indented: false, html_flow: false, html_text: false, + mdx_expression_flow: true, + mdx_expression_text: true, mdx_jsx_flow: true, mdx_jsx_text: true, ..Self::default() diff --git a/src/state.rs b/src/state.rs index db03a90..2158966 100644 --- a/src/state.rs +++ b/src/state.rs @@ -150,6 +150,7 @@ pub enum Name { FlowBeforeCodeIndented, FlowBeforeRaw, FlowBeforeHtml, + FlowBeforeMdxExpression, FlowBeforeMdxJsx, FlowBeforeHeadingAtx, FlowBeforeHeadingSetext, @@ -434,12 +435,38 @@ pub enum Name { TitleAtBlankLine, TitleEscape, TitleInside, + + MdxExpressionTextStart, + MdxExpressionTextAfter, + + MdxExpressionFlowStart, + MdxExpressionFlowBefore, + MdxExpressionFlowAfter, + MdxExpressionFlowEnd, + + MdxExpressionStart, + MdxExpressionBefore, + MdxExpressionInside, + MdxExpressionEolAfter, } #[allow(clippy::too_many_lines)] /// Call the corresponding state for a state name. pub fn call(tokenizer: &mut Tokenizer, name: Name) -> State { let func = match name { + Name::MdxExpressionTextStart => construct::mdx_expression_text::start, + Name::MdxExpressionTextAfter => construct::mdx_expression_text::after, + + Name::MdxExpressionFlowStart => construct::mdx_expression_flow::start, + Name::MdxExpressionFlowBefore => construct::mdx_expression_flow::before, + Name::MdxExpressionFlowAfter => construct::mdx_expression_flow::after, + Name::MdxExpressionFlowEnd => construct::mdx_expression_flow::end, + + Name::MdxExpressionStart => construct::partial_mdx_expression::start, + Name::MdxExpressionBefore => construct::partial_mdx_expression::before, + Name::MdxExpressionInside => construct::partial_mdx_expression::inside, + Name::MdxExpressionEolAfter => construct::partial_mdx_expression::eol_after, + Name::AttentionStart => construct::attention::start, Name::AttentionInside => construct::attention::inside, @@ -555,6 +582,7 @@ pub fn call(tokenizer: &mut Tokenizer, name: Name) -> State { Name::FlowBeforeCodeIndented => construct::flow::before_code_indented, Name::FlowBeforeRaw => construct::flow::before_raw, Name::FlowBeforeHtml => construct::flow::before_html, + Name::FlowBeforeMdxExpression => construct::flow::before_mdx_expression, Name::FlowBeforeMdxJsx => construct::flow::before_mdx_jsx, Name::FlowBeforeHeadingAtx => construct::flow::before_heading_atx, Name::FlowBeforeHeadingSetext => construct::flow::before_heading_setext, |