oxidar 0.0.0

A lightweight web framework written in rust inspired by Django.
Documentation
mod error;

use super::TemplateVar;
pub use error::TemplateParsingError;

struct CharStream {
    idx: usize,
    chars: Vec<char>,
}

impl CharStream {
    fn new(txt: String) -> CharStream {
        CharStream {
            idx: 0,
            chars: txt.chars().collect(),
        }
    }

    fn get(&mut self) -> Option<&char> {
        let c = self.chars.get(self.idx);
        self.idx += 1;
        return c;
    }
}

struct TokenStream<'a> {
    idx: usize,
    tokens: Vec<(TemplateToken<'a>, usize)>,
}

impl<'a> TokenStream<'a> {
    fn new(tokens: Vec<(TemplateToken<'a>, usize)>) -> TokenStream<'a> {
        TokenStream { idx: 0, tokens }
    }

    fn get(&mut self) -> Option<&(TemplateToken<'a>, usize)> {
        let token = match self.tokens.get(self.idx) {
            Some(token) => Some(token),
            None => None,
        };

        self.idx += 1;
        return token;
    }

    fn next_is_html(&self) -> bool {
        if let Some((TemplateToken::Html(_), _)) = self.tokens.get(self.idx) {
            return true;
        }

        return false;
    }
}

#[derive(Debug)]
enum TemplateToken<'a> {
    If,
    End,
    Else,
    For,
    In,
    Safe,
    Block,
    Let,
    Add,
    Subtract,
    Eq,
    NotEq,
    Not,
    Less,
    Greater,
    LessEq,
    GreaterEq,
    Set,
    And,
    Or,
    Value(TemplateVar),
    ValueRef(&'a TemplateVar),
    Html(String),
}

#[derive(Debug)]
enum ParserState {
    Html,
    Str(char, bool),
    KeyWordOperator,
    Num(bool),
    None,
}

fn finalize_block_token<'a>(
    output: &mut Vec<(TemplateToken<'a>, usize)>,
    token: &mut String,
    data: &'a TemplateVar,
    char_stream: CharStream,
) -> Result<CharStream, TemplateParsingError> {
    let block_token = match token.as_str() {
        "if" => (true, TemplateToken::If),
        "end" => (true, TemplateToken::End),
        "else" => (true, TemplateToken::Else),
        "for" => (true, TemplateToken::For),
        "in" => (true, TemplateToken::In),
        "block" => (true, TemplateToken::Block),
        "let" => (false, TemplateToken::Let),
        "safe" => (true, TemplateToken::Let),
        "+" => (false, TemplateToken::Add),
        "-" => (false, TemplateToken::Subtract),
        "==" => (true, TemplateToken::Eq),
        "!=" => (true, TemplateToken::NotEq),
        "!" => (true, TemplateToken::Not),
        "<" => (true, TemplateToken::Less),
        ">" => (true, TemplateToken::Greater),
        "<=" => (true, TemplateToken::LessEq),
        ">=" => (true, TemplateToken::GreaterEq),
        "=" => (false, TemplateToken::Set),
        "||" => (false, TemplateToken::Or),
        "&&" => (false, TemplateToken::And),
        "true" => (true, TemplateToken::Value(TemplateVar::Bool(true))),
        "false" => (true, TemplateToken::Value(TemplateVar::Bool(false))),
        "None" => (true, TemplateToken::Value(TemplateVar::None)),
        _ => (true, TemplateToken::ValueRef(data.resolve(token))),
    };

    if !block_token.0 {
        return Err(TemplateParsingError::from_charstream(
            &format!(
                "{block_token:?} {} {}",
                "is only partially implimented. Because it will be implimented in",
                "the future versions you can not use it here as a variable name."
            ),
            char_stream,
        ));
    }

    output.push((block_token.1, char_stream.idx));
    token.clear();
    return Ok(char_stream);
}

fn handle_char<'a>(
    output: &mut Vec<(TemplateToken<'a>, usize)>,
    c: char,
    state: &mut ParserState,
    current_content: &mut String,
    mut char_stream: CharStream,
    data: &'a TemplateVar,
) -> Result<CharStream, TemplateParsingError> {
    match state {
        ParserState::Html => match c {
            '{' => {
                if let Some('{') = char_stream.get() {
                    current_content.push('{');
                } else {
                    char_stream.idx -= 1;
                    output.push((
                        TemplateToken::Html(current_content.clone()),
                        char_stream.idx,
                    ));
                    current_content.clear();
                    *state = ParserState::None;
                }
            }
            _ => current_content.push(c),
        },
        ParserState::Str(quote_char, escaped) => {
            if *escaped {
                let c = match c {
                    '"' => '"',
                    '\'' => '\'',
                    'n' => '\n',
                    't' => '\t',
                    'r' => '\r',
                    '\\' => '\\',
                    '\0' => '\0',
                    _ => {
                        return Err(TemplateParsingError::from_charstream(
                            &format!("\"\\{c}\" is not a valid escape sequence."),
                            char_stream,
                        ))
                    }
                };

                *escaped = false;
                current_content.push(c);
            } else {
                if c == *quote_char {
                    output.push((
                        TemplateToken::Value(TemplateVar::Str(current_content.clone())),
                        char_stream.idx,
                    ));

                    current_content.clear();
                    *state = ParserState::None;
                } else if c == '\'' {
                    *escaped = true;
                } else {
                    current_content.push(c);
                }
            }
        }
        ParserState::KeyWordOperator => {
            if c.is_whitespace() {
                char_stream = finalize_block_token(output, current_content, data, char_stream)?;
                *state = ParserState::None;
            } else if c == '}' {
                char_stream = finalize_block_token(output, current_content, data, char_stream)?;
                *state = ParserState::Html;
            } else {
                current_content.push(c);
            }
        }
        ParserState::Num(is_dec) => {
            if c.is_ascii_digit() {
                current_content.push(c);
            } else if c == '.' {
                match is_dec {
                    false => {
                        current_content.push('.');
                        *is_dec = true
                    }
                    true => {
                        return Err(TemplateParsingError::from_charstream(
                            &format!(
                                "The number \"{current_content}.\" {}",
                                "already has a decimal in it. It may not have more than one."
                            ),
                            char_stream,
                        ));
                    }
                }
            } else if c.is_whitespace() || c == '}' {
                output.push((
                    TemplateToken::Value(TemplateVar::Num(match current_content.parse() {
                        Ok(num) => num,
                        Err(err) => {
                            return Err(TemplateParsingError::from_charstream(
                                &format!("Error parsing number: {err}"),
                                char_stream,
                            ))
                        }
                    })),
                    char_stream.idx,
                ));

                *state = match c {
                    '}' => ParserState::Html,
                    _ => ParserState::None,
                };
                current_content.clear();
            } else {
                return Err(TemplateParsingError::from_charstream(
                    &format!("\"{c}\" is an invalid character for a number."),
                    char_stream,
                ));
            }
        }
        ParserState::None => {
            if c.is_whitespace() {
                return Ok(char_stream);
            } else if c.is_numeric() {
                current_content.push(c);
                *state = ParserState::Num(false)
            } else if "\"'".contains(c) {
                *state = ParserState::Str(c, false)
            } else if c == '}' {
                *state = ParserState::Html;
            } else {
                *state = ParserState::KeyWordOperator;
                current_content.push(c);
            }
        }
    }

    return Ok(char_stream);
}

fn lex(
    initial: String,
    data: &TemplateVar,
) -> Result<(Vec<(TemplateToken, usize)>, Vec<char>), TemplateParsingError> {
    let mut output = Vec::new();
    let mut state = ParserState::Html;
    let mut current_content = String::new();
    let mut char_stream = CharStream::new(initial);
    while let Some(c) = char_stream.get() {
        char_stream = handle_char(
            &mut output,
            *c,
            &mut state,
            &mut current_content,
            char_stream,
            data,
        )?;
    }

    if let ParserState::Html = state {
        output.push((TemplateToken::Html(current_content), char_stream.idx));
    } else {
        return Err(TemplateParsingError::from_charstream(
            "Template block not closed.",
            char_stream,
        ));
    }

    return Ok((output, char_stream.chars));
}

fn resolve_value_led_block(
    value: TemplateVar,
    idx: usize,
    token_stream: &TokenStream,
    output: &mut String,
    chars: Vec<char>,
) -> Result<Vec<char>, TemplateParsingError> {
    println!("{value:?}");

    if token_stream.next_is_html() {
        output.push_str(&value.string());
    } else {
        return Err(TemplateParsingError::err(
            format!(
                "If a value is the leading token in a template block, {}",
                "it is expected that is the only token in the block."
            ),
            idx,
            chars,
        ));
    }

    return Ok(chars);
}

fn resolve_block(
    token_stream: &mut TokenStream,
    output: &mut String,
    mut chars: Vec<char>,
) -> Result<Vec<char>, TemplateParsingError> {
    if let Some((token, idx)) = token_stream.get() {
        match token {
            TemplateToken::If => todo!(),
            TemplateToken::End => todo!(),
            TemplateToken::Else => todo!(),
            TemplateToken::For => todo!(),
            TemplateToken::In => todo!(),
            TemplateToken::Block => todo!(),
            TemplateToken::Not => todo!(),
            TemplateToken::Value(template_var) => {
                chars = resolve_value_led_block(
                    template_var.clone(),
                    *idx,
                    token_stream,
                    output,
                    chars,
                )?;
            }
            TemplateToken::ValueRef(template_var) => {
                chars = resolve_value_led_block(
                    (*template_var).clone(),
                    *idx,
                    token_stream,
                    output,
                    chars,
                )?;
            }
            _ => {
                return Err(TemplateParsingError::err(
                    format!("Invalid leading token \"{:?}\".", token),
                    *idx,
                    chars,
                ))
            }
        }
    }

    return Ok(chars);
}

pub fn parse(initial: String, data: &TemplateVar) -> Result<String, TemplateParsingError> {
    let (token_list, mut chars) = lex(initial, data)?;
    let mut token_stream = TokenStream::new(token_list);

    let mut output = String::new();
    while let Some((TemplateToken::Html(html), _idx)) = token_stream.get() {
        output.push_str(html);
        chars = resolve_block(&mut token_stream, &mut output, chars)?;
    }

    return Ok(output);
}