villain 0.0.1

A webframework that utilizes Vue templates to generate HTML. Aimed to replace the need for JavaScript in web development.
Documentation
use thiserror::Error;

use super::token::{Token, TokenType};

#[derive(Error, Debug, PartialEq)]
pub(crate) enum ParserError {
    #[error("Unexpected end of file at position {0}")]
    UnexpectedEof(usize),

    #[error("Unexpected token {0}")]
    UnexpectedToken(Token),

    #[error("Unmatching closing tag. Expected {0} but found {1}")]
    UnmatchingClosing(String, String),
}

#[derive(Debug, PartialEq)]
pub(crate) enum NodeType {
    Root,
    Tag(String),
    Code(String),
    Text(String),
    Attribute(String, Option<Token>, bool),
}

#[derive(Debug, PartialEq)]
pub(crate) struct Node {
    pub(crate) node_type: NodeType,
    pub(crate) children: Vec<Node>,
}

impl Node {
    pub(crate) fn new(node_type: NodeType) -> Self {
        Self {
            node_type,
            children: Vec::new(),
        }
    }

    pub(crate) fn add_child(&mut self, node: Node) {
        self.children.push(node);
    }
}

pub(crate) struct Parser {
    tokens: Vec<Token>,
}

impl Parser {
    pub(crate) fn new(mut tokens: Vec<Token>) -> Self {
        tokens.reverse();
        Self { tokens }
    }

    fn peek(&self) -> Option<&Token> {
        self.tokens.last()
    }

    fn next(&mut self) -> Option<Token> {
        self.tokens.pop()
    }

    fn expect(&mut self, token_type: TokenType) -> Result<Token, ParserError> {
        if let Some(token) = self.next() {
            if token.token_type == token_type {
                Ok(token)
            } else {
                Err(ParserError::UnexpectedToken(token))
            }
        } else {
            Err(ParserError::UnexpectedEof(0))
        }
    }

    fn take_if_present(&mut self, token_type: TokenType) -> Result<Option<Token>, ParserError> {
        if let Some(token) = self.peek() {
            if token.token_type == token_type {
                return Ok(self.next());
            }
        } else {
            return Err(ParserError::UnexpectedEof(0));
        }

        Ok(None)
    }

    fn parse_text_node(&mut self) -> Result<Node, ParserError> {
        let text_node = self.expect(TokenType::TextNode)?.value.unwrap();

        Ok(Node::new(NodeType::Text(text_node)))
    }

    fn parse_interpolation_node(&mut self) -> Result<Node, ParserError> {
        let code = self.expect(TokenType::Interpolation)?.value.unwrap();

        Ok(Node::new(NodeType::Code(code)))
    }

    fn parse_attribute(&mut self, is_bound: bool) -> Result<Node, ParserError> {
        if is_bound {
            self.expect(TokenType::Colon)?;
        }
        let attribute = self.expect(TokenType::Attribute)?.value.unwrap();
        let value = self.take_if_present(TokenType::AttributeValue)?;

        Ok(Node::new(NodeType::Attribute(attribute, value, is_bound)))
    }

    fn parse_tag(&mut self) -> Result<Node, ParserError> {
        let open_tag = self.next().unwrap();
        let tag_name = open_tag.value.as_ref().unwrap();
        let mut node = Node::new(NodeType::Tag(tag_name.clone()));

        while let Some(token) = self.peek() {
            let attribute = match token.token_type {
                TokenType::Colon => self.parse_attribute(true)?,
                TokenType::Attribute => self.parse_attribute(false)?,
                TokenType::TagOpen => self.parse_tag()?,
                TokenType::TextNode => self.parse_text_node()?,
                TokenType::Interpolation => self.parse_interpolation_node()?,
                _ => break,
            };

            node.add_child(attribute);
        }

        let closing = self.expect(TokenType::TagClose)?;
        if closing.value.as_ref() != open_tag.value.as_ref() {
            return Err(ParserError::UnmatchingClosing(
                closing.value.as_ref().unwrap().to_string(),
                open_tag.value.as_ref().unwrap().to_string(),
            ));
        }

        Ok(node)
    }

