aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Titus Wormer <tituswormer@gmail.com>2022-09-09 13:17:59 +0200
committerLibravatar Titus Wormer <tituswormer@gmail.com>2022-09-09 13:17:59 +0200
commit118cc91fd56a9b4c93bec5b1cb4c5f25924d353e (patch)
tree3a33911c5b3f7da33919dfb48f1a0b8f8b46bb1b /src
parentffef323d3c927f84e94cae21afeb541be7320f1c (diff)
downloadmarkdown-rs-118cc91fd56a9b4c93bec5b1cb4c5f25924d353e.tar.gz
markdown-rs-118cc91fd56a9b4c93bec5b1cb4c5f25924d353e.tar.bz2
markdown-rs-118cc91fd56a9b4c93bec5b1cb4c5f25924d353e.zip
Add mdx expression (flow, text)
Diffstat (limited to 'src')
-rw-r--r--src/compiler.rs21
-rw-r--r--src/construct/flow.rs24
-rw-r--r--src/construct/mdx_expression_flow.rs84
-rw-r--r--src/construct/mdx_expression_text.rs34
-rw-r--r--src/construct/mod.rs8
-rw-r--r--src/construct/partial_mdx_expression.rs83
-rw-r--r--src/construct/partial_mdx_jsx.rs2
-rw-r--r--src/construct/text.rs11
-rw-r--r--src/event.rs5
-rw-r--r--src/lib.rs18
-rw-r--r--src/state.rs28
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.
diff --git a/src/lib.rs b/src/lib.rs
index 2d5b044..420b14d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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,