From 32df16f1c6dd6a5ee4b6bb1ea5f327635d271eae Mon Sep 17 00:00:00 2001 From: wrapperup Date: Wed, 20 Sep 2023 01:50:24 -0400 Subject: [PATCH 01/43] hotreload prototype --- maud/Cargo.toml | 4 +- maud/src/lib.rs | 15 ++ maud_macros/Cargo.toml | 1 + maud_macros/src/lib.rs | 31 +-- maud_macros_impl/Cargo.toml | 20 ++ {maud_macros => maud_macros_impl}/src/ast.rs | 4 - .../src/escape.rs | 0 .../src/generate.rs | 1 - maud_macros_impl/src/lib.rs | 89 ++++++++ .../src/parse.rs | 126 +++-------- maud_macros_impl/src/runtime.rs | 210 ++++++++++++++++++ 11 files changed, 377 insertions(+), 124 deletions(-) create mode 100644 maud_macros_impl/Cargo.toml rename {maud_macros => maud_macros_impl}/src/ast.rs (98%) rename {maud_macros => maud_macros_impl}/src/escape.rs (100%) rename {maud_macros => maud_macros_impl}/src/generate.rs (99%) create mode 100644 maud_macros_impl/src/lib.rs rename {maud_macros => maud_macros_impl}/src/parse.rs (83%) create mode 100644 maud_macros_impl/src/runtime.rs diff --git a/maud/Cargo.toml b/maud/Cargo.toml index d9088537..be7d3adf 100644 --- a/maud/Cargo.toml +++ b/maud/Cargo.toml @@ -12,7 +12,8 @@ categories = ["template-engine"] edition = "2021" [features] -default = [] +default = ["hotreload"] +hotreload = ["maud_macros_impl"] # Web framework integrations actix-web = ["actix-web-dep", "futures-util"] @@ -20,6 +21,7 @@ axum = ["axum-core", "http"] [dependencies] maud_macros = { version = "0.25.0", path = "../maud_macros" } +maud_macros_impl = { version = "0.25.0", path = "../maud_macros_impl", optional = true } itoa = "1" rocket = { version = ">= 0.3, < 0.5", optional = true } futures-util = { version = "0.3.0", optional = true, default-features = false } diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 53aa3fa9..0868a49c 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -16,6 +16,21 @@ use core::fmt::{self, Arguments, Display, Write}; pub use maud_macros::html; +#[cfg(feature = "hotreload")] +pub use maud_macros::html_hotreload; + +#[cfg(feature = "hotreload")] +pub use maud_macros_impl::expand; + +#[cfg(feature = "hotreload")] +pub use maud_macros_impl::gather_html_macro_invocations; + +#[cfg(feature = "hotreload")] +pub use maud_macros_impl::format_str; + +#[cfg(feature = "hotreload")] +pub use maud_macros_impl::parse; + mod escape; /// An adapter that escapes HTML special characters. diff --git a/maud_macros/Cargo.toml b/maud_macros/Cargo.toml index 1c5c4e83..cf1f5d10 100644 --- a/maud_macros/Cargo.toml +++ b/maud_macros/Cargo.toml @@ -11,6 +11,7 @@ description = "Compile-time HTML templates." edition = "2021" [dependencies] +maud_macros_impl = { path = "../maud_macros_impl" } syn = "1.0.8" quote = "1.0.7" proc-macro2 = "1.0.23" diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs index 660dbcf8..9da97901 100644 --- a/maud_macros/src/lib.rs +++ b/maud_macros/src/lib.rs @@ -1,37 +1,16 @@ #![doc(html_root_url = "https://docs.rs/maud_macros/0.25.0")] -// TokenStream values are reference counted, and the mental overhead of tracking -// lifetimes outweighs the marginal gains from explicit borrowing -#![allow(clippy::needless_pass_by_value)] - extern crate proc_macro; -mod ast; -mod escape; -mod generate; -mod parse; - -use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use proc_macro_error::proc_macro_error; -use quote::quote; #[proc_macro] #[proc_macro_error] pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - expand(input.into()).into() + maud_macros_impl::expand(input.into()).into() } -fn expand(input: TokenStream) -> TokenStream { - let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); - // Heuristic: the size of the resulting markup tends to correlate with the - // code size of the template itself - let size_hint = input.to_string().len(); - let markups = parse::parse(input); - let stmts = generate::generate(markups, output_ident.clone()); - quote!({ - extern crate alloc; - extern crate maud; - let mut #output_ident = alloc::string::String::with_capacity(#size_hint); - #stmts - maud::PreEscaped(#output_ident) - }) +#[proc_macro] +#[proc_macro_error] +pub fn html_hotreload(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + maud_macros_impl::expand_runtime(input.into()).into() } diff --git a/maud_macros_impl/Cargo.toml b/maud_macros_impl/Cargo.toml new file mode 100644 index 00000000..595d9d74 --- /dev/null +++ b/maud_macros_impl/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "maud_macros_impl" +# When releasing a new version, please update html_root_url in src/lib.rs +version = "0.25.0" +authors = ["Chris Wong "] +license = "MIT/Apache-2.0" +documentation = "https://docs.rs/maud_macros/" +homepage = "https://maud.lambda.xyz/" +repository = "https://github.com/lambda-fairy/maud" +description = "Compile-time HTML templates." +edition = "2021" + +[dependencies] +syn = "1.0.8" +quote = "1.0.7" +proc-macro2 = "1.0.23" +proc-macro-error = "1.0.0" + +[lib] +name = "maud_macros_impl" diff --git a/maud_macros/src/ast.rs b/maud_macros_impl/src/ast.rs similarity index 98% rename from maud_macros/src/ast.rs rename to maud_macros_impl/src/ast.rs index cd8a2cef..253106f2 100644 --- a/maud_macros/src/ast.rs +++ b/maud_macros_impl/src/ast.rs @@ -4,9 +4,6 @@ use proc_macro_error::SpanRange; #[derive(Debug)] pub enum Markup { /// Used as a placeholder value on parse error. - ParseError { - span: SpanRange, - }, Block(Block), Literal { content: String, @@ -42,7 +39,6 @@ pub enum Markup { impl Markup { pub fn span(&self) -> SpanRange { match *self { - Markup::ParseError { span } => span, Markup::Block(ref block) => block.span(), Markup::Literal { span, .. } => span, Markup::Symbol { ref symbol } => span_tokens(symbol.clone()), diff --git a/maud_macros/src/escape.rs b/maud_macros_impl/src/escape.rs similarity index 100% rename from maud_macros/src/escape.rs rename to maud_macros_impl/src/escape.rs diff --git a/maud_macros/src/generate.rs b/maud_macros_impl/src/generate.rs similarity index 99% rename from maud_macros/src/generate.rs rename to maud_macros_impl/src/generate.rs index ee27a55d..c05c248d 100644 --- a/maud_macros/src/generate.rs +++ b/maud_macros_impl/src/generate.rs @@ -31,7 +31,6 @@ impl Generator { fn markup(&self, markup: Markup, build: &mut Builder) { match markup { - Markup::ParseError { .. } => {} Markup::Block(Block { markups, outer_span, diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs new file mode 100644 index 00000000..68dd28ba --- /dev/null +++ b/maud_macros_impl/src/lib.rs @@ -0,0 +1,89 @@ +#![doc(html_root_url = "https://docs.rs/maud_macros_impl/0.25.0")] +// TokenStream values are reference counted, and the mental overhead of tracking +// lifetimes outweighs the marginal gains from explicit borrowing +#![allow(clippy::needless_pass_by_value)] + +mod ast; +mod escape; +mod generate; +mod runtime; +mod parse; + +use std::{io::{BufReader, BufRead}, fs::File}; + +use proc_macro2::{Ident, Span, TokenStream, TokenTree}; +use quote::quote; + +pub use parse::parse; +pub use runtime::format_str; + +pub fn expand(input: TokenStream) -> TokenStream { + let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); + // Heuristic: the size of the resulting markup tends to correlate with the + // code size of the template itself + let size_hint = input.to_string().len(); + let markups = parse::parse(input); + + let stmts = generate::generate(markups, output_ident.clone()); + quote!({ + extern crate alloc; + extern crate maud; + let mut #output_ident = alloc::string::String::with_capacity(#size_hint); + #stmts + maud::PreEscaped(#output_ident) + }) +} + +// For the hot-reloadable version, maud will instead embed a tiny runtime +// that will render any markup-only changes. Any other changes will +// require a recompile. Of course, this is miles slower than the +// normal version, but it can be miles faster to iterate on. +pub fn expand_runtime(input: TokenStream) -> TokenStream { + let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); + let markups = parse::parse(input); + let stmts = runtime::generate(markups); + quote!({ + extern crate alloc; + extern crate maud; + let mut #output_ident = String::new(); + let mut vars: ::std::collections::HashMap<&'static str, String> = ::std::collections::HashMap::new(); + + let input = ::maud::gather_html_macro_invocations(file!(), line!()); + + let markups = ::maud::parse(input.parse().unwrap()); + let format_str = ::maud::format_str(markups); + + #stmts + + let template = ::leon::Template::parse(&format_str).unwrap(); + + maud::PreEscaped(template.render(&vars).unwrap()) + }) +} + +/// Grabs the inside of an html! {} invocation and returns it as a string +pub fn gather_html_macro_invocations(file_path: &'static str, start_column: u32) -> String { + let buf_reader = BufReader::new(File::open(file_path).unwrap()); + + let mut braces_diff = 0; + + let mut html_invocation = buf_reader + .lines() + .skip(start_column as usize) + .take_while(|line| { + let line = line.as_ref().unwrap(); + for c in line.chars() { + if c == '{' { + braces_diff += 1; + } else if c == '}' { + braces_diff -= 1; + } + } + braces_diff != -1 + }) + .map(|line| line.unwrap()) + .collect::>() + .join("\n"); + + html_invocation +} diff --git a/maud_macros/src/parse.rs b/maud_macros_impl/src/parse.rs similarity index 83% rename from maud_macros/src/parse.rs rename to maud_macros_impl/src/parse.rs index dee037e4..123e9fe6 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros_impl/src/parse.rs @@ -1,5 +1,5 @@ use proc_macro2::{Delimiter, Ident, Literal, Spacing, Span, TokenStream, TokenTree}; -use proc_macro_error::{abort, abort_call_site, emit_error, SpanRange}; +use proc_macro_error::SpanRange; use std::collections::HashMap; use syn::Lit; @@ -87,7 +87,7 @@ impl Parser { let token = match self.peek() { Some(token) => token, None => { - abort_call_site!("unexpected end of input"); + panic!("unexpected end of input"); } }; let markup = match token { @@ -113,23 +113,15 @@ impl Parser { "for" => self.for_expr(at_span, keyword), "match" => self.match_expr(at_span, keyword), "let" => { - let span = SpanRange { - first: at_span, - last: ident.span(), - }; - abort!(span, "`@let` only works inside a block"); + panic!("`@let` only works inside a block"); } other => { - let span = SpanRange { - first: at_span, - last: ident.span(), - }; - abort!(span, "unknown keyword `@{}`", other); + panic!("unknown keyword `@{}`", other); } } } _ => { - abort!(at_span, "expected keyword after `@`"); + panic!("expected keyword after `@`"); } } } @@ -138,25 +130,15 @@ impl Parser { let ident_string = ident.to_string(); match ident_string.as_str() { "if" | "while" | "for" | "match" | "let" => { - abort!( - ident, - "found keyword `{}`", ident_string; - help = "should this be a `@{}`?", ident_string + panic!( + "found keyword `{}`", ident_string ); } "true" | "false" => { - if let Some(attr_name) = &self.current_attr { - emit_error!( - ident, - r#"attribute value must be a string"#; - help = "to declare an empty attribute, omit the equals sign: `{}`", - attr_name; - help = "to toggle the attribute, use square brackets: `{}[some_boolean_flag]`", - attr_name; + if let Some(_attr_name) = &self.current_attr { + panic!( + r#"attribute value must be a string"# ); - return ast::Markup::ParseError { - span: SpanRange::single_span(ident.span()), - }; } } _ => {} @@ -186,8 +168,8 @@ impl Parser { ast::Markup::Block(self.block(group.stream(), SpanRange::single_span(group.span()))) } // ??? - token => { - abort!(token, "invalid syntax"); + _token => { + panic!("invalid syntax"); } }; markup @@ -205,22 +187,18 @@ impl Parser { // Boolean literals are idents, so `Lit::Bool` is handled in // `markup`, not here. Lit::Int(..) | Lit::Float(..) => { - emit_error!(literal, r#"literal must be double-quoted: `"{}"`"#, literal); + panic!(r#"literal must be double-quoted: `"{}"`"#, literal); } Lit::Char(lit_char) => { - emit_error!( - literal, + panic!( r#"literal must be double-quoted: `"{}"`"#, lit_char.value(), ); } _ => { - emit_error!(literal, "expected string"); + panic!("expected string"); } } - ast::Markup::ParseError { - span: SpanRange::single_span(literal.span()), - } } /// Parses an `@if` expression. @@ -237,7 +215,7 @@ impl Parser { None => { let mut span = ast::span_tokens(head); span.first = at_span; - abort!(span, "expected body for this `@if`"); + panic!("expected body for this `@if`"); } } }; @@ -281,11 +259,7 @@ impl Parser { }); } _ => { - let span = SpanRange { - first: at_span, - last: else_keyword.span(), - }; - abort!(span, "expected body for this `@else`"); + panic!("expected body for this `@else`"); } }, } @@ -299,7 +273,6 @@ impl Parser { /// /// The leading `@while` should already be consumed. fn while_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { - let keyword_span = keyword.span(); let mut head = vec![keyword]; let body = loop { match self.next() { @@ -308,11 +281,7 @@ impl Parser { } Some(token) => head.push(token), None => { - let span = SpanRange { - first: at_span, - last: keyword_span, - }; - abort!(span, "expected body for this `@while`"); + panic!("expected body for this `@while`"); } } }; @@ -329,7 +298,6 @@ impl Parser { /// /// The leading `@for` should already be consumed. fn for_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { - let keyword_span = keyword.span(); let mut head = vec![keyword]; loop { match self.next() { @@ -339,11 +307,7 @@ impl Parser { } Some(token) => head.push(token), None => { - let span = SpanRange { - first: at_span, - last: keyword_span, - }; - abort!(span, "missing `in` in `@for` loop"); + panic!("missing `in` in `@for` loop"); } } } @@ -354,11 +318,7 @@ impl Parser { } Some(token) => head.push(token), None => { - let span = SpanRange { - first: at_span, - last: keyword_span, - }; - abort!(span, "expected body for this `@for`"); + panic!("expected body for this `@for`"); } } }; @@ -375,7 +335,6 @@ impl Parser { /// /// The leading `@match` should already be consumed. fn match_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { - let keyword_span = keyword.span(); let mut head = vec![keyword]; let (arms, arms_span) = loop { match self.next() { @@ -385,11 +344,7 @@ impl Parser { } Some(token) => head.push(token), None => { - let span = SpanRange { - first: at_span, - last: keyword_span, - }; - abort!(span, "expected body for this `@match`"); + panic!("expected body for this `@match`"); } } }; @@ -431,8 +386,7 @@ impl Parser { if head.is_empty() { return None; } else { - let head_span = ast::span_tokens(head); - abort!(head_span, "unexpected end of @match pattern"); + panic!("unexpected end of @match pattern"); } } } @@ -466,8 +420,7 @@ impl Parser { self.block(body.into_iter().collect(), span) } None => { - let span = ast::span_tokens(head); - abort!(span, "unexpected end of @match arm"); + panic!("unexpected end of @match arm"); } }; Some(ast::MatchArm { @@ -493,7 +446,7 @@ impl Parser { None => { let mut span = ast::span_tokens(tokens); span.first = at_span; - abort!(span, "unexpected end of `@let` expression"); + panic!("unexpected end of `@let` expression"); } } } @@ -509,10 +462,8 @@ impl Parser { None => { let mut span = ast::span_tokens(tokens); span.first = at_span; - abort!( - span, - "unexpected end of `@let` expression"; - help = "are you missing a semicolon?" + panic!( + "unexpected end of `@let` expression" ); } } @@ -528,8 +479,7 @@ impl Parser { /// The element name should already be consumed. fn element(&mut self, name: TokenStream) -> ast::Markup { if self.current_attr.is_some() { - let span = ast::span_tokens(name); - abort!(span, "unexpected element"); + panic!("unexpected element"); } let attrs = self.attrs(); let body = match self.peek() { @@ -539,11 +489,8 @@ impl Parser { // Void element self.advance(); if punct.as_char() == '/' { - emit_error!( - punct, - "void elements must use `;`, not `/`"; - help = "change this to `;`"; - help = "see https://github.com/lambda-fairy/maud/pull/315 for details"; + panic!( + "void elements must use `;`, not `/`" ); } ast::ElementBody::Void { @@ -552,16 +499,13 @@ impl Parser { } Some(_) => match self.markup() { ast::Markup::Block(block) => ast::ElementBody::Block { block }, - markup => { - let markup_span = markup.span(); - abort!( - markup_span, - "element body must be wrapped in braces"; - help = "see https://github.com/lambda-fairy/maud/pull/137 for details" + _markup => { + panic!( + "element body must be wrapped in braces" ); } }, - None => abort_call_site!("expected `;`, found end of macro"), + None => panic!("expected `;`, found end of macro"), }; ast::Markup::Element { name, attrs, body } } @@ -667,9 +611,7 @@ impl Parser { for (name, spans) in attr_map { if spans.len() > 1 { - let mut spans = spans.into_iter(); - let first_span = spans.next().expect("spans should be non-empty"); - abort!(first_span, "duplicate attribute `{}`", name); + panic!("duplicate attribute `{}`", name); } } diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs new file mode 100644 index 00000000..c81a7649 --- /dev/null +++ b/maud_macros_impl/src/runtime.rs @@ -0,0 +1,210 @@ +use proc_macro2::{Ident, Span, TokenStream, TokenTree}; +use proc_macro_error::SpanRange; +use quote::quote; + +use crate::{ast::*, escape}; + +pub fn generate(markups: Vec) -> TokenStream { + let mut build = RuntimeBuilder::new(); + RuntimeGenerator::new().markups(markups, &mut build); + build.finish() +} + +pub fn format_str(markups: Vec) -> String { + let mut build = RuntimeBuilder::new(); + RuntimeGenerator::new().markups(markups, &mut build); + build.format_str() +} + +struct RuntimeGenerator {} + +impl RuntimeGenerator { + fn new() -> RuntimeGenerator { + RuntimeGenerator {} + } + + fn builder(&self) -> RuntimeBuilder { + RuntimeBuilder::new() + } + + fn markups(&self, markups: Vec, build: &mut RuntimeBuilder) { + for markup in markups { + self.markup(markup, build); + } + } + + fn markup(&self, markup: Markup, build: &mut RuntimeBuilder) { + match markup { + Markup::Block( .. ) => { } + Markup::Literal { content, .. } => build.push_escaped(&content), + Markup::Symbol { symbol } => {} // don't gather + Markup::Splice { expr, .. } => build.push_format_arg(expr), + Markup::Element { name, attrs, body } => self.element(name, attrs, body, build), + Markup::Let { tokens, .. } => build.push_format_arg(tokens), + Markup::Special { .. } => {} // TODO + Markup::Match { .. } => {} // TODO + } + } + + fn element(&self, name: TokenStream, attrs: Vec, body: ElementBody, build: &mut RuntimeBuilder) { + build.push_str("<"); + self.name(name.clone(), build); + self.attrs(attrs, build); + build.push_str(">"); + if let ElementBody::Block { block } = body { + self.markups(block.markups, build); + build.push_str(""); + } + } + + fn name(&self, name: TokenStream, build: &mut RuntimeBuilder) { + build.push_escaped(&name_to_string(name)); + } + + fn attrs(&self, attrs: Vec, build: &mut RuntimeBuilder) { + for NamedAttr { name, attr_type } in desugar_attrs(attrs) { + match attr_type { + AttrType::Normal { value } => { + build.push_str(" "); + self.name(name, build); + build.push_str("=\""); + self.markup(value, build); + build.push_str("\""); + } + AttrType::Optional { .. } => {} + AttrType::Empty { toggler: None } => { + build.push_str(" "); + self.name(name, build); + } + AttrType::Empty { .. } => {} + } + } + } +} + +//////////////////////////////////////////////////////// + +fn desugar_attrs(attrs: Vec) -> Vec { + let mut classes_static = vec![]; + let mut classes_toggled = vec![]; + let mut ids = vec![]; + let mut named_attrs = vec![]; + for attr in attrs { + match attr { + Attr::Class { + name, + toggler: Some(toggler), + .. + } => classes_toggled.push((name, toggler)), + Attr::Class { + name, + toggler: None, + .. + } => classes_static.push(name), + Attr::Id { name, .. } => ids.push(name), + Attr::Named { named_attr } => named_attrs.push(named_attr), + } + } + let classes = desugar_classes_or_ids("class", classes_static, classes_toggled); + let ids = desugar_classes_or_ids("id", ids, vec![]); + classes.into_iter().chain(ids).chain(named_attrs).collect() +} + +fn desugar_classes_or_ids( + attr_name: &'static str, + values_static: Vec, + values_toggled: Vec<(Markup, Toggler)>, +) -> Option { + if values_static.is_empty() && values_toggled.is_empty() { + return None; + } + let mut markups = Vec::new(); + let mut leading_space = false; + for name in values_static { + markups.extend(prepend_leading_space(name, &mut leading_space)); + } + for (name, Toggler { cond, cond_span }) in values_toggled { + let body = Block { + markups: prepend_leading_space(name, &mut leading_space), + // TODO: is this correct? + outer_span: cond_span, + }; + markups.push(Markup::Special { + segments: vec![Special { + at_span: SpanRange::call_site(), + head: quote!(if (#cond)), + body, + }], + }); + } + Some(NamedAttr { + name: TokenStream::from(TokenTree::Ident(Ident::new(attr_name, Span::call_site()))), + attr_type: AttrType::Normal { + value: Markup::Block(Block { + markups, + outer_span: SpanRange::call_site(), + }), + }, + }) +} + +fn prepend_leading_space(name: Markup, leading_space: &mut bool) -> Vec { + let mut markups = Vec::new(); + if *leading_space { + markups.push(Markup::Literal { + content: " ".to_owned(), + span: name.span(), + }); + } + *leading_space = true; + markups.push(name); + markups +} + +//////////////////////////////////////////////////////// + +struct RuntimeBuilder { + tokens: Vec, + format_str: String, + arg_track: u32, +} + +impl RuntimeBuilder { + fn new() -> RuntimeBuilder { + RuntimeBuilder { + tokens: Vec::new(), + format_str: String::new(), + arg_track: 0, + } + } + + fn push_str(&mut self, string: &str) { + self.format_str.push_str(string); + } + + fn push_escaped(&mut self, string: &str) { + escape::escape_to_string(string, &mut self.format_str); + } + + fn push_format_arg(&mut self, tokens_expr: TokenStream) { + let arg_track = self.arg_track.to_string(); + self.tokens.extend(quote! { + vars.insert(#arg_track, { #tokens_expr }.into()); + }); + self.arg_track = self.arg_track + 1; + self.format_str.push_str(&format!("{{{}}}", arg_track)); + } + + fn format_str(&self) -> String { + self.format_str.clone() + } + + fn finish(mut self) -> TokenStream { + let tokens = self.tokens.into_iter().collect::(); + quote! { + #tokens + }.into() + } +} From 1c8ca91eb9cf13283847d2c511719eb806098b10 Mon Sep 17 00:00:00 2001 From: wrapperup Date: Wed, 20 Sep 2023 02:41:04 -0400 Subject: [PATCH 02/43] add error handling with crappy panic unwind --- maud_macros_impl/src/lib.rs | 33 ++++++++++++++++++++++++++++----- maud_macros_impl/src/runtime.rs | 2 +- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 68dd28ba..ea54656d 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -40,9 +40,13 @@ pub fn expand(input: TokenStream) -> TokenStream { // normal version, but it can be miles faster to iterate on. pub fn expand_runtime(input: TokenStream) -> TokenStream { let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); - let markups = parse::parse(input); + let markups = parse::parse(input.clone()); let stmts = runtime::generate(markups); + let expand_compile_time = expand(input); quote!({ + // Epic Hack Template compile time check + #expand_compile_time; + extern crate alloc; extern crate maud; let mut #output_ident = String::new(); @@ -50,14 +54,33 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream { let input = ::maud::gather_html_macro_invocations(file!(), line!()); - let markups = ::maud::parse(input.parse().unwrap()); + let res = ::std::panic::catch_unwind(|| { + ::maud::parse(input.parse().unwrap()) + }); + + let markups = if res.is_ok() { + ::maud::parse(input.parse().unwrap()) + } else { + return ::maud::PreEscaped(format!("

Template Errors:

{:?}
", res.unwrap_err()));
+        };
+
         let format_str = ::maud::format_str(markups);
 
         #stmts
 
-        let template = ::leon::Template::parse(&format_str).unwrap();
+        let template = if let Ok(template) = ::leon::Template::parse(&format_str) {
+            template
+        } else {
+            std::process::exit(1);
+        };
+
+        let template = if let Ok(template) = template.render(&vars) {
+            template
+        } else {
+            std::process::exit(1);
+        };
 
-        maud::PreEscaped(template.render(&vars).unwrap())
+        maud::PreEscaped(template)
     })
 }
 
