lualexer 0.1.2

Read Lua code and produce tokens
Documentation
use crate::token::Token;
use crate::lexer::LexerErrorType;
use crate::utils::{
    ParseResult,
    accumulate_until,
    is_digit,
    number::parse_number,
};

pub fn parse_symbols<'a>(
    input: &'a str,
    characters: (char, Option<char>, Option<char>)
) -> ParseResult<'a> {
    match characters {
        ('.', Some('.'), Some('.')) => {
            let (symbol, next_input) = input.split_at(3);
            Ok((Token::new_symbol(symbol), next_input))
        },
        ('.', Some('.'), _) => {
            let (symbol, next_input) = input.split_at(2);
            Ok((Token::new_symbol(symbol), next_input))
        },
        ('.', Some(character), _) if is_digit(character) => parse_number(input),
        ('-', Some('-'), _) => parse_comment(input),
        ('[', Some('['), _) => {
            input.find("]]").map_or_else(
                || Err(LexerErrorType::UnfinishedString),
                |index| {
                    let (string, next_input) = input.split_at(index + 2);

                    Ok((Token::new_string(string), next_input))
                }

            )
        },
        ('[', Some('='), _) => {
            match try_parse_balanced_brackets(input) {
                Some((content, next_input)) => Ok((Token::new_string(content), next_input)),
                None => {
                    let (symbol, next_input) = input.split_at(1);
                    Ok((Token::new_symbol(symbol), next_input))
                },
            }
        },
        ('=', Some('='), _)
        | ('~', Some('='), _)
        | ('<', Some('='), _)
        | ('>', Some('='), _) => {
            let (symbol, next_input) = input.split_at(2);
            Ok((Token::new_symbol(symbol), next_input))
        },
        _ => {
            let (symbol, next_input) = input.split_at(1);
            Ok((Token::new_symbol(symbol), next_input))
        },
    }
}

// This function assumes that the input starts with two dashes (`-`).
fn parse_comment<'a>(input: &'a str) -> ParseResult<'a> {
    let (comment, next_input) = input.get(2..)
        .and_then(try_parse_balanced_brackets)
        .map(|(content, _)| {
            input.split_at(content.len() + 2)
        }).unwrap_or_else(|| {
            accumulate_until(input, |character| character == '\n' || character == '\r')
        });

    Ok((Token::new_comment(comment), next_input))
}

// This function will try to parse the following pattern `[=*[.*]=*]`, where the
// amount of equal (`=`) symbols is the same on both sides. If the beginning of the
// input matches the pattern, it will return two strings: the first will be the
// captured pattern and the second the rest of the input.
fn try_parse_balanced_brackets<'a>(input: &'a str) -> Option<(&'a str, &'a str)> {
    let mut chars = input.chars();

    if chars.next() != Some('[') {
        return None
    }

    let mut closing_pattern = "]".to_owned();

    loop {
        match chars.next() {
            Some('=') => {
                closing_pattern.push('=');
            },
            Some('[') => break,
            _ => return None,
        }
    }

    closing_pattern.push(']');

    input.find(&closing_pattern)
        .map(|index| {
            input.split_at(index + closing_pattern.len())
        })
}