diff --git a/maud_macros/src/ast.rs b/maud_macros/src/ast.rs index 15bcf09..c437a1d 100644 --- a/maud_macros/src/ast.rs +++ b/maud_macros/src/ast.rs @@ -1,18 +1,19 @@ -use proc_macro2::{Span, TokenStream, TokenTree}; +use proc_macro2::{TokenStream, TokenTree}; +use proc_macro_error::SpanRange; #[derive(Debug)] pub enum Markup { Block(Block), Literal { content: String, - span: Span, + span: SpanRange, }, Symbol { symbol: TokenStream, }, Splice { expr: TokenStream, - outer_span: Span, + outer_span: SpanRange, }, Element { name: TokenStream, @@ -20,22 +21,22 @@ pub enum Markup { body: ElementBody, }, Let { - at_span: Span, + at_span: SpanRange, tokens: TokenStream, }, Special { segments: Vec<Special>, }, Match { - at_span: Span, + at_span: SpanRange, head: TokenStream, arms: Vec<MatchArm>, - arms_span: Span, + arms_span: SpanRange, }, } impl Markup { - pub fn span(&self) -> Span { + pub fn span(&self) -> SpanRange { match *self { Markup::Block(ref block) => block.span(), Markup::Literal { span, .. } => span, @@ -43,16 +44,16 @@ impl Markup { Markup::Splice { outer_span, .. } => outer_span, Markup::Element { ref name, ref body, .. } => { let name_span = span_tokens(name.clone()); - name_span.join(body.span()).unwrap_or(name_span) + name_span.join_range(body.span()) }, Markup::Let { at_span, ref tokens } => { - at_span.join(span_tokens(tokens.clone())).unwrap_or(at_span) + at_span.join_range(span_tokens(tokens.clone())) }, Markup::Special { ref segments } => { - join_spans(segments.iter().map(Special::span)) + join_ranges(segments.iter().map(Special::span)) }, Markup::Match { at_span, arms_span, .. } => { - at_span.join(arms_span).unwrap_or(at_span) + at_span.join_range(arms_span) }, } } @@ -63,12 +64,12 @@ pub type Attrs = Vec<Attr>; #[derive(Debug)] pub enum Attr { Class { - dot_span: Span, + dot_span: SpanRange, name: Markup, toggler: Option<Toggler>, }, Id { - hash_span: Span, + hash_span: SpanRange, name: Markup, }, Attribute { @@ -77,20 +78,20 @@ pub enum Attr { } impl Attr { - pub fn span(&self) -> Span { + pub fn span(&self) -> SpanRange { match *self { Attr::Class { dot_span, ref name, ref toggler } => { let name_span = name.span(); - let dot_name_span = dot_span.join(name_span).unwrap_or(dot_span); + let dot_name_span = dot_span.join_range(name_span); if let Some(toggler) = toggler { - dot_name_span.join(toggler.cond_span).unwrap_or(name_span) + dot_name_span.join_range(toggler.cond_span) } else { dot_name_span } }, Attr::Id { hash_span, ref name } => { let name_span = name.span(); - hash_span.join(name_span).unwrap_or(hash_span) + hash_span.join_range(name_span) }, Attr::Attribute { ref attribute } => attribute.span(), } @@ -99,12 +100,12 @@ impl Attr { #[derive(Debug)] pub enum ElementBody { - Void { semi_span: Span }, + Void { semi_span: SpanRange }, Block { block: Block }, } impl ElementBody { - pub fn span(&self) -> Span { + pub fn span(&self) -> SpanRange { match *self { ElementBody::Void { semi_span } => semi_span, ElementBody::Block { ref block } => block.span(), @@ -115,26 +116,26 @@ impl ElementBody { #[derive(Debug)] pub struct Block { pub markups: Vec<Markup>, - pub outer_span: Span, + pub outer_span: SpanRange, } impl Block { - pub fn span(&self) -> Span { + pub fn span(&self) -> SpanRange { self.outer_span } } #[derive(Debug)] pub struct Special { - pub at_span: Span, + pub at_span: SpanRange, pub head: TokenStream, pub body: Block, } impl Special { - pub fn span(&self) -> Span { + pub fn span(&self) -> SpanRange { let body_span = self.body.span(); - self.at_span.join(body_span).unwrap_or(self.at_span) + self.at_span.join_range(body_span) } } @@ -145,10 +146,10 @@ pub struct Attribute { } impl Attribute { - fn span(&self) -> Span { + fn span(&self) -> SpanRange { let name_span = span_tokens(self.name.clone()); if let Some(attr_type_span) = self.attr_type.span() { - name_span.join(attr_type_span).unwrap_or(name_span) + name_span.join_range(attr_type_span) } else { name_span } @@ -166,7 +167,7 @@ pub enum AttrType { } impl AttrType { - fn span(&self) -> Option<Span> { + fn span(&self) -> Option<SpanRange> { match *self { AttrType::Normal { ref value } => Some(value.span()), AttrType::Empty { ref toggler } => toggler.as_ref().map(Toggler::span), @@ -177,11 +178,11 @@ impl AttrType { #[derive(Debug)] pub struct Toggler { pub cond: TokenStream, - pub cond_span: Span, + pub cond_span: SpanRange, } impl Toggler { - fn span(&self) -> Span { + fn span(&self) -> SpanRange { self.cond_span } } @@ -192,20 +193,16 @@ pub struct MatchArm { pub body: Block, } -pub fn span_tokens<I: IntoIterator<Item=TokenTree>>(tokens: I) -> Span { - join_spans(tokens.into_iter().map(|token| token.span())) +pub fn span_tokens<I: IntoIterator<Item=TokenTree>>(tokens: I) -> SpanRange { + join_ranges(tokens.into_iter().map(|s| SpanRange::single_span(s.span()))) } -pub fn join_spans<I: IntoIterator<Item=Span>>(spans: I) -> Span { - let mut iter = spans.into_iter(); - let mut span = match iter.next() { +pub fn join_ranges<I: IntoIterator<Item=SpanRange>>(ranges: I) -> SpanRange { + let mut iter = ranges.into_iter(); + let first = match iter.next() { Some(span) => span, - None => return Span::call_site(), + None => return SpanRange::call_site(), }; - for new_span in iter { - if let Some(joined) = span.join(new_span) { - span = joined; - } - } - span + let last = iter.last().unwrap_or(first); + first.join_range(last) } diff --git a/maud_macros/src/generate.rs b/maud_macros/src/generate.rs index 811dc6a..24ad277 100644 --- a/maud_macros/src/generate.rs +++ b/maud_macros/src/generate.rs @@ -9,6 +9,7 @@ use proc_macro2::{ TokenTree, }; use quote::quote; +use proc_macro_error::SpanRange; use crate::ast::*; @@ -63,7 +64,7 @@ impl Generator { .map(|arm| self.match_arm(arm)) .collect(); let mut body = TokenTree::Group(Group::new(Delimiter::Brace, body)); - body.set_span(arms_span.into()); + body.set_span(arms_span.collapse()); let head: TokenStream = head.into(); quote!(#head #body) }); @@ -75,7 +76,7 @@ impl Generator { let mut build = self.builder(); self.markups(markups, &mut build); let mut block = TokenTree::Group(Group::new(Delimiter::Brace, build.finish())); - block.set_span(outer_span.into()); + block.set_span(outer_span.collapse()); TokenStream::from(block) } @@ -203,7 +204,7 @@ fn desugar_classes_or_ids( }; let head = desugar_toggler(toggler); markups.push(Markup::Special { - segments: vec![Special { at_span: Span::call_site(), head: head.into(), body }], + segments: vec![Special { at_span: SpanRange::call_site(), head: head.into(), body }], }); } Some(Attribute { @@ -211,7 +212,7 @@ fn desugar_classes_or_ids( attr_type: AttrType::Normal { value: Markup::Block(Block { markups, - outer_span: Span::call_site(), + outer_span: SpanRange::call_site(), }), }, }) @@ -236,7 +237,7 @@ fn desugar_toggler(Toggler { cond, cond_span }: Toggler) -> TokenStream { // wrap it in parentheses to avoid parse errors if cond.clone().into_iter().any(is_braced_block) { let mut wrapped_cond = TokenTree::Group(Group::new(Delimiter::Parenthesis, cond)); - wrapped_cond.set_span(cond_span.into()); + wrapped_cond.set_span(cond_span.collapse()); cond = TokenStream::from(wrapped_cond); } quote!(if #cond) diff --git a/maud_macros/src/parse.rs b/maud_macros/src/parse.rs index f8d1a3d..67849ba 100644 --- a/maud_macros/src/parse.rs +++ b/maud_macros/src/parse.rs @@ -7,7 +7,7 @@ use proc_macro2::{ TokenStream, TokenTree, }; -use proc_macro_error::{abort, abort_call_site}; +use proc_macro_error::{abort, abort_call_site, SpanRange}; use std::collections::HashMap; use std::mem; @@ -128,13 +128,11 @@ impl Parser { "for" => self.for_expr(at_span, keyword), "match" => self.match_expr(at_span, keyword), "let" => { - let ident_span = ident.span(); - let span = at_span.join(ident_span).unwrap_or(ident_span); + let span = SpanRange { first: at_span, last: ident.span() }; abort!(span, "`@let` only works inside a block"); }, other => { - let ident_span = ident.span(); - let span = at_span.join(ident_span).unwrap_or(ident_span); + let span = SpanRange { first: at_span, last: ident.span() }; abort!(span, "unknown keyword `@{}`", other); } } @@ -146,7 +144,7 @@ impl Parser { }, // Element TokenTree::Ident(ident) => { - let ident_string = ident.to_string(); + let _ident_string = ident.to_string(); // Is this a keyword that's missing a '@'? // TODO: warning or error? // match ident_string.as_str() { @@ -171,12 +169,12 @@ impl Parser { // Splice TokenTree::Group(ref group) if group.delimiter() == Delimiter::Parenthesis => { self.advance(); - ast::Markup::Splice { expr: group.stream(), outer_span: group.span() } + ast::Markup::Splice { expr: group.stream(), outer_span: SpanRange::single_span(group.span()) } } // Block TokenTree::Group(ref group) if group.delimiter() == Delimiter::Brace => { self.advance(); - ast::Markup::Block(self.block(group.stream(), group.span())) + ast::Markup::Block(self.block(group.stream(), SpanRange::single_span(group.span()))) }, // ??? token => { @@ -193,7 +191,7 @@ impl Parser { .unwrap_or_else(|_| abort!(lit, "expected string")); ast::Markup::Literal { content, - span: lit.span(), + span: SpanRange::single_span(lit.span()), } } @@ -210,18 +208,18 @@ impl Parser { let body = loop { match self.next() { Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => { - break self.block(block.stream(), block.span()); + break self.block(block.stream(), SpanRange::single_span(block.span())); }, Some(token) => head.push(token), None => { - let head_span = ast::span_tokens(head); - let span = at_span.join(head_span).unwrap_or(head_span); + let mut span = ast::span_tokens(head); + span.first = at_span; abort!(span, "expected body for this `@if`"); }, } }; segments.push(ast::Special { - at_span, + at_span: SpanRange::single_span(at_span), head: head.into_iter().collect(), body, }); @@ -251,16 +249,15 @@ impl Parser { _ => { match self.next() { Some(TokenTree::Group(ref group)) if group.delimiter() == Delimiter::Brace => { - let body = self.block(group.stream(), group.span()); + let body = self.block(group.stream(), SpanRange::single_span(group.span())); segments.push(ast::Special { - at_span, + at_span: SpanRange::single_span(at_span), head: vec![else_keyword].into_iter().collect(), body, }); }, _ => { - let else_span = else_keyword.span(); - let span = at_span.join(else_span).unwrap_or(else_span); + let span = SpanRange { first: at_span, last: else_keyword.span() }; abort!(span, "expected body for this `@else`"); }, } @@ -281,17 +278,17 @@ impl Parser { let body = loop { match self.next() { Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => { - break self.block(block.stream(), block.span()); + break self.block(block.stream(), SpanRange::single_span(block.span())); }, Some(token) => head.push(token), None => { - let span = at_span.join(keyword_span).unwrap_or(keyword_span); + let span = SpanRange { first: at_span, last: keyword_span }; abort!(span, "expected body for this `@while`"); }, } }; ast::Markup::Special { - segments: vec![ast::Special { at_span, head: head.into_iter().collect(), body }], + segments: vec![ast::Special { at_span: SpanRange::single_span(at_span), head: head.into_iter().collect(), body }], } } @@ -309,7 +306,7 @@ impl Parser { }, Some(token) => head.push(token), None => { - let span = at_span.join(keyword_span).unwrap_or(keyword_span); + let span = SpanRange { first: at_span, last: keyword_span }; abort!(span, "missing `in` in `@for` loop"); }, } @@ -317,17 +314,17 @@ impl Parser { let body = loop { match self.next() { Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => { - break self.block(block.stream(), block.span()); + break self.block(block.stream(), SpanRange::single_span(block.span())); }, Some(token) => head.push(token), None => { - let span = at_span.join(keyword_span).unwrap_or(keyword_span); + let span = SpanRange { first: at_span, last: keyword_span }; abort!(span, "expected body for this `@for`"); }, } }; ast::Markup::Special { - segments: vec![ast::Special { at_span, head: head.into_iter().collect(), body }], + segments: vec![ast::Special { at_span: SpanRange::single_span(at_span), head: head.into_iter().collect(), body }], } } @@ -340,17 +337,17 @@ impl Parser { let (arms, arms_span) = loop { match self.next() { Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => { - let span = body.span(); + let span = SpanRange::single_span(body.span()); break (self.with_input(body.stream()).match_arms(), span); }, Some(token) => head.push(token), None => { - let span = at_span.join(keyword_span).unwrap_or(keyword_span); + let span = SpanRange { first: at_span, last: keyword_span }; abort!(span, "expected body for this `@match`"); }, } }; - ast::Markup::Match { at_span, head: head.into_iter().collect(), arms, arms_span } + ast::Markup::Match { at_span: SpanRange::single_span(at_span), head: head.into_iter().collect(), arms, arms_span } } fn match_arms(&mut self) -> Vec<ast::MatchArm> { @@ -389,7 +386,7 @@ impl Parser { let body = match self.next() { // $pat => { $stmts } Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => { - let body = self.block(body.stream(), body.span()); + let body = self.block(body.stream(), SpanRange::single_span(body.span())); // Trailing commas are optional if the match arm is a braced block if let Some(TokenTree::Punct(ref punct)) = self.peek() { if punct.as_char() == ',' { @@ -400,15 +397,13 @@ impl Parser { }, // $pat => $expr Some(first_token) => { - let mut span = first_token.span(); + let mut span = SpanRange::single_span(first_token.span()); let mut body = vec![first_token]; loop { match self.next() { Some(TokenTree::Punct(ref punct)) if punct.as_char() == ',' => break, Some(token) => { - if let Some(bigger_span) = span.join(token.span()) { - span = bigger_span; - } + span.last = token.span(); body.push(token); }, None => break, @@ -441,8 +436,8 @@ impl Parser { } }, None => { - let tokens_span = ast::span_tokens(tokens); - let span = at_span.join(tokens_span).unwrap_or(tokens_span); + let mut span = ast::span_tokens(tokens); + span.first = at_span; abort!(span, "unexpected end of `@let` expression"); } } @@ -459,8 +454,8 @@ impl Parser { } }, None => { - let tokens_span = ast::span_tokens(tokens); - let span = at_span.join(tokens_span).unwrap_or(tokens_span); + let mut span = ast::span_tokens(tokens); + span.first = at_span; abort!( span, "unexpected end of `@let` expression"; @@ -469,7 +464,7 @@ impl Parser { }, } } - ast::Markup::Let { at_span, tokens: tokens.into_iter().collect() } + ast::Markup::Let { at_span: SpanRange::single_span(at_span), tokens: tokens.into_iter().collect() } } /// Parses an element node. @@ -486,7 +481,7 @@ impl Parser { if punct.as_char() == ';' || punct.as_char() == '/' => { // Void element self.advance(); - ast::ElementBody::Void { semi_span: punct.span() } + ast::ElementBody::Void { semi_span: SpanRange::single_span(punct.span()) } }, _ => { match self.markup() { @@ -546,20 +541,20 @@ impl Parser { self.commit(attempt); let name = self.class_or_id_name(); let toggler = self.attr_toggler(); - attrs.push(ast::Attr::Class { dot_span: punct.span(), name, toggler }); + attrs.push(ast::Attr::Class { dot_span: SpanRange::single_span(punct.span()), name, toggler }); }, // ID shorthand (None, Some(TokenTree::Punct(ref punct))) if punct.as_char() == '#' => { self.commit(attempt); let name = self.class_or_id_name(); - attrs.push(ast::Attr::Id { hash_span: punct.span(), name }); + attrs.push(ast::Attr::Id { hash_span: SpanRange::single_span(punct.span()), name }); }, // If it's not a valid attribute, backtrack and bail out _ => break, } } - let mut attr_map: HashMap<String, Vec<Span>> = HashMap::new(); + let mut attr_map: HashMap<String, Vec<SpanRange>> = HashMap::new(); let mut has_class = false; for attr in &attrs { let name = match attr { @@ -607,7 +602,7 @@ impl Parser { self.advance(); Some(ast::Toggler { cond: group.stream(), - cond_span: group.span(), + cond_span: SpanRange::single_span(group.span()), }) }, _ => None, @@ -657,7 +652,7 @@ impl Parser { } /// Parses the given token stream as a Maud expression. - fn block(&mut self, body: TokenStream, outer_span: Span) -> ast::Block { + fn block(&mut self, body: TokenStream, outer_span: SpanRange) -> ast::Block { let markups = self.with_input(body).markups(); ast::Block { markups, outer_span } }