    fn parse(&mut self) -> Result<Node, ParserError> {
        let mut root = Node::new(NodeType::Root);

        while let Some(token) = self.peek() {
            let next = match token.token_type {
                TokenType::TagOpen => self.parse_tag()?,
                TokenType::TextNode => self.parse_text_node()?,
                _ => return Err(ParserError::UnexpectedToken(token.clone())),
            };

            root.add_child(next);
        }

        Ok(root)
    }
}

impl TryInto<String> for Parser {
    type Error = ParserError;

    fn try_into(mut self) -> Result<String, Self::Error> {
        match self.parse() {
            Ok(root) => Ok(format!("{root:?}")),
            Err(e) => Err(e),
        }
    }
}

impl TryInto<Node> for Parser {
    type Error = ParserError;

    fn try_into(mut self) -> Result<Node, Self::Error> {
        match self.parse() {
            Ok(root) => Ok(root),
            Err(e) => Err(e),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::parser::scanner;

    use super::*;

    #[test]
    fn test_parse_text_node() {
        let tokens = vec![Token::new_with_value(
            TokenType::TextNode,
            0,
            "Hello, world!",
        )];
        let mut parser = Parser::new(tokens);

        let expected = Node::new(NodeType::Text("Hello, world!".to_string()));
        let actual = parser.parse_text_node().unwrap();

        assert_eq!(expected, actual);
    }

    #[test]
    fn test_parse_attribute() {
        let tokens = vec![
            Token::new_with_value(TokenType::Attribute, 0, "class"),
            Token::new_with_value(TokenType::AttributeValue, 0, "foo"),
        ];
        let mut parser = Parser::new(tokens);

        let expected = Node::new(NodeType::Attribute(
            "class".to_string(),
            Some(Token::new_with_value(TokenType::AttributeValue, 0, "foo")),
            false,
        ));
        let actual = parser.parse_attribute(false).unwrap();

        assert_eq!(expected, actual);
    }

    #[test]
    fn test_parse_tag() {
        let tokens = vec![
            Token::new_with_value(TokenType::TagOpen, 0, "div"),
            Token::new_with_value(TokenType::Attribute, 0, "class"),
            Token::new_with_value(TokenType::AttributeValue, 0, "foo"),
            Token::new_with_value(TokenType::TagClose, 0, "div"),
        ];
        let mut parser = Parser::new(tokens);

        let mut expected = Node::new(NodeType::Tag("div".to_string()));
        expected.add_child(Node::new(NodeType::Attribute(
            "class".to_string(),
            Some(Token::new_with_value(TokenType::AttributeValue, 0, "foo")),
            false,
        )));
        let actual = parser.parse_tag().unwrap();

        assert_eq!(expected, actual);
    }

    #[test]
    fn test_parse_nested_tag_with_attributes() {
        let tokens = vec![
            Token::new_with_value(TokenType::TagOpen, 0, "div"),
            Token::new_with_value(TokenType::Attribute, 0, "class"),
            Token::new_with_value(TokenType::AttributeValue, 0, "foo"),
            Token::new_with_value(TokenType::TagOpen, 0, "span"),
            Token::new_with_value(TokenType::TagClose, 0, "span"),
            Token::new_with_value(TokenType::TagClose, 0, "div"),
        ];
        let mut parser = Parser::new(tokens);

        let mut expected = Node::new(NodeType::Tag("div".to_string()));
        expected.add_child(Node::new(NodeType::Attribute(
            "class".to_string(),
            Some(Token::new_with_value(TokenType::AttributeValue, 0, "foo")),
            false,
        )));
        expected.add_child(Node::new(NodeType::Tag("span".to_string())));

        let actual = parser.parse_tag().unwrap();

        assert_eq!(expected, actual);
    }

    #[test]
    fn test_parses_vue() {
        let input = r#"
        <script setup>
        import { ref } from "vue";

        const msg = ref("Hello World!");
        </script>

        <template>
          <h1>{{ msg }}</h1>
          <input v-model="msg" />

          <h2>{{ {"a": 1, b: {}} }}</h2>
        </template>
        "#;

        let scanner = scanner::Scanner::new(input.into());
        let parser = Parser::new(scanner.try_into().unwrap());

        let _s: String = parser.try_into().unwrap();
    }
}