diff --git a/src/expression_evaluator.rs b/src/expression_evaluator.rs index 6e5774a..169f9c6 100644 --- a/src/expression_evaluator.rs +++ b/src/expression_evaluator.rs @@ -1,4 +1,3 @@ -use crate::error::Result; use crate::renderer::Render; use crate::value::visitors; use crate::value::Value; @@ -7,7 +6,6 @@ use std::io::Write; pub trait Evaluate { fn evaluate(&self) -> Value; } - #[derive(Debug)] pub enum BinaryOperation { Plus, diff --git a/src/expression_parser.rs b/src/expression_parser.rs index cc803ed..2eaaca4 100644 --- a/src/expression_parser.rs +++ b/src/expression_parser.rs @@ -24,7 +24,7 @@ impl ExpressionParser { Ok(ExpressionRenderer::new(evaluator)) } - fn full_expresion_parser<'a>( + pub fn full_expresion_parser<'a>( mut lexer: &mut Peekable>>, ) -> Result { let mut evaluator = FullExpressionEvaluator::new(); @@ -160,9 +160,7 @@ impl ExpressionParser { )); } - fn parse_unary_plus_min<'a>( - mut lexer: &mut Peekable>>, - ) -> Result { + fn parse_unary_plus_min<'a>(lexer: &mut Peekable>>) -> Result { let unary_op = match lexer.peek() { Some(Token::Plus) => Some(UnaryOperation::Plus), Some(Token::Minus) => Some(UnaryOperation::Minus), diff --git a/src/lib.rs b/src/lib.rs index 2265721..b54d19d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ mod expression_parser; mod keyword; mod lexer; mod renderer; +mod statement; mod template; mod template_env; mod template_parser; diff --git a/src/statement/mod.rs b/src/statement/mod.rs new file mode 100644 index 0000000..6c0988c --- /dev/null +++ b/src/statement/mod.rs @@ -0,0 +1,150 @@ +use crate::lexer::Token; +use std::io::Write; + +use crate::expression_evaluator::Evaluate; +use crate::renderer::ComposedRenderer; +use crate::renderer::Render; +use crate::value::Value; +use std::rc::Rc; +pub mod parser; +pub struct IfStatement<'a> { + expression: Box, + body: Option>>, + else_branches: Vec>, +} +impl<'a> IfStatement<'a> { + pub fn new(expression: Box) -> Self { + Self { + expression, + body: None, + else_branches: vec![], + } + } + fn set_main_body(&mut self, body: Rc>) { + let if_body = body.clone(); + self.body = Some(if_body); + } + pub fn add_else_branch(&mut self, branch: Statement<'a>) { + self.else_branches.push(branch); + } +} +impl<'a> Render for IfStatement<'a> { + fn render(&self, out: &mut dyn Write) { + let value = self.expression.evaluate(); + if let Value::Boolean(true) = value { + self.body.as_ref().unwrap().render(out) + } else { + for branch in &self.else_branches { + if let Statement::Else(else_branch) = branch { + if else_branch.should_render() { + branch.render(out); + break; + } + } else { + todo!() + } + } + }; + } +} + +pub struct ElseStatement<'a> { + expression: Option>, + body: Option>>, +} + +impl<'a> ElseStatement<'a> { + pub fn new() -> Self { + Self { + expression: None, + body: None, + } + } + fn set_main_body(&mut self, body: Rc>) { + let else_body = body.clone(); + self.body = Some(else_body); + } + + fn should_render(&self) -> bool { + self.expression.is_none() + || match self.expression.as_ref().unwrap().evaluate() { + Value::Boolean(boolean) => boolean, + _ => todo!(), + } + } +} +impl<'a> Render for ElseStatement<'a> { + fn render(&self, out: &mut dyn Write) { + self.body.as_ref().unwrap().render(out); + } +} + +pub enum Statement<'a> { + If(IfStatement<'a>), + Else(ElseStatement<'a>), +} +impl<'a> Statement<'a> { + pub fn set_main_body(&mut self, body: Rc>) { + match self { + Statement::If(statement) => statement.set_main_body(body), + Statement::Else(statement) => statement.set_main_body(body), + } + } + pub fn add_else_branch(&mut self, branch: Statement<'a>) { + match self { + Statement::If(statement) => statement.add_else_branch(branch), + Statement::Else(statement) => todo!(), + } + } +} +impl<'a> Render for Statement<'a> { + fn render(&self, out: &mut dyn Write) { + match self { + Statement::If(statement) => statement.render(out), + Statement::Else(statement) => statement.render(out), + } + } +} + +pub struct StatementInfo<'a> { + mode: StatementInfoType, + pub current_composition: Rc>, + compositions: Vec>>, + token: Token<'a>, + renderer: Option>, +} + +pub enum StatementInfoType { + TemplateRoot, + IfStatement, + ElseIfStatement, + ForStatement, + SetStatement, + ExtendsStatement, + BlockStatement, + ParentBlockStatement, + MacroStatement, + MacroCallStatement, + WithStatement, + FilterStatement, +} + +impl<'a> StatementInfo<'a> { + pub fn new( + mode: StatementInfoType, + token: Token<'a>, + renderers: Rc>, + ) -> Self { + let current_composition = renderers.clone(); + let compositions = vec![renderers]; + Self { + mode, + token, + current_composition, + compositions, + renderer: None, + } + } +} + +pub type StatementInfoList<'a> = Vec>; diff --git a/src/statement/parser.rs b/src/statement/parser.rs new file mode 100644 index 0000000..4857495 --- /dev/null +++ b/src/statement/parser.rs @@ -0,0 +1,94 @@ +use super::{ + ElseStatement, IfStatement, Statement, StatementInfo, StatementInfoList, StatementInfoType, +}; +use crate::error::{Error, ErrorKind, Result, SourceLocation}; +use crate::expression_parser::ExpressionParser; +use crate::lexer::Token; +use crate::renderer::ComposedRenderer; +use logos::{Lexer, Logos}; +use std::iter::Peekable; +pub struct StatementParser; +use std::rc::Rc; + +impl StatementParser { + pub fn parse<'a, 'b>( + text: &'a str, + mut statementinfo_list: &mut StatementInfoList<'a>, + ) -> Result<()> { + let lexer: Lexer> = Token::lexer(text); + let mut lexer: Peekable>> = lexer.peekable(); + let tok = lexer.next(); + + let result = match tok { + Some(Token::If) => StatementParser::parse_if(&mut lexer, &mut statementinfo_list), + Some(Token::Else) => StatementParser::parse_else(&mut statementinfo_list), + Some(Token::EndIf) => StatementParser::parse_endif(&mut statementinfo_list), + _ => todo!(), + }; + + result + } + fn parse_if<'a>( + lexer: &mut Peekable>>, + statementinfo_list: &mut StatementInfoList, + ) -> Result<()> { + let value = ExpressionParser::full_expresion_parser(lexer)?; + let composed_renderer = Rc::new(ComposedRenderer::new()); + let renderer = Statement::If(IfStatement::new(Box::new(value))); + let mut statement_info = + StatementInfo::new(StatementInfoType::IfStatement, Token::If, composed_renderer); + statement_info.renderer = Some(renderer); + + statementinfo_list.push(statement_info); + Ok(()) + } + + fn parse_else<'a>(statementinfo_list: &mut StatementInfoList) -> Result<()> { + let composed_renderer = Rc::new(ComposedRenderer::new()); + let renderer = Statement::Else(ElseStatement::new()); + let mut statement_info = StatementInfo::new( + StatementInfoType::ElseIfStatement, + Token::Else, + composed_renderer, + ); + statement_info.renderer = Some(renderer); + statementinfo_list.push(statement_info); + Ok(()) + } + fn parse_endif<'a>(statementinfo_list: &mut StatementInfoList<'a>) -> Result<()> { + if statementinfo_list.len() <= 1 { + return Err(Error::from(ErrorKind::UnexpectedStatement( + SourceLocation::new(110, 220), + ))); + } + let mut info; + let mut else_branches = vec![]; + loop { + info = statementinfo_list.pop().unwrap(); + match info.mode { + StatementInfoType::IfStatement => { + break; + } + StatementInfoType::ElseIfStatement => { + let mut renderer = info.renderer.unwrap(); + renderer.set_main_body(info.compositions.remove(0)); + else_branches.push(renderer); + } + _ => todo!(), + } + } + let mut renderer = info.renderer.unwrap(); + let body = info.compositions.remove(0); + renderer.set_main_body(body); + + for else_branch in else_branches { + renderer.add_else_branch(else_branch); + } + statementinfo_list + .last_mut() + .unwrap() + .current_composition + .add_renderer(Box::new(renderer)); + Ok(()) + } +} diff --git a/src/template_parser.rs b/src/template_parser.rs index 5729f65..7e60c18 100644 --- a/src/template_parser.rs +++ b/src/template_parser.rs @@ -2,9 +2,12 @@ use crate::error::{Error, ErrorKind, Result, SourceLocation}; use crate::expression_parser::ExpressionParser; use crate::keyword::{RegexEnum, KEYWORDS, ROUGH_TOKENIZER}; use crate::lexer::Token; -use crate::renderer::{ComposedRenderer, RawTextRenderer, Render}; +use crate::renderer::{ComposedRenderer, RawTextRenderer}; +use crate::statement::parser::StatementParser; +use crate::statement::{StatementInfo, StatementInfoList, StatementInfoType}; use crate::template_env::TemplateEnv; use regex::Regex; +use std::rc::Rc; use std::sync::RwLock; #[derive(Debug)] @@ -36,7 +39,7 @@ impl<'a, 'b> TemplateParser<'a, 'b> { }) } - fn fine_parsing(&self, renderer: &mut ComposedRenderer<'a>) -> Result<()> { + fn fine_parsing(&self, renderer: Rc>) -> Result<()> { let mut statements_stack: StatementInfoList = vec![]; let root = StatementInfo::new(StatementInfoType::TemplateRoot, Token::Unknown, renderer); statements_stack.push(root); @@ -66,7 +69,14 @@ impl<'a, 'b> TemplateParser<'a, 'b> { .current_composition .add_renderer(Box::new(new_renderer)); } - _ => {} + TextBlockType::Comment => {} + TextBlockType::Statement | TextBlockType::LineStatement => { + let text = self.template_body.read().unwrap(); + StatementParser::parse( + &text[orig_block.range.start..orig_block.range.end], + &mut statements_stack, + )?; + } } } Ok(()) @@ -75,9 +85,9 @@ impl<'a, 'b> TemplateParser<'a, 'b> { pub fn parse(&mut self) -> Result> { match self.rough_parsing() { Ok(_) => { - let mut renderer = ComposedRenderer::new(); - self.fine_parsing(&mut renderer)?; - Ok(renderer) + let renderer = Rc::new(ComposedRenderer::new()); + self.fine_parsing(renderer.clone())?; + Ok(Rc::try_unwrap(renderer).unwrap()) } Err(error) => Err(error), } @@ -174,8 +184,25 @@ impl<'a, 'b> TemplateParser<'a, 'b> { self.current_block_info.write().unwrap().range.start = self.finish_current_block(match_start, TextBlockType::RawText, None); } - RegexEnum::StmtBegin => {} - RegexEnum::StmtEnd => {} + RegexEnum::StmtBegin => { + self.start_control_block(TextBlockType::Statement, match_start, match_end); + } + RegexEnum::StmtEnd => { + match self.current_block_info.read().unwrap().mode { + TextBlockType::RawBlock => { + self.finish_current_line(match_end); + return Err(Error::from(ErrorKind::UnexpectedStmtEnd( + SourceLocation::new(match_start, match_end), + ))); + } + TextBlockType::Statement => {} + _ => { + continue; + } + } + self.current_block_info.write().unwrap().range.start = + self.finish_current_block(match_start, TextBlockType::RawText, None); + } RegexEnum::RawBegin => { match self.current_block_info.read().unwrap().mode { TextBlockType::RawBlock => continue, @@ -339,46 +366,3 @@ impl Default for LineInfo { } } } - -struct StatementInfo<'a, 'b> { - mode: StatementInfoType, - current_composition: &'a ComposedRenderer<'b>, - compositions: Vec>, - token: Token<'a>, - renderer: Option>, -} - -enum StatementInfoType { - TemplateRoot, - IfStatement, - ElseIfStatement, - ForStatement, - SetStatement, - ExtendsStatement, - BlockStatement, - ParentBlockStatement, - MacroStatement, - MacroCallStatement, - WithStatement, - FilterStatement, -} - -impl<'a, 'b> StatementInfo<'a, 'b> { - pub fn new( - mode: StatementInfoType, - token: Token<'a>, - renderers: &'a mut ComposedRenderer<'b>, - ) -> Self { - let compositions = vec![]; - let current_composition = renderers; - Self { - mode, - token, - current_composition, - compositions, - renderer: None, - } - } -} - -type StatementInfoList<'a, 'b> = Vec>; diff --git a/tests/lib.rs b/tests/lib.rs index d73e6c7..d990969 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -4,4 +4,5 @@ extern crate assert_matches; mod basic; mod error; mod expressions; +mod statement_if; mod utils; diff --git a/tests/statement_if.rs b/tests/statement_if.rs new file mode 100644 index 0000000..329b66a --- /dev/null +++ b/tests/statement_if.rs @@ -0,0 +1,45 @@ +use super::utils::assert_render_template_eq; +use temple::error::Result; + +#[test] +fn render_if_body() -> Result<()> { + assert_render_template_eq( + "{% if true %} +Hello, world! +{% endif %}", + " +Hello, world! +", + ) +} + +#[test] +fn dont_render_if_body() -> Result<()> { + assert_render_template_eq( + "Only render this.{% if false %} +this not +{% endif %}", + "Only render this.", + ) +} + +#[test] +fn render_else() -> Result<()> { + assert_render_template_eq( + "{% if true == false %} + This should not be rendered + {% else %}Rendered from else branch{% endif %}", + "Rendered from else branch", + ) +} + +fn render_elif() -> Result<()> { + assert_render_template_eq( + "{% if 5 > 7 %} + This should not be rendered + {% elif 5 == 6 %}Not rendered from elif elif branch + {% elif 5 == 5 %}Rendered from elif branch{% else %} + Ignored{% endif %}", + "Rendered from elif branch", + ) +}