aboutsummaryrefslogblamecommitdiffstats
path: root/src/construct/list.rs
blob: 96b2496243cac89c9d6a9512291bdf17fa0c9e3e (plain) (tree)


































































































































































































                                                                                               
//! 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<Token> {
    vec![Token::List]
}