diff --git a/sailfish-compiler/src/parser.rs b/sailfish-compiler/src/parser.rs index 63a0c4b..debeec3 100644 --- a/sailfish-compiler/src/parser.rs +++ b/sailfish-compiler/src/parser.rs @@ -56,6 +56,7 @@ impl Default for Parser { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum TokenKind { + NestedTemplateOnce, BufferedCode { escape: bool }, Code, Comment, @@ -161,6 +162,10 @@ impl<'a> ParseStream<'a> { token_kind = TokenKind::BufferedCode { escape: false }; start += 1; } + Some(b'^') => { + token_kind = TokenKind::NestedTemplateOnce; + start += 1; + } _ => {} } @@ -402,6 +407,33 @@ mod tests { use super::*; use pretty_assertions::assert_eq; + #[test] + fn nested_render_once() { + let src = r#"outer <%^ inner|upper %> outer"#; + let parser = Parser::default(); + let tokens = parser.parse(src).into_vec().unwrap(); + assert_eq!( + &tokens, + &[ + Token { + content: "outer ", + offset: 0, + kind: TokenKind::Text, + }, + Token { + content: "inner|upper", + offset: 10, + kind: TokenKind::NestedTemplateOnce, + }, + Token { + content: " outer", + offset: 24, + kind: TokenKind::Text, + }, + ] + ); + } + #[test] fn non_ascii_delimiter() { let src = r##"foo <🍣# This is a comment 🍣> bar <🍣= r"🍣>" 🍣> baz <🍣🍣"##; diff --git a/sailfish-compiler/src/translator.rs b/sailfish-compiler/src/translator.rs index 527a1da..60ae027 100644 --- a/sailfish-compiler/src/translator.rs +++ b/sailfish-compiler/src/translator.rs @@ -135,6 +135,15 @@ impl SourceBuilder { &mut self, token: &Token<'a>, escape: bool, + ) -> Result<(), Error> { + self.write_buffered_code_with_suffix(token, escape, "") + } + + fn write_buffered_code_with_suffix<'a>( + &mut self, + token: &Token<'a>, + escape: bool, + suffix: &str, ) -> Result<(), Error> { // parse and split off filter let code_block = syn::parse_str::(token.as_str()).map_err(|e| { @@ -154,7 +163,7 @@ impl SourceBuilder { self.source.push_str("!(__sf_buf, "); if let Some(filter) = code_block.filter { - let expr_str = code_block.expr.into_token_stream().to_string(); + let expr_str = format!("{}{}", code_block.expr.into_token_stream(), suffix); let (name, extra_args) = match filter { Filter::Ident(i) => (i.to_string(), None), Filter::Call(c) => ( @@ -188,6 +197,7 @@ impl SourceBuilder { self.source.push(')'); } else { self.write_token(token); + self.source.push_str(suffix); } self.source.push_str(");\n"); @@ -205,6 +215,11 @@ impl SourceBuilder { TokenKind::BufferedCode { escape } => { self.write_buffered_code(&token, escape)? } + TokenKind::NestedTemplateOnce => self.write_buffered_code_with_suffix( + &token, + false, + ".render_once()?", + )?, TokenKind::Text => { // concatenate repeated text token let offset = token.offset(); @@ -367,4 +382,48 @@ mod tests { ps.feed_tokens(token_iter.clone()).unwrap(); Translator::new().translate(token_iter).unwrap(); } + + #[test] + fn translate_nested_render_once() { + let src = r#"outer <%^ inner %> outer"#; + let lexer = Parser::new(); + let token_iter = lexer.parse(src); + let mut ps = SourceBuilder { + escape: true, + source: String::with_capacity(token_iter.original_source.len()), + source_map: SourceMap::default(), + }; + ps.feed_tokens(token_iter.clone()).unwrap(); + assert_eq!( + &Translator::new() + .translate(token_iter) + .unwrap() + .ast + .into_token_stream() + .to_string(), + r#"{ __sf_rt :: render_text ! (__sf_buf , "outer ") ; __sf_rt :: render ! (__sf_buf , inner . render_once () ?) ; __sf_rt :: render_text ! (__sf_buf , " outer") ; }"# + ); + } + + #[test] + fn translate_nested_render_once_with_filter() { + let src = r#"outer <%^ inner|upper %> outer"#; + let lexer = Parser::new(); + let token_iter = lexer.parse(src); + let mut ps = SourceBuilder { + escape: true, + source: String::with_capacity(token_iter.original_source.len()), + source_map: SourceMap::default(), + }; + ps.feed_tokens(token_iter.clone()).unwrap(); + assert_eq!( + &Translator::new() + .translate(token_iter) + .unwrap() + .ast + .into_token_stream() + .to_string(), + r#"{ __sf_rt :: render_text ! (__sf_buf , "outer ") ; __sf_rt :: render ! (__sf_buf , sailfish :: runtime :: filter :: upper (& (inner . render_once () ?))) ; __sf_rt :: render_text ! (__sf_buf , " outer") ; }"# + ); + } }