From 75522b867b15b9a400275cfec9a2ead4ff535473 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 12 Jul 2022 13:00:53 +0200 Subject: Add initial support for lists --- src/construct/list.rs | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/construct/mod.rs | 4 +- 2 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 src/construct/list.rs (limited to 'src/construct') diff --git a/src/construct/list.rs b/src/construct/list.rs new file mode 100644 index 0000000..96b2496 --- /dev/null +++ b/src/construct/list.rs @@ -0,0 +1,195 @@ +//! To do. + +use crate::constant::{LIST_ITEM_VALUE_SIZE_MAX, TAB_SIZE}; +use crate::construct::partial_space_or_tab::space_or_tab_min_max; +use crate::token::Token; +use crate::tokenizer::{Code, State, StateFnResult, Tokenizer}; + +/// Type of title. +#[derive(Debug, PartialEq)] +enum Kind { + /// In a dot (`.`) list. + /// + /// ## Example + /// + /// ```markdown + /// 1. a + /// ``` + Dot, + /// In a paren (`)`) list. + /// + /// ## Example + /// + /// ```markdown + /// 1) a + /// ``` + Paren, + /// In an asterisk (`*`) list. + /// + /// ## Example + /// + /// ```markdown + /// * a + /// ``` + Asterisk, + /// In a plus (`+`) list. + /// + /// ## Example + /// + /// ```markdown + /// + a + /// ``` + Plus, + /// In a dash (`-`) list. + /// + /// ## Example + /// + /// ```markdown + /// - a + /// ``` + Dash, +} + +impl Kind { + /// Turn the kind into a [char]. + fn as_char(&self) -> char { + match self { + Kind::Dot => '.', + Kind::Paren => ')', + Kind::Asterisk => '*', + Kind::Plus => '+', + Kind::Dash => '-', + } + } + /// Turn a [char] into a kind. + /// + /// ## Panics + /// + /// Panics if `char` is not `.`, `)`, `*`, `+`, or `-`. + fn from_char(char: char) -> Kind { + match char { + '.' => Kind::Dot, + ')' => Kind::Paren, + '*' => Kind::Asterisk, + '+' => Kind::Plus, + '-' => Kind::Dash, + _ => unreachable!("invalid char"), + } + } + /// Turn [Code] into a kind. + /// + /// ## Panics + /// + /// Panics if `code` is not `Code::Char('.' | ')' | '*' | '+' | '-')`. + fn from_code(code: Code) -> Kind { + match code { + Code::Char(char) => Kind::from_char(char), + _ => unreachable!("invalid code"), + } + } +} + +/// To do. +pub fn start(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult { + // To do: allow arbitrary when code (indented) is turned off. + tokenizer.go(space_or_tab_min_max(0, TAB_SIZE - 1), before)(tokenizer, code) +} + +/// To do. +fn before(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult { + match code { + // Unordered. + Code::Char('*' | '+' | '-') => { + // To do: check if this is a thematic break? + tokenizer.enter(Token::List); + tokenizer.enter(Token::ListItemPrefix); + marker(tokenizer, code) + } + // Ordered. + Code::Char(char) if char.is_ascii_digit() => { + tokenizer.enter(Token::List); + tokenizer.enter(Token::ListItemPrefix); + tokenizer.enter(Token::ListItemValue); + // To do: `interrupt || !1`? + inside(tokenizer, code, 0) + } + _ => (State::Nok, None), + } +} + +/// To do. +fn inside(tokenizer: &mut Tokenizer, code: Code, mut size: usize) -> StateFnResult { + match code { + Code::Char(char) if char.is_ascii_digit() && size < LIST_ITEM_VALUE_SIZE_MAX => { + tokenizer.consume(code); + size += 1; + (State::Fn(Box::new(move |t, c| inside(t, c, size))), None) + } + // To do: `(!self.interrupt || size < 2)` + Code::Char('.' | ')') => { + tokenizer.exit(Token::ListItemValue); + marker(tokenizer, code) + } + _ => (State::Nok, None), + } +} + +/// To do. +fn marker(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult { + let kind = Kind::from_code(code); + println!("list item kind: {:?}", kind); + tokenizer.enter(Token::ListItemMarker); + tokenizer.consume(code); + tokenizer.exit(Token::ListItemMarker); + // To do: check blank line, if true `State::Nok` else `on_blank`. + (State::Fn(Box::new(marker_after)), None) +} + +/// To do. +fn marker_after(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult { + tokenizer.attempt(list_item_prefix_whitespace, |ok| { + let func = if ok { prefix_end } else { prefix_other }; + Box::new(func) + })(tokenizer, code) +} + +// To do: `on_blank`. + +/// To do. +fn prefix_other(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult { + match code { + Code::VirtualSpace | Code::Char('\t' | ' ') => { + tokenizer.enter(Token::SpaceOrTab); + tokenizer.consume(code); + tokenizer.exit(Token::SpaceOrTab); + (State::Fn(Box::new(prefix_end)), None) + } + _ => (State::Nok, None), + } +} + +/// To do. +fn prefix_end(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult { + // To do: calculate size. + tokenizer.exit(Token::ListItemPrefix); + (State::Ok, Some(vec![code])) +} + +/// To do. +fn list_item_prefix_whitespace(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult { + // To do: check how big this should be? + tokenizer.go( + space_or_tab_min_max(1, TAB_SIZE - 1), + list_item_prefix_whitespace_after, + )(tokenizer, code) +} + +fn list_item_prefix_whitespace_after(_tokenizer: &mut Tokenizer, code: Code) -> StateFnResult { + // To do: check some stuff? + (State::Ok, Some(vec![code])) +} + +/// End of a block quote. +pub fn end() -> Vec { + vec![Token::List] +} diff --git a/src/construct/mod.rs b/src/construct/mod.rs index 06ff4e9..be9dfe3 100644 --- a/src/construct/mod.rs +++ b/src/construct/mod.rs @@ -33,7 +33,7 @@ //! * [label end][label_end] //! * [label start (image)][label_start_image] //! * [label start (link)][label_start_link] -//! * list +//! * [list][] //! * [paragraph][] //! * [thematic break][thematic_break] //! @@ -42,6 +42,7 @@ //! * [data][partial_data] //! * [destination][partial_destination] //! * [label][partial_label] +//! * [non lazy continuation][partial_non_lazy_continuation] //! * [space or tab][partial_space_or_tab] //! * [title][partial_title] //! * [whitespace][partial_whitespace] @@ -80,6 +81,7 @@ pub mod html_text; pub mod label_end; pub mod label_start_image; pub mod label_start_link; +pub mod list; pub mod paragraph; pub mod partial_data; pub mod partial_destination; -- cgit