From 043f02b342076edfe076723fde0f9f44f81f3123 Mon Sep 17 00:00:00 2001 From: Ross MacArthur Date: Sat, 18 Jan 2025 14:07:37 +0200 Subject: [PATCH] Key map literals with quoted strings --- .../literals/{minijinja.html => jinja.html} | 0 benches/benchdata/literals/upon.html | 104 ------------------ benches/benches/engines.rs | 4 +- benches/src/tests.rs | 4 +- src/compile/parse.rs | 37 ++++--- src/render/core.rs | 2 +- src/types/ast.rs | 6 +- src/types/program.rs | 2 +- tests/render.rs | 6 +- 9 files changed, 33 insertions(+), 132 deletions(-) rename benches/benchdata/literals/{minijinja.html => jinja.html} (100%) delete mode 100644 benches/benchdata/literals/upon.html diff --git a/benches/benchdata/literals/minijinja.html b/benches/benchdata/literals/jinja.html similarity index 100% rename from benches/benchdata/literals/minijinja.html rename to benches/benchdata/literals/jinja.html diff --git a/benches/benchdata/literals/upon.html b/benches/benchdata/literals/upon.html deleted file mode 100644 index 604b7f6..0000000 --- a/benches/benchdata/literals/upon.html +++ /dev/null @@ -1,104 +0,0 @@ -{% for user in [ - { name: "Margaret Brewer" }, - { name: "Cruz McCarty" }, - { name: "Halo Rollins" }, - { name: "Wes Velazquez" }, - { name: "Jaliyah Ford" }, - { name: "Luis Mullen" }, - { name: "Shay Navarro" }, - { name: "Reid Glass" }, - { name: "Clare Collins" }, - { name: "Miles Christensen" }, - { name: "Carmen Solomon" }, - { name: "Musa Steele" }, - { name: "Rylie Dunlap" }, - { name: "Aries Miranda" }, - { name: "Amina Davenport" }, - { name: "Dariel Medrano" }, - { name: "Halle Glenn" }, - { name: "Zaid Hahn" }, - { name: "Fallon Wyatt" }, - { name: "Sam Tanner" }, - { name: "Harmoni McBride" }, - { name: "Denver Campos" }, - { name: "Sutton Choi" }, - { name: "Khari Medina" }, - { name: "Elliana Sweeney" }, - { name: "Nixon Andrews" }, - { name: "Payton Bautista" }, - { name: "Raul Callahan" }, - { name: "Kimber Lyons" }, - { name: "Cyrus Hodge" }, - { name: "Coraline Santos" }, - { name: "Walker Navarro" }, - { name: "Winter Ferguson" }, - { name: "Miguel Garza" }, - { name: "River Hartman" }, - { name: "Baker Aguilar" }, - { name: "Josie Cook" }, - { name: "Ezekiel Sawyer" }, - { name: "Marina Schmidt" }, - { name: "Zayden Russell" }, - { name: "Raelynn Mendez" }, - { name: "Arthur Bates" }, - { name: "Madilyn Medina" }, - { name: "George Rangel" }, - { name: "Gloria Henson" }, - { name: "Bellamy Raymond" }, - { name: "Hadlee Case" }, - { name: "Bentlee Kramer" }, - { name: "Hanna Holland" }, - { name: "Brady Douglas" }, - { name: "Aniyah Duffy" }, - { name: "Kyng Morton" }, - { name: "Mallory Green" }, - { name: "Anthony Chandler" }, - { name: "Viviana Shepard" }, - { name: "Damari McCann" }, - { name: "Joyce George" }, - { name: "Mark Reynolds" }, - { name: "Isabelle Andrews" }, - { name: "Lukas Archer" }, - { name: "Kadence Cardenas" }, - { name: "Johnathan Vazquez" }, - { name: "Journee Cohen" }, - { name: "Killian Orozco" }, - { name: "Renata Vega" }, - { name: "Aidan Phillips" }, - { name: "Naomi Evans" }, - { name: "Elias Spence" }, - { name: "Aislinn Strickland" }, - { name: "Keegan Cline" }, - { name: "Lina Trejo" }, - { name: "Wesson Hale" }, - { name: "Brinley Cabrera" }, - { name: "Cade Ibarra" }, - { name: "Madilynn Nunez" }, - { name: "Caden Sharp" }, - { name: "Camryn Hoffman" }, - { name: "Steven Young" }, - { name: "Zoey Gross" }, - { name: "Quinn Allison" }, - { name: "Chelsea Terrell" }, - { name: "Jaxen Russell" }, - { name: "Raelynn Payne" }, - { name: "Edward Gillespie" }, - { name: "Alianna Bernal" }, - { name: "Eithan Lane" }, - { name: "Amy Turner" }, - { name: "Joshua Hester" }, - { name: "Zendaya Camacho" }, - { name: "Tatum Williamson" }, - { name: "Catherine Kaur" }, - { name: "Augustine Hunt" }, - { name: "Genevieve Villanueva" }, - { name: "Huxley Sweeney" }, - { name: "Yara Barajas" }, - { name: "Brennan Carrillo" }, - { name: "Kaylani Ingram" }, - { name: "Tripp Palmer" }, - { name: "Juniper Schroeder" }, - { name: "Izaiah Schultz" }, -] %} - {{ user.name }} -{% endfor %} diff --git a/benches/benches/engines.rs b/benches/benches/engines.rs index 6f4e804..7eb16ec 100644 --- a/benches/benches/engines.rs +++ b/benches/benches/engines.rs @@ -76,8 +76,8 @@ pub fn bench_compile(c: &mut Criterion) { { let mut g = c.benchmark_group("compile/literals"); - bench!(g, Minijinja, "literals/minijinja.html"); - bench!(g, Upon, "literals/upon.html"); + bench!(g, Minijinja, "literals/jinja.html"); + bench!(g, Upon, "literals/jinja.html"); } } diff --git a/benches/src/tests.rs b/benches/src/tests.rs index c6e49ff..fb4735f 100644 --- a/benches/src/tests.rs +++ b/benches/src/tests.rs @@ -66,12 +66,12 @@ fn filters_upon() { #[test] fn literals_minijinja() { - t!(Minijinja, "../benchdata/literals/minijinja.html"); + t!(Minijinja, "../benchdata/literals/jinja.html"); } #[test] fn literals_upon() { - t!(Upon, "../benchdata/literals/upon.html"); + t!(Upon, "../benchdata/literals/jinja.html"); } #[test] diff --git a/src/compile/parse.rs b/src/compile/parse.rs index e9799cd..ead27c0 100644 --- a/src/compile/parse.rs +++ b/src/compile/parse.rs @@ -7,9 +7,9 @@ use crate::{Engine, Error, Result, Value}; /// A parser that constructs an AST from a token stream. /// -/// The parser is implemented as a simple hand written parser with no recursion. -/// It sometimes needs to peek at the next token to know how to proceed and uses -/// the `peeked` buffer to do this. +/// The parser is implemented as a simple hand written parser. It sometimes +/// needs to peek at the next token to know how to proceed and uses the `peeked` +/// buffer to do this. pub struct Parser<'engine, 'source> { /// A lexer that tokenizes the template source. tokens: Lexer<'engine, 'source>, @@ -403,9 +403,7 @@ impl<'engine, 'source> Parser<'engine, 'source> { } Keyword::EndWith => Ok(Block::EndWith), Keyword::Include => { - let span = self.expect(Token::String)?; - let name = self.parse_string(span)?; - let name = ast::String { name, span }; + let name = self.parse_string()?; let globals = if self.is_next_keyword(Keyword::With)? { self.expect_keyword(Keyword::With)?; Some(self.parse_expr()?) @@ -437,7 +435,7 @@ impl<'engine, 'source> Parser<'engine, 'source> { /// Parses an expression. /// - /// This is a variable with zero or more function calls. For example: + /// This is a base expression with zero or more function calls. For example: /// /// user.name | lower | prefix: "Mr. " /// @@ -678,7 +676,7 @@ impl<'engine, 'source> Parser<'engine, 'source> { } } - /// Parse an integer. + /// Parse a literal integer. fn parse_literal_integer(&self, raw: &str, span: Span, sign: Sign) -> Result { let digits = raw.as_bytes(); let (i, radix) = match digits { @@ -718,7 +716,7 @@ impl<'engine, 'source> Parser<'engine, 'source> { Ok(ast::Literal { value, span }) } - /// Parses a float. + /// Parses a literal float. fn parse_literal_float(&self, raw: &str, span: Span, sign: Sign) -> Result { let float: f64 = raw .parse() @@ -730,14 +728,21 @@ impl<'engine, 'source> Parser<'engine, 'source> { Ok(ast::Literal { value, span }) } - /// Parses a string. + /// Parses a literal string. fn parse_literal_string(&self, span: Span) -> Result { - let value = Value::String(self.parse_string(span)?); + let value = Value::String(self.parse_quoted_string(span)?); Ok(ast::Literal { value, span }) } - /// Parses a string and handles escape characters. - fn parse_string(&self, span: Span) -> Result { + /// Parses a string. + fn parse_string(&mut self) -> Result { + let span = self.expect(Token::String)?; + let value = self.parse_quoted_string(span)?; + Ok(ast::String { value, span }) + } + + /// Parses a quoted string and handles escape characters. + fn parse_quoted_string(&self, span: Span) -> Result { let raw = &self.source()[span]; let string = if raw.contains('\\') { let mut iter = raw.char_indices().map(|(i, c)| (span.m + i, c)); @@ -774,7 +779,7 @@ impl<'engine, 'source> Parser<'engine, 'source> { Ok(string) } - /// Parses a list literal. + /// Parses a list. fn parse_list(&mut self, span: Span) -> Result { let mut items = Vec::new(); loop { @@ -792,14 +797,14 @@ impl<'engine, 'source> Parser<'engine, 'source> { Ok(ast::List { items, span }) } - /// Parses a map literal. + /// Parses a map. fn parse_map(&mut self, span: Span) -> Result { let mut items = Vec::new(); loop { if self.is_next(Token::CloseBrace)? { break; } - let key = self.parse_ident()?; + let key = self.parse_string()?; self.expect(Token::Colon)?; let value = self.parse_base_expr()?; items.push((key, value)); diff --git a/src/render/core.rs b/src/render/core.rs index cf21d22..b806338 100644 --- a/src/render/core.rs +++ b/src/render/core.rs @@ -246,7 +246,7 @@ where } Instr::ExprMapInsert(key) => { - let key = t.source[key.span].to_owned(); + let key = key.value.clone(); let (value, _) = exprs.pop().unwrap(); match exprs.last_mut().unwrap() { (ValueCow::Owned(Value::Map(m)), _) => { diff --git a/src/types/ast.rs b/src/types/ast.rs index 09a07db..9a2efb7 100644 --- a/src/types/ast.rs +++ b/src/types/ast.rs @@ -36,7 +36,7 @@ pub struct Include { #[cfg_attr(internal_debug, derive(Debug))] pub struct String { - pub name: std::string::String, + pub value: std::string::String, pub span: Span, } @@ -155,7 +155,7 @@ pub struct List { #[cfg_attr(internal_debug, derive(Debug))] pub struct Map { - pub items: Vec<(Ident, BaseExpr)>, + pub items: Vec<(String, BaseExpr)>, pub span: Span, } @@ -167,7 +167,7 @@ impl Scope { impl String { pub fn as_str(&self) -> &str { - self.name.as_str() + self.value.as_str() } } diff --git a/src/types/program.rs b/src/types/program.rs index 58b5575..119aa6d 100644 --- a/src/types/program.rs +++ b/src/types/program.rs @@ -71,7 +71,7 @@ pub enum Instr { ExprListPush, /// Insert an item to the current map expression - ExprMapInsert(ast::Ident), + ExprMapInsert(ast::String), /// Apply the filter using the value and args on the top of the stack. /// diff --git a/tests/render.rs b/tests/render.rs index 9e6122c..4383a73 100644 --- a/tests/render.rs +++ b/tests/render.rs @@ -138,7 +138,7 @@ fn render_inline_expr_literal_map() { let mut engine = Engine::new(); engine.add_formatter("debug", debug); let result = engine - .compile(r#"{{ {a: true, b: 123, c: -3.14, d: "test", e: lorem} | debug }}"#) + .compile(r#"{{ {"a": true, "b": 123, "c": -3.14, "d": "test", "e": lorem} | debug }}"#) .unwrap() .render(&engine, value! { lorem: "ipsum" }) .to_string() @@ -151,7 +151,7 @@ fn render_inline_expr_nested_lists_and_maps() { let mut engine = Engine::new(); engine.add_formatter("debug", debug); let result = engine - .compile(r#"{{ [true, [123, -3.14], {a: "test", b: lorem, c: [1, 2]}] | debug }}"#) + .compile(r#"{{ [true, [123, -3.14], {"a": "test", "b": lorem, "c": [1, 2]}] | debug }}"#) .unwrap() .render(&engine, value! { lorem: "ipsum" }) .to_string() @@ -1170,7 +1170,7 @@ fn render_include_with_statement_map() { let mut engine = Engine::new(); engine.add_template("nested", "{{ dolor }}").unwrap(); let result = engine - .compile(r#"lorem {% include "nested" with { dolor: dolor } %} sit"#) + .compile(r#"lorem {% include "nested" with { "dolor": dolor } %} sit"#) .unwrap() .render(&engine, value! { dolor: "test" }) .to_string()