@@ -67,7 +90,7 @@ pub fn gather_html_macro_invocations(file_path: &'static str, start_column: u32)
 
     let mut braces_diff = 0;
 
-    let mut html_invocation = buf_reader
+    let html_invocation = buf_reader
         .lines()
         .skip(start_column as usize)
         .take_while(|line| {
diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs
index c81a7649..db124d01 100644
--- a/maud_macros_impl/src/runtime.rs
+++ b/maud_macros_impl/src/runtime.rs
@@ -201,7 +201,7 @@ impl RuntimeBuilder {
         self.format_str.clone()
     }
 
-    fn finish(mut self) -> TokenStream {
+    fn finish(self) -> TokenStream {
         let tokens = self.tokens.into_iter().collect::();
         quote! {
             #tokens

From ebb7e86fd9d0f5b51af35e0f49c6071393d74077 Mon Sep 17 00:00:00 2001
From: wrapperup 
Date: Sat, 30 Sep 2023 23:19:35 -0400
Subject: [PATCH 03/43] re-export leon placeholder runtime templating

---
 maud/Cargo.toml             | 1 +
 maud/src/lib.rs             | 3 +++
 maud_macros_impl/src/lib.rs | 2 +-
 3 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/maud/Cargo.toml b/maud/Cargo.toml
index be7d3adf..ee994fdd 100644
--- a/maud/Cargo.toml
+++ b/maud/Cargo.toml
@@ -29,6 +29,7 @@ actix-web-dep = { package = "actix-web", version = "4", optional = true, default
 tide = { version = "0.16.0", optional = true, default-features = false }
 axum-core = { version = "0.3", optional = true }
 http = { version = "0.2", optional = true }
+leon = "2.0.1"
 
 [dev-dependencies]
 trybuild = { version = "1.0.33", features = ["diff"] }
diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index 0868a49c..ceb5edc4 100644
--- a/maud/src/lib.rs
+++ b/maud/src/lib.rs
@@ -31,6 +31,9 @@ pub use maud_macros_impl::format_str;
 #[cfg(feature = "hotreload")]
 pub use maud_macros_impl::parse;
 
+#[cfg(feature = "hotreload")]
+pub use leon;
+
 mod escape;
 
 /// An adapter that escapes HTML special characters.
diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs
index ea54656d..7bc3dede 100644
--- a/maud_macros_impl/src/lib.rs
+++ b/maud_macros_impl/src/lib.rs
@@ -68,7 +68,7 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream {
 
         #stmts
 
-        let template = if let Ok(template) = ::leon::Template::parse(&format_str) {
+        let template = if let Ok(template) = ::maud::leon::Template::parse(&format_str) {
             template
         } else {
             std::process::exit(1);

From e38f4deaf005858699c8fc49e7c653dfe82240c7 Mon Sep 17 00:00:00 2001
From: Simon Bruder 
Date: Sat, 7 Sep 2024 20:34:33 +0200
Subject: [PATCH 04/43] Switch to proc_macro_error2

Fixes #441.

proc_macro_error is marked as unmaintained in rustsec:
https://rustsec.org/advisories/RUSTSEC-2024-0370.html

Because proc_macro_error2 removed automatic nightly detection, the error
messages changed slightly.
---
 CHANGELOG.md                                  |  4 ++++
 docs/Cargo.lock                               | 24 +++++++------------
 maud/tests/warnings/keyword-without-at.stderr |  5 ++--
 maud/tests/warnings/non-string-literal.stderr | 14 ++++++-----
 maud/tests/warnings/void-element-slash.stderr | 14 ++++++-----
 maud_macros/Cargo.toml                        |  2 +-
 maud_macros/src/ast.rs                        |  2 +-
 maud_macros/src/generate.rs                   |  2 +-
 maud_macros/src/lib.rs                        |  2 +-
 maud_macros/src/parse.rs                      |  2 +-
 10 files changed, 36 insertions(+), 35 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6458fe8..7ef26407 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 ## [Unreleased]
 
+- Switch to `proc_macro_error2`
+  [#441](https://github.com/lambda-fairy/maud/issues/441)
+  [#442](https://github.com/lambda-fairy/maud/pull/442)
+
 ## [0.26.0] - 2024-01-15
 
 - Remove `AsRef` restriction from `PreEscaped`
diff --git a/docs/Cargo.lock b/docs/Cargo.lock
index 1f7fce2c..05172b2a 100644
--- a/docs/Cargo.lock
+++ b/docs/Cargo.lock
@@ -298,7 +298,7 @@ dependencies = [
 name = "maud_macros"
 version = "0.26.0"
 dependencies = [
- "proc-macro-error",
+ "proc-macro-error2",
  "proc-macro2",
  "quote",
  "syn",
@@ -379,26 +379,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
 
 [[package]]
-name = "proc-macro-error"
-version = "1.0.4"
+name = "proc-macro-error-attr2"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
 dependencies = [
- "proc-macro-error-attr",
  "proc-macro2",
  "quote",
- "version_check",
 ]
 
 [[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
+name = "proc-macro-error2"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
 dependencies = [
+ "proc-macro-error-attr2",
  "proc-macro2",
  "quote",
- "version_check",
 ]
 
 [[package]]
@@ -653,12 +651,6 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
 
-[[package]]
-name = "version_check"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
-
 [[package]]
 name = "walkdir"
 version = "2.5.0"
diff --git a/maud/tests/warnings/keyword-without-at.stderr b/maud/tests/warnings/keyword-without-at.stderr
index 86918fad..9ff4d4be 100644
--- a/maud/tests/warnings/keyword-without-at.stderr
+++ b/maud/tests/warnings/keyword-without-at.stderr
@@ -1,7 +1,8 @@
 error: found keyword `if`
+
+         = help: should this be a `@if`?
+
  --> $DIR/keyword-without-at.rs:5:9
   |
 5 |         if {}
   |         ^^
-  |
-  = help: should this be a `@if`?
diff --git a/maud/tests/warnings/non-string-literal.stderr b/maud/tests/warnings/non-string-literal.stderr
index d0a128b2..527304a6 100644
--- a/maud/tests/warnings/non-string-literal.stderr
+++ b/maud/tests/warnings/non-string-literal.stderr
@@ -35,19 +35,21 @@ error: expected string
    |         ^^^^
 
 error: attribute value must be a string
+
+         = help: to declare an empty attribute, omit the equals sign: `disabled`
+         = help: to toggle the attribute, use square brackets: `disabled[some_boolean_flag]`
+
   --> tests/warnings/non-string-literal.rs:13:24
    |
 13 |         input disabled=true;
    |                        ^^^^
-   |
-   = help: to declare an empty attribute, omit the equals sign: `disabled`
-   = help: to toggle the attribute, use square brackets: `disabled[some_boolean_flag]`
 
 error: attribute value must be a string
+
+         = help: to declare an empty attribute, omit the equals sign: `disabled`
+         = help: to toggle the attribute, use square brackets: `disabled[some_boolean_flag]`
+
   --> tests/warnings/non-string-literal.rs:14:24
    |
 14 |         input disabled=false;
    |                        ^^^^^
-   |
-   = help: to declare an empty attribute, omit the equals sign: `disabled`
-   = help: to toggle the attribute, use square brackets: `disabled[some_boolean_flag]`
diff --git a/maud/tests/warnings/void-element-slash.stderr b/maud/tests/warnings/void-element-slash.stderr
index eb0f6d38..9ffedf86 100644
--- a/maud/tests/warnings/void-element-slash.stderr
+++ b/maud/tests/warnings/void-element-slash.stderr
@@ -1,17 +1,19 @@
 error: void elements must use `;`, not `/`
+
+         = help: change this to `;`
+         = help: see https://github.com/lambda-fairy/maud/pull/315 for details
+
  --> $DIR/void-element-slash.rs:5:12
   |
 5 |         br /
   |            ^
-  |
-  = help: change this to `;`
-  = help: see https://github.com/lambda-fairy/maud/pull/315 for details
 
 error: void elements must use `;`, not `/`
+
+         = help: change this to `;`
+         = help: see https://github.com/lambda-fairy/maud/pull/315 for details
+
  --> $DIR/void-element-slash.rs:7:27
   |
 7 |         input type="text" /
   |                           ^
-  |
-  = help: change this to `;`
-  = help: see https://github.com/lambda-fairy/maud/pull/315 for details
diff --git a/maud_macros/Cargo.toml b/maud_macros/Cargo.toml
index 1a938be0..944d6c96 100644
--- a/maud_macros/Cargo.toml
+++ b/maud_macros/Cargo.toml
@@ -15,7 +15,7 @@ edition.workspace = true
 syn = "2"
 quote = "1.0.7"
 proc-macro2 = "1.0.23"
-proc-macro-error = { version = "1.0.0", default-features = false }
+proc-macro-error2 = { version = "2.0.1", default-features = false }
 
 [lib]
 name = "maud_macros"
diff --git a/maud_macros/src/ast.rs b/maud_macros/src/ast.rs
index b95665ec..2fe60ed0 100644
--- a/maud_macros/src/ast.rs
+++ b/maud_macros/src/ast.rs
@@ -1,5 +1,5 @@
 use proc_macro2::{TokenStream, TokenTree};
-use proc_macro_error::SpanRange;
+use proc_macro_error2::SpanRange;
 use syn::Lit;
 
 #[derive(Debug)]
diff --git a/maud_macros/src/generate.rs b/maud_macros/src/generate.rs
index ee27a55d..f94c37cd 100644
--- a/maud_macros/src/generate.rs
+++ b/maud_macros/src/generate.rs
@@ -1,5 +1,5 @@
 use proc_macro2::{Delimiter, Group, Ident, Literal, Span, TokenStream, TokenTree};
-use proc_macro_error::SpanRange;
+use proc_macro_error2::SpanRange;
 use quote::quote;
 
 use crate::{ast::*, escape};
diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs
index f52e27e4..98200952 100644
--- a/maud_macros/src/lib.rs
+++ b/maud_macros/src/lib.rs
@@ -11,7 +11,7 @@ mod generate;
 mod parse;
 
 use proc_macro2::{Ident, Span, TokenStream, TokenTree};
-use proc_macro_error::proc_macro_error;
+use proc_macro_error2::proc_macro_error;
 use quote::quote;
 
 #[proc_macro]
diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs
index 05af2894..91e34401 100644
--- a/maud_macros/src/parse.rs
+++ b/maud_macros/src/parse.rs
@@ -1,5 +1,5 @@
 use proc_macro2::{Delimiter, Ident, Literal, Spacing, Span, TokenStream, TokenTree};
-use proc_macro_error::{abort, abort_call_site, emit_error, SpanRange};
+use proc_macro_error2::{abort, abort_call_site, emit_error, SpanRange};
 use std::collections::HashMap;
 
 use syn::Lit;

From 4d0a6b4968856b4cde81c0ca2810b67cc1ee692c Mon Sep 17 00:00:00 2001
From: Markus Unterwaditzer 
Date: Sun, 10 Nov 2024 05:27:20 +0100
Subject: [PATCH 05/43] smaller fixes

---
 maud_macros_impl/Cargo.toml   | 2 +-
 maud_macros_impl/src/lib.rs   | 4 ++--
 maud_macros_impl/src/parse.rs | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/maud_macros_impl/Cargo.toml b/maud_macros_impl/Cargo.toml
index 595d9d74..740134f8 100644
--- a/maud_macros_impl/Cargo.toml
+++ b/maud_macros_impl/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "maud_macros_impl"
 # When releasing a new version, please update html_root_url in src/lib.rs
-version = "0.25.0"
+version = "0.26.0"
 authors = ["Chris Wong "]
 license = "MIT/Apache-2.0"
 documentation = "https://docs.rs/maud_macros/"
diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs
index 7bc3dede..e026888a 100644
--- a/maud_macros_impl/src/lib.rs
+++ b/maud_macros_impl/src/lib.rs
@@ -43,7 +43,7 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream {
     let markups = parse::parse(input.clone());
     let stmts = runtime::generate(markups);
     let expand_compile_time = expand(input);
-    quote!({
+    quote!('maud_hotreload: {
         // Epic Hack Template compile time check
         #expand_compile_time;
 
@@ -61,7 +61,7 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream {
         let markups = if res.is_ok() {
             ::maud::parse(input.parse().unwrap())
         } else {
-            return ::maud::PreEscaped(format!("

Template Errors:

{:?}
", res.unwrap_err()));
+            break 'maud_hotreload ::maud::PreEscaped(format!("

Template Errors:

{:?}
", res.unwrap_err()));
         };
 
         let format_str = ::maud::format_str(markups);
diff --git a/maud_macros_impl/src/parse.rs b/maud_macros_impl/src/parse.rs
index ca5543d2..59a92473 100644
--- a/maud_macros_impl/src/parse.rs
+++ b/maud_macros_impl/src/parse.rs
@@ -1,5 +1,5 @@
 use proc_macro2::{Delimiter, Ident, Literal, Spacing, Span, TokenStream, TokenTree};
-use proc_macro_error::SpanRange;
+use proc_macro_error::{SpanRange, emit_error};
 use std::collections::HashMap;
 
 use syn::Lit;

From 69f014de0b08fb660735b6c0f8ca75fde6616161 Mon Sep 17 00:00:00 2001
From: Markus Unterwaditzer 
Date: Sun, 10 Nov 2024 05:41:55 +0100
Subject: [PATCH 06/43] remove use of label to avoid compiler warnings

---
 maud_macros_impl/src/lib.rs | 37 ++++++++++++++++++-------------------
 1 file changed, 18 insertions(+), 19 deletions(-)

diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs
index e026888a..6edef35f 100644
--- a/maud_macros_impl/src/lib.rs
+++ b/maud_macros_impl/src/lib.rs
@@ -43,7 +43,7 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream {
     let markups = parse::parse(input.clone());
     let stmts = runtime::generate(markups);
     let expand_compile_time = expand(input);
-    quote!('maud_hotreload: {
+    quote!({
         // Epic Hack Template compile time check
         #expand_compile_time;
 
@@ -58,29 +58,28 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream {
             ::maud::parse(input.parse().unwrap())
         });
 
-        let markups = if res.is_ok() {
-            ::maud::parse(input.parse().unwrap())
-        } else {
-            break 'maud_hotreload ::maud::PreEscaped(format!("

Template Errors:

{:?}
", res.unwrap_err()));
-        };
+        if res.is_ok() {
+            let markups = ::maud::parse(input.parse().unwrap());
+            let format_str = ::maud::format_str(markups);
 
-        let format_str = ::maud::format_str(markups);
+            #stmts
 
-        #stmts
+            let template = if let Ok(template) = ::maud::leon::Template::parse(&format_str) {
+                template
+            } else {
+                std::process::exit(1);
+            };
 
-        let template = if let Ok(template) = ::maud::leon::Template::parse(&format_str) {
-            template
-        } else {
-            std::process::exit(1);
-        };
+            let template = if let Ok(template) = template.render(&vars) {
+                template
+            } else {
+                std::process::exit(1);
+            };
 
-        let template = if let Ok(template) = template.render(&vars) {
-            template
+            maud::PreEscaped(template)
         } else {
-            std::process::exit(1);
-        };
-
-        maud::PreEscaped(template)
+            ::maud::PreEscaped(format!("

Template Errors:

{:?}
", res.unwrap_err()))
+        }
     })
 }
 

From 9b20dace8f1bfdd9ff5f5d1a908dda509bf14e12 Mon Sep 17 00:00:00 2001
From: Markus Unterwaditzer 
Date: Sun, 10 Nov 2024 05:50:37 +0100
Subject: [PATCH 07/43] try to print a message

---
 maud_macros_impl/src/lib.rs | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs
index 6edef35f..16bfdcbf 100644
--- a/maud_macros_impl/src/lib.rs
+++ b/maud_macros_impl/src/lib.rs
@@ -64,17 +64,8 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream {
 
             #stmts
 
-            let template = if let Ok(template) = ::maud::leon::Template::parse(&format_str) {
-                template
-            } else {
-                std::process::exit(1);
-            };
-
-            let template = if let Ok(template) = template.render(&vars) {
-                template
-            } else {
-                std::process::exit(1);
-            };
+            let template = ::maud::leon::Template::parse(&format_str).unwrap();
+            let template = template.render(&vars).unwrap();
 
             maud::PreEscaped(template)
         } else {

From 0c796c04c23dc045220bd4b2f55b5da8015686f4 Mon Sep 17 00:00:00 2001
From: Markus Unterwaditzer 
Date: Sun, 10 Nov 2024 06:04:34 +0100
Subject: [PATCH 08/43] render div.foo { } correctly

---
 maud_macros_impl/src/runtime.rs | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs
index db124d01..1ef0e6ae 100644
--- a/maud_macros_impl/src/runtime.rs
+++ b/maud_macros_impl/src/runtime.rs
@@ -35,9 +35,13 @@ impl RuntimeGenerator {
 
     fn markup(&self, markup: Markup, build: &mut RuntimeBuilder) {
         match markup {
-            Markup::Block( .. ) => { }
+            Markup::Block(Block { markups, .. }) => {
+                for markup in markups {
+                    self.markup(markup, build);
+                }
+            }
             Markup::Literal { content, .. } => build.push_escaped(&content),
-            Markup::Symbol { symbol } => {} // don't gather
+            Markup::Symbol { symbol } => build.push_str(&symbol.to_string()),
             Markup::Splice { expr, .. } => build.push_format_arg(expr),
             Markup::Element { name, attrs, body } => self.element(name, attrs, body, build),
             Markup::Let { tokens, .. } => build.push_format_arg(tokens),

From 3a7cc9a2c3cfb7538905f90ad44b0c9a30b7b670 Mon Sep 17 00:00:00 2001
From: Markus Unterwaditzer 
Date: Sun, 10 Nov 2024 06:15:58 +0100
Subject: [PATCH 09/43] make hotreload feature optional

---
 maud/Cargo.toml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/maud/Cargo.toml b/maud/Cargo.toml
index 26868938..25b76a72 100644
--- a/maud/Cargo.toml
+++ b/maud/Cargo.toml
@@ -13,8 +13,8 @@ repository.workspace = true
 edition.workspace = true
 
 [features]
-default = ["hotreload"]
-hotreload = ["maud_macros_impl"]
+default = []
+hotreload = ["maud_macros_impl", "leon"]
 
 # Web framework integrations
 actix-web = ["actix-web-dep", "futures-util"]
@@ -28,7 +28,7 @@ rocket = { version = "0.5", optional = true }
 futures-util = { version = "0.3.0", optional = true, default-features = false }
 actix-web-dep = { package = "actix-web", version = "4", optional = true, default-features = false }
 tide = { version = "0.16.0", optional = true, default-features = false }
-leon = "2.0.1"
+leon = { version = "2.0.1", optional = true }
 axum-core = { version = "0.4", optional = true }
 submillisecond = { version = "0.4.1", optional = true }
 http = { version = "1", optional = true }

From aaeeb87e109b72f28fe63dc036f9657d2ea0077b Mon Sep 17 00:00:00 2001
From: Markus Unterwaditzer 
Date: Sun, 10 Nov 2024 15:53:51 +0100
Subject: [PATCH 10/43] revert move to panics, tweak feature mgmt, fix warnings

---
 maud/src/lib.rs                  | 24 ++++++++----------
 maud_macros_impl/src/ast.rs      |  4 +++
 maud_macros_impl/src/generate.rs |  1 +
 maud_macros_impl/src/parse.rs    | 42 ++++++++++++++++++++------------
 maud_macros_impl/src/runtime.rs  |  5 +---
 5 files changed, 43 insertions(+), 33 deletions(-)

diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index 5302f62d..07822945 100644
--- a/maud/src/lib.rs
+++ b/maud/src/lib.rs
@@ -14,25 +14,21 @@ extern crate alloc;
 use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc};
 use core::fmt::{self, Arguments, Display, Write};
 
+#[cfg(not(feature = "hotreload"))]
 pub use maud_macros::html;
 
 #[cfg(feature = "hotreload")]
-pub use maud_macros::html_hotreload;
-
-#[cfg(feature = "hotreload")]
-pub use maud_macros_impl::expand;
-
-#[cfg(feature = "hotreload")]
-pub use maud_macros_impl::gather_html_macro_invocations;
-
-#[cfg(feature = "hotreload")]
-pub use maud_macros_impl::format_str;
-
-#[cfg(feature = "hotreload")]
-pub use maud_macros_impl::parse;
+pub use maud_macros::html_hotreload as html;
 
+#[doc(hidden)]
 #[cfg(feature = "hotreload")]
-pub use leon;
+pub use {
+    maud_macros_impl::expand,
+    maud_macros_impl::gather_html_macro_invocations,
+    maud_macros_impl::format_str,
+    maud_macros_impl::parse,
+    leon,
+};
 
 mod escape;
 
diff --git a/maud_macros_impl/src/ast.rs b/maud_macros_impl/src/ast.rs
index 14db1afa..b95665ec 100644
--- a/maud_macros_impl/src/ast.rs
+++ b/maud_macros_impl/src/ast.rs
@@ -5,6 +5,9 @@ use syn::Lit;
 #[derive(Debug)]
 pub enum Markup {
     /// Used as a placeholder value on parse error.
+    ParseError {
+        span: SpanRange,
+    },
     Block(Block),
     Literal {
         content: String,
@@ -40,6 +43,7 @@ pub enum Markup {
 impl Markup {
     pub fn span(&self) -> SpanRange {
         match *self {
+            Markup::ParseError { span } => span,
             Markup::Block(ref block) => block.span(),
             Markup::Literal { span, .. } => span,
             Markup::Symbol { ref symbol } => span_tokens(symbol.clone()),
diff --git a/maud_macros_impl/src/generate.rs b/maud_macros_impl/src/generate.rs
index c05c248d..ee27a55d 100644
--- a/maud_macros_impl/src/generate.rs
+++ b/maud_macros_impl/src/generate.rs
@@ -31,6 +31,7 @@ impl Generator {
 
     fn markup(&self, markup: Markup, build: &mut Builder) {
         match markup {
+            Markup::ParseError { .. } => {}
             Markup::Block(Block {
                 markups,
                 outer_span,
diff --git a/maud_macros_impl/src/parse.rs b/maud_macros_impl/src/parse.rs
index 59a92473..5ac4f793 100644
--- a/maud_macros_impl/src/parse.rs
+++ b/maud_macros_impl/src/parse.rs
@@ -1,5 +1,5 @@
 use proc_macro2::{Delimiter, Ident, Literal, Spacing, Span, TokenStream, TokenTree};
-use proc_macro_error::{SpanRange, emit_error};
+use proc_macro_error::{abort, abort_call_site, emit_error, SpanRange};
 use std::collections::HashMap;
 
 use syn::Lit;
@@ -87,7 +87,7 @@ impl Parser {
         let token = match self.peek() {
             Some(token) => token,
             None => {
-                panic!("unexpected end of input");
+                abort_call_site!("unexpected end of input");
             }
         };
         let markup = match token {
@@ -130,8 +130,10 @@ impl Parser {
                 let ident_string = ident.to_string();
                 match ident_string.as_str() {
                     "if" | "while" | "for" | "match" | "let" => {
-                        panic!(
-                            "found keyword `{}`", ident_string
+                        abort!(
+                            ident,
+                            "found keyword `{}`", ident_string;
+                            help = "should this be a `@{}`?", ident_string
                         );
                     }
                     "true" | "false" => {
@@ -201,18 +203,22 @@ impl Parser {
                 };
             }
             Lit::Int(..) | Lit::Float(..) => {
-                panic!(r#"literal must be double-quoted: `"{}"`"#, literal);
+                emit_error!(literal, r#"literal must be double-quoted: `"{}"`"#, literal);
             }
             Lit::Char(lit_char) => {
-                panic!(
+                emit_error!(
+                    literal,
                     r#"literal must be double-quoted: `"{}"`"#,
                     lit_char.value(),
                 );
             }
             _ => {
-                panic!("expected string");
+                emit_error!(literal, "expected string");
             }
         }
+        ast::Markup::ParseError {
+            span: SpanRange::single_span(literal.span()),
+        }
     }
 
     /// Parses an `@if` expression.
@@ -493,7 +499,8 @@ impl Parser {
     /// The element name should already be consumed.
     fn element(&mut self, name: TokenStream) -> ast::Markup {
         if self.current_attr.is_some() {
-            panic!("unexpected element");
+            let span = ast::span_tokens(name);
+            abort!(span, "unexpected element");
         }
         let attrs = self.attrs();
         let body = match self.peek() {
@@ -503,23 +510,28 @@ impl Parser {
                 // Void element
                 self.advance();
                 if punct.as_char() == '/' {
-                    panic!(
-                        "void elements must use `;`, not `/`"
+                    emit_error!(
+                        punct,
+                        "void elements must use `;`, not `/`";
+                        help = "change this to `;`";
+                        help = "see https://github.com/lambda-fairy/maud/pull/315 for details";
                     );
                 }
                 ast::ElementBody::Void {
                     semi_span: SpanRange::single_span(punct.span()),
                 }
             }
-            Some(_) => match self.markup() {
+            Some(markup) => match self.markup() {
                 ast::Markup::Block(block) => ast::ElementBody::Block { block },
                 _markup => {
-                    panic!(
-                        "element body must be wrapped in braces"
-                    );
+                    abort!(
+                        markup,
+                        "element body must be wrapped in braces";
+                        help = "see https://github.com/lambda-fairy/maud/pull/137 for details"
+                    )
                 }
             },
-            None => panic!("expected `;`, found end of macro"),
+            None => abort_call_site!("expected `;`, found end of macro"),
         };
         ast::Markup::Element { name, attrs, body }
     }
diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs
index 1ef0e6ae..1b95477c 100644
--- a/maud_macros_impl/src/runtime.rs
+++ b/maud_macros_impl/src/runtime.rs
@@ -23,10 +23,6 @@ impl RuntimeGenerator {
         RuntimeGenerator {}
     }
 
-    fn builder(&self) -> RuntimeBuilder {
-        RuntimeBuilder::new()
-    }
-
     fn markups(&self, markups: Vec, build: &mut RuntimeBuilder) {
         for markup in markups {
             self.markup(markup, build);
@@ -35,6 +31,7 @@ impl RuntimeGenerator {
 
     fn markup(&self, markup: Markup, build: &mut RuntimeBuilder) {
         match markup {
+            Markup::ParseError { .. } => {},
             Markup::Block(Block { markups, .. }) => {
                 for markup in markups {
                     self.markup(markup, build);

From a2b0c762fe033985dd1d21aab6afda454dc4ece1 Mon Sep 17 00:00:00 2001
From: Markus Unterwaditzer 
Date: Sun, 10 Nov 2024 15:58:47 +0100
Subject: [PATCH 11/43] use render_to! for attribute values

---
 maud_macros_impl/src/runtime.rs | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs
index 1b95477c..f77367a9 100644
--- a/maud_macros_impl/src/runtime.rs
+++ b/maud_macros_impl/src/runtime.rs
@@ -189,10 +189,14 @@ impl RuntimeBuilder {
         escape::escape_to_string(string, &mut self.format_str);
     }
 
-    fn push_format_arg(&mut self, tokens_expr: TokenStream) {
+    fn push_format_arg(&mut self, expr: TokenStream) {
         let arg_track = self.arg_track.to_string();
         self.tokens.extend(quote! {
-            vars.insert(#arg_track, { #tokens_expr }.into());
+            vars.insert(#arg_track, {
+                let mut buf = String::new();
+                ::maud::macro_private::render_to!(&(#expr), &mut buf);
+                buf
+            });
         });
         self.arg_track = self.arg_track + 1;
         self.format_str.push_str(&format!("{{{}}}", arg_track));

From 594a67b07af6cdf7da7a90dad43ad2045fb9bad2 Mon Sep 17 00:00:00 2001
From: Markus Unterwaditzer 
Date: Sun, 10 Nov 2024 16:34:43 +0100
Subject: [PATCH 12/43] restore errors for compile-time maud

---
 maud/src/lib.rs               | 32 ++++++++++++------
 maud_macros_impl/src/lib.rs   | 41 ++++++++++++++++-------
 maud_macros_impl/src/parse.rs | 61 +++++++++++++++++++++++++++--------
 3 files changed, 98 insertions(+), 36 deletions(-)

diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index 07822945..ccadf826 100644
--- a/maud/src/lib.rs
+++ b/maud/src/lib.rs
@@ -1,4 +1,4 @@
-#![no_std]
+#![cfg_attr(not(feature = "hotreload"), no_std)]
 
 //! A macro for writing HTML templates.
 //!
@@ -14,20 +14,15 @@ extern crate alloc;
 use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc};
 use core::fmt::{self, Arguments, Display, Write};
 
+pub use maud_macros::html as html_static;
+
 #[cfg(not(feature = "hotreload"))]
 pub use maud_macros::html;
 
-#[cfg(feature = "hotreload")]
-pub use maud_macros::html_hotreload as html;
-
-#[doc(hidden)]
 #[cfg(feature = "hotreload")]
 pub use {
-    maud_macros_impl::expand,
-    maud_macros_impl::gather_html_macro_invocations,
-    maud_macros_impl::format_str,
-    maud_macros_impl::parse,
-    leon,
+    maud_macros::html_hotreload,
+    maud_macros::html_hotreload as html,
 };
 
 mod escape;
@@ -475,4 +470,21 @@ pub mod macro_private {
             display(value).render_to(buffer);
         }
     }
+
+    #[cfg(feature = "hotreload")]
+    pub fn render_runtime_error(e: &str) -> crate::Markup {
+        // print error to console, as we have no guarantee that the error will be seen in the
+        // browser (arbitrary styles may be applied)
+        println!("TEMPLATE ERROR: {}", e);
+        crate::PreEscaped(alloc::format!("

Template Errors:

{}
", e)) + } + + #[cfg(feature = "hotreload")] + pub use { + maud_macros_impl::expand, + maud_macros_impl::gather_html_macro_invocations, + maud_macros_impl::format_str, + maud_macros_impl::parse_at_runtime, + leon, + }; } diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 16bfdcbf..42ea3b18 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -14,7 +14,7 @@ use std::{io::{BufReader, BufRead}, fs::File}; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::quote; -pub use parse::parse; +pub use parse::parse_at_runtime; pub use runtime::format_str; pub fn expand(input: TokenStream) -> TokenStream { @@ -52,24 +52,41 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream { let mut #output_ident = String::new(); let mut vars: ::std::collections::HashMap<&'static str, String> = ::std::collections::HashMap::new(); - let input = ::maud::gather_html_macro_invocations(file!(), line!()); + let input = ::maud::macro_private::gather_html_macro_invocations(file!(), line!()); let res = ::std::panic::catch_unwind(|| { - ::maud::parse(input.parse().unwrap()) + ::maud::macro_private::parse_at_runtime(input.parse().unwrap()) }); - if res.is_ok() { - let markups = ::maud::parse(input.parse().unwrap()); - let format_str = ::maud::format_str(markups); + if let Err(e) = res { + if let Some(s) = e + // Try to convert it to a String, then turn that into a str + .downcast_ref::() + .map(String::as_str) + // If that fails, try to turn it into a &'static str + .or_else(|| e.downcast_ref::<&'static str>().map(::std::ops::Deref::deref)) + { + ::maud::macro_private::render_runtime_error(s) + } else { + ::maud::macro_private::render_runtime_error("unknown panic") + } + } else { + let markups = ::maud::macro_private::parse_at_runtime(input.parse().unwrap()); + let format_str = ::maud::macro_private::format_str(markups); #stmts - let template = ::maud::leon::Template::parse(&format_str).unwrap(); - let template = template.render(&vars).unwrap(); - - maud::PreEscaped(template) - } else { - ::maud::PreEscaped(format!("

Template Errors:

{:?}
", res.unwrap_err()))
+            // cannot use return here, and block labels come with strings attached (cant nest them
+            // without compiler warnings)
+            match ::maud::macro_private::leon::Template::parse(&format_str) {
+                Ok(template) => {
+                    match template.render(&vars) {
+                        Ok(template) => maud::PreEscaped(template),
+                        Err(e) => ::maud::macro_private::render_runtime_error(&e.to_string())
+                    }
+                },
+                Err(e) => ::maud::macro_private::render_runtime_error(&e.to_string())
+            }
         }
     })
 }
diff --git a/maud_macros_impl/src/parse.rs b/maud_macros_impl/src/parse.rs
index 5ac4f793..a3a23c0d 100644
--- a/maud_macros_impl/src/parse.rs
+++ b/maud_macros_impl/src/parse.rs
@@ -10,11 +10,16 @@ pub fn parse(input: TokenStream) -> Vec {
     Parser::new(input).markups()
 }
 
+pub fn parse_at_runtime(input: TokenStream) -> Vec {
+    Parser::new_at_runtime(input).markups()
+}
+
 #[derive(Clone)]
 struct Parser {
     /// If we're inside an attribute, then this contains the attribute name.
     current_attr: Option,
     input: ::IntoIter,
+    is_runtime: bool,
 }
 
 impl Iterator for Parser {
@@ -30,6 +35,15 @@ impl Parser {
         Parser {
             current_attr: None,
             input: input.into_iter(),
+            is_runtime: false,
+        }
+    }
+
+    fn new_at_runtime(input: TokenStream) -> Parser {
+        Parser {
+            current_attr: None,
+            input: input.into_iter(),
+            is_runtime: true,
         }
     }
 
@@ -37,6 +51,7 @@ impl Parser {
         Parser {
             current_attr: self.current_attr.clone(),
             input: input.into_iter(),
+            is_runtime: self.is_runtime,
         }
     }
 
@@ -499,8 +514,12 @@ impl Parser {
     /// The element name should already be consumed.
     fn element(&mut self, name: TokenStream) -> ast::Markup {
         if self.current_attr.is_some() {
-            let span = ast::span_tokens(name);
-            abort!(span, "unexpected element");
+            if self.is_runtime {
+                panic!("unexpected element: {}", name);
+            } else {
+                let span = ast::span_tokens(name);
+                abort!(span, "unexpected element");
+            }
         }
         let attrs = self.attrs();
         let body = match self.peek() {
@@ -510,12 +529,16 @@ impl Parser {
                 // Void element
                 self.advance();
                 if punct.as_char() == '/' {
-                    emit_error!(
-                        punct,
-                        "void elements must use `;`, not `/`";
-                        help = "change this to `;`";
-                        help = "see https://github.com/lambda-fairy/maud/pull/315 for details";
-                    );
+                    if self.is_runtime {
+                        panic!("void elements must use `;`, not `/`");
+                    } else {
+                        emit_error!(
+                            punct,
+                            "void elements must use `;`, not `/`";
+                            help = "change this to `;`";
+                            help = "see https://github.com/lambda-fairy/maud/pull/315 for details";
+                        );
+                    }
                 }
                 ast::ElementBody::Void {
                     semi_span: SpanRange::single_span(punct.span()),
@@ -524,14 +547,24 @@ impl Parser {
             Some(markup) => match self.markup() {
                 ast::Markup::Block(block) => ast::ElementBody::Block { block },
                 _markup => {
-                    abort!(
-                        markup,
-                        "element body must be wrapped in braces";
-                        help = "see https://github.com/lambda-fairy/maud/pull/137 for details"
-                    )
+                    if self.is_runtime {
+                        panic!("element body must be wrapped in braces")
+                    } else {
+                        abort!(
+                            markup,
+                            "element body must be wrapped in braces";
+                            help = "see https://github.com/lambda-fairy/maud/pull/137 for details"
+                        )
+                    }
+                }
+            },
+            None => {
+                if self.is_runtime {
+                    panic!("expected `;`, found end of macro: {}", name)
+                } else {
+                    abort_call_site!("expected `;`, found end of macro")
                 }
             },
-            None => abort_call_site!("expected `;`, found end of macro"),
         };
         ast::Markup::Element { name, attrs, body }
     }

From 2e50953d88720874f017a9909ae617eff15516e4 Mon Sep 17 00:00:00 2001
From: Markus Unterwaditzer 
Date: Sun, 10 Nov 2024 17:30:02 +0100
Subject: [PATCH 13/43] fallback to static rendering for control structures

---
 maud_macros_impl/src/ast.rs      |  2 +-
 maud_macros_impl/src/generate.rs |  2 +-
 maud_macros_impl/src/lib.rs      | 10 ++++++----
 maud_macros_impl/src/runtime.rs  | 19 ++++++++++---------
 4 files changed, 18 insertions(+), 15 deletions(-)

diff --git a/maud_macros_impl/src/ast.rs b/maud_macros_impl/src/ast.rs
index b95665ec..6125ff78 100644
--- a/maud_macros_impl/src/ast.rs
+++ b/maud_macros_impl/src/ast.rs
@@ -58,7 +58,7 @@ impl Markup {
                 at_span,
                 ref tokens,
             } => at_span.join_range(span_tokens(tokens.clone())),
-            Markup::Special { ref segments } => join_ranges(segments.iter().map(Special::span)),
+            Markup::Special { ref segments, .. } => join_ranges(segments.iter().map(Special::span)),
             Markup::Match {
                 at_span, arms_span, ..
             } => at_span.join_range(arms_span),
diff --git a/maud_macros_impl/src/generate.rs b/maud_macros_impl/src/generate.rs
index ee27a55d..095651e7 100644
--- a/maud_macros_impl/src/generate.rs
+++ b/maud_macros_impl/src/generate.rs
@@ -56,7 +56,7 @@ impl Generator {
             Markup::Splice { expr, .. } => self.splice(expr, build),
             Markup::Element { name, attrs, body } => self.element(name, attrs, body, build),
             Markup::Let { tokens, .. } => build.push_tokens(tokens),
-            Markup::Special { segments } => {
+            Markup::Special { segments, .. } => {
                 for Special { head, body, .. } in segments {
                     build.push_tokens(head);
                     self.block(body, build);
diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs
index 42ea3b18..fddb8f3d 100644
--- a/maud_macros_impl/src/lib.rs
+++ b/maud_macros_impl/src/lib.rs
@@ -17,6 +17,8 @@ use quote::quote;
 pub use parse::parse_at_runtime;
 pub use runtime::format_str;
 
+use crate::ast::Markup;
+
 pub fn expand(input: TokenStream) -> TokenStream {
     let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site()));
     // Heuristic: the size of the resulting markup tends to correlate with the
@@ -24,6 +26,10 @@ pub fn expand(input: TokenStream) -> TokenStream {
     let size_hint = input.to_string().len();
     let markups = parse::parse(input);
 
+    expand_from_parsed(markups, output_ident, size_hint)
+}
+
+fn expand_from_parsed(markups: Vec, output_ident: TokenTree, size_hint: usize) -> TokenStream {
     let stmts = generate::generate(markups, output_ident.clone());
     quote!({
         extern crate alloc;
@@ -42,11 +48,7 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream {
     let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site()));
     let markups = parse::parse(input.clone());
     let stmts = runtime::generate(markups);
-    let expand_compile_time = expand(input);
     quote!({
-        // Epic Hack Template compile time check
-        #expand_compile_time;
-
         extern crate alloc;
         extern crate maud;
         let mut #output_ident = String::new();
diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs
index f77367a9..344287bf 100644
--- a/maud_macros_impl/src/runtime.rs
+++ b/maud_macros_impl/src/runtime.rs
@@ -1,8 +1,8 @@
 use proc_macro2::{Ident, Span, TokenStream, TokenTree};
 use proc_macro_error::SpanRange;
-use quote::quote;
+use quote::{quote};
 
-use crate::{ast::*, escape};
+use crate::{ast::*, escape, expand_from_parsed};
 
 pub fn generate(markups: Vec) -> TokenStream {
     let mut build = RuntimeBuilder::new();
@@ -32,18 +32,19 @@ impl RuntimeGenerator {
     fn markup(&self, markup: Markup, build: &mut RuntimeBuilder) {
         match markup {
             Markup::ParseError { .. } => {},
-            Markup::Block(Block { markups, .. }) => {
-                for markup in markups {
-                    self.markup(markup, build);
-                }
-            }
+            Markup::Block(Block { markups, .. }) => self.markups(markups, build),
             Markup::Literal { content, .. } => build.push_escaped(&content),
             Markup::Symbol { symbol } => build.push_str(&symbol.to_string()),
             Markup::Splice { expr, .. } => build.push_format_arg(expr),
             Markup::Element { name, attrs, body } => self.element(name, attrs, body, build),
             Markup::Let { tokens, .. } => build.push_format_arg(tokens),
-            Markup::Special { .. } => {} // TODO
-            Markup::Match { .. } => {} // TODO
+            // fallback case: use static generator to render a subset of the template
+            markup => {
+                let output_ident = TokenTree::Ident(Ident::new("__maud_fallback_output", Span::mixed_site()));
+                let tt = expand_from_parsed(vec![markup], output_ident, 0);
+
+                build.push_format_arg(tt);
+            }
         }
     }
 

From d9eeeff7e3ec627caef3867c4aa5d796427a0e31 Mon Sep 17 00:00:00 2001
From: Markus Unterwaditzer 
Date: Sun, 10 Nov 2024 18:04:49 +0100
Subject: [PATCH 14/43] some bugfixes

---
 maud/src/lib.rs                 |  3 +-
 maud_macros_impl/src/lib.rs     | 82 ++++++++++++++++++---------------
 maud_macros_impl/src/runtime.rs | 12 +++--
 3 files changed, 55 insertions(+), 42 deletions(-)

diff --git a/maud/src/lib.rs b/maud/src/lib.rs
index ccadf826..5ae93a30 100644
--- a/maud/src/lib.rs
+++ b/maud/src/lib.rs
@@ -472,10 +472,11 @@ pub mod macro_private {
     }
 
     #[cfg(feature = "hotreload")]
-    pub fn render_runtime_error(e: &str) -> crate::Markup {
+    pub fn render_runtime_error(input: &str, e: &str) -> crate::Markup {
         // print error to console, as we have no guarantee that the error will be seen in the
         // browser (arbitrary styles may be applied)
         println!("TEMPLATE ERROR: {}", e);
+        println!("for sub-template:\n{}", input);
         crate::PreEscaped(alloc::format!("

Template Errors:

{}
", e)) } diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index fddb8f3d..a60f0ea9 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -55,55 +55,62 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream { let mut vars: ::std::collections::HashMap<&'static str, String> = ::std::collections::HashMap::new(); let input = ::maud::macro_private::gather_html_macro_invocations(file!(), line!()); - - let res = ::std::panic::catch_unwind(|| { - ::maud::macro_private::parse_at_runtime(input.parse().unwrap()) - }); - - if let Err(e) = res { - if let Some(s) = e - // Try to convert it to a String, then turn that into a str - .downcast_ref::() - .map(String::as_str) - // If that fails, try to turn it into a &'static str - .or_else(|| e.downcast_ref::<&'static str>().map(::std::ops::Deref::deref)) - { - ::maud::macro_private::render_runtime_error(s) + if let Some(input) = input { + let res = ::std::panic::catch_unwind(|| { + ::maud::macro_private::parse_at_runtime(input.parse().unwrap()) + }); + + if let Err(e) = res { + if let Some(s) = e + // Try to convert it to a String, then turn that into a str + .downcast_ref::() + .map(String::as_str) + // If that fails, try to turn it into a &'static str + .or_else(|| e.downcast_ref::<&'static str>().map(::std::ops::Deref::deref)) + { + ::maud::macro_private::render_runtime_error(&input, s) + } else { + ::maud::macro_private::render_runtime_error(&input, "unknown panic") + } } else { - ::maud::macro_private::render_runtime_error("unknown panic") + let markups = ::maud::macro_private::parse_at_runtime(input.parse().unwrap()); + let format_str = ::maud::macro_private::format_str(markups); + + #stmts + + // cannot use return here, and block labels come with strings attached (cant nest them + // without compiler warnings) + match ::maud::macro_private::leon::Template::parse(&format_str) { + Ok(template) => { + match template.render(&vars) { + Ok(template) => maud::PreEscaped(template), + Err(e) => ::maud::macro_private::render_runtime_error(&input, &e.to_string()) + } + }, + Err(e) => ::maud::macro_private::render_runtime_error(&input, &e.to_string()) + } } } else { - let markups = ::maud::macro_private::parse_at_runtime(input.parse().unwrap()); - let format_str = ::maud::macro_private::format_str(markups); - - #stmts - - // cannot use return here, and block labels come with strings attached (cant nest them - // without compiler warnings) - match ::maud::macro_private::leon::Template::parse(&format_str) { - Ok(template) => { - match template.render(&vars) { - Ok(template) => maud::PreEscaped(template), - Err(e) => ::maud::macro_private::render_runtime_error(&e.to_string()) - } - }, - Err(e) => ::maud::macro_private::render_runtime_error(&e.to_string()) - } + ::maud::macro_private::render_runtime_error("", &format!("can't find template source at {}:{}, please recompile", file!(), line!())) } }) } /// Grabs the inside of an html! {} invocation and returns it as a string -pub fn gather_html_macro_invocations(file_path: &'static str, start_column: u32) -> String { +pub fn gather_html_macro_invocations(file_path: &'static str, start_line: u32) -> Option { let buf_reader = BufReader::new(File::open(file_path).unwrap()); let mut braces_diff = 0; let html_invocation = buf_reader .lines() - .skip(start_column as usize) + .skip(start_line as usize - 1) + .map(|line| line.unwrap()) + // scan for beginning of the macro. start_line may point to it directly, but we want to + // handle code flowing slightly downward. + .skip_while(|line| !line.contains("html!")) + .skip(1) .take_while(|line| { - let line = line.as_ref().unwrap(); for c in line.chars() { if c == '{' { braces_diff += 1; @@ -113,9 +120,12 @@ pub fn gather_html_macro_invocations(file_path: &'static str, start_column: u32) } braces_diff != -1 }) - .map(|line| line.unwrap()) .collect::>() .join("\n"); - html_invocation + if !html_invocation.is_empty() { + Some(html_invocation) + } else { + None + } } diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index 344287bf..9fcdaa8d 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -187,7 +187,12 @@ impl RuntimeBuilder { } fn push_escaped(&mut self, string: &str) { - escape::escape_to_string(string, &mut self.format_str); + // escape for leon templating. the string itself cannot contain raw {} otherwise + let string = string + .replace(r"\", r"\\") + .replace(r"{", r"\{") + .replace(r"}", r"\}"); + escape::escape_to_string(&string, &mut self.format_str); } fn push_format_arg(&mut self, expr: TokenStream) { @@ -208,9 +213,6 @@ impl RuntimeBuilder { } fn finish(self) -> TokenStream { - let tokens = self.tokens.into_iter().collect::(); - quote! { - #tokens - }.into() + self.tokens.into_iter().collect::() } } From d4539e1196fecbbb62946092f18cf636c0a7ba90 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 10 Nov 2024 19:22:51 +0100 Subject: [PATCH 15/43] refactor --- maud/Cargo.toml | 3 +- maud/src/lib.rs | 16 +++--- maud_macros/Cargo.toml | 3 + maud_macros/src/lib.rs | 1 + maud_macros_impl/Cargo.toml | 4 ++ maud_macros_impl/src/lib.rs | 99 +++++++++++++++++++-------------- maud_macros_impl/src/runtime.rs | 8 ++- 7 files changed, 80 insertions(+), 54 deletions(-) diff --git a/maud/Cargo.toml b/maud/Cargo.toml index 25b76a72..289d63f7 100644 --- a/maud/Cargo.toml +++ b/maud/Cargo.toml @@ -14,7 +14,7 @@ edition.workspace = true [features] default = [] -hotreload = ["maud_macros_impl", "leon"] +hotreload = ["maud_macros_impl/hotreload", "maud_macros/hotreload"] # Web framework integrations actix-web = ["actix-web-dep", "futures-util"] @@ -28,7 +28,6 @@ rocket = { version = "0.5", optional = true } futures-util = { version = "0.3.0", optional = true, default-features = false } actix-web-dep = { package = "actix-web", version = "4", optional = true, default-features = false } tide = { version = "0.16.0", optional = true, default-features = false } -leon = { version = "2.0.1", optional = true } axum-core = { version = "0.4", optional = true } submillisecond = { version = "0.4.1", optional = true } http = { version = "1", optional = true } diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 5ae93a30..a3605c9c 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -471,21 +471,19 @@ pub mod macro_private { } } + + #[cfg(feature = "hotreload")] + pub use { + maud_macros_impl::*, + }; + #[cfg(feature = "hotreload")] pub fn render_runtime_error(input: &str, e: &str) -> crate::Markup { // print error to console, as we have no guarantee that the error will be seen in the // browser (arbitrary styles may be applied) println!("TEMPLATE ERROR: {}", e); println!("for sub-template:\n{}", input); - crate::PreEscaped(alloc::format!("

Template Errors:

{}
", e)) + crate::PreEscaped(format!("

Template Errors:

{}
", e)) } - #[cfg(feature = "hotreload")] - pub use { - maud_macros_impl::expand, - maud_macros_impl::gather_html_macro_invocations, - maud_macros_impl::format_str, - maud_macros_impl::parse_at_runtime, - leon, - }; } diff --git a/maud_macros/Cargo.toml b/maud_macros/Cargo.toml index 1996f6fb..2e4ffffb 100644 --- a/maud_macros/Cargo.toml +++ b/maud_macros/Cargo.toml @@ -11,6 +11,9 @@ homepage.workspace = true repository.workspace = true edition.workspace = true +[features] +hotreload = ["maud_macros_impl/hotreload"] + [dependencies] maud_macros_impl = { path = "../maud_macros_impl" } syn = "2" diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs index f012f6c0..4c8f232b 100644 --- a/maud_macros/src/lib.rs +++ b/maud_macros/src/lib.rs @@ -13,6 +13,7 @@ pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream { maud_macros_impl::expand(input.into()).into() } +#[cfg(feature = "hotreload")] #[proc_macro] #[proc_macro_error] pub fn html_hotreload(input: proc_macro::TokenStream) -> proc_macro::TokenStream { diff --git a/maud_macros_impl/Cargo.toml b/maud_macros_impl/Cargo.toml index 740134f8..9fd9317a 100644 --- a/maud_macros_impl/Cargo.toml +++ b/maud_macros_impl/Cargo.toml @@ -10,11 +10,15 @@ repository = "https://github.com/lambda-fairy/maud" description = "Compile-time HTML templates." edition = "2021" +[features] +hotreload = ["leon"] + [dependencies] syn = "1.0.8" quote = "1.0.7" proc-macro2 = "1.0.23" proc-macro-error = "1.0.0" +leon = { version = "2.0.1", optional = true } [lib] name = "maud_macros_impl" diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index a60f0ea9..7cc4c72c 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -6,18 +6,19 @@ mod ast; mod escape; mod generate; +#[cfg(feature = "hotreload")] mod runtime; mod parse; -use std::{io::{BufReader, BufRead}, fs::File}; +use std::{io::{BufReader, BufRead}, fs::File, collections::HashMap}; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::quote; -pub use parse::parse_at_runtime; -pub use runtime::format_str; +use crate::{ast::Markup, parse::parse_at_runtime}; -use crate::ast::Markup; +#[cfg(feature = "hotreload")] +use crate::runtime::format_str; pub fn expand(input: TokenStream) -> TokenStream { let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); @@ -36,7 +37,7 @@ fn expand_from_parsed(markups: Vec, output_ident: TokenTree, size_hint: extern crate maud; let mut #output_ident = alloc::string::String::with_capacity(#size_hint); #stmts - maud::PreEscaped(#output_ident) + ::maud::PreEscaped(#output_ident) }) } @@ -44,6 +45,7 @@ fn expand_from_parsed(markups: Vec, output_ident: TokenTree, size_hint: // that will render any markup-only changes. Any other changes will // require a recompile. Of course, this is miles slower than the // normal version, but it can be miles faster to iterate on. +#[cfg(feature = "hotreload")] pub fn expand_runtime(input: TokenStream) -> TokenStream { let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); let markups = parse::parse(input.clone()); @@ -52,48 +54,63 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream { extern crate alloc; extern crate maud; let mut #output_ident = String::new(); + let file_info = file!(); + let line_info = line!(); + + let input = ::maud::macro_private::gather_html_macro_invocations(file_info, line_info); let mut vars: ::std::collections::HashMap<&'static str, String> = ::std::collections::HashMap::new(); + #stmts; + + match ::maud::macro_private::expand_runtime_main( + vars, + input.as_deref(), + file_info, + line_info, + ) { + Ok(x) => ::maud::PreEscaped(x), + Err(e) => ::maud::macro_private::render_runtime_error(&input.unwrap_or_default(), &e), + } + }) +} - let input = ::maud::macro_private::gather_html_macro_invocations(file!(), line!()); - if let Some(input) = input { - let res = ::std::panic::catch_unwind(|| { - ::maud::macro_private::parse_at_runtime(input.parse().unwrap()) - }); - - if let Err(e) = res { - if let Some(s) = e - // Try to convert it to a String, then turn that into a str - .downcast_ref::() - .map(String::as_str) - // If that fails, try to turn it into a &'static str - .or_else(|| e.downcast_ref::<&'static str>().map(::std::ops::Deref::deref)) - { - ::maud::macro_private::render_runtime_error(&input, s) - } else { - ::maud::macro_private::render_runtime_error(&input, "unknown panic") - } +#[cfg(feature = "hotreload")] +pub fn expand_runtime_main(vars: HashMap<&'static str, String>, input: Option<&str>, file_info: &str, line_info: u32) -> Result { + if let Some(input) = input { + let res = ::std::panic::catch_unwind(|| { + parse_at_runtime(input.parse().unwrap()) + }); + + if let Err(e) = res { + if let Some(s) = e + // Try to convert it to a String, then turn that into a str + .downcast_ref::() + .map(String::as_str) + // If that fails, try to turn it into a &'static str + .or_else(|| e.downcast_ref::<&'static str>().map(::std::ops::Deref::deref)) + { + return Err(s.to_string()); } else { - let markups = ::maud::macro_private::parse_at_runtime(input.parse().unwrap()); - let format_str = ::maud::macro_private::format_str(markups); - - #stmts - - // cannot use return here, and block labels come with strings attached (cant nest them - // without compiler warnings) - match ::maud::macro_private::leon::Template::parse(&format_str) { - Ok(template) => { - match template.render(&vars) { - Ok(template) => maud::PreEscaped(template), - Err(e) => ::maud::macro_private::render_runtime_error(&input, &e.to_string()) - } - }, - Err(e) => ::maud::macro_private::render_runtime_error(&input, &e.to_string()) - } + return Err("unknown panic".to_owned()); } } else { - ::maud::macro_private::render_runtime_error("", &format!("can't find template source at {}:{}, please recompile", file!(), line!())) + let markups = parse_at_runtime(input.parse().unwrap()); + let format_str = format_str(markups); + + // cannot use return here, and block labels come with strings attached (cant nest them + // without compiler warnings) + match leon::Template::parse(&format_str) { + Ok(template) => { + match template.render(&vars) { + Ok(template) => Ok(template), + Err(e) => Err(e.to_string()) + } + }, + Err(e) => Err(e.to_string()) + } } - }) + } else { + Err(format!("can't find template source at {}:{}, please recompile", file_info, line_info)) + } } /// Grabs the inside of an html! {} invocation and returns it as a string diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index 9fcdaa8d..8e781686 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -23,6 +23,10 @@ impl RuntimeGenerator { RuntimeGenerator {} } + fn builder(&self) -> RuntimeBuilder { + RuntimeBuilder::new() + } + fn markups(&self, markups: Vec, build: &mut RuntimeBuilder) { for markup in markups { self.markup(markup, build); @@ -75,12 +79,12 @@ impl RuntimeGenerator { self.markup(value, build); build.push_str("\""); } - AttrType::Optional { .. } => {} + AttrType::Optional { .. } => todo!(), AttrType::Empty { toggler: None } => { build.push_str(" "); self.name(name, build); } - AttrType::Empty { .. } => {} + AttrType::Empty { .. } => todo!(), } } } From 2ba0b9dfb61b0c0edb205a1810e4bc9bfb6bbf1e Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 10 Nov 2024 22:25:55 +0100 Subject: [PATCH 16/43] fix more bugs --- maud_macros_impl/src/generate.rs | 3 ++ maud_macros_impl/src/lib.rs | 54 +++++++++++++++---------- maud_macros_impl/src/parse.rs | 2 + maud_macros_impl/src/runtime.rs | 67 ++++++++++++++++++++++++++------ 4 files changed, 94 insertions(+), 32 deletions(-) diff --git a/maud_macros_impl/src/generate.rs b/maud_macros_impl/src/generate.rs index 095651e7..954dfc59 100644 --- a/maud_macros_impl/src/generate.rs +++ b/maud_macros_impl/src/generate.rs @@ -88,6 +88,7 @@ impl Generator { Block { markups, outer_span, + .. }: Block, build: &mut Builder, ) { @@ -214,6 +215,7 @@ fn desugar_classes_or_ids( markups: prepend_leading_space(name, &mut leading_space), // TODO: is this correct? outer_span: cond_span, + }; markups.push(Markup::Special { segments: vec![Special { @@ -229,6 +231,7 @@ fn desugar_classes_or_ids( value: Markup::Block(Block { markups, outer_span: SpanRange::call_site(), + }), }, }) diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 7cc4c72c..090ff554 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -12,7 +12,7 @@ mod parse; use std::{io::{BufReader, BufRead}, fs::File, collections::HashMap}; -use proc_macro2::{Ident, Span, TokenStream, TokenTree}; +use proc_macro2::{Ident, Span, TokenStream, TokenTree, Literal}; use quote::quote; use crate::{ast::Markup, parse::parse_at_runtime}; @@ -21,16 +21,16 @@ use crate::{ast::Markup, parse::parse_at_runtime}; use crate::runtime::format_str; pub fn expand(input: TokenStream) -> TokenStream { - let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); // Heuristic: the size of the resulting markup tends to correlate with the // code size of the template itself let size_hint = input.to_string().len(); let markups = parse::parse(input); - expand_from_parsed(markups, output_ident, size_hint) + expand_from_parsed(markups, size_hint) } -fn expand_from_parsed(markups: Vec, output_ident: TokenTree, size_hint: usize) -> TokenStream { +fn expand_from_parsed(markups: Vec, size_hint: usize) -> TokenStream { + let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); let stmts = generate::generate(markups, output_ident.clone()); quote!({ extern crate alloc; @@ -47,18 +47,24 @@ fn expand_from_parsed(markups: Vec, output_ident: TokenTree, size_hint: // normal version, but it can be miles faster to iterate on. #[cfg(feature = "hotreload")] pub fn expand_runtime(input: TokenStream) -> TokenStream { - let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); let markups = parse::parse(input.clone()); + expand_runtime_from_parsed(markups, "html!") +} + +#[cfg(feature = "hotreload")] +fn expand_runtime_from_parsed(markups: Vec, skip_to_keyword: &str) -> TokenStream { let stmts = runtime::generate(markups); - quote!({ + + let skip_to_keyword = TokenTree::Literal(Literal::string(skip_to_keyword)); + + let tok = quote!({ extern crate alloc; extern crate maud; - let mut #output_ident = String::new(); let file_info = file!(); let line_info = line!(); - let input = ::maud::macro_private::gather_html_macro_invocations(file_info, line_info); let mut vars: ::std::collections::HashMap<&'static str, String> = ::std::collections::HashMap::new(); + let input = ::maud::macro_private::gather_html_macro_invocations(file_info, line_info, #skip_to_keyword); #stmts; match ::maud::macro_private::expand_runtime_main( @@ -70,7 +76,15 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream { Ok(x) => ::maud::PreEscaped(x), Err(e) => ::maud::macro_private::render_runtime_error(&input.unwrap_or_default(), &e), } - }) + }); + + let s = tok.to_string(); + + if s.contains("unwrap_or_default") { + // panic!("{}", s); + } + + tok } #[cfg(feature = "hotreload")] @@ -114,7 +128,7 @@ pub fn expand_runtime_main(vars: HashMap<&'static str, String>, input: Option<&s } /// Grabs the inside of an html! {} invocation and returns it as a string -pub fn gather_html_macro_invocations(file_path: &'static str, start_line: u32) -> Option { +pub fn gather_html_macro_invocations(file_path: &str, start_line: u32, skip_to_keyword: &str) -> Option { let buf_reader = BufReader::new(File::open(file_path).unwrap()); let mut braces_diff = 0; @@ -125,20 +139,20 @@ pub fn gather_html_macro_invocations(file_path: &'static str, start_line: u32) - .map(|line| line.unwrap()) // scan for beginning of the macro. start_line may point to it directly, but we want to // handle code flowing slightly downward. - .skip_while(|line| !line.contains("html!")) + .skip_while(|line| !line.contains(skip_to_keyword)) .skip(1) - .take_while(|line| { - for c in line.chars() { - if c == '{' { - braces_diff += 1; - } else if c == '}' { - braces_diff -= 1; - } + .collect::>() + .join("\n") + .chars() + .take_while(|&c| { + if c == '{' { + braces_diff += 1; + } else if c == '}' { + braces_diff -= 1; } braces_diff != -1 }) - .collect::>() - .join("\n"); + .collect::(); if !html_invocation.is_empty() { Some(html_invocation) diff --git a/maud_macros_impl/src/parse.rs b/maud_macros_impl/src/parse.rs index a3a23c0d..30895ebb 100644 --- a/maud_macros_impl/src/parse.rs +++ b/maud_macros_impl/src/parse.rs @@ -19,6 +19,7 @@ struct Parser { /// If we're inside an attribute, then this contains the attribute name. current_attr: Option, input: ::IntoIter, + /// Whether this parsing is happening at runtime. Should only be used to control error reporting. is_runtime: bool, } @@ -754,6 +755,7 @@ impl Parser { ast::Block { markups, outer_span, + } } } diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index 8e781686..ce7388f8 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -1,8 +1,8 @@ use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use proc_macro_error::SpanRange; -use quote::{quote}; +use quote::quote; -use crate::{ast::*, escape, expand_from_parsed}; +use crate::{ast::*, escape, expand_from_parsed, expand_runtime, expand_runtime_from_parsed}; pub fn generate(markups: Vec) -> TokenStream { let mut build = RuntimeBuilder::new(); @@ -23,10 +23,6 @@ impl RuntimeGenerator { RuntimeGenerator {} } - fn builder(&self) -> RuntimeBuilder { - RuntimeBuilder::new() - } - fn markups(&self, markups: Vec, build: &mut RuntimeBuilder) { for markup in markups { self.markup(markup, build); @@ -39,19 +35,44 @@ impl RuntimeGenerator { Markup::Block(Block { markups, .. }) => self.markups(markups, build), Markup::Literal { content, .. } => build.push_escaped(&content), Markup::Symbol { symbol } => build.push_str(&symbol.to_string()), - Markup::Splice { expr, .. } => build.push_format_arg(expr), + Markup::Splice { expr, .. } => self.splice(expr, build), Markup::Element { name, attrs, body } => self.element(name, attrs, body, build), - Markup::Let { tokens, .. } => build.push_format_arg(tokens), + Markup::Let { tokens, .. } => { + // this is a bit dicey + build.tokens.extend(tokens); + }, + Markup::Special { segments, .. } => self.special(segments, build), // fallback case: use static generator to render a subset of the template markup => { - let output_ident = TokenTree::Ident(Ident::new("__maud_fallback_output", Span::mixed_site())); - let tt = expand_from_parsed(vec![markup], output_ident, 0); + let tt = expand_from_parsed(vec![markup], 0); build.push_format_arg(tt); } } } + fn special(&self, segments: Vec, build: &mut RuntimeBuilder) { + let output_ident = TokenTree::Ident(Ident::new("__maud_special_output", Span::mixed_site())); + let mut tt = TokenStream::new(); + for Special { head, body, .. } in segments { + let body = expand_runtime_from_parsed(body.markups, &head.to_string()); + tt.extend(quote! { + #head { + ::maud::Render::render_to(&#body, &mut #output_ident); + } + }); + } + build.push_format_arg(quote! {{ + let mut #output_ident = String::new(); + #tt + ::maud::PreEscaped(#output_ident) + }}); + } + + fn splice(&self, expr: TokenStream, build: &mut RuntimeBuilder) { + build.push_format_arg(expr); + } + fn element(&self, name: TokenStream, attrs: Vec, body: ElementBody, build: &mut RuntimeBuilder) { build.push_str("<"); self.name(name.clone(), build); @@ -79,12 +100,32 @@ impl RuntimeGenerator { self.markup(value, build); build.push_str("\""); } - AttrType::Optional { .. } => todo!(), + AttrType::Optional { toggler: Toggler { cond, .. } } => { + let inner_value = quote!(inner_value); + let name_tok = name_to_string(name); + let body = expand_runtime(quote! { + (::maud::PreEscaped(" ")) + (::maud::PreEscaped(#name_tok)) + (::maud::PreEscaped("=\"")) + (#inner_value) + (::maud::PreEscaped("\"")) + }); + + build.push_format_arg(quote!(if let Some(#inner_value) = (#cond) { #body })); + }, AttrType::Empty { toggler: None } => { build.push_str(" "); self.name(name, build); } - AttrType::Empty { .. } => todo!(), + AttrType::Empty { toggler: Some(Toggler { cond, .. }) } => { + let name_tok = name_to_string(name); + let body = expand_runtime(quote! { + " " + (::maud::PreEscaped(#name_tok)) + }); + + build.push_format_arg(quote!(if (#cond) { #body })); + } } } } @@ -136,6 +177,7 @@ fn desugar_classes_or_ids( markups: prepend_leading_space(name, &mut leading_space), // TODO: is this correct? outer_span: cond_span, + }; markups.push(Markup::Special { segments: vec![Special { @@ -151,6 +193,7 @@ fn desugar_classes_or_ids( value: Markup::Block(Block { markups, outer_span: SpanRange::call_site(), + }), }, }) From 73abc4dbb1157ce351e12c5c0e4c1cb129548509 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 10 Nov 2024 22:33:19 +0100 Subject: [PATCH 17/43] deduplicate code --- maud_macros_impl/src/generate.rs | 2 +- maud_macros_impl/src/runtime.rs | 83 +------------------------------- 2 files changed, 2 insertions(+), 83 deletions(-) diff --git a/maud_macros_impl/src/generate.rs b/maud_macros_impl/src/generate.rs index 954dfc59..7b0ae069 100644 --- a/maud_macros_impl/src/generate.rs +++ b/maud_macros_impl/src/generate.rs @@ -171,7 +171,7 @@ impl Generator { //////////////////////////////////////////////////////// -fn desugar_attrs(attrs: Vec) -> Vec { +pub fn desugar_attrs(attrs: Vec) -> Vec { let mut classes_static = vec![]; let mut classes_toggled = vec![]; let mut ids = vec![]; diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index ce7388f8..140922d8 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -1,8 +1,8 @@ use proc_macro2::{Ident, Span, TokenStream, TokenTree}; -use proc_macro_error::SpanRange; use quote::quote; use crate::{ast::*, escape, expand_from_parsed, expand_runtime, expand_runtime_from_parsed}; +use crate::generate::desugar_attrs; pub fn generate(markups: Vec) -> TokenStream { let mut build = RuntimeBuilder::new(); @@ -133,87 +133,6 @@ impl RuntimeGenerator { //////////////////////////////////////////////////////// -fn desugar_attrs(attrs: Vec) -> Vec { - let mut classes_static = vec![]; - let mut classes_toggled = vec![]; - let mut ids = vec![]; - let mut named_attrs = vec![]; - for attr in attrs { - match attr { - Attr::Class { - name, - toggler: Some(toggler), - .. - } => classes_toggled.push((name, toggler)), - Attr::Class { - name, - toggler: None, - .. - } => classes_static.push(name), - Attr::Id { name, .. } => ids.push(name), - Attr::Named { named_attr } => named_attrs.push(named_attr), - } - } - let classes = desugar_classes_or_ids("class", classes_static, classes_toggled); - let ids = desugar_classes_or_ids("id", ids, vec![]); - classes.into_iter().chain(ids).chain(named_attrs).collect() -} - -fn desugar_classes_or_ids( - attr_name: &'static str, - values_static: Vec, - values_toggled: Vec<(Markup, Toggler)>, -) -> Option { - if values_static.is_empty() && values_toggled.is_empty() { - return None; - } - let mut markups = Vec::new(); - let mut leading_space = false; - for name in values_static { - markups.extend(prepend_leading_space(name, &mut leading_space)); - } - for (name, Toggler { cond, cond_span }) in values_toggled { - let body = Block { - markups: prepend_leading_space(name, &mut leading_space), - // TODO: is this correct? - outer_span: cond_span, - - }; - markups.push(Markup::Special { - segments: vec![Special { - at_span: SpanRange::call_site(), - head: quote!(if (#cond)), - body, - }], - }); - } - Some(NamedAttr { - name: TokenStream::from(TokenTree::Ident(Ident::new(attr_name, Span::call_site()))), - attr_type: AttrType::Normal { - value: Markup::Block(Block { - markups, - outer_span: SpanRange::call_site(), - - }), - }, - }) -} - -fn prepend_leading_space(name: Markup, leading_space: &mut bool) -> Vec { - let mut markups = Vec::new(); - if *leading_space { - markups.push(Markup::Literal { - content: " ".to_owned(), - span: name.span(), - }); - } - *leading_space = true; - markups.push(name); - markups -} - -//////////////////////////////////////////////////////// - struct RuntimeBuilder { tokens: Vec, format_str: String, From ba33b13f86b6b11573ccd6cec6105334f69b8b8a Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 10 Nov 2024 22:39:05 +0100 Subject: [PATCH 18/43] more refactoring --- maud/Cargo.toml | 2 +- maud/src/escape.rs | 34 ---------------------------------- maud/src/lib.rs | 6 ++---- maud_macros_impl/src/escape.rs | 6 +++--- maud_macros_impl/src/lib.rs | 17 +++++++++++++---- maud_macros_impl/src/parse.rs | 2 ++ 6 files changed, 21 insertions(+), 46 deletions(-) delete mode 100644 maud/src/escape.rs diff --git a/maud/Cargo.toml b/maud/Cargo.toml index 289d63f7..0de523f8 100644 --- a/maud/Cargo.toml +++ b/maud/Cargo.toml @@ -22,7 +22,7 @@ axum = ["axum-core", "http"] [dependencies] maud_macros = { version = "0.26.0", path = "../maud_macros" } -maud_macros_impl = { version = "0.26.0", path = "../maud_macros_impl", optional = true } +maud_macros_impl = { version = "0.26.0", path = "../maud_macros_impl" } itoa = "1" rocket = { version = "0.5", optional = true } futures-util = { version = "0.3.0", optional = true, default-features = false } diff --git a/maud/src/escape.rs b/maud/src/escape.rs deleted file mode 100644 index 94cdeec1..00000000 --- a/maud/src/escape.rs +++ /dev/null @@ -1,34 +0,0 @@ -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// !!!!! PLEASE KEEP THIS IN SYNC WITH `maud_macros/src/escape.rs` !!!!! -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -extern crate alloc; - -use alloc::string::String; - -pub fn escape_to_string(input: &str, output: &mut String) { - for b in input.bytes() { - match b { - b'&' => output.push_str("&"), - b'<' => output.push_str("<"), - b'>' => output.push_str(">"), - b'"' => output.push_str("""), - _ => unsafe { output.as_mut_vec().push(b) }, - } - } -} - -#[cfg(test)] -mod test { - extern crate alloc; - - use super::escape_to_string; - use alloc::string::String; - - #[test] - fn it_works() { - let mut s = String::new(); - escape_to_string("", &mut s); - assert_eq!(s, "<script>launchMissiles()</script>"); - } -} diff --git a/maud/src/lib.rs b/maud/src/lib.rs index a3605c9c..9d973eed 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -25,8 +25,6 @@ pub use { maud_macros::html_hotreload as html, }; -mod escape; - /// An adapter that escapes HTML special characters. /// /// The following characters are escaped: @@ -61,7 +59,7 @@ impl<'a> Escaper<'a> { impl fmt::Write for Escaper<'_> { fn write_str(&mut self, s: &str) -> fmt::Result { - escape::escape_to_string(s, self.0); + maud_macros_impl::escape_to_string(s, self.0); Ok(()) } } @@ -119,7 +117,7 @@ pub trait Render { impl Render for str { fn render_to(&self, w: &mut String) { - escape::escape_to_string(self, w); + maud_macros_impl::escape_to_string(self, w); } } diff --git a/maud_macros_impl/src/escape.rs b/maud_macros_impl/src/escape.rs index 786d8c77..6bc17f3a 100644 --- a/maud_macros_impl/src/escape.rs +++ b/maud_macros_impl/src/escape.rs @@ -1,6 +1,6 @@ -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// !!!!!!!! PLEASE KEEP THIS IN SYNC WITH `maud/src/escape.rs` !!!!!!!!! -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +extern crate alloc; + +use alloc::string::String; pub fn escape_to_string(input: &str, output: &mut String) { for b in input.bytes() { diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 090ff554..04f1871a 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -10,15 +10,24 @@ mod generate; mod runtime; mod parse; -use std::{io::{BufReader, BufRead}, fs::File, collections::HashMap}; +use std::{io::{BufReader, BufRead}, fs::File}; -use proc_macro2::{Ident, Span, TokenStream, TokenTree, Literal}; +use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::quote; -use crate::{ast::Markup, parse::parse_at_runtime}; + +use crate::ast::Markup; #[cfg(feature = "hotreload")] -use crate::runtime::format_str; +use { + std::collections::HashMap, + proc_macro2::Literal, + crate::parse::parse_at_runtime, + crate::runtime::format_str +}; + + +pub use crate::escape::escape_to_string; pub fn expand(input: TokenStream) -> TokenStream { // Heuristic: the size of the resulting markup tends to correlate with the diff --git a/maud_macros_impl/src/parse.rs b/maud_macros_impl/src/parse.rs index 30895ebb..d8d1888b 100644 --- a/maud_macros_impl/src/parse.rs +++ b/maud_macros_impl/src/parse.rs @@ -10,6 +10,7 @@ pub fn parse(input: TokenStream) -> Vec { Parser::new(input).markups() } +#[cfg(feature = "hotreload")] pub fn parse_at_runtime(input: TokenStream) -> Vec { Parser::new_at_runtime(input).markups() } @@ -40,6 +41,7 @@ impl Parser { } } + #[cfg(feature = "hotreload")] fn new_at_runtime(input: TokenStream) -> Parser { Parser { current_attr: None, From 3279c401b6a0c2fbeb0936f63b102e1fb3f481ea Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 10 Nov 2024 22:40:54 +0100 Subject: [PATCH 19/43] fmt --- maud/src/lib.rs | 11 ++----- maud_macros_impl/src/generate.rs | 2 -- maud_macros_impl/src/lib.rs | 50 +++++++++++++++++++------------- maud_macros_impl/src/parse.rs | 7 ++--- maud_macros_impl/src/runtime.rs | 27 ++++++++++++----- 5 files changed, 53 insertions(+), 44 deletions(-) diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 9d973eed..8403363f 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -20,10 +20,7 @@ pub use maud_macros::html as html_static; pub use maud_macros::html; #[cfg(feature = "hotreload")] -pub use { - maud_macros::html_hotreload, - maud_macros::html_hotreload as html, -}; +pub use {maud_macros::html_hotreload, maud_macros::html_hotreload as html}; /// An adapter that escapes HTML special characters. /// @@ -469,11 +466,8 @@ pub mod macro_private { } } - #[cfg(feature = "hotreload")] - pub use { - maud_macros_impl::*, - }; + pub use maud_macros_impl::*; #[cfg(feature = "hotreload")] pub fn render_runtime_error(input: &str, e: &str) -> crate::Markup { @@ -483,5 +477,4 @@ pub mod macro_private { println!("for sub-template:\n{}", input); crate::PreEscaped(format!("

Template Errors:

{}
", e)) } - } diff --git a/maud_macros_impl/src/generate.rs b/maud_macros_impl/src/generate.rs index 7b0ae069..b52eb6dc 100644 --- a/maud_macros_impl/src/generate.rs +++ b/maud_macros_impl/src/generate.rs @@ -215,7 +215,6 @@ fn desugar_classes_or_ids( markups: prepend_leading_space(name, &mut leading_space), // TODO: is this correct? outer_span: cond_span, - }; markups.push(Markup::Special { segments: vec![Special { @@ -231,7 +230,6 @@ fn desugar_classes_or_ids( value: Markup::Block(Block { markups, outer_span: SpanRange::call_site(), - }), }, }) diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 04f1871a..82018a97 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -6,27 +6,26 @@ mod ast; mod escape; mod generate; +mod parse; #[cfg(feature = "hotreload")] mod runtime; -mod parse; -use std::{io::{BufReader, BufRead}, fs::File}; +use std::{ + fs::File, + io::{BufRead, BufReader}, +}; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::quote; - use crate::ast::Markup; #[cfg(feature = "hotreload")] use { + crate::parse::parse_at_runtime, crate::runtime::format_str, proc_macro2::Literal, std::collections::HashMap, - proc_macro2::Literal, - crate::parse::parse_at_runtime, - crate::runtime::format_str }; - pub use crate::escape::escape_to_string; pub fn expand(input: TokenStream) -> TokenStream { @@ -97,11 +96,14 @@ fn expand_runtime_from_parsed(markups: Vec, skip_to_keyword: &str) -> To } #[cfg(feature = "hotreload")] -pub fn expand_runtime_main(vars: HashMap<&'static str, String>, input: Option<&str>, file_info: &str, line_info: u32) -> Result { +pub fn expand_runtime_main( + vars: HashMap<&'static str, String>, + input: Option<&str>, + file_info: &str, + line_info: u32, +) -> Result { if let Some(input) = input { - let res = ::std::panic::catch_unwind(|| { - parse_at_runtime(input.parse().unwrap()) - }); + let res = ::std::panic::catch_unwind(|| parse_at_runtime(input.parse().unwrap())); if let Err(e) = res { if let Some(s) = e @@ -109,7 +111,10 @@ pub fn expand_runtime_main(vars: HashMap<&'static str, String>, input: Option<&s .downcast_ref::() .map(String::as_str) // If that fails, try to turn it into a &'static str - .or_else(|| e.downcast_ref::<&'static str>().map(::std::ops::Deref::deref)) + .or_else(|| { + e.downcast_ref::<&'static str>() + .map(::std::ops::Deref::deref) + }) { return Err(s.to_string()); } else { @@ -122,22 +127,27 @@ pub fn expand_runtime_main(vars: HashMap<&'static str, String>, input: Option<&s // cannot use return here, and block labels come with strings attached (cant nest them // without compiler warnings) match leon::Template::parse(&format_str) { - Ok(template) => { - match template.render(&vars) { - Ok(template) => Ok(template), - Err(e) => Err(e.to_string()) - } + Ok(template) => match template.render(&vars) { + Ok(template) => Ok(template), + Err(e) => Err(e.to_string()), }, - Err(e) => Err(e.to_string()) + Err(e) => Err(e.to_string()), } } } else { - Err(format!("can't find template source at {}:{}, please recompile", file_info, line_info)) + Err(format!( + "can't find template source at {}:{}, please recompile", + file_info, line_info + )) } } /// Grabs the inside of an html! {} invocation and returns it as a string -pub fn gather_html_macro_invocations(file_path: &str, start_line: u32, skip_to_keyword: &str) -> Option { +pub fn gather_html_macro_invocations( + file_path: &str, + start_line: u32, + skip_to_keyword: &str, +) -> Option { let buf_reader = BufReader::new(File::open(file_path).unwrap()); let mut braces_diff = 0; diff --git a/maud_macros_impl/src/parse.rs b/maud_macros_impl/src/parse.rs index d8d1888b..b1301843 100644 --- a/maud_macros_impl/src/parse.rs +++ b/maud_macros_impl/src/parse.rs @@ -500,9 +500,7 @@ impl Parser { None => { let mut span = ast::span_tokens(tokens); span.first = at_span; - panic!( - "unexpected end of `@let` expression" - ); + panic!("unexpected end of `@let` expression"); } } } @@ -567,7 +565,7 @@ impl Parser { } else { abort_call_site!("expected `;`, found end of macro") } - }, + } }; ast::Markup::Element { name, attrs, body } } @@ -757,7 +755,6 @@ impl Parser { ast::Block { markups, outer_span, - } } } diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index 140922d8..5d9aff77 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -1,8 +1,8 @@ use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::quote; -use crate::{ast::*, escape, expand_from_parsed, expand_runtime, expand_runtime_from_parsed}; use crate::generate::desugar_attrs; +use crate::{ast::*, escape, expand_from_parsed, expand_runtime, expand_runtime_from_parsed}; pub fn generate(markups: Vec) -> TokenStream { let mut build = RuntimeBuilder::new(); @@ -31,7 +31,7 @@ impl RuntimeGenerator { fn markup(&self, markup: Markup, build: &mut RuntimeBuilder) { match markup { - Markup::ParseError { .. } => {}, + Markup::ParseError { .. } => {} Markup::Block(Block { markups, .. }) => self.markups(markups, build), Markup::Literal { content, .. } => build.push_escaped(&content), Markup::Symbol { symbol } => build.push_str(&symbol.to_string()), @@ -40,7 +40,7 @@ impl RuntimeGenerator { Markup::Let { tokens, .. } => { // this is a bit dicey build.tokens.extend(tokens); - }, + } Markup::Special { segments, .. } => self.special(segments, build), // fallback case: use static generator to render a subset of the template markup => { @@ -52,7 +52,8 @@ impl RuntimeGenerator { } fn special(&self, segments: Vec, build: &mut RuntimeBuilder) { - let output_ident = TokenTree::Ident(Ident::new("__maud_special_output", Span::mixed_site())); + let output_ident = + TokenTree::Ident(Ident::new("__maud_special_output", Span::mixed_site())); let mut tt = TokenStream::new(); for Special { head, body, .. } in segments { let body = expand_runtime_from_parsed(body.markups, &head.to_string()); @@ -73,7 +74,13 @@ impl RuntimeGenerator { build.push_format_arg(expr); } - fn element(&self, name: TokenStream, attrs: Vec, body: ElementBody, build: &mut RuntimeBuilder) { + fn element( + &self, + name: TokenStream, + attrs: Vec, + body: ElementBody, + build: &mut RuntimeBuilder, + ) { build.push_str("<"); self.name(name.clone(), build); self.attrs(attrs, build); @@ -100,7 +107,9 @@ impl RuntimeGenerator { self.markup(value, build); build.push_str("\""); } - AttrType::Optional { toggler: Toggler { cond, .. } } => { + AttrType::Optional { + toggler: Toggler { cond, .. }, + } => { let inner_value = quote!(inner_value); let name_tok = name_to_string(name); let body = expand_runtime(quote! { @@ -112,12 +121,14 @@ impl RuntimeGenerator { }); build.push_format_arg(quote!(if let Some(#inner_value) = (#cond) { #body })); - }, + } AttrType::Empty { toggler: None } => { build.push_str(" "); self.name(name, build); } - AttrType::Empty { toggler: Some(Toggler { cond, .. }) } => { + AttrType::Empty { + toggler: Some(Toggler { cond, .. }), + } => { let name_tok = name_to_string(name); let body = expand_runtime(quote! { " " From e0083db209b53f938934776c34dfde40e45e0473 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 10 Nov 2024 22:53:57 +0100 Subject: [PATCH 20/43] fix all errors --- maud_macros_impl/src/parse.rs | 168 ++++++++++++++++++++++++++++------ 1 file changed, 141 insertions(+), 27 deletions(-) diff --git a/maud_macros_impl/src/parse.rs b/maud_macros_impl/src/parse.rs index b1301843..bb73a3ff 100644 --- a/maud_macros_impl/src/parse.rs +++ b/maud_macros_impl/src/parse.rs @@ -131,15 +131,35 @@ impl Parser { "for" => self.for_expr(at_span, keyword), "match" => self.match_expr(at_span, keyword), "let" => { - panic!("`@let` only works inside a block"); + if self.is_runtime { + panic!("`@let` only works inside a block"); + } else { + let span = SpanRange { + first: at_span, + last: ident.span(), + }; + abort!(span, "`@let` only works inside a block"); + } } other => { - panic!("unknown keyword `@{}`", other); + if self.is_runtime { + panic!("unknown keyword `@{}`", other); + } else { + let span = SpanRange { + first: at_span, + last: ident.span(), + }; + abort!(span, "unknown keyword `@{}`", other); + } } } } _ => { - panic!("expected keyword after `@`"); + if self.is_runtime { + panic!("expected keyword after `@`"); + } else { + abort!(at_span, "expected keyword after `@`"); + } } } } @@ -164,6 +184,9 @@ impl Parser { help = "to toggle the attribute, use square brackets: `{}[some_boolean_flag]`", attr_name; ); + return ast::Markup::ParseError { + span: SpanRange::single_span(ident.span()), + }; } } _ => {} @@ -193,8 +216,12 @@ impl Parser { ast::Markup::Block(self.block(group.stream(), SpanRange::single_span(group.span()))) } // ??? - _token => { - panic!("invalid syntax"); + token => { + if self.is_runtime { + panic!("invalid syntax"); + } else { + abort!(token, "invalid syntax"); + } } }; markup @@ -221,17 +248,29 @@ impl Parser { }; } Lit::Int(..) | Lit::Float(..) => { - emit_error!(literal, r#"literal must be double-quoted: `"{}"`"#, literal); + if self.is_runtime { + panic!(r#"literal must be double-quoted: `"{}"`"#, literal); + } else { + emit_error!(literal, r#"literal must be double-quoted: `"{}"`"#, literal); + } } Lit::Char(lit_char) => { - emit_error!( - literal, - r#"literal must be double-quoted: `"{}"`"#, - lit_char.value(), - ); + if self.is_runtime { + panic!(r#"literal must be double-quoted: `"{}"`"#, lit_char.value(),); + } else { + emit_error!( + literal, + r#"literal must be double-quoted: `"{}"`"#, + lit_char.value(), + ); + } } _ => { - emit_error!(literal, "expected string"); + if self.is_runtime { + panic!("expected string"); + } else { + emit_error!(literal, "expected string"); + } } } ast::Markup::ParseError { @@ -253,7 +292,11 @@ impl Parser { None => { let mut span = ast::span_tokens(head); span.first = at_span; - panic!("expected body for this `@if`"); + if self.is_runtime { + panic!("expected body for this `@if`"); + } else { + abort!(span, "expected body for this `@if`"); + } } } }; @@ -297,7 +340,15 @@ impl Parser { }); } _ => { - panic!("expected body for this `@else`"); + if self.is_runtime { + panic!("expected body for this `@else`"); + } else { + let span = SpanRange { + first: at_span, + last: else_keyword.span(), + }; + abort!(span, "expected body for this `@else`"); + } } }, } @@ -311,6 +362,7 @@ impl Parser { /// /// The leading `@while` should already be consumed. fn while_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { + let keyword_span = keyword.span(); let mut head = vec![keyword]; let body = loop { match self.next() { @@ -319,7 +371,15 @@ impl Parser { } Some(token) => head.push(token), None => { - panic!("expected body for this `@while`"); + if self.is_runtime { + panic!("expected body for this `@while`"); + } else { + let span = SpanRange { + first: at_span, + last: keyword_span, + }; + abort!(span, "expected body for this `@while`"); + } } } }; @@ -336,6 +396,7 @@ impl Parser { /// /// The leading `@for` should already be consumed. fn for_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { + let keyword_span = keyword.span(); let mut head = vec![keyword]; loop { match self.next() { @@ -345,7 +406,15 @@ impl Parser { } Some(token) => head.push(token), None => { - panic!("missing `in` in `@for` loop"); + if self.is_runtime { + panic!("missing `in` in `@for` loop"); + } else { + let span = SpanRange { + first: at_span, + last: keyword_span, + }; + abort!(span, "missing `in` in `@for` loop"); + } } } } @@ -356,7 +425,15 @@ impl Parser { } Some(token) => head.push(token), None => { - panic!("expected body for this `@for`"); + if self.is_runtime { + panic!("expected body for this `@for`"); + } else { + let span = SpanRange { + first: at_span, + last: keyword_span, + }; + abort!(span, "expected body for this `@for`"); + } } } }; @@ -373,6 +450,7 @@ impl Parser { /// /// The leading `@match` should already be consumed. fn match_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup { + let keyword_span = keyword.span(); let mut head = vec![keyword]; let (arms, arms_span) = loop { match self.next() { @@ -382,7 +460,15 @@ impl Parser { } Some(token) => head.push(token), None => { - panic!("expected body for this `@match`"); + if self.is_runtime { + panic!("expected body for this `@match`"); + } else { + let span = SpanRange { + first: at_span, + last: keyword_span, + }; + abort!(span, "expected body for this `@match`"); + } } } }; @@ -424,7 +510,12 @@ impl Parser { if head.is_empty() { return None; } else { - panic!("unexpected end of @match pattern"); + if self.is_runtime { + panic!("unexpected end of @match pattern"); + } else { + let head_span = ast::span_tokens(head); + abort!(head_span, "unexpected end of @match pattern"); + } } } } @@ -458,7 +549,12 @@ impl Parser { self.block(body.into_iter().collect(), span) } None => { - panic!("unexpected end of @match arm"); + if self.is_runtime { + panic!("unexpected end of @match arm"); + } else { + let span = ast::span_tokens(head); + abort!(span, "unexpected end of @match arm"); + } } }; Some(ast::MatchArm { @@ -482,9 +578,13 @@ impl Parser { _ => tokens.push(token), }, None => { - let mut span = ast::span_tokens(tokens); - span.first = at_span; - panic!("unexpected end of `@let` expression"); + if self.is_runtime { + panic!("unexpected end of `@let` expression"); + } else { + let mut span = ast::span_tokens(tokens); + span.first = at_span; + abort!(span, "unexpected end of `@let` expression"); + } } } } @@ -498,9 +598,17 @@ impl Parser { _ => tokens.push(token), }, None => { - let mut span = ast::span_tokens(tokens); - span.first = at_span; - panic!("unexpected end of `@let` expression"); + if self.is_runtime { + panic!("unexpected end of `@let` expression"); + } else { + let mut span = ast::span_tokens(tokens); + span.first = at_span; + abort!( + span, + "unexpected end of `@let` expression"; + help = "are you missing a semicolon?" + ); + } } } } @@ -671,7 +779,13 @@ impl Parser { for (name, spans) in attr_map { if spans.len() > 1 { - panic!("duplicate attribute `{}`", name); + if self.is_runtime { + panic!("duplicate attribute `{}`", name); + } else { + let mut spans = spans.into_iter(); + let first_span = spans.next().expect("spans should be non-empty"); + abort!(first_span, "duplicate attribute `{}`", name); + } } } From e0c8948d4da7efeb830cb425a55389337c3d2dc4 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 11 Nov 2024 01:48:09 +0100 Subject: [PATCH 21/43] fix tests under hotreload flag as well --- maud/src/lib.rs | 11 +- maud_macros_impl/src/ast.rs | 1 + maud_macros_impl/src/generate.rs | 4 + maud_macros_impl/src/lib.rs | 195 ++++++++++++++++++------------- maud_macros_impl/src/parse.rs | 3 +- maud_macros_impl/src/runtime.rs | 103 ++++++++++++---- 6 files changed, 205 insertions(+), 112 deletions(-) diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 8403363f..8354dbae 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -419,8 +419,10 @@ mod submillisecond_support { #[doc(hidden)] pub mod macro_private { use crate::{display, Render}; - use alloc::string::String; + pub use alloc::string::String; use core::fmt::Display; + #[cfg(feature = "hotreload")] + pub use std::collections::HashMap; #[doc(hidden)] #[macro_export] @@ -471,10 +473,7 @@ pub mod macro_private { #[cfg(feature = "hotreload")] pub fn render_runtime_error(input: &str, e: &str) -> crate::Markup { - // print error to console, as we have no guarantee that the error will be seen in the - // browser (arbitrary styles may be applied) - println!("TEMPLATE ERROR: {}", e); - println!("for sub-template:\n{}", input); - crate::PreEscaped(format!("

Template Errors:

{}
", e)) + // TODO: print to stdout as well? + crate::PreEscaped(alloc::format!("\"> -->

Template Errors:

{}

input:
{}
", e, input)) } } diff --git a/maud_macros_impl/src/ast.rs b/maud_macros_impl/src/ast.rs index 6125ff78..ec7c8c29 100644 --- a/maud_macros_impl/src/ast.rs +++ b/maud_macros_impl/src/ast.rs @@ -129,6 +129,7 @@ impl ElementBody { pub struct Block { pub markups: Vec, pub outer_span: SpanRange, + pub raw_body: Option, } impl Block { diff --git a/maud_macros_impl/src/generate.rs b/maud_macros_impl/src/generate.rs index b52eb6dc..fe7d45e6 100644 --- a/maud_macros_impl/src/generate.rs +++ b/maud_macros_impl/src/generate.rs @@ -35,6 +35,7 @@ impl Generator { Markup::Block(Block { markups, outer_span, + raw_body, }) => { if markups .iter() @@ -44,6 +45,7 @@ impl Generator { Block { markups, outer_span, + raw_body, }, build, ); @@ -215,6 +217,7 @@ fn desugar_classes_or_ids( markups: prepend_leading_space(name, &mut leading_space), // TODO: is this correct? outer_span: cond_span, + raw_body: None, }; markups.push(Markup::Special { segments: vec![Special { @@ -230,6 +233,7 @@ fn desugar_classes_or_ids( value: Markup::Block(Block { markups, outer_span: SpanRange::call_site(), + raw_body: None, }), }, }) diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 82018a97..66f44b0a 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -3,6 +3,9 @@ // lifetimes outweighs the marginal gains from explicit borrowing #![allow(clippy::needless_pass_by_value)] +extern crate alloc; +use alloc::string::String; + mod ast; mod escape; mod generate; @@ -32,7 +35,7 @@ pub fn expand(input: TokenStream) -> TokenStream { // Heuristic: the size of the resulting markup tends to correlate with the // code size of the template itself let size_hint = input.to_string().len(); - let markups = parse::parse(input); + let markups = parse::parse(input.clone()); expand_from_parsed(markups, size_hint) } @@ -41,9 +44,8 @@ fn expand_from_parsed(markups: Vec, size_hint: usize) -> TokenStream { let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); let stmts = generate::generate(markups, output_ident.clone()); quote!({ - extern crate alloc; extern crate maud; - let mut #output_ident = alloc::string::String::with_capacity(#size_hint); + let mut #output_ident = ::maud::macro_private::String::with_capacity(#size_hint); #stmts ::maud::PreEscaped(#output_ident) }) @@ -56,89 +58,91 @@ fn expand_from_parsed(markups: Vec, size_hint: usize) -> TokenStream { #[cfg(feature = "hotreload")] pub fn expand_runtime(input: TokenStream) -> TokenStream { let markups = parse::parse(input.clone()); - expand_runtime_from_parsed(markups, "html!") + expand_runtime_from_parsed(input, markups, "html!") } #[cfg(feature = "hotreload")] -fn expand_runtime_from_parsed(markups: Vec, skip_to_keyword: &str) -> TokenStream { - let stmts = runtime::generate(markups); - +fn expand_runtime_from_parsed( + input: TokenStream, + markups: Vec, + skip_to_keyword: &str, +) -> TokenStream { + let vars_ident = TokenTree::Ident(Ident::new("__maud_vars", Span::mixed_site())); let skip_to_keyword = TokenTree::Literal(Literal::string(skip_to_keyword)); + let input_string = input.to_string(); + let original_input = TokenTree::Literal(Literal::string(&input_string)); + + let stmts = runtime::generate(Some(vars_ident.clone()), markups); - let tok = quote!({ - extern crate alloc; + quote!({ extern crate maud; - let file_info = file!(); - let line_info = line!(); - let mut vars: ::std::collections::HashMap<&'static str, String> = ::std::collections::HashMap::new(); - let input = ::maud::macro_private::gather_html_macro_invocations(file_info, line_info, #skip_to_keyword); + let __maud_file_info = ::std::file!(); + let __maud_line_info = ::std::line!(); + + let mut #vars_ident: ::maud::macro_private::HashMap<&'static str, ::maud::macro_private::String> = ::std::collections::HashMap::new(); + let __maud_input = ::maud::macro_private::gather_html_macro_invocations( + __maud_file_info, + __maud_line_info, + #skip_to_keyword + ); + + let __maud_input = if let Some(ref input) = __maud_input { + input + } else { + // fall back to original, unedited input when finding file info fails + // TODO: maybe expose envvar to abort and force recompile? + #original_input + }; + #stmts; match ::maud::macro_private::expand_runtime_main( - vars, - input.as_deref(), - file_info, - line_info, + #vars_ident, + __maud_input, ) { Ok(x) => ::maud::PreEscaped(x), - Err(e) => ::maud::macro_private::render_runtime_error(&input.unwrap_or_default(), &e), + Err(e) => ::maud::macro_private::render_runtime_error(&__maud_input, &e), } - }); - - let s = tok.to_string(); - - if s.contains("unwrap_or_default") { - // panic!("{}", s); - } - - tok + }) } #[cfg(feature = "hotreload")] pub fn expand_runtime_main( vars: HashMap<&'static str, String>, - input: Option<&str>, - file_info: &str, - line_info: u32, + input: &str, ) -> Result { - if let Some(input) = input { - let res = ::std::panic::catch_unwind(|| parse_at_runtime(input.parse().unwrap())); - - if let Err(e) = res { - if let Some(s) = e - // Try to convert it to a String, then turn that into a str - .downcast_ref::() - .map(String::as_str) - // If that fails, try to turn it into a &'static str - .or_else(|| { - e.downcast_ref::<&'static str>() - .map(::std::ops::Deref::deref) - }) - { - return Err(s.to_string()); - } else { - return Err("unknown panic".to_owned()); - } + let input: TokenStream = input.parse().unwrap_or_else(|_| panic!("{}", input)); + let res = ::std::panic::catch_unwind(|| parse_at_runtime(input.clone())); + + if let Err(e) = res { + if let Some(s) = e + // Try to convert it to a String, then turn that into a str + .downcast_ref::() + .map(String::as_str) + // If that fails, try to turn it into a &'static str + .or_else(|| { + e.downcast_ref::<&'static str>() + .map(::std::ops::Deref::deref) + }) + { + return Err(s.to_string()); } else { - let markups = parse_at_runtime(input.parse().unwrap()); - let format_str = format_str(markups); - - // cannot use return here, and block labels come with strings attached (cant nest them - // without compiler warnings) - match leon::Template::parse(&format_str) { - Ok(template) => match template.render(&vars) { - Ok(template) => Ok(template), - Err(e) => Err(e.to_string()), - }, - Err(e) => Err(e.to_string()), - } + return Err("unknown panic".to_owned()); } } else { - Err(format!( - "can't find template source at {}:{}, please recompile", - file_info, line_info - )) + let markups = res.unwrap(); + let format_str = format_str(None, markups); + + // cannot use return here, and block labels come with strings attached (cant nest them + // without compiler warnings) + match leon::Template::parse(&format_str) { + Ok(template) => match template.render(&vars) { + Ok(template) => Ok(template), + Err(e) => Err(e.to_string()), + }, + Err(e) => Err(e.to_string()), + } } } @@ -148,33 +152,58 @@ pub fn gather_html_macro_invocations( start_line: u32, skip_to_keyword: &str, ) -> Option { - let buf_reader = BufReader::new(File::open(file_path).unwrap()); + let buf_reader = BufReader::new(File::open(file_path).ok()?); - let mut braces_diff = 0; + let mut output = String::new(); - let html_invocation = buf_reader + let mut lines_iter = buf_reader .lines() .skip(start_line as usize - 1) - .map(|line| line.unwrap()) - // scan for beginning of the macro. start_line may point to it directly, but we want to - // handle code flowing slightly downward. - .skip_while(|line| !line.contains(skip_to_keyword)) - .skip(1) - .collect::>() - .join("\n") - .chars() - .take_while(|&c| { - if c == '{' { + .map(|line| line.unwrap()); + + const OPEN_BRACE: &[char] = &['[', '{', '(']; + const CLOSE_BRACE: &[char] = &[']', '}', ')']; + + // scan for beginning of the macro. start_line may point to it directly, but we want to + // handle code flowing slightly downward. + for line in &mut lines_iter { + if let Some((_, after)) = line.split_once(skip_to_keyword) { + // in case that the line is something inline like html! { .. }, we want to append the + // rest of the line + // skip ahead until first opening brace after match + let after = if let Some((_, after2)) = after.split_once(OPEN_BRACE) { + after2 + } else { + after + }; + + output.push_str(after); + break; + } + } + + let mut braces_diff = 0; + + 'characterwise: for line in &mut lines_iter { + for c in line.chars() { + if OPEN_BRACE.contains(&c) { braces_diff += 1; - } else if c == '}' { + } else if CLOSE_BRACE.contains(&c) { braces_diff -= 1; } - braces_diff != -1 - }) - .collect::(); - if !html_invocation.is_empty() { - Some(html_invocation) + if braces_diff == -1 { + break 'characterwise; + } + + output.push(c); + } + + output.push('\n'); + } + + if !output.is_empty() { + Some(output) } else { None } diff --git a/maud_macros_impl/src/parse.rs b/maud_macros_impl/src/parse.rs index bb73a3ff..0c18d7a0 100644 --- a/maud_macros_impl/src/parse.rs +++ b/maud_macros_impl/src/parse.rs @@ -865,10 +865,11 @@ impl Parser { /// Parses the given token stream as a Maud expression. fn block(&mut self, body: TokenStream, outer_span: SpanRange) -> ast::Block { - let markups = self.with_input(body).markups(); + let markups = self.with_input(body.clone()).markups(); ast::Block { markups, outer_span, + raw_body: Some(body), } } } diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index 5d9aff77..cc62d5c4 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -1,17 +1,21 @@ +extern crate alloc; +use alloc::string::String; + use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::quote; +use crate::expand; use crate::generate::desugar_attrs; -use crate::{ast::*, escape, expand_from_parsed, expand_runtime, expand_runtime_from_parsed}; +use crate::{ast::*, escape, expand_from_parsed, expand_runtime_from_parsed}; -pub fn generate(markups: Vec) -> TokenStream { - let mut build = RuntimeBuilder::new(); +pub fn generate(vars_ident: Option, markups: Vec) -> TokenStream { + let mut build = RuntimeBuilder::new(vars_ident.clone()); RuntimeGenerator::new().markups(markups, &mut build); build.finish() } -pub fn format_str(markups: Vec) -> String { - let mut build = RuntimeBuilder::new(); +pub fn format_str(vars_ident: Option, markups: Vec) -> String { + let mut build = RuntimeBuilder::new(vars_ident.clone()); RuntimeGenerator::new().markups(markups, &mut build); build.format_str() } @@ -32,9 +36,29 @@ impl RuntimeGenerator { fn markup(&self, markup: Markup, build: &mut RuntimeBuilder) { match markup { Markup::ParseError { .. } => {} - Markup::Block(Block { markups, .. }) => self.markups(markups, build), + Markup::Block(Block { + markups, + outer_span, + raw_body, + }) => { + if markups + .iter() + .any(|markup| matches!(*markup, Markup::Let { .. })) + { + self.block( + Block { + markups, + outer_span, + raw_body, + }, + build, + ); + } else { + self.markups(markups, build); + } + } Markup::Literal { content, .. } => build.push_escaped(&content), - Markup::Symbol { symbol } => build.push_str(&symbol.to_string()), + Markup::Symbol { symbol } => self.name(symbol, build), Markup::Splice { expr, .. } => self.splice(expr, build), Markup::Element { name, attrs, body } => self.element(name, attrs, body, build), Markup::Let { tokens, .. } => { @@ -51,12 +75,27 @@ impl RuntimeGenerator { } } + fn block(&self, block: Block, build: &mut RuntimeBuilder) { + self.special( + vec![Special { + at_span: block.outer_span, + head: quote!(), + body: block, + }], + build, + ); + } + fn special(&self, segments: Vec, build: &mut RuntimeBuilder) { let output_ident = TokenTree::Ident(Ident::new("__maud_special_output", Span::mixed_site())); let mut tt = TokenStream::new(); for Special { head, body, .. } in segments { - let body = expand_runtime_from_parsed(body.markups, &head.to_string()); + let body = if let Some(raw_body) = body.raw_body { + expand_runtime_from_parsed(raw_body, body.markups, &head.to_string()) + } else { + expand_from_parsed(body.markups, 0) + }; tt.extend(quote! { #head { ::maud::Render::render_to(&#body, &mut #output_ident); @@ -64,7 +103,8 @@ impl RuntimeGenerator { }); } build.push_format_arg(quote! {{ - let mut #output_ident = String::new(); + extern crate maud; + let mut #output_ident = ::maud::macro_private::String::new(); #tt ::maud::PreEscaped(#output_ident) }}); @@ -112,15 +152,21 @@ impl RuntimeGenerator { } => { let inner_value = quote!(inner_value); let name_tok = name_to_string(name); - let body = expand_runtime(quote! { + let body = expand(quote! { (::maud::PreEscaped(" ")) - (::maud::PreEscaped(#name_tok)) + (#name_tok) (::maud::PreEscaped("=\"")) (#inner_value) (::maud::PreEscaped("\"")) }); - build.push_format_arg(quote!(if let Some(#inner_value) = (#cond) { #body })); + build.push_format_arg(quote! { + if let Some(#inner_value) = (#cond) { + #body + } else { + ::maud::PreEscaped("".to_owned()) + } + }); } AttrType::Empty { toggler: None } => { build.push_str(" "); @@ -130,12 +176,18 @@ impl RuntimeGenerator { toggler: Some(Toggler { cond, .. }), } => { let name_tok = name_to_string(name); - let body = expand_runtime(quote! { + let body = expand(quote! { " " - (::maud::PreEscaped(#name_tok)) + (#name_tok) }); - build.push_format_arg(quote!(if (#cond) { #body })); + build.push_format_arg(quote! { + if (#cond) { + #body + } else { + ::maud::PreEscaped("".to_owned()) + } + }); } } } @@ -145,14 +197,16 @@ impl RuntimeGenerator { //////////////////////////////////////////////////////// struct RuntimeBuilder { + vars_ident: Option, tokens: Vec, format_str: String, arg_track: u32, } impl RuntimeBuilder { - fn new() -> RuntimeBuilder { + fn new(vars_ident: Option) -> RuntimeBuilder { RuntimeBuilder { + vars_ident, tokens: Vec::new(), format_str: String::new(), arg_track: 0, @@ -174,13 +228,18 @@ impl RuntimeBuilder { fn push_format_arg(&mut self, expr: TokenStream) { let arg_track = self.arg_track.to_string(); - self.tokens.extend(quote! { - vars.insert(#arg_track, { - let mut buf = String::new(); - ::maud::macro_private::render_to!(&(#expr), &mut buf); - buf + + if let Some(ref vars) = self.vars_ident { + self.tokens.extend(quote! { + #vars.insert(#arg_track, { + extern crate maud; + let mut buf = ::maud::macro_private::String::new(); + ::maud::macro_private::render_to!(&(#expr), &mut buf); + buf + }); }); - }); + } + self.arg_track = self.arg_track + 1; self.format_str.push_str(&format!("{{{}}}", arg_track)); } From f3913b88385c21281a9f156c95c073aabce05339 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 11 Nov 2024 01:49:03 +0100 Subject: [PATCH 22/43] clippy --- maud_macros_impl/src/parse.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/maud_macros_impl/src/parse.rs b/maud_macros_impl/src/parse.rs index 0c18d7a0..6a16e57c 100644 --- a/maud_macros_impl/src/parse.rs +++ b/maud_macros_impl/src/parse.rs @@ -509,13 +509,13 @@ impl Parser { None => { if head.is_empty() { return None; + } + + if self.is_runtime { + panic!("unexpected end of @match pattern"); } else { - if self.is_runtime { - panic!("unexpected end of @match pattern"); - } else { - let head_span = ast::span_tokens(head); - abort!(head_span, "unexpected end of @match pattern"); - } + let head_span = ast::span_tokens(head); + abort!(head_span, "unexpected end of @match pattern"); } } } From ffad273d72a5b24599aebb6813eb30e79d28c00a Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 11 Nov 2024 02:08:46 +0100 Subject: [PATCH 23/43] refactor block and implement match --- maud_macros_impl/src/runtime.rs | 43 +++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index cc62d5c4..e3b69c74 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -1,7 +1,7 @@ extern crate alloc; use alloc::string::String; -use proc_macro2::{Ident, Span, TokenStream, TokenTree}; +use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; use quote::quote; use crate::expand; @@ -66,24 +66,35 @@ impl RuntimeGenerator { build.tokens.extend(tokens); } Markup::Special { segments, .. } => self.special(segments, build), - // fallback case: use static generator to render a subset of the template - markup => { - let tt = expand_from_parsed(vec![markup], 0); + Markup::Match { + head, + arms, + arms_span, + .. + } => { + let mut tt = TokenStream::new(); + for MatchArm { head, body } in arms { + tt.extend(head.clone()); + tt.extend(self.get_block(head.clone(), body)); + } - build.push_format_arg(tt); + let mut body = TokenTree::Group(Group::new(Delimiter::Brace, tt)); + body.set_span(arms_span.collapse()); + build.push_format_arg(quote!(#head #body)); } } } fn block(&self, block: Block, build: &mut RuntimeBuilder) { - self.special( - vec![Special { - at_span: block.outer_span, - head: quote!(), - body: block, - }], - build, - ); + build.push_format_arg(self.get_block(quote!(), block)); + } + + fn get_block(&self, scan_head: TokenStream, block: Block) -> TokenStream { + if let Some(raw_body) = block.raw_body { + expand_runtime_from_parsed(raw_body, block.markups, &scan_head.to_string()) + } else { + expand_from_parsed(block.markups, 0) + } } fn special(&self, segments: Vec, build: &mut RuntimeBuilder) { @@ -91,11 +102,7 @@ impl RuntimeGenerator { TokenTree::Ident(Ident::new("__maud_special_output", Span::mixed_site())); let mut tt = TokenStream::new(); for Special { head, body, .. } in segments { - let body = if let Some(raw_body) = body.raw_body { - expand_runtime_from_parsed(raw_body, body.markups, &head.to_string()) - } else { - expand_from_parsed(body.markups, 0) - }; + let body = self.get_block(head.clone(), body); tt.extend(quote! { #head { ::maud::Render::render_to(&#body, &mut #output_ident); From c71446beda5d0790c475902ade29b047c82e1f40 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 11 Nov 2024 02:57:08 +0100 Subject: [PATCH 24/43] merge fixup --- maud_macros_impl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maud_macros_impl/Cargo.toml b/maud_macros_impl/Cargo.toml index 9fd9317a..920cf895 100644 --- a/maud_macros_impl/Cargo.toml +++ b/maud_macros_impl/Cargo.toml @@ -17,7 +17,7 @@ hotreload = ["leon"] syn = "1.0.8" quote = "1.0.7" proc-macro2 = "1.0.23" -proc-macro-error = "1.0.0" +proc-macro-error2 = "2.0.1" leon = { version = "2.0.1", optional = true } [lib] From 6cc257b02782e12dff8224157be6950c965573da Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 11 Nov 2024 05:47:53 +0100 Subject: [PATCH 25/43] add hotreload.rs --- maud/src/lib.rs | 1 + maud/tests/hotreload.rs | 45 +++++++++++ maud_macros_impl/src/lib.rs | 129 +++++++++++++++++++++----------- maud_macros_impl/src/parse.rs | 16 ++-- maud_macros_impl/src/runtime.rs | 21 ++---- 5 files changed, 150 insertions(+), 62 deletions(-) create mode 100644 maud/tests/hotreload.rs diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 8354dbae..e994b2ca 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -423,6 +423,7 @@ pub mod macro_private { use core::fmt::Display; #[cfg(feature = "hotreload")] pub use std::collections::HashMap; + pub use std::env::var as env_var; #[doc(hidden)] #[macro_export] diff --git a/maud/tests/hotreload.rs b/maud/tests/hotreload.rs new file mode 100644 index 00000000..fd39e0a7 --- /dev/null +++ b/maud/tests/hotreload.rs @@ -0,0 +1,45 @@ +//! track regressions specific to the hotreload feature in maud +use maud::{html, Markup}; + +#[test] +fn regression_match_inline_tag() { + fn render(x: Option) -> Markup { + html! { + div id="main" { + @match x { + Some(x) if x == 42 => div.green { + "yes! fourty! two!" + }, + Some(_) => div.yellow { + "it's a number?" + }, + None => div.red { + "okay." + }, + } + } + } + } + + assert_eq!( + render(Some(42)).into_string(), + r#"
yes! fourty! two!
"# + ); + assert_eq!( + render(Some(420)).into_string(), + r#"
it's a number?
"# + ); + assert_eq!( + render(None).into_string(), + r#"
okay.
"# + ); +} + +#[test] +fn regression_basic() { + let result = html! { + "hello world" + }; + + assert_eq!(result.into_string(), "hello world"); +} diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 66f44b0a..eb367656 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -16,6 +16,7 @@ mod runtime; use std::{ fs::File, io::{BufRead, BufReader}, + path::Path, }; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; @@ -24,10 +25,7 @@ use quote::quote; use crate::ast::Markup; #[cfg(feature = "hotreload")] -use { - crate::parse::parse_at_runtime, crate::runtime::format_str, proc_macro2::Literal, - std::collections::HashMap, -}; +use {crate::parse::parse_at_runtime, proc_macro2::Literal, std::collections::HashMap}; pub use crate::escape::escape_to_string; @@ -58,7 +56,7 @@ fn expand_from_parsed(markups: Vec, size_hint: usize) -> TokenStream { #[cfg(feature = "hotreload")] pub fn expand_runtime(input: TokenStream) -> TokenStream { let markups = parse::parse(input.clone()); - expand_runtime_from_parsed(input, markups, "html!") + expand_runtime_from_parsed(input, markups, "html!{") } #[cfg(feature = "hotreload")] @@ -72,7 +70,10 @@ fn expand_runtime_from_parsed( let input_string = input.to_string(); let original_input = TokenTree::Literal(Literal::string(&input_string)); - let stmts = runtime::generate(Some(vars_ident.clone()), markups); + let (stmts, format_str) = runtime::generate(Some(vars_ident.clone()), markups); + + // only printed for debugging + let format_str = TokenTree::Literal(Literal::string(&format_str)); quote!({ extern crate maud; @@ -87,14 +88,20 @@ fn expand_runtime_from_parsed( #skip_to_keyword ); - let __maud_input = if let Some(ref input) = __maud_input { - input - } else { - // fall back to original, unedited input when finding file info fails - // TODO: maybe expose envvar to abort and force recompile? - #original_input + let __maud_input = match __maud_input { + Ok(ref x) => x, + Err(e) => { + if ::maud::macro_private::env_var("MAUD_SOURCE_NO_FALLBACK").as_deref() == Ok("1") { + panic!("failed to find sourcecode for {}:{}, scanning for: {:?}, error: {:?}", __maud_file_info, __maud_line_info, #skip_to_keyword, e); + } + + // fall back to original, unedited input when finding file info fails + #original_input + } }; + let __maud_unused_format_str = #format_str; + #stmts; match ::maud::macro_private::expand_runtime_main( @@ -132,7 +139,8 @@ pub fn expand_runtime_main( } } else { let markups = res.unwrap(); - let format_str = format_str(None, markups); + let (_, format_str) = runtime::generate(None, markups); + println!("RUNTIME FORMAT: {:?}", format_str); // cannot use return here, and block labels come with strings attached (cant nest them // without compiler warnings) @@ -150,9 +158,40 @@ pub fn expand_runtime_main( pub fn gather_html_macro_invocations( file_path: &str, start_line: u32, - skip_to_keyword: &str, -) -> Option { - let buf_reader = BufReader::new(File::open(file_path).ok()?); + mut skip_to_keyword: &str, +) -> Result { + let mut errors = String::new(); + let mut file = None; + + let initial_opening_brace = skip_to_keyword.chars().last().unwrap(); + let should_skip_opening_brace = matches!(initial_opening_brace, '[' | '(' | '{'); + if should_skip_opening_brace { + skip_to_keyword = &skip_to_keyword[..skip_to_keyword.len()]; + } + + for path in [ + Path::new(file_path).to_owned(), + Path::new("../").join(file_path), + ] { + let path = std::path::absolute(path).unwrap(); + match File::open(&path) { + Ok(f) => { + file = Some(f); + break; + } + Err(e) => { + errors.push_str(&e.to_string()); + errors.push('\n'); + } + } + } + + let file = match file { + Some(x) => x, + None => return Err(errors), + }; + + let buf_reader = BufReader::new(file); let mut output = String::new(); @@ -161,50 +200,54 @@ pub fn gather_html_macro_invocations( .skip(start_line as usize - 1) .map(|line| line.unwrap()); - const OPEN_BRACE: &[char] = &['[', '{', '(']; - const CLOSE_BRACE: &[char] = &[']', '}', ')']; + let mut rest_of_line = String::new(); // scan for beginning of the macro. start_line may point to it directly, but we want to // handle code flowing slightly downward. for line in &mut lines_iter { - if let Some((_, after)) = line.split_once(skip_to_keyword) { - // in case that the line is something inline like html! { .. }, we want to append the - // rest of the line - // skip ahead until first opening brace after match - let after = if let Some((_, after2)) = after.split_once(OPEN_BRACE) { - after2 - } else { - after - }; - - output.push_str(after); + if let Some((_, mut after)) = line.split_once(skip_to_keyword) { + if should_skip_opening_brace { + after = if let Some((_, after2)) = after.split_once(initial_opening_brace) { + after2 + } else { + after + }; + } + + rest_of_line.push_str(after); break; } } let mut braces_diff = 0; - 'characterwise: for line in &mut lines_iter { + 'linewise: for line in Some(rest_of_line).into_iter().chain(lines_iter) { for c in line.chars() { - if OPEN_BRACE.contains(&c) { - braces_diff += 1; - } else if CLOSE_BRACE.contains(&c) { - braces_diff -= 1; + match c { + '[' | '{' | '(' => { + braces_diff += 1; + output.push(c); + } + ']' | '}' | ')' => { + braces_diff -= 1; + + if braces_diff == -1 { + break 'linewise; + } + + output.push(c); + } + c => output.push(c), } - - if braces_diff == -1 { - break 'characterwise; - } - - output.push(c); } output.push('\n'); } - if !output.is_empty() { - Some(output) + if !output.trim().is_empty() { + println!("scanning for {:?}: {:?}", skip_to_keyword, output); + Ok(output) } else { - None + Err("output is empty".to_string()) } } diff --git a/maud_macros_impl/src/parse.rs b/maud_macros_impl/src/parse.rs index aea338ea..51951401 100644 --- a/maud_macros_impl/src/parse.rs +++ b/maud_macros_impl/src/parse.rs @@ -168,11 +168,15 @@ impl Parser { let ident_string = ident.to_string(); match ident_string.as_str() { "if" | "while" | "for" | "match" | "let" => { - abort!( - ident, - "found keyword `{}`", ident_string; - help = "should this be a `@{}`?", ident_string - ); + if self.is_runtime { + panic!("found keyword `{}`, should this be a @...?", ident_string); + } else { + abort!( + ident, + "found keyword `{}`", ident_string; + help = "should this be a `@{}`?", ident_string + ); + } } "true" | "false" => { if let Some(attr_name) = &self.current_attr { @@ -218,7 +222,7 @@ impl Parser { // ??? token => { if self.is_runtime { - panic!("invalid syntax"); + panic!("invalid syntax: {}", token); } else { abort!(token, "invalid syntax"); } diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index e3b69c74..a970285a 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -8,16 +8,11 @@ use crate::expand; use crate::generate::desugar_attrs; use crate::{ast::*, escape, expand_from_parsed, expand_runtime_from_parsed}; -pub fn generate(vars_ident: Option, markups: Vec) -> TokenStream { +pub fn generate(vars_ident: Option, markups: Vec) -> (TokenStream, String) { let mut build = RuntimeBuilder::new(vars_ident.clone()); RuntimeGenerator::new().markups(markups, &mut build); - build.finish() -} - -pub fn format_str(vars_ident: Option, markups: Vec) -> String { - let mut build = RuntimeBuilder::new(vars_ident.clone()); - RuntimeGenerator::new().markups(markups, &mut build); - build.format_str() + let s = build.format_str(); + (build.finish(), s) } struct RuntimeGenerator {} @@ -75,7 +70,7 @@ impl RuntimeGenerator { let mut tt = TokenStream::new(); for MatchArm { head, body } in arms { tt.extend(head.clone()); - tt.extend(self.get_block(head.clone(), body)); + tt.extend(self.get_block(&format!("{}{{", &head.to_string()), body)); } let mut body = TokenTree::Group(Group::new(Delimiter::Brace, tt)); @@ -86,12 +81,12 @@ impl RuntimeGenerator { } fn block(&self, block: Block, build: &mut RuntimeBuilder) { - build.push_format_arg(self.get_block(quote!(), block)); + build.push_format_arg(self.get_block(" {", block)); } - fn get_block(&self, scan_head: TokenStream, block: Block) -> TokenStream { + fn get_block(&self, scan_head: &str, block: Block) -> TokenStream { if let Some(raw_body) = block.raw_body { - expand_runtime_from_parsed(raw_body, block.markups, &scan_head.to_string()) + expand_runtime_from_parsed(raw_body, block.markups, &scan_head) } else { expand_from_parsed(block.markups, 0) } @@ -102,7 +97,7 @@ impl RuntimeGenerator { TokenTree::Ident(Ident::new("__maud_special_output", Span::mixed_site())); let mut tt = TokenStream::new(); for Special { head, body, .. } in segments { - let body = self.get_block(head.clone(), body); + let body = self.get_block(&format!("{}{{", head.to_string()), body); tt.extend(quote! { #head { ::maud::Render::render_to(&#body, &mut #output_ident); From f934dc99fd11da6042386a0a49bc1cced1b64385 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 11 Nov 2024 05:49:33 +0100 Subject: [PATCH 26/43] remove debug prints --- maud_macros_impl/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index eb367656..1a8a703c 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -140,7 +140,6 @@ pub fn expand_runtime_main( } else { let markups = res.unwrap(); let (_, format_str) = runtime::generate(None, markups); - println!("RUNTIME FORMAT: {:?}", format_str); // cannot use return here, and block labels come with strings attached (cant nest them // without compiler warnings) @@ -245,7 +244,6 @@ pub fn gather_html_macro_invocations( } if !output.trim().is_empty() { - println!("scanning for {:?}: {:?}", skip_to_keyword, output); Ok(output) } else { Err("output is empty".to_string()) From 9dc45a56c1f9fd583462088e44531e0c62a9f6af Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 11 Nov 2024 17:47:22 +0100 Subject: [PATCH 27/43] remove leon dependency --- maud/src/lib.rs | 1 + maud_macros_impl/Cargo.toml | 3 +- maud_macros_impl/src/lib.rs | 20 ++------- maud_macros_impl/src/runtime.rs | 77 +++++++++++++++++++++++++-------- 4 files changed, 65 insertions(+), 36 deletions(-) diff --git a/maud/src/lib.rs b/maud/src/lib.rs index e994b2ca..996de2cf 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -423,6 +423,7 @@ pub mod macro_private { use core::fmt::Display; #[cfg(feature = "hotreload")] pub use std::collections::HashMap; + #[cfg(feature = "hotreload")] pub use std::env::var as env_var; #[doc(hidden)] diff --git a/maud_macros_impl/Cargo.toml b/maud_macros_impl/Cargo.toml index 920cf895..5b67db4f 100644 --- a/maud_macros_impl/Cargo.toml +++ b/maud_macros_impl/Cargo.toml @@ -11,14 +11,13 @@ description = "Compile-time HTML templates." edition = "2021" [features] -hotreload = ["leon"] +hotreload = [] [dependencies] syn = "1.0.8" quote = "1.0.7" proc-macro2 = "1.0.23" proc-macro-error2 = "2.0.1" -leon = { version = "2.0.1", optional = true } [lib] name = "maud_macros_impl" diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 1a8a703c..e390d70a 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -70,10 +70,7 @@ fn expand_runtime_from_parsed( let input_string = input.to_string(); let original_input = TokenTree::Literal(Literal::string(&input_string)); - let (stmts, format_str) = runtime::generate(Some(vars_ident.clone()), markups); - - // only printed for debugging - let format_str = TokenTree::Literal(Literal::string(&format_str)); + let stmts = runtime::generate(Some(vars_ident.clone()), markups); quote!({ extern crate maud; @@ -100,8 +97,6 @@ fn expand_runtime_from_parsed( } }; - let __maud_unused_format_str = #format_str; - #stmts; match ::maud::macro_private::expand_runtime_main( @@ -139,17 +134,8 @@ pub fn expand_runtime_main( } } else { let markups = res.unwrap(); - let (_, format_str) = runtime::generate(None, markups); - - // cannot use return here, and block labels come with strings attached (cant nest them - // without compiler warnings) - match leon::Template::parse(&format_str) { - Ok(template) => match template.render(&vars) { - Ok(template) => Ok(template), - Err(e) => Err(e.to_string()), - }, - Err(e) => Err(e.to_string()), - } + let interpreter = runtime::build_interpreter(markups); + interpreter.run(&vars) } } diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index a970285a..9fc120eb 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -1,5 +1,4 @@ -extern crate alloc; -use alloc::string::String; +use std::collections::HashMap; use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; use quote::quote; @@ -8,11 +7,16 @@ use crate::expand; use crate::generate::desugar_attrs; use crate::{ast::*, escape, expand_from_parsed, expand_runtime_from_parsed}; -pub fn generate(vars_ident: Option, markups: Vec) -> (TokenStream, String) { +pub fn generate(vars_ident: Option, markups: Vec) -> TokenStream { let mut build = RuntimeBuilder::new(vars_ident.clone()); RuntimeGenerator::new().markups(markups, &mut build); - let s = build.format_str(); - (build.finish(), s) + build.finish() +} + +pub fn build_interpreter(markups: Vec) -> Interpreter { + let mut build = RuntimeBuilder::new(None); + RuntimeGenerator::new().markups(markups, &mut build); + build.interpreter() } struct RuntimeGenerator {} @@ -201,7 +205,7 @@ impl RuntimeGenerator { struct RuntimeBuilder { vars_ident: Option, tokens: Vec, - format_str: String, + commands: Vec, arg_track: u32, } @@ -210,22 +214,19 @@ impl RuntimeBuilder { RuntimeBuilder { vars_ident, tokens: Vec::new(), - format_str: String::new(), + commands: Vec::new(), arg_track: 0, } } fn push_str(&mut self, string: &str) { - self.format_str.push_str(string); + self.commands.push(Command::String(string.to_owned())); } fn push_escaped(&mut self, string: &str) { - // escape for leon templating. the string itself cannot contain raw {} otherwise - let string = string - .replace(r"\", r"\\") - .replace(r"{", r"\{") - .replace(r"}", r"\}"); - escape::escape_to_string(&string, &mut self.format_str); + let mut s = String::new(); + escape::escape_to_string(&string, &mut s); + self.push_str(&s); } fn push_format_arg(&mut self, expr: TokenStream) { @@ -243,14 +244,56 @@ impl RuntimeBuilder { } self.arg_track = self.arg_track + 1; - self.format_str.push_str(&format!("{{{}}}", arg_track)); + self.commands.push(Command::Variable(arg_track.to_string())); } - fn format_str(&self) -> String { - self.format_str.clone() + fn interpreter(self) -> Interpreter { + Interpreter { + commands: self.commands, + } } fn finish(self) -> TokenStream { self.tokens.into_iter().collect::() } } + +// /////// INTERPRETER + +pub enum Command { + String(String), + Variable(String), + VariableFromVariable(String), +} + +pub struct Interpreter { + pub commands: Vec, +} + +impl Interpreter { + pub fn run(&self, variables: &HashMap<&str, String>) -> Result { + let mut rv = String::new(); + for command in &self.commands { + match command { + Command::String(s) => rv.push_str(s), + Command::Variable(v) => { + let s = variables + .get(v.as_str()) + .ok_or_else(|| format!("unknown var: {:?}", v))?; + rv.push_str(&s); + } + Command::VariableFromVariable(v) => { + let v = variables + .get(v.as_str()) + .ok_or_else(|| format!("unknown var: {:?}", v))?; + let s = variables + .get(v.as_str()) + .ok_or_else(|| format!("unknown secondary var: {:?}", v))?; + rv.push_str(&s); + } + } + } + + Ok(rv) + } +} From 606e03a10c0c78a85ce5e4f56cc3e3248533731d Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 11 Nov 2024 19:03:53 +0100 Subject: [PATCH 28/43] wip on lazy eval --- maud/src/lib.rs | 6 +- maud_macros/src/lib.rs | 4 +- maud_macros_impl/src/lib.rs | 69 +++++++------ maud_macros_impl/src/runtime.rs | 174 +++++++++++++++++++++----------- 4 files changed, 158 insertions(+), 95 deletions(-) diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 996de2cf..93b121b5 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -419,7 +419,9 @@ mod submillisecond_support { #[doc(hidden)] pub mod macro_private { use crate::{display, Render}; + pub use alloc::boxed::Box; pub use alloc::string::String; + pub use alloc::vec::Vec; use core::fmt::Display; #[cfg(feature = "hotreload")] pub use std::collections::HashMap; @@ -474,8 +476,8 @@ pub mod macro_private { pub use maud_macros_impl::*; #[cfg(feature = "hotreload")] - pub fn render_runtime_error(input: &str, e: &str) -> crate::Markup { + pub fn render_runtime_error(e: &str) -> crate::Markup { // TODO: print to stdout as well? - crate::PreEscaped(alloc::format!("\"> -->

Template Errors:

{}

input:
{}
", e, input)) + crate::PreEscaped(alloc::format!("\"> -->

Template Errors:

{}
", e)) } } diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs index 12eb3c4d..04c998c8 100644 --- a/maud_macros/src/lib.rs +++ b/maud_macros/src/lib.rs @@ -17,5 +17,7 @@ pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream { #[proc_macro] #[proc_macro_error] pub fn html_hotreload(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - maud_macros_impl::expand_runtime(input.into()).into() + let x = maud_macros_impl::expand_runtime(input.into()).into(); + // panic!("{}", x); + x } diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index e390d70a..f36e72f5 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -27,6 +27,9 @@ use crate::ast::Markup; #[cfg(feature = "hotreload")] use {crate::parse::parse_at_runtime, proc_macro2::Literal, std::collections::HashMap}; +#[cfg(feature = "hotreload")] +pub use crate::runtime::PartialTemplate; + pub use crate::escape::escape_to_string; pub fn expand(input: TokenStream) -> TokenStream { @@ -55,63 +58,67 @@ fn expand_from_parsed(markups: Vec, size_hint: usize) -> TokenStream { // normal version, but it can be miles faster to iterate on. #[cfg(feature = "hotreload")] pub fn expand_runtime(input: TokenStream) -> TokenStream { - let markups = parse::parse(input.clone()); - expand_runtime_from_parsed(input, markups, "html!{") -} - -#[cfg(feature = "hotreload")] -fn expand_runtime_from_parsed( - input: TokenStream, - markups: Vec, - skip_to_keyword: &str, -) -> TokenStream { - let vars_ident = TokenTree::Ident(Ident::new("__maud_vars", Span::mixed_site())); - let skip_to_keyword = TokenTree::Literal(Literal::string(skip_to_keyword)); let input_string = input.to_string(); + let markups = parse::parse(input.clone()); + let partial_template = expand_runtime_from_parsed(markups); let original_input = TokenTree::Literal(Literal::string(&input_string)); - let stmts = runtime::generate(Some(vars_ident.clone()), markups); - - quote!({ + quote! {{ extern crate maud; let __maud_file_info = ::std::file!(); let __maud_line_info = ::std::line!(); - let mut #vars_ident: ::maud::macro_private::HashMap<&'static str, ::maud::macro_private::String> = ::std::collections::HashMap::new(); let __maud_input = ::maud::macro_private::gather_html_macro_invocations( __maud_file_info, __maud_line_info, - #skip_to_keyword + "html!{", ); - let __maud_input = match __maud_input { - Ok(ref x) => x, + let __maud_input: ::maud::macro_private::String = match __maud_input { + Ok(x) => x, Err(e) => { if ::maud::macro_private::env_var("MAUD_SOURCE_NO_FALLBACK").as_deref() == Ok("1") { - panic!("failed to find sourcecode for {}:{}, scanning for: {:?}, error: {:?}", __maud_file_info, __maud_line_info, #skip_to_keyword, e); + panic!("failed to find sourcecode for {}:{}, error: {:?}", __maud_file_info, __maud_line_info, e); } // fall back to original, unedited input when finding file info fails - #original_input + ::maud::macro_private::String::from(#original_input) } }; - #stmts; - - match ::maud::macro_private::expand_runtime_main( - #vars_ident, - __maud_input, - ) { + match #partial_template(::maud::macro_private::Vec::from([__maud_input.clone()])) { Ok(x) => ::maud::PreEscaped(x), - Err(e) => ::maud::macro_private::render_runtime_error(&__maud_input, &e), + Err(e) => ::maud::macro_private::render_runtime_error(&e), } + }} +} + +#[cfg(feature = "hotreload")] +fn expand_runtime_from_parsed(markups: Vec) -> TokenStream { + let vars_ident = TokenTree::Ident(Ident::new("__maud_vars", Span::mixed_site())); + let stmts = runtime::generate(Some(vars_ident.clone()), markups); + + quote!({ + let mut #vars_ident: ::maud::macro_private::HashMap<&'static str, ::maud::macro_private::PartialTemplate> = ::std::default::Default::default(); + + #stmts + + let f : ::maud::macro_private::PartialTemplate = ::maud::macro_private::Box::new(move |sources| { + let input = &sources[0]; + ::maud::macro_private::expand_runtime_main( + #vars_ident, + input, + ) + }); + + f }) } #[cfg(feature = "hotreload")] pub fn expand_runtime_main( - vars: HashMap<&'static str, String>, + vars: HashMap<&'static str, PartialTemplate>, input: &str, ) -> Result { let input: TokenStream = input.parse().unwrap_or_else(|_| panic!("{}", input)); @@ -128,14 +135,14 @@ pub fn expand_runtime_main( .map(::std::ops::Deref::deref) }) { - return Err(s.to_string()); + return Err(format!("{}, source: {}", s, input)); } else { return Err("unknown panic".to_owned()); } } else { let markups = res.unwrap(); let interpreter = runtime::build_interpreter(markups); - interpreter.run(&vars) + interpreter.run(vars) } } diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index 9fc120eb..2f31b625 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; +use proc_macro2::{Delimiter, Group, TokenStream, TokenTree}; use quote::quote; use crate::expand; @@ -72,52 +72,89 @@ impl RuntimeGenerator { .. } => { let mut tt = TokenStream::new(); - for MatchArm { head, body } in arms { + let mut sources = Vec::new(); + for (i, MatchArm { head, body }) in arms.into_iter().enumerate() { + if let Some(ref template_source) = body.raw_body { + sources.push(template_source.to_string()); + } else { + sources.push("TODO MATCH".to_owned()); + } tt.extend(head.clone()); - tt.extend(self.get_block(&format!("{}{{", &head.to_string()), body)); + let partial = self.get_block(body); + tt.extend(quote! {{ + let __maud_match_partial = #partial; + Box::new(|sources| __maud_match_partial(vec![sources[#i].clone()])) + }}); } let mut body = TokenTree::Group(Group::new(Delimiter::Brace, tt)); body.set_span(arms_span.collapse()); - build.push_format_arg(quote!(#head #body)); + build.push_lazy_format_arg(quote!(#head #body), sources); } } } fn block(&self, block: Block, build: &mut RuntimeBuilder) { - build.push_format_arg(self.get_block(" {", block)); + let source = if let Some(ref template_source) = block.raw_body { + template_source.to_string() + } else { + "TODO BLOCK".to_owned() + }; + + build.push_lazy_format_arg(self.get_block(block), vec![source]); } - fn get_block(&self, scan_head: &str, block: Block) -> TokenStream { - if let Some(raw_body) = block.raw_body { - expand_runtime_from_parsed(raw_body, block.markups, &scan_head) - } else { - expand_from_parsed(block.markups, 0) - } + fn get_block(&self, block: Block) -> TokenStream { + if block.raw_body.is_some() { + expand_runtime_from_parsed(block.markups) + } else { + // necessary to avoid bogus sources + let static_result = expand_from_parsed(block.markups, 0); + quote! {{ + let __maud_static_result = (#static_result); + let partial: ::maud::macro_private::PartialTemplate = Box::new(|_| Ok(__maud_static_result.into_string())); + partial + }} + } } fn special(&self, segments: Vec, build: &mut RuntimeBuilder) { - let output_ident = - TokenTree::Ident(Ident::new("__maud_special_output", Span::mixed_site())); let mut tt = TokenStream::new(); - for Special { head, body, .. } in segments { - let body = self.get_block(&format!("{}{{", head.to_string()), body); + let mut sources = Vec::new(); + for (i, Special { head, body, .. }) in segments.into_iter().enumerate() { + if let Some(ref template_source) = body.raw_body { + sources.push(template_source.to_string()); + } else { + sources.push("TODO SPECIAL".to_owned()); + } + + let block = self.get_block(body); tt.extend(quote! { #head { - ::maud::Render::render_to(&#body, &mut #output_ident); + __maud_special_res.push((#i, #block)); } }); } - build.push_format_arg(quote! {{ + let output = quote! {{ extern crate maud; - let mut #output_ident = ::maud::macro_private::String::new(); + let mut __maud_special_res = Vec::new(); #tt - ::maud::PreEscaped(#output_ident) - }}); + Box::new(move |sources| { + let mut maud_special_output = ::maud::macro_private::String::new(); + for (source_i, subpartial) in __maud_special_res { + let new_sources = ::maud::macro_private::Vec::from([sources[source_i].clone()]); + maud_special_output.push_str(&subpartial(new_sources)?); + } + + Ok(maud_special_output) + }) + }}; + + build.push_lazy_format_arg(output, sources); } fn splice(&self, expr: TokenStream, build: &mut RuntimeBuilder) { - build.push_format_arg(expr); + build.push_format_arg(expr, vec!["TODO SPLICE".to_owned()]); } fn element( @@ -166,13 +203,16 @@ impl RuntimeGenerator { (::maud::PreEscaped("\"")) }); - build.push_format_arg(quote! { - if let Some(#inner_value) = (#cond) { - #body - } else { - ::maud::PreEscaped("".to_owned()) - } - }); + build.push_format_arg( + quote! { + if let Some(#inner_value) = (#cond) { + #body + } else { + ::maud::PreEscaped("".to_owned()) + } + }, + vec!["TODO ATTR TYPE OPTIONAL".to_owned()], + ); } AttrType::Empty { toggler: None } => { build.push_str(" "); @@ -187,13 +227,16 @@ impl RuntimeGenerator { (#name_tok) }); - build.push_format_arg(quote! { - if (#cond) { - #body - } else { - ::maud::PreEscaped("".to_owned()) - } - }); + build.push_format_arg( + quote! { + if (#cond) { + #body + } else { + ::maud::PreEscaped("".to_owned()) + } + }, + vec!["TODO ATTR TYPE EMPTY".to_owned()], + ); } } } @@ -229,22 +272,32 @@ impl RuntimeBuilder { self.push_str(&s); } - fn push_format_arg(&mut self, expr: TokenStream) { + fn push_format_arg(&mut self, expr: TokenStream, template_sources: Vec) { + self.push_lazy_format_arg( + quote! {{ + extern crate maud; + let mut buf = ::maud::macro_private::String::new(); + ::maud::macro_private::render_to!(&(#expr), &mut buf); + ::maud::macro_private::Box::new(move |_| Ok(buf)) + }}, + template_sources, + ); + } + + fn push_lazy_format_arg(&mut self, expr: TokenStream, template_sources: Vec) { let arg_track = self.arg_track.to_string(); if let Some(ref vars) = self.vars_ident { self.tokens.extend(quote! { - #vars.insert(#arg_track, { - extern crate maud; - let mut buf = ::maud::macro_private::String::new(); - ::maud::macro_private::render_to!(&(#expr), &mut buf); - buf - }); + #vars.insert(#arg_track, #expr); }); } self.arg_track = self.arg_track + 1; - self.commands.push(Command::Variable(arg_track.to_string())); + self.commands.push(Command::Variable { + name: arg_track.to_string(), + template_sources, + }); } fn interpreter(self) -> Interpreter { @@ -262,8 +315,10 @@ impl RuntimeBuilder { pub enum Command { String(String), - Variable(String), - VariableFromVariable(String), + Variable { + name: String, + template_sources: Vec, + }, } pub struct Interpreter { @@ -271,25 +326,19 @@ pub struct Interpreter { } impl Interpreter { - pub fn run(&self, variables: &HashMap<&str, String>) -> Result { + pub fn run(self, mut variables: HashMap<&str, PartialTemplate>) -> Result { let mut rv = String::new(); - for command in &self.commands { + for command in self.commands { match command { - Command::String(s) => rv.push_str(s), - Command::Variable(v) => { - let s = variables - .get(v.as_str()) - .ok_or_else(|| format!("unknown var: {:?}", v))?; - rv.push_str(&s); - } - Command::VariableFromVariable(v) => { - let v = variables - .get(v.as_str()) - .ok_or_else(|| format!("unknown var: {:?}", v))?; + Command::String(s) => rv.push_str(&s), + Command::Variable { + name, + template_sources, + } => { let s = variables - .get(v.as_str()) - .ok_or_else(|| format!("unknown secondary var: {:?}", v))?; - rv.push_str(&s); + .remove(name.as_str()) + .ok_or_else(|| format!("unknown var: {:?}", name))?; + rv.push_str(&s(template_sources)?); } } } @@ -297,3 +346,6 @@ impl Interpreter { Ok(rv) } } + +// partial templates are generated code that take their own sourcecode for live reloading. +pub type PartialTemplate = Box) -> Result>; From 0064a6db884105cdd9a688b46838fc84eb589417 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 11 Nov 2024 20:36:26 +0100 Subject: [PATCH 29/43] simplify source code scanning --- maud_macros_impl/src/lib.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index f36e72f5..fdd977e8 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -72,7 +72,6 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream { let __maud_input = ::maud::macro_private::gather_html_macro_invocations( __maud_file_info, __maud_line_info, - "html!{", ); let __maud_input: ::maud::macro_private::String = match __maud_input { @@ -150,18 +149,13 @@ pub fn expand_runtime_main( pub fn gather_html_macro_invocations( file_path: &str, start_line: u32, - mut skip_to_keyword: &str, ) -> Result { let mut errors = String::new(); let mut file = None; - let initial_opening_brace = skip_to_keyword.chars().last().unwrap(); - let should_skip_opening_brace = matches!(initial_opening_brace, '[' | '(' | '{'); - if should_skip_opening_brace { - skip_to_keyword = &skip_to_keyword[..skip_to_keyword.len()]; - } - for path in [ + // try a few paths to deal with workspaces. insta has a more sophisticated, complete + // version of this Path::new(file_path).to_owned(), Path::new("../").join(file_path), ] { @@ -197,14 +191,12 @@ pub fn gather_html_macro_invocations( // scan for beginning of the macro. start_line may point to it directly, but we want to // handle code flowing slightly downward. for line in &mut lines_iter { - if let Some((_, mut after)) = line.split_once(skip_to_keyword) { - if should_skip_opening_brace { - after = if let Some((_, after2)) = after.split_once(initial_opening_brace) { - after2 - } else { - after - }; - } + if let Some((_, after)) = line.split_once("html!") { + let after = if let Some((_, after2)) = after.split_once(&['[', '{', '(']) { + after2 + } else { + after + }; rest_of_line.push_str(after); break; From 87cbe6fd6141aeffbc73f64858f49b46ba99817f Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 11 Nov 2024 21:04:08 +0100 Subject: [PATCH 30/43] handle reordering of sourcecode better --- maud_macros_impl/src/runtime.rs | 39 +++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index 2f31b625..a3d864ba 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -89,7 +89,7 @@ impl RuntimeGenerator { let mut body = TokenTree::Group(Group::new(Delimiter::Brace, tt)); body.set_span(arms_span.collapse()); - build.push_lazy_format_arg(quote!(#head #body), sources); + build.push_lazy_format_arg(quote!(#head #body), sources, &format!("match_expr: {}", head)); } } } @@ -101,7 +101,7 @@ impl RuntimeGenerator { "TODO BLOCK".to_owned() }; - build.push_lazy_format_arg(self.get_block(block), vec![source]); + build.push_lazy_format_arg(self.get_block(block), vec![source], "block"); } fn get_block(&self, block: Block) -> TokenStream { @@ -121,8 +121,11 @@ impl RuntimeGenerator { fn special(&self, segments: Vec, build: &mut RuntimeBuilder) { let mut tt = TokenStream::new(); let mut sources = Vec::new(); + let mut varname = String::from("special: "); for (i, Special { head, body, .. }) in segments.into_iter().enumerate() { if let Some(ref template_source) = body.raw_body { + varname.push_str(&normalize_source_for_hashing(head.to_string())); + varname.push('\n'); sources.push(template_source.to_string()); } else { sources.push("TODO SPECIAL".to_owned()); @@ -150,11 +153,11 @@ impl RuntimeGenerator { }) }}; - build.push_lazy_format_arg(output, sources); + build.push_lazy_format_arg(output, sources, &varname); } fn splice(&self, expr: TokenStream, build: &mut RuntimeBuilder) { - build.push_format_arg(expr, vec!["TODO SPLICE".to_owned()]); + build.push_format_arg(expr, vec!["TODO SPLICE".to_owned()], "splice"); } fn element( @@ -212,6 +215,7 @@ impl RuntimeGenerator { } }, vec!["TODO ATTR TYPE OPTIONAL".to_owned()], + "optional_attr", ); } AttrType::Empty { toggler: None } => { @@ -236,6 +240,7 @@ impl RuntimeGenerator { } }, vec!["TODO ATTR TYPE EMPTY".to_owned()], + "empty_attr", ); } } @@ -272,7 +277,7 @@ impl RuntimeBuilder { self.push_str(&s); } - fn push_format_arg(&mut self, expr: TokenStream, template_sources: Vec) { + fn push_format_arg(&mut self, expr: TokenStream, template_sources: Vec, named_variable: &str) { self.push_lazy_format_arg( quote! {{ extern crate maud; @@ -281,23 +286,25 @@ impl RuntimeBuilder { ::maud::macro_private::Box::new(move |_| Ok(buf)) }}, template_sources, + named_variable ); } - fn push_lazy_format_arg(&mut self, expr: TokenStream, template_sources: Vec) { - let arg_track = self.arg_track.to_string(); + fn push_lazy_format_arg(&mut self, expr: TokenStream, template_sources: Vec, named_variable: &str) { + let variable_name = format!("{}_{}", self.arg_track, named_variable); if let Some(ref vars) = self.vars_ident { self.tokens.extend(quote! { - #vars.insert(#arg_track, #expr); + #vars.insert(#variable_name, #expr); }); } - self.arg_track = self.arg_track + 1; self.commands.push(Command::Variable { - name: arg_track.to_string(), + name: variable_name, template_sources, }); + + self.arg_track = self.arg_track + 1; } fn interpreter(self) -> Interpreter { @@ -337,7 +344,7 @@ impl Interpreter { } => { let s = variables .remove(name.as_str()) - .ok_or_else(|| format!("unknown var: {:?}", name))?; + .ok_or_else(|| format!("unknown var: {:?}\nremaining variables: {:?}", name, variables.keys()))?; rv.push_str(&s(template_sources)?); } } @@ -349,3 +356,13 @@ impl Interpreter { // partial templates are generated code that take their own sourcecode for live reloading. pub type PartialTemplate = Box) -> Result>; + +// we add hashes of source code to our variable names to prevent the chances of mis-rendering +// something, such as when a user swaps blocks around in the template +fn normalize_source_for_hashing(mut input: String) -> String { + input.retain(|c| { + !c.is_ascii_whitespace() + }); + + input +} From 6202b5429e000faab92c93fa01bb0d646436eae4 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 11 Nov 2024 21:08:52 +0100 Subject: [PATCH 31/43] add MAUD_ON_ERROR --- maud/src/lib.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 93b121b5..03960bd6 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -477,7 +477,17 @@ pub mod macro_private { #[cfg(feature = "hotreload")] pub fn render_runtime_error(e: &str) -> crate::Markup { - // TODO: print to stdout as well? + eprintln!("{}", e); + + match env_var("MAUD_ON_ERROR").as_deref() { + Ok("panic") => panic!("{}", e), + Ok("exit") => { + eprintln!("{}", e); + ::std::process::exit(2); + } + _ => {} + } + crate::PreEscaped(alloc::format!("\"> -->

Template Errors:

{}
", e)) } } From 362f6bd91d0af12d90970ed53381a032d59e27c1 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 11:15:11 +0100 Subject: [PATCH 32/43] fix testsuite --- maud/src/lib.rs | 3 ++- maud/tests/misc.rs | 8 ++++---- maud_macros_impl/src/lib.rs | 6 ++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 03960bd6..1ecf85ee 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -199,7 +199,8 @@ impl_render_with_itoa! { /// # Example /// /// ```rust -/// use maud::html; +/// # use maud::html_static as html; +/// // use maud:html; /// use std::net::Ipv4Addr; /// /// let ip_address = Ipv4Addr::new(127, 0, 0, 1); diff --git a/maud/tests/misc.rs b/maud/tests/misc.rs index 2a573013..527d2633 100644 --- a/maud/tests/misc.rs +++ b/maud/tests/misc.rs @@ -1,4 +1,4 @@ -use maud::html; +use maud::{html, html_static}; #[test] fn issue_13() { @@ -13,7 +13,7 @@ fn issue_21() { macro_rules! greet { () => {{ let name = "Pinkie Pie"; - html! { + html_static! { p { "Hello, " (name) "!" } } }}; @@ -26,7 +26,7 @@ fn issue_21() { fn issue_21_2() { macro_rules! greet { ($name:expr) => {{ - html! { + html_static! { p { "Hello, " ($name) "!" } } }}; @@ -42,7 +42,7 @@ fn issue_21_2() { fn issue_23() { macro_rules! wrapper { ($($x:tt)*) => {{ - html! { $($x)* } + html_static! { $($x)* } }} } diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index fdd977e8..f61c15e5 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -120,8 +120,10 @@ pub fn expand_runtime_main( vars: HashMap<&'static str, PartialTemplate>, input: &str, ) -> Result { - let input: TokenStream = input.parse().unwrap_or_else(|_| panic!("{}", input)); - let res = ::std::panic::catch_unwind(|| parse_at_runtime(input.clone())); + let res = ::std::panic::catch_unwind(|| { + let input: TokenStream = input.parse().unwrap(); + parse_at_runtime(input.clone()) + }); if let Err(e) = res { if let Some(s) = e From bf62e4a1cc1cbc6b24f102c9a8012da6e42a9d6b Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 11:37:58 +0100 Subject: [PATCH 33/43] fall back to original source in more cases --- maud/tests/hotreload.rs | 16 ++++++++++++++++ maud_macros_impl/src/lib.rs | 22 +++++++++++----------- maud_macros_impl/src/runtime.rs | 26 +++++++++++++------------- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/maud/tests/hotreload.rs b/maud/tests/hotreload.rs index fd39e0a7..bcc66815 100644 --- a/maud/tests/hotreload.rs +++ b/maud/tests/hotreload.rs @@ -1,5 +1,6 @@ //! track regressions specific to the hotreload feature in maud use maud::{html, Markup}; +use maud_macros_impl::gather_html_macro_invocations; #[test] fn regression_match_inline_tag() { @@ -43,3 +44,18 @@ fn regression_basic() { assert_eq!(result.into_string(), "hello world"); } + +#[test] +fn test_gather_html_macro_invocations() { + let file = file!(); + let line = line!(); + + let _foo = maud::html! { + "Hello world" + }; + + assert_eq!( + gather_html_macro_invocations(file, line).unwrap().to_string(), + "\"Hello world\"" + ); +} diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index f61c15e5..9fd6be8a 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -1,3 +1,5 @@ +//! Helper crate for `maud` and `maud_macros`. Nothing in this crate is semver-compliant, and is +//! not meant for direct consumption. #![doc(html_root_url = "https://docs.rs/maud_macros_impl/0.25.0")] // TokenStream values are reference counted, and the mental overhead of tracking // lifetimes outweighs the marginal gains from explicit borrowing @@ -74,7 +76,7 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream { __maud_line_info, ); - let __maud_input: ::maud::macro_private::String = match __maud_input { + let __maud_input = match __maud_input { Ok(x) => x, Err(e) => { if ::maud::macro_private::env_var("MAUD_SOURCE_NO_FALLBACK").as_deref() == Ok("1") { @@ -82,7 +84,7 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream { } // fall back to original, unedited input when finding file info fails - ::maud::macro_private::String::from(#original_input) + #original_input.parse().unwrap() } }; @@ -103,8 +105,9 @@ fn expand_runtime_from_parsed(markups: Vec) -> TokenStream { #stmts - let f : ::maud::macro_private::PartialTemplate = ::maud::macro_private::Box::new(move |sources| { - let input = &sources[0]; + let f : ::maud::macro_private::PartialTemplate = ::maud::macro_private::Box::new(move |mut sources| { + assert!(sources.len() == 1); + let input = sources.pop().unwrap(); ::maud::macro_private::expand_runtime_main( #vars_ident, input, @@ -118,12 +121,9 @@ fn expand_runtime_from_parsed(markups: Vec) -> TokenStream { #[cfg(feature = "hotreload")] pub fn expand_runtime_main( vars: HashMap<&'static str, PartialTemplate>, - input: &str, + input: TokenStream, ) -> Result { - let res = ::std::panic::catch_unwind(|| { - let input: TokenStream = input.parse().unwrap(); - parse_at_runtime(input.clone()) - }); + let res = ::std::panic::catch_unwind(|| parse_at_runtime(input.clone())); if let Err(e) = res { if let Some(s) = e @@ -151,7 +151,7 @@ pub fn expand_runtime_main( pub fn gather_html_macro_invocations( file_path: &str, start_line: u32, -) -> Result { +) -> Result { let mut errors = String::new(); let mut file = None; @@ -231,7 +231,7 @@ pub fn gather_html_macro_invocations( } if !output.trim().is_empty() { - Ok(output) + output.parse().map_err(|e| format!("failed to parse output: {}", e)) } else { Err("output is empty".to_string()) } diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index a3d864ba..10d232cb 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -75,9 +75,9 @@ impl RuntimeGenerator { let mut sources = Vec::new(); for (i, MatchArm { head, body }) in arms.into_iter().enumerate() { if let Some(ref template_source) = body.raw_body { - sources.push(template_source.to_string()); + sources.push(template_source.clone()); } else { - sources.push("TODO MATCH".to_owned()); + sources.push(quote!( "TODO MATCH" )); } tt.extend(head.clone()); let partial = self.get_block(body); @@ -96,9 +96,9 @@ impl RuntimeGenerator { fn block(&self, block: Block, build: &mut RuntimeBuilder) { let source = if let Some(ref template_source) = block.raw_body { - template_source.to_string() + template_source.clone() } else { - "TODO BLOCK".to_owned() + quote!( "TODO BLOCK" ) }; build.push_lazy_format_arg(self.get_block(block), vec![source], "block"); @@ -126,9 +126,9 @@ impl RuntimeGenerator { if let Some(ref template_source) = body.raw_body { varname.push_str(&normalize_source_for_hashing(head.to_string())); varname.push('\n'); - sources.push(template_source.to_string()); + sources.push(template_source.clone()); } else { - sources.push("TODO SPECIAL".to_owned()); + sources.push(quote!( "TODO SPECIAL" )); } let block = self.get_block(body); @@ -157,7 +157,7 @@ impl RuntimeGenerator { } fn splice(&self, expr: TokenStream, build: &mut RuntimeBuilder) { - build.push_format_arg(expr, vec!["TODO SPLICE".to_owned()], "splice"); + build.push_format_arg(expr, vec![quote!( "TODO SPLICE" )], "splice"); } fn element( @@ -214,7 +214,7 @@ impl RuntimeGenerator { ::maud::PreEscaped("".to_owned()) } }, - vec!["TODO ATTR TYPE OPTIONAL".to_owned()], + vec![quote!( "TODO ATTR TYPE OPTIONAL" )], "optional_attr", ); } @@ -239,7 +239,7 @@ impl RuntimeGenerator { ::maud::PreEscaped("".to_owned()) } }, - vec!["TODO ATTR TYPE EMPTY".to_owned()], + vec![quote!( "TODO ATTR TYPE EMPTY" )], "empty_attr", ); } @@ -277,7 +277,7 @@ impl RuntimeBuilder { self.push_str(&s); } - fn push_format_arg(&mut self, expr: TokenStream, template_sources: Vec, named_variable: &str) { + fn push_format_arg(&mut self, expr: TokenStream, template_sources: Vec, named_variable: &str) { self.push_lazy_format_arg( quote! {{ extern crate maud; @@ -290,7 +290,7 @@ impl RuntimeBuilder { ); } - fn push_lazy_format_arg(&mut self, expr: TokenStream, template_sources: Vec, named_variable: &str) { + fn push_lazy_format_arg(&mut self, expr: TokenStream, template_sources: Vec, named_variable: &str) { let variable_name = format!("{}_{}", self.arg_track, named_variable); if let Some(ref vars) = self.vars_ident { @@ -324,7 +324,7 @@ pub enum Command { String(String), Variable { name: String, - template_sources: Vec, + template_sources: Vec, }, } @@ -355,7 +355,7 @@ impl Interpreter { } // partial templates are generated code that take their own sourcecode for live reloading. -pub type PartialTemplate = Box) -> Result>; +pub type PartialTemplate = Box) -> Result>; // we add hashes of source code to our variable names to prevent the chances of mis-rendering // something, such as when a user swaps blocks around in the template From f5b95df15fa0d5ed930b8b6c5929386bfb62a11c Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 11:38:45 +0100 Subject: [PATCH 34/43] Revert "fix testsuite" This reverts commit 362f6bd91d0af12d90970ed53381a032d59e27c1. --- maud/src/lib.rs | 3 +-- maud/tests/misc.rs | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 1ecf85ee..03960bd6 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -199,8 +199,7 @@ impl_render_with_itoa! { /// # Example /// /// ```rust -/// # use maud::html_static as html; -/// // use maud:html; +/// use maud::html; /// use std::net::Ipv4Addr; /// /// let ip_address = Ipv4Addr::new(127, 0, 0, 1); diff --git a/maud/tests/misc.rs b/maud/tests/misc.rs index 527d2633..2a573013 100644 --- a/maud/tests/misc.rs +++ b/maud/tests/misc.rs @@ -1,4 +1,4 @@ -use maud::{html, html_static}; +use maud::html; #[test] fn issue_13() { @@ -13,7 +13,7 @@ fn issue_21() { macro_rules! greet { () => {{ let name = "Pinkie Pie"; - html_static! { + html! { p { "Hello, " (name) "!" } } }}; @@ -26,7 +26,7 @@ fn issue_21() { fn issue_21_2() { macro_rules! greet { ($name:expr) => {{ - html_static! { + html! { p { "Hello, " ($name) "!" } } }}; @@ -42,7 +42,7 @@ fn issue_21_2() { fn issue_23() { macro_rules! wrapper { ($($x:tt)*) => {{ - html_static! { $($x)* } + html! { $($x)* } }} } From 999bb714a4c8fc380b263a4641fe86953640c33b Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 12:18:39 +0100 Subject: [PATCH 35/43] fix tests again --- maud_macros_impl/src/lib.rs | 53 ++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 9fd6be8a..ea382541 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -189,10 +189,26 @@ pub fn gather_html_macro_invocations( .map(|line| line.unwrap()); let mut rest_of_line = String::new(); + let mut braces_diff = 0; + + fn track_braces(c: char) -> i32 { + match c { + '[' | '{' | '(' => 1, + ']' | '}' | ')' => -1, + _ => 0 + } + } // scan for beginning of the macro. start_line may point to it directly, but we want to // handle code flowing slightly downward. for line in &mut lines_iter { + for c in line.chars() { + braces_diff += track_braces(c); + if braces_diff < 0 { + return Err("too many closing braces".to_owned()); + } + } + if let Some((_, after)) = line.split_once("html!") { let after = if let Some((_, after2)) = after.split_once(&['[', '{', '(']) { after2 @@ -205,34 +221,29 @@ pub fn gather_html_macro_invocations( } } - let mut braces_diff = 0; + braces_diff = 0; 'linewise: for line in Some(rest_of_line).into_iter().chain(lines_iter) { for c in line.chars() { - match c { - '[' | '{' | '(' => { - braces_diff += 1; - output.push(c); - } - ']' | '}' | ')' => { - braces_diff -= 1; - - if braces_diff == -1 { - break 'linewise; - } - - output.push(c); - } - c => output.push(c), + braces_diff += track_braces(c); + if braces_diff == -1 { + break 'linewise; } + output.push(c); } - output.push('\n'); } - if !output.trim().is_empty() { - output.parse().map_err(|e| format!("failed to parse output: {}", e)) - } else { - Err("output is empty".to_string()) + let output = output.trim(); + + if output.is_empty() { + return Err("output is empty".to_string()); + } + + if output.starts_with("///") { + // line/file information in doctests is 100% wrong and will lead to catastrophic results. + return Err("cannot handle livereload in doctests".to_string()); } + + output.parse().map_err(|e| format!("failed to parse output: {}", e)) } From 8bab5c5ffa531fb95f450ab3d3e17499e9dbe875 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 12:18:48 +0100 Subject: [PATCH 36/43] fmt --- maud/tests/hotreload.rs | 6 ++- maud_macros_impl/src/lib.rs | 6 ++- maud_macros_impl/src/runtime.rs | 70 ++++++++++++++++++++------------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/maud/tests/hotreload.rs b/maud/tests/hotreload.rs index bcc66815..23ed6ecd 100644 --- a/maud/tests/hotreload.rs +++ b/maud/tests/hotreload.rs @@ -55,7 +55,9 @@ fn test_gather_html_macro_invocations() { }; assert_eq!( - gather_html_macro_invocations(file, line).unwrap().to_string(), + gather_html_macro_invocations(file, line) + .unwrap() + .to_string(), "\"Hello world\"" - ); + ); } diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index ea382541..9fab71a2 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -195,7 +195,7 @@ pub fn gather_html_macro_invocations( match c { '[' | '{' | '(' => 1, ']' | '}' | ')' => -1, - _ => 0 + _ => 0, } } @@ -245,5 +245,7 @@ pub fn gather_html_macro_invocations( return Err("cannot handle livereload in doctests".to_string()); } - output.parse().map_err(|e| format!("failed to parse output: {}", e)) + output + .parse() + .map_err(|e| format!("failed to parse output: {}", e)) } diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index 10d232cb..656af1ec 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -77,7 +77,7 @@ impl RuntimeGenerator { if let Some(ref template_source) = body.raw_body { sources.push(template_source.clone()); } else { - sources.push(quote!( "TODO MATCH" )); + sources.push(quote!("TODO MATCH")); } tt.extend(head.clone()); let partial = self.get_block(body); @@ -89,7 +89,11 @@ impl RuntimeGenerator { let mut body = TokenTree::Group(Group::new(Delimiter::Brace, tt)); body.set_span(arms_span.collapse()); - build.push_lazy_format_arg(quote!(#head #body), sources, &format!("match_expr: {}", head)); + build.push_lazy_format_arg( + quote!(#head #body), + sources, + &format!("match_expr: {}", head), + ); } } } @@ -98,24 +102,24 @@ impl RuntimeGenerator { let source = if let Some(ref template_source) = block.raw_body { template_source.clone() } else { - quote!( "TODO BLOCK" ) + quote!("TODO BLOCK") }; build.push_lazy_format_arg(self.get_block(block), vec![source], "block"); } fn get_block(&self, block: Block) -> TokenStream { - if block.raw_body.is_some() { - expand_runtime_from_parsed(block.markups) - } else { - // necessary to avoid bogus sources - let static_result = expand_from_parsed(block.markups, 0); - quote! {{ - let __maud_static_result = (#static_result); - let partial: ::maud::macro_private::PartialTemplate = Box::new(|_| Ok(__maud_static_result.into_string())); - partial - }} - } + if block.raw_body.is_some() { + expand_runtime_from_parsed(block.markups) + } else { + // necessary to avoid bogus sources + let static_result = expand_from_parsed(block.markups, 0); + quote! {{ + let __maud_static_result = (#static_result); + let partial: ::maud::macro_private::PartialTemplate = Box::new(|_| Ok(__maud_static_result.into_string())); + partial + }} + } } fn special(&self, segments: Vec, build: &mut RuntimeBuilder) { @@ -128,7 +132,7 @@ impl RuntimeGenerator { varname.push('\n'); sources.push(template_source.clone()); } else { - sources.push(quote!( "TODO SPECIAL" )); + sources.push(quote!("TODO SPECIAL")); } let block = self.get_block(body); @@ -157,7 +161,7 @@ impl RuntimeGenerator { } fn splice(&self, expr: TokenStream, build: &mut RuntimeBuilder) { - build.push_format_arg(expr, vec![quote!( "TODO SPLICE" )], "splice"); + build.push_format_arg(expr, vec![quote!("TODO SPLICE")], "splice"); } fn element( @@ -214,7 +218,7 @@ impl RuntimeGenerator { ::maud::PreEscaped("".to_owned()) } }, - vec![quote!( "TODO ATTR TYPE OPTIONAL" )], + vec![quote!("TODO ATTR TYPE OPTIONAL")], "optional_attr", ); } @@ -239,7 +243,7 @@ impl RuntimeGenerator { ::maud::PreEscaped("".to_owned()) } }, - vec![quote!( "TODO ATTR TYPE EMPTY" )], + vec![quote!("TODO ATTR TYPE EMPTY")], "empty_attr", ); } @@ -277,7 +281,12 @@ impl RuntimeBuilder { self.push_str(&s); } - fn push_format_arg(&mut self, expr: TokenStream, template_sources: Vec, named_variable: &str) { + fn push_format_arg( + &mut self, + expr: TokenStream, + template_sources: Vec, + named_variable: &str, + ) { self.push_lazy_format_arg( quote! {{ extern crate maud; @@ -286,11 +295,16 @@ impl RuntimeBuilder { ::maud::macro_private::Box::new(move |_| Ok(buf)) }}, template_sources, - named_variable + named_variable, ); } - fn push_lazy_format_arg(&mut self, expr: TokenStream, template_sources: Vec, named_variable: &str) { + fn push_lazy_format_arg( + &mut self, + expr: TokenStream, + template_sources: Vec, + named_variable: &str, + ) { let variable_name = format!("{}_{}", self.arg_track, named_variable); if let Some(ref vars) = self.vars_ident { @@ -342,9 +356,13 @@ impl Interpreter { name, template_sources, } => { - let s = variables - .remove(name.as_str()) - .ok_or_else(|| format!("unknown var: {:?}\nremaining variables: {:?}", name, variables.keys()))?; + let s = variables.remove(name.as_str()).ok_or_else(|| { + format!( + "unknown var: {:?}\nremaining variables: {:?}", + name, + variables.keys() + ) + })?; rv.push_str(&s(template_sources)?); } } @@ -360,9 +378,7 @@ pub type PartialTemplate = Box) -> Result String { - input.retain(|c| { - !c.is_ascii_whitespace() - }); + input.retain(|c| !c.is_ascii_whitespace()); input } From 0b5d700029a9ca2824d7eafeafd31f3b2e4c119e Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 12:37:16 +0100 Subject: [PATCH 37/43] remove invalid stub sourcecode --- maud_macros_impl/src/lib.rs | 4 ++-- maud_macros_impl/src/runtime.rs | 32 +++++++++++++++----------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 9fab71a2..5b3f79d8 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -88,7 +88,7 @@ pub fn expand_runtime(input: TokenStream) -> TokenStream { } }; - match #partial_template(::maud::macro_private::Vec::from([__maud_input.clone()])) { + match #partial_template(::maud::macro_private::Vec::from([Some(__maud_input.clone())])) { Ok(x) => ::maud::PreEscaped(x), Err(e) => ::maud::macro_private::render_runtime_error(&e), } @@ -107,7 +107,7 @@ fn expand_runtime_from_parsed(markups: Vec) -> TokenStream { let f : ::maud::macro_private::PartialTemplate = ::maud::macro_private::Box::new(move |mut sources| { assert!(sources.len() == 1); - let input = sources.pop().unwrap(); + let input = sources.pop().unwrap().unwrap(); ::maud::macro_private::expand_runtime_main( #vars_ident, input, diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index 656af1ec..62fa5848 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -75,9 +75,9 @@ impl RuntimeGenerator { let mut sources = Vec::new(); for (i, MatchArm { head, body }) in arms.into_iter().enumerate() { if let Some(ref template_source) = body.raw_body { - sources.push(template_source.clone()); + sources.push(Some(template_source.clone())); } else { - sources.push(quote!("TODO MATCH")); + sources.push(None); } tt.extend(head.clone()); let partial = self.get_block(body); @@ -99,12 +99,7 @@ impl RuntimeGenerator { } fn block(&self, block: Block, build: &mut RuntimeBuilder) { - let source = if let Some(ref template_source) = block.raw_body { - template_source.clone() - } else { - quote!("TODO BLOCK") - }; - + let source = block.raw_body.clone(); build.push_lazy_format_arg(self.get_block(block), vec![source], "block"); } @@ -130,9 +125,9 @@ impl RuntimeGenerator { if let Some(ref template_source) = body.raw_body { varname.push_str(&normalize_source_for_hashing(head.to_string())); varname.push('\n'); - sources.push(template_source.clone()); + sources.push(Some(template_source.clone())); } else { - sources.push(quote!("TODO SPECIAL")); + sources.push(None); } let block = self.get_block(body); @@ -161,7 +156,7 @@ impl RuntimeGenerator { } fn splice(&self, expr: TokenStream, build: &mut RuntimeBuilder) { - build.push_format_arg(expr, vec![quote!("TODO SPLICE")], "splice"); + build.push_format_arg(expr, vec![None], "splice"); } fn element( @@ -218,7 +213,7 @@ impl RuntimeGenerator { ::maud::PreEscaped("".to_owned()) } }, - vec![quote!("TODO ATTR TYPE OPTIONAL")], + vec![None], "optional_attr", ); } @@ -243,7 +238,7 @@ impl RuntimeGenerator { ::maud::PreEscaped("".to_owned()) } }, - vec![quote!("TODO ATTR TYPE EMPTY")], + vec![None], "empty_attr", ); } @@ -284,7 +279,7 @@ impl RuntimeBuilder { fn push_format_arg( &mut self, expr: TokenStream, - template_sources: Vec, + template_sources: TemplateSourceContext, named_variable: &str, ) { self.push_lazy_format_arg( @@ -302,7 +297,7 @@ impl RuntimeBuilder { fn push_lazy_format_arg( &mut self, expr: TokenStream, - template_sources: Vec, + template_sources: TemplateSourceContext, named_variable: &str, ) { let variable_name = format!("{}_{}", self.arg_track, named_variable); @@ -338,7 +333,7 @@ pub enum Command { String(String), Variable { name: String, - template_sources: Vec, + template_sources: TemplateSourceContext, }, } @@ -372,8 +367,11 @@ impl Interpreter { } } +pub type TemplateSource = TokenStream; +pub type TemplateSourceContext = Vec>; + // partial templates are generated code that take their own sourcecode for live reloading. -pub type PartialTemplate = Box) -> Result>; +pub type PartialTemplate = Box Result>; // we add hashes of source code to our variable names to prevent the chances of mis-rendering // something, such as when a user swaps blocks around in the template From 62c235694544a3be461029596ead11c2a0fb0a0d Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 13:06:19 +0100 Subject: [PATCH 38/43] update lockfile for docs --- docs/Cargo.lock | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/docs/Cargo.lock b/docs/Cargo.lock index 05172b2a..5766e7a9 100644 --- a/docs/Cargo.lock +++ b/docs/Cargo.lock @@ -132,7 +132,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.75", ] [[package]] @@ -143,7 +143,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.75", ] [[package]] @@ -173,7 +173,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.75", ] [[package]] @@ -183,7 +183,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.75", ] [[package]] @@ -292,16 +292,28 @@ version = "0.26.0" dependencies = [ "itoa", "maud_macros", + "maud_macros_impl", ] [[package]] name = "maud_macros" version = "0.26.0" dependencies = [ + "maud_macros_impl", "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.75", +] + +[[package]] +name = "maud_macros_impl" +version = "0.26.0" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -397,6 +409,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", + "syn 2.0.75", ] [[package]] @@ -487,7 +500,7 @@ checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.75", ] [[package]] @@ -524,6 +537,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.75" @@ -575,7 +599,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.75", ] [[package]] @@ -683,7 +707,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.75", "wasm-bindgen-shared", ] @@ -705,7 +729,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.75", "wasm-bindgen-backend", "wasm-bindgen-shared", ] From b0b4d7469c8b55a4d520314e9f138940e623df26 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 13:07:47 +0100 Subject: [PATCH 39/43] nightly fmt --- maud/src/lib.rs | 4 +--- maud_macros_impl/src/runtime.rs | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 03960bd6..6cc3a2ec 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -419,9 +419,7 @@ mod submillisecond_support { #[doc(hidden)] pub mod macro_private { use crate::{display, Render}; - pub use alloc::boxed::Box; - pub use alloc::string::String; - pub use alloc::vec::Vec; + pub use alloc::{boxed::Box, string::String, vec::Vec}; use core::fmt::Display; #[cfg(feature = "hotreload")] pub use std::collections::HashMap; diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index 62fa5848..eb80d99c 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; use proc_macro2::{Delimiter, Group, TokenStream, TokenTree}; use quote::quote; -use crate::expand; -use crate::generate::desugar_attrs; -use crate::{ast::*, escape, expand_from_parsed, expand_runtime_from_parsed}; +use crate::{ + ast::*, escape, expand, expand_from_parsed, expand_runtime_from_parsed, generate::desugar_attrs, +}; pub fn generate(vars_ident: Option, markups: Vec) -> TokenStream { let mut build = RuntimeBuilder::new(vars_ident.clone()); From cfa9a5b74b4e61f7df4d868455c7e81e2eb8fdac Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 13:10:01 +0100 Subject: [PATCH 40/43] clippy --- maud_macros_impl/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 5b3f79d8..7658d251 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -210,7 +210,7 @@ pub fn gather_html_macro_invocations( } if let Some((_, after)) = line.split_once("html!") { - let after = if let Some((_, after2)) = after.split_once(&['[', '{', '(']) { + let after = if let Some((_, after2)) = after.split_once(['[', '{', '(') { after2 } else { after From 308f34e81d3d25746ba3c6b7367b07c2c519b35e Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 13:41:47 +0100 Subject: [PATCH 41/43] fix syntax error --- maud_macros_impl/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 7658d251..8253fc0d 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -210,7 +210,7 @@ pub fn gather_html_macro_invocations( } if let Some((_, after)) = line.split_once("html!") { - let after = if let Some((_, after2)) = after.split_once(['[', '{', '(') { + let after = if let Some((_, after2)) = after.split_once(['[', '{', '(']) { after2 } else { after From 71745731ec249504fdefe7d0e9429b4248693a10 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 13:43:21 +0100 Subject: [PATCH 42/43] more clippy --- maud_macros/src/lib.rs | 4 +--- maud_macros_impl/src/lib.rs | 4 ++-- maud_macros_impl/src/runtime.rs | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs index 04c998c8..12eb3c4d 100644 --- a/maud_macros/src/lib.rs +++ b/maud_macros/src/lib.rs @@ -17,7 +17,5 @@ pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream { #[proc_macro] #[proc_macro_error] pub fn html_hotreload(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let x = maud_macros_impl::expand_runtime(input.into()).into(); - // panic!("{}", x); - x + maud_macros_impl::expand_runtime(input.into()).into() } diff --git a/maud_macros_impl/src/lib.rs b/maud_macros_impl/src/lib.rs index 8253fc0d..301f6ce6 100644 --- a/maud_macros_impl/src/lib.rs +++ b/maud_macros_impl/src/lib.rs @@ -136,9 +136,9 @@ pub fn expand_runtime_main( .map(::std::ops::Deref::deref) }) { - return Err(format!("{}, source: {}", s, input)); + Err(format!("{}, source: {}", s, input)) } else { - return Err("unknown panic".to_owned()); + Err("unknown panic".to_owned()) } } else { let markups = res.unwrap(); diff --git a/maud_macros_impl/src/runtime.rs b/maud_macros_impl/src/runtime.rs index eb80d99c..545d7499 100644 --- a/maud_macros_impl/src/runtime.rs +++ b/maud_macros_impl/src/runtime.rs @@ -272,7 +272,7 @@ impl RuntimeBuilder { fn push_escaped(&mut self, string: &str) { let mut s = String::new(); - escape::escape_to_string(&string, &mut s); + escape::escape_to_string(string, &mut s); self.push_str(&s); } @@ -313,7 +313,7 @@ impl RuntimeBuilder { template_sources, }); - self.arg_track = self.arg_track + 1; + self.arg_track += 1; } fn interpreter(self) -> Interpreter { From f585e29b318150c6df3b595b8f81d833b210f4ca Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 17 Nov 2024 13:46:07 +0100 Subject: [PATCH 43/43] another clippy moment --- maud/tests/hotreload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maud/tests/hotreload.rs b/maud/tests/hotreload.rs index 23ed6ecd..6f48b31d 100644 --- a/maud/tests/hotreload.rs +++ b/maud/tests/hotreload.rs @@ -8,7 +8,7 @@ fn regression_match_inline_tag() { html! { div id="main" { @match x { - Some(x) if x == 42 => div.green { + Some(42) => div.green { "yes! fourty! two!" }, Some(_) => div.yellow {