aboutsummaryrefslogtreecommitdiffstats
path: root/src/construct/partial_space_or_tab_eol.rs
diff options
context:
space:
mode:
authorLibravatar Titus Wormer <tituswormer@gmail.com>2022-08-11 15:54:13 +0200
committerLibravatar Titus Wormer <tituswormer@gmail.com>2022-08-11 15:54:13 +0200
commit3048b7aca0690691d25cb8409d543b2377e065e1 (patch)
tree565aa373293f69fc5c80bc4ef48e4af904ab2134 /src/construct/partial_space_or_tab_eol.rs
parentcf9f0039911597cd5c9bc8e98f61b5df09b02130 (diff)
downloadmarkdown-rs-3048b7aca0690691d25cb8409d543b2377e065e1.tar.gz
markdown-rs-3048b7aca0690691d25cb8409d543b2377e065e1.tar.bz2
markdown-rs-3048b7aca0690691d25cb8409d543b2377e065e1.zip
Refactor to move `space_or_tab_eol` to own file
Diffstat (limited to 'src/construct/partial_space_or_tab_eol.rs')
-rw-r--r--src/construct/partial_space_or_tab_eol.rs174
1 files changed, 174 insertions, 0 deletions
diff --git a/src/construct/partial_space_or_tab_eol.rs b/src/construct/partial_space_or_tab_eol.rs
new file mode 100644
index 0000000..0807a5f
--- /dev/null
+++ b/src/construct/partial_space_or_tab_eol.rs
@@ -0,0 +1,174 @@
+//! Several helpers to parse whitespace (`space_or_tab`, `space_or_tab_eol`).
+//!
+//! ## References
+//!
+//! * [`micromark-factory-space/index.js` in `micromark`](https://github.com/micromark/micromark/blob/main/packages/micromark-factory-space/dev/index.js)
+
+use crate::construct::partial_space_or_tab::{
+ space_or_tab_with_options, Options as SpaceOrTabOptions,
+};
+use crate::event::{Content, Name};
+use crate::state::{Name as StateName, State};
+use crate::subtokenize::link;
+use crate::tokenizer::Tokenizer;
+
+/// Options to parse `space_or_tab` and one optional eol, but no blank line.
+#[derive(Debug)]
+pub struct Options {
+ /// Connect this whitespace to the previous.
+ pub connect: bool,
+ /// Embedded content type to use.
+ pub content_type: Option<Content>,
+}
+
+/// `space_or_tab`, or optionally `space_or_tab`, one `eol`, and
+/// optionally `space_or_tab`.
+///
+/// ```bnf
+/// space_or_tab_eol ::= 1*( ' ' '\t' ) | 0*( ' ' '\t' ) eol 0*( ' ' '\t' )
+/// ```
+pub fn space_or_tab_eol(tokenizer: &mut Tokenizer) -> StateName {
+ space_or_tab_eol_with_options(
+ tokenizer,
+ Options {
+ content_type: None,
+ connect: false,
+ },
+ )
+}
+
+/// `space_or_tab_eol`, with the given options.
+pub fn space_or_tab_eol_with_options(tokenizer: &mut Tokenizer, options: Options) -> StateName {
+ tokenizer.tokenize_state.space_or_tab_eol_content_type = options.content_type;
+ tokenizer.tokenize_state.space_or_tab_eol_connect = options.connect;
+ StateName::SpaceOrTabEolStart
+}
+
+pub fn eol_start(tokenizer: &mut Tokenizer) -> State {
+ let name = space_or_tab_with_options(
+ tokenizer,
+ SpaceOrTabOptions {
+ kind: Name::SpaceOrTab,
+ min: 1,
+ max: usize::MAX,
+ content_type: tokenizer
+ .tokenize_state
+ .space_or_tab_eol_content_type
+ .clone(),
+ connect: tokenizer.tokenize_state.space_or_tab_eol_connect,
+ },
+ );
+
+ tokenizer.attempt(
+ name,
+ State::Next(StateName::SpaceOrTabEolAfterFirst),
+ State::Next(StateName::SpaceOrTabEolAtEol),
+ )
+}
+
+pub fn eol_after_first(tokenizer: &mut Tokenizer) -> State {
+ tokenizer.tokenize_state.space_or_tab_eol_ok = true;
+
+ if tokenizer
+ .tokenize_state
+ .space_or_tab_eol_content_type
+ .is_some()
+ {
+ tokenizer.tokenize_state.space_or_tab_eol_connect = true;
+ }
+
+ State::Retry(StateName::SpaceOrTabEolAtEol)
+}
+
+/// `space_or_tab_eol`: after optionally first `space_or_tab`.
+///
+/// ```markdown
+/// > | a
+/// ^
+/// | b
+/// ```
+pub fn eol_at_eol(tokenizer: &mut Tokenizer) -> State {
+ if let Some(b'\n') = tokenizer.current {
+ tokenizer.enter_with_content(
+ Name::LineEnding,
+ tokenizer
+ .tokenize_state
+ .space_or_tab_eol_content_type
+ .clone(),
+ );
+
+ if tokenizer.tokenize_state.space_or_tab_eol_connect {
+ let index = tokenizer.events.len() - 1;
+ link(&mut tokenizer.events, index);
+ } else if tokenizer
+ .tokenize_state
+ .space_or_tab_eol_content_type
+ .is_some()
+ {
+ tokenizer.tokenize_state.space_or_tab_eol_connect = true;
+ }
+
+ tokenizer.consume();
+ tokenizer.exit(Name::LineEnding);
+ State::Next(StateName::SpaceOrTabEolAfterEol)
+ } else {
+ let ok = tokenizer.tokenize_state.space_or_tab_eol_ok;
+ tokenizer.tokenize_state.space_or_tab_eol_content_type = None;
+ tokenizer.tokenize_state.space_or_tab_eol_connect = false;
+ tokenizer.tokenize_state.space_or_tab_eol_ok = false;
+ if ok {
+ State::Ok
+ } else {
+ State::Nok
+ }
+ }
+}
+
+/// `space_or_tab_eol`: after eol.
+///
+/// ```markdown
+/// | a
+/// > | b
+/// ^
+/// ```
+#[allow(clippy::needless_pass_by_value)]
+pub fn eol_after_eol(tokenizer: &mut Tokenizer) -> State {
+ let name = space_or_tab_with_options(
+ tokenizer,
+ SpaceOrTabOptions {
+ kind: Name::SpaceOrTab,
+ min: 1,
+ max: usize::MAX,
+ content_type: tokenizer
+ .tokenize_state
+ .space_or_tab_eol_content_type
+ .clone(),
+ connect: tokenizer.tokenize_state.space_or_tab_eol_connect,
+ },
+ );
+ tokenizer.attempt(
+ name,
+ State::Next(StateName::SpaceOrTabEolAfterMore),
+ State::Next(StateName::SpaceOrTabEolAfterMore),
+ )
+}
+
+/// `space_or_tab_eol`: after more (optional) `space_or_tab`.
+///
+/// ```markdown
+/// | a
+/// > | b
+/// ^
+/// ```
+pub fn eol_after_more(tokenizer: &mut Tokenizer) -> State {
+ tokenizer.tokenize_state.space_or_tab_eol_content_type = None;
+ tokenizer.tokenize_state.space_or_tab_eol_connect = false;
+ tokenizer.tokenize_state.space_or_tab_eol_ok = false;
+
+ // Blank line not allowed.
+ if matches!(tokenizer.current, None | Some(b'\n')) {
+ State::Nok
+ } else {
+ State::Ok
+ }
+}