aboutsummaryrefslogblamecommitdiffstats
path: root/src/construct/partial_title.rs
blob: 4c7b52713fe0a46a01dc3fc9ec1ed6f3d676fa78 (plain) (tree)







































































































































                                                                                                 
// To do: pass token types in.

use crate::construct::partial_whitespace::start as whitespace;
use crate::tokenizer::{Code, State, StateFnResult, TokenType, Tokenizer};

/// Type of quote, if we’re in an attribure, in complete (condition 7).
#[derive(Debug, Clone, PartialEq)]
enum TitleKind {
    /// In a parenthesised (`(` and `)`) title.
    Paren,
    /// In a double quoted (`"`) title.
    Double,
    /// In a single quoted (`"`) title.
    Single,
}

fn kind_to_marker(kind: &TitleKind) -> char {
    match kind {
        TitleKind::Double => '"',
        TitleKind::Single => '\'',
        TitleKind::Paren => ')',
    }
}

pub fn start(tokenizer: &mut Tokenizer, code: Code) -> StateFnResult {
    let kind = match code {
        Code::Char('"') => Some(TitleKind::Double),
        Code::Char('\'') => Some(TitleKind::Single),
        Code::Char('(') => Some(TitleKind::Paren),
        _ => None,
    };

    if let Some(kind) = kind {
        tokenizer.enter(TokenType::DefinitionTitle);
        tokenizer.enter(TokenType::DefinitionTitleMarker);
        tokenizer.consume(code);
        tokenizer.exit(TokenType::DefinitionTitleMarker);
        (State::Fn(Box::new(|t, c| at_first_break(t, c, kind))), None)
    } else {
        (State::Nok, None)
    }
}

/// To do.
fn at_first_break(tokenizer: &mut Tokenizer, code: Code, kind: TitleKind) -> StateFnResult {
    match code {
        Code::Char(char) if char == kind_to_marker(&kind) => {
            tokenizer.enter(TokenType::DefinitionTitleMarker);
            tokenizer.consume(code);
            tokenizer.exit(TokenType::DefinitionTitleMarker);
            tokenizer.exit(TokenType::DefinitionTitle);
            (State::Ok, None)
        }
        _ => {
            tokenizer.enter(TokenType::DefinitionTitleString);
            at_break(tokenizer, code, kind)
        }
    }
}

/// To do.
fn at_break(tokenizer: &mut Tokenizer, code: Code, kind: TitleKind) -> StateFnResult {
    match code {
        Code::Char(char) if char == kind_to_marker(&kind) => {
            tokenizer.exit(TokenType::DefinitionTitleString);
            at_first_break(tokenizer, code, kind)
        }
        Code::None => (State::Nok, None),
        Code::CarriageReturnLineFeed | Code::Char('\r' | '\n') => {
            tokenizer.enter(TokenType::LineEnding);
            tokenizer.consume(code);
            tokenizer.exit(TokenType::LineEnding);
            (
                State::Fn(Box::new(|t, c| at_break_line_start(t, c, kind))),
                None,
            )
        }
        _ => {
            // To do: link.
            tokenizer.enter(TokenType::ChunkString);
            title(tokenizer, code, kind)
        }
    }
}

fn at_break_line_start(tokenizer: &mut Tokenizer, code: Code, kind: TitleKind) -> StateFnResult {
    tokenizer.attempt(
        |t, c| whitespace(t, c, TokenType::Whitespace),
        |_ok| Box::new(|t, c| at_break_line_begin(t, c, kind)),
    )(tokenizer, code)
}

fn at_break_line_begin(tokenizer: &mut Tokenizer, code: Code, kind: TitleKind) -> StateFnResult {
    match code {
        // Blank line not allowed.
        Code::CarriageReturnLineFeed | Code::Char('\r' | '\n') => (State::Nok, None),
        _ => at_break(tokenizer, code, kind),
    }
}

/// To do.
fn title(tokenizer: &mut Tokenizer, code: Code, kind: TitleKind) -> StateFnResult {
    match code {
        Code::Char(char) if char == kind_to_marker(&kind) => {
            tokenizer.exit(TokenType::ChunkString);
            at_break(tokenizer, code, kind)
        }
        Code::None | Code::CarriageReturnLineFeed | Code::Char('\r' | '\n') => {
            tokenizer.exit(TokenType::ChunkString);
            at_break(tokenizer, code, kind)
        }
        Code::Char('\\') => {
            tokenizer.consume(code);
            (State::Fn(Box::new(|t, c| escape(t, c, kind))), None)
        }
        _ => {
            tokenizer.consume(code);
            (State::Fn(Box::new(|t, c| title(t, c, kind))), None)
        }
    }
}

/// To do.
fn escape(tokenizer: &mut Tokenizer, code: Code, kind: TitleKind) -> StateFnResult {
    match code {
        Code::Char(char) if char == kind_to_marker(&kind) => {
            tokenizer.consume(code);
            (State::Fn(Box::new(move |t, c| title(t, c, kind))), None)
        }
        Code::Char('\\') => {
            tokenizer.consume(code);
            (State::Fn(Box::new(move |t, c| title(t, c, kind))), None)
        }
        _ => title(tokenizer, code, kind),
    }
}