use chumsky::{ prelude::{filter, Simple}, primitive::{any, choice, end, just}, recursive::recursive, select, Parser, }; use std::vec; use chumsky::{ primitive::take_until, text::{ident, int, TextParser}, }; pub type Tokens = Vec; #[derive(PartialEq, Eq, Debug, Clone, Hash)] pub enum Token { HtmlFragment(String), Inline(Stack), Expression(Stack), Client(Stack), Both(Stack), LayoutSel(Stack), InlineBlock(Stack), Component(Stack), Function(Stack), Else, ClientClose, BothClose, LayoutSelClose, InlineClose, ComponentClose, FunctionClose(String), } pub type Stack = Vec; #[derive(PartialEq, Eq, Debug, Clone, Hash)] pub enum StackToken { Function(String), Ident(Ident), Literal(Literal), } pub type Ident = Vec; #[derive(PartialEq, Eq, Debug, Clone, Hash)] pub enum IdentSpecifiers { Ident(String), Expr(Stack), } #[derive(PartialEq, Eq, Debug, Clone, Hash)] pub enum Literal { Str(String), Nr(i32), } fn literal() -> impl Parser> { let literal_str = just('\'') .ignore_then(take_until(just('\''))) .map(|(between, _)| between) .or(just('"') .ignore_then(take_until(just('"'))) .map(|(between, _)| between)) .collect::() .map(Literal::Str); let literal_nr = int(10).from_str().unwrapped().map(Literal::Nr); literal_nr.or(literal_str).padded() } fn mustache( parser: impl Parser>, ) -> impl Parser> { just("{{") .ignore_then(parser.padded()) .then_ignore(just("}}")) } fn my_ident> + Clone>( arg_parser: T, ) -> impl Parser> { let bracket = just('[') .ignore_then(arg_parser) .then_ignore(just(']')) .map(IdentSpecifiers::Expr); let dot = just('.').ignore_then(ident()).map(IdentSpecifiers::Ident); let initial = ident().map(IdentSpecifiers::Ident).or(bracket.clone()); initial.chain(dot.or(bracket).repeated()) } fn stack() -> impl Parser> { recursive(|expr| { let function = just('!').ignore_then(ident()).map(StackToken::Function); let ident = my_ident(expr).map(StackToken::Ident); choice((function, ident, literal().map(StackToken::Literal))) .padded() .repeated() }) } fn flat_token() -> impl Parser> { let inline = mustache(just("Inline").ignore_then(stack())).map(Token::Inline); let expression_mus = mustache(stack()).map(Token::Expression); inline.or(expression_mus) } fn block_token() -> impl Parser> { fn block_mus(name: &'static str) -> impl Parser> { mustache(just('#').padded().then(just(name)).ignore_then(stack())) } let function = mustache(just('#').padded().ignore_then(stack())); choice(( block_mus("client").map(Token::Client), block_mus("Both").map(Token::Both), block_mus("Layout").map(Token::LayoutSel), block_mus("Inline").map(Token::InlineBlock), block_mus("Component").map(Token::Component), function.map(Token::Function), )) } fn block_close() -> impl Parser> { fn block_mus(name: &'static str) -> impl Parser> { mustache(just('/').padded().then(just(name)).ignored()) } let function = mustache(just('/').padded().ignore_then(ident())); choice(( block_mus("client").to(Token::ClientClose), block_mus("Both").to(Token::BothClose), block_mus("Layout").to(Token::LayoutSelClose), block_mus("Inline").to(Token::InlineClose), block_mus("Component").to(Token::ComponentClose), function.map(Token::FunctionClose), )) } fn non_html() -> impl Parser> { let else_token = mustache(just("Else")).to(Token::Else); choice((block_token(), else_token, block_close(), flat_token())) } pub fn token_parser() -> impl Parser> { take_until(non_html()) .map(|(html, token)| vec![Token::HtmlFragment(html.into_iter().collect()), token]) .repeated() .flatten() .chain(any().repeated().collect().map(Token::HtmlFragment)) .then_ignore(end()) } pub type AST = Vec; #[derive(PartialEq, Eq, Debug, Clone)] pub enum Node { Html(Html), Inline(Stack), Expression(Stack), Client(Stack, AST), Both(Stack, AST), InlineBlock(Stack, AST), LayoutSel(Stack, AST), Component(Stack, AST), FunctionBlock(Stack, AST), FunctionDoubleBlock(Stack, AST, AST), } #[derive(PartialEq, Eq, Debug, Clone)] pub enum Html { Node(String, AST, String), Leaf(String), } fn flat() -> impl Parser> { select! { Token::Inline(x) => Node::Inline(x), Token::Expression(x) => Node::Expression(x) } } // there are more beautiful solutions, but I'm not smart enough macro_rules! matching { ($pat_open:pat $(if $guard_open:expr)?, $middle:expr, $pat_close:pat $(if $guard_close:expr)?, $convert:expr) => { filter(|open| matches!(open, $pat_open $(if $guard_open)?)) .then($middle) .then_with(|(open, ast)|{ let $pat_open = open.clone() else { panic!("Token does not match same pattern twice. This cannot happen") }; filter(move |close| matches!(close, $pat_close $(if $guard_close)?)).to({ let $pat_open = open else { panic!("Token does not match same pattern twice. This cannot happen") }; $convert(ast) }) }) }; } fn block> + Clone>( ast_parser: T, ) -> impl Parser> { let ast_clone = move || ast_parser.clone(); let client = matching!(Token::Client(x), ast_clone(), Token::ClientClose, |ast| { Node::Client(x, ast) }); let both = matching!(Token::Both(x), ast_clone(), Token::BothClose, |ast| { Node::Both(x, ast) }); let inline_block = matching!( Token::InlineBlock(x), ast_clone(), Token::InlineClose, |ast| { Node::InlineBlock(x, ast) } ); let layout_sel = matching!( Token::LayoutSel(x), ast_clone(), Token::LayoutSelClose, |ast| { Node::LayoutSel(x, ast) } ); let component = matching!( Token::Component(x), ast_clone(), Token::ComponentClose, |ast| { Node::Component(x, ast) } ); let function = matching!( Token::Function(x) if matches!(x.as_slice(), [StackToken::Function(_)]), ast_clone(), Token::FunctionClose(y) if matches!(x.as_slice(), [StackToken::Function(z)] if z == y), |ast| { Node::FunctionBlock(x, ast) } ); let function_double_block = matching!( Token::Function(x) if matches!(x.as_slice(), [StackToken::Function(_)]), ast_clone().then_ignore(just(Token::Else)).then(ast_clone()), Token::FunctionClose(y) if matches!(x.as_slice(), [StackToken::Function(z)] if z == y), |(ast1, ast2)| { Node::FunctionDoubleBlock(x, ast1, ast2) } ); choice(( client, both, inline_block, layout_sel, component, function, function_double_block, )) } fn html> + Clone>( ast_parser: T, ) -> impl Parser> { let html_frag = select! { Token::HtmlFragment(x) => x }; let node = html_frag .clone() .then(ast_parser) .then(html_frag.clone()) .map(|((before, middle), after)| Html::Node(before, middle, after)); let leaf = html_frag.map(|str| Html::Leaf(str)); node.or(leaf).map(|html| Node::Html(html)) } pub fn ast_parser() -> impl Parser> { recursive(|ast| choice((html(ast.clone()), block(ast), flat())).repeated()).then_ignore(end()) } #[test] fn ast_just_html() { let simple_html = "\ hello world \ "; let tokens = token_parser().parse(simple_html).unwrap(); let ast = ast_parser().parse(tokens); assert_eq!( ast, Ok(vec![Node::Html(Html::Leaf(simple_html.to_string()))]) ) } #[test] fn ast_simple() { let simple_html = "\ {{ x }} \ "; let first_part = "\ " .to_string(); let last_part = " \ " .to_string(); let tokenized = token_parser().parse(simple_html).unwrap(); let ast = ast_parser().parse(tokenized); assert_eq!( ast, Ok(vec![Node::Html(Html::Node( first_part, vec![Node::Expression(vec![StackToken::Ident(vec![ IdentSpecifiers::Ident('x'.to_string()) ])])], last_part ))]) ) } #[test] fn ast_if_else() { let simple_html = "\ {{#!if}} span hello {{Else}} span world {{/if}} \ "; let first_part = "\ " .to_string(); let last_part = " \ " .to_string(); let tokenized = token_parser().parse(simple_html).unwrap(); let ast = ast_parser().parse(tokenized); assert_eq!( ast, Ok(vec![Node::Html(Html::Node( first_part, vec![Node::FunctionDoubleBlock( vec![StackToken::Function("if".to_string())], vec![Node::Html(Html::Leaf("\nspan hello\n".to_string()))], vec![Node::Html(Html::Leaf("\nspan world\n".to_string()))] )], last_part ))]) ) }