From 72954cdad84fa0bb95e4c3ecec194a6696ad1acd Mon Sep 17 00:00:00 2001
From: ef3d0c3e <ef3d0c3e@pundalik.org>
Date: Wed, 23 Oct 2024 22:25:34 +0200
Subject: [PATCH] Reports refactor [1/2]

---
 src/elements/blockquote.rs  |  33 ++----
 src/elements/customstyle.rs | 125 +++++++-------------
 src/elements/elemstyle.rs   |  82 ++++++-------
 src/elements/layout.rs      |  69 ++++++-----
 src/elements/link.rs        | 104 ++++++++---------
 src/elements/list.rs        |  35 ++----
 src/elements/media.rs       | 176 ++++++++++++++--------------
 src/elements/raw.rs         | 135 ++++++++++------------
 src/elements/reference.rs   |  96 ++++++++--------
 src/elements/script.rs      | 112 +++++++++---------
 src/elements/section.rs     | 136 +++++++++++-----------
 src/elements/style.rs       |  46 ++++----
 src/elements/tex.rs         | 128 ++++++++++-----------
 src/elements/text.rs        |   4 +-
 src/elements/variable.rs    | 223 +++++++++++++++++-------------------
 src/parser/customstyle.rs   |  11 +-
 src/parser/parser.rs        |   8 +-
 src/parser/reports.rs       | 100 ++++++++++------
 src/parser/rule.rs          |   8 +-
 src/parser/state.rs         |  10 +-
 20 files changed, 757 insertions(+), 884 deletions(-)

diff --git a/src/elements/blockquote.rs b/src/elements/blockquote.rs
index fb5cb4c..364bfb3 100644
--- a/src/elements/blockquote.rs
+++ b/src/elements/blockquote.rs
@@ -4,9 +4,6 @@ use std::collections::HashMap;
 use std::ops::Range;
 use std::rc::Rc;
 
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use blockquote_style::AuthorPos::After;
 use blockquote_style::AuthorPos::Before;
 use blockquote_style::BlockquoteStyle;
@@ -36,6 +33,8 @@ use crate::parser::style::StyleHolder;
 use crate::parser::util::escape_text;
 use crate::parser::util::Property;
 use crate::parser::util::PropertyParser;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 
 #[derive(Debug)]
 pub struct Blockquote {
@@ -254,7 +253,7 @@ impl Rule for BlockquoteRule {
 		document: &'a (dyn Document<'a> + 'a),
 		cursor: Cursor,
 		_match_data: Box<dyn Any>,
-	) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
+	) -> (Cursor, Vec<Report>) {
 		let mut reports = vec![];
 
 		let content = cursor.source.content();
@@ -273,19 +272,8 @@ impl Rule for BlockquoteRule {
 			if let Some(properties) = captures.get(1) {
 				match self.parse_properties(properties) {
 					Err(err) => {
-						reports.push(
-							Report::build(
-								ReportKind::Warning,
-								cursor.source.clone(),
-								properties.start(),
-							)
-							.with_message("Invalid Blockquote Properties")
-							.with_label(
-								Label::new((cursor.source.clone(), properties.range()))
-									.with_message(err)
-									.with_color(state.parser.colors().warning),
-							)
-							.finish(),
+						report_err!(&mut reports, cursor.source.clone(), "Invalid Blockquote Properties".into(),
+								span(properties.range(), err)
 						);
 						return (end_cursor, reports);
 					}
@@ -342,15 +330,8 @@ impl Rule for BlockquoteRule {
 				} else if elem.downcast_ref::<Blockquote>().is_some() {
 					parsed_content.push(elem);
 				} else {
-					reports.push(
-						Report::build(ReportKind::Error, token.source(), token.range.start)
-							.with_message("Unable to Parse Blockquote Entry")
-							.with_label(
-								Label::new((token.source(), token.range.clone()))
-									.with_message("Blockquotes may only contain paragraphs and other blockquotes")
-									.with_color(state.parser.colors().error),
-							)
-							.finish(),
+					report_err!(&mut reports, token.source(), "Unable to Parse Blockquote Entry".into(),
+						span(token.range.clone(), "Blockquotes may only contain paragraphs and other blockquotes".into())
 					);
 					return (end_cursor, reports);
 				}
diff --git a/src/elements/customstyle.rs b/src/elements/customstyle.rs
index d010904..aefaf84 100644
--- a/src/elements/customstyle.rs
+++ b/src/elements/customstyle.rs
@@ -9,9 +9,6 @@ use std::rc::Rc;
 use std::sync::Arc;
 
 use ariadne::Fmt;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use mlua::Error::BadArgument;
 use mlua::Function;
 use mlua::Lua;
@@ -29,6 +26,8 @@ use crate::parser::source::Source;
 use crate::parser::source::Token;
 use crate::parser::state::RuleState;
 use crate::parser::state::Scope;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 
 use super::paragraph::Paragraph;
 
@@ -50,7 +49,7 @@ impl CustomStyle for LuaCustomStyle {
 		location: Token,
 		state: &ParserState,
 		document: &'a dyn Document<'a>,
-	) -> Vec<Report<(Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let kernel: Ref<'_, Kernel> =
 			Ref::map(state.shared.kernels.borrow(), |b| b.get("main").unwrap());
 		//let kernel = RefMut::map(parser_state.shared.kernels.borrow(), |ker| ker.get("main").unwrap());
@@ -64,19 +63,12 @@ impl CustomStyle for LuaCustomStyle {
 		kernel.run_with_context(ctx, |lua| {
 			let chunk = lua.load(self.start.as_str());
 			if let Err(err) = chunk.eval::<()>() {
-				reports.push(
-					Report::build(ReportKind::Error, location.source(), location.start())
-						.with_message("Lua execution failed")
-						.with_label(
-							Label::new((location.source(), location.range.clone()))
-								.with_message(err.to_string())
-								.with_color(state.parser.colors().error),
-						)
-						.with_note(format!(
+				report_err!(&mut reports, location.source(), "Lua execution failed".into(),
+						span(location.range.clone(), err.to_string()),
+						note(format!(
 							"When trying to start custom style {}",
 							self.name().fg(state.parser.colors().info)
 						))
-						.finish(),
 				);
 			}
 		});
@@ -89,7 +81,7 @@ impl CustomStyle for LuaCustomStyle {
 		location: Token,
 		state: &ParserState,
 		document: &'a dyn Document<'a>,
-	) -> Vec<Report<(Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let kernel: Ref<'_, Kernel> =
 			Ref::map(state.shared.kernels.borrow(), |b| b.get("main").unwrap());
 		let ctx = KernelContext {
@@ -102,19 +94,12 @@ impl CustomStyle for LuaCustomStyle {
 		kernel.run_with_context(ctx, |lua| {
 			let chunk = lua.load(self.end.as_str());
 			if let Err(err) = chunk.eval::<()>() {
-				reports.push(
-					Report::build(ReportKind::Error, location.source(), location.start())
-						.with_message("Lua execution failed")
-						.with_label(
-							Label::new((location.source(), location.range.clone()))
-								.with_message(err.to_string())
-								.with_color(state.parser.colors().error),
-						)
-						.with_note(format!(
+				report_err!(&mut reports, location.source(), "Lua execution failed".into(),
+					span(location.range.clone(), err.to_string()),
+					note(format!(
 							"When trying to end custom style {}",
 							self.name().fg(state.parser.colors().info)
-						))
-						.finish(),
+					))
 				);
 			}
 		});
@@ -130,11 +115,11 @@ struct CustomStyleState {
 impl RuleState for CustomStyleState {
 	fn scope(&self) -> Scope { Scope::PARAGRAPH }
 
-	fn on_remove<'a>(
+	fn on_remove(
 		&self,
 		state: &ParserState,
 		document: &dyn Document,
-	) -> Vec<Report<'a, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let mut reports = vec![];
 
 		self.toggled.iter().for_each(|(style, token)| {
@@ -150,26 +135,13 @@ impl RuleState for CustomStyleState {
 				})
 				.unwrap();
 
-			reports.push(
-				Report::build(ReportKind::Error, token.source(), token.start())
-					.with_message("Unterminated Custom Style")
-					.with_label(
-						Label::new((token.source(), token.range.clone()))
-							.with_order(1)
-							.with_message(format!(
+			report_err!(&mut reports, token.source(), "Unterminated Custom Style".into(),
+						span(token.range.clone(), format!(
 								"Style {} starts here",
 								style.fg(state.parser.colors().info)
-							))
-							.with_color(state.parser.colors().error),
-					)
-					.with_label(
-						Label::new(paragraph_end)
-							.with_order(1)
-							.with_message("Paragraph ends here".to_string())
-							.with_color(state.parser.colors().error),
-					)
-					.with_note("Styles cannot span multiple documents (i.e @import)")
-					.finish(),
+							)),
+						span(paragraph_end.1, "Paragraph ends here".into()),
+						note("Styles cannot span multiple documents (i.e @import)".into())
 			);
 		});
 
@@ -248,7 +220,7 @@ impl Rule for CustomStyleRule {
 		document: &'a dyn Document<'a>,
 		cursor: Cursor,
 		match_data: Box<dyn Any>,
-	) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
+	) -> (Cursor, Vec<Report>) {
 		let (style, end) = match_data
 			.downcast_ref::<(Rc<dyn CustomStyle>, bool)>()
 			.unwrap();
@@ -299,22 +271,16 @@ impl Rule for CustomStyleRule {
 					let token =
 						Token::new(cursor.pos..cursor.pos + s_end.len(), cursor.source.clone());
 					if style_state.toggled.get(style.name()).is_none() {
-						return (
-							cursor.at(cursor.pos + s_end.len()),
-							vec![
-								Report::build(ReportKind::Error, token.source(), token.start())
-									.with_message("Invalid End of Style")
-									.with_label(
-										Label::new((token.source(), token.range.clone()))
-											.with_order(1)
-											.with_message(format!(
-											"Cannot end style {} here, is it not started anywhere",
+						let mut reports = vec![];
+						report_err!(&mut reports, token.source(), "Invalid End of Style".into(),
+						span(token.range.clone(), format!(
+											"Cannot end style {} here, it does not started anywhere",
 											style.name().fg(state.parser.colors().info)
 										))
-											.with_color(state.parser.colors().error),
-									)
-									.finish(),
-							],
+						);
+						return (
+							cursor.at(cursor.pos + s_end.len()),
+							reports
 						);
 					}
 
@@ -327,33 +293,20 @@ impl Rule for CustomStyleRule {
 						cursor.source.clone(),
 					);
 					if let Some(start_token) = style_state.toggled.get(style.name()) {
+						let mut reports = vec![];
+						report_err!(&mut reports, token.source(), "Invalid Start of Style".into(),
+							span(token.range.clone(), format!(
+									"When trying to start custom style {}",
+									self.name().fg(state.parser.colors().info)
+							)),
+							span(start_token.range.clone(), format!(
+									"Style {} previously starts here",
+									self.name().fg(state.parser.colors().info)
+							)),
+						);
 						return (
 							cursor.at(cursor.pos + s_end.len()),
-							vec![Report::build(
-								ReportKind::Error,
-								start_token.source(),
-								start_token.start(),
-							)
-							.with_message("Invalid Start of Style")
-							.with_label(
-								Label::new((token.source(), token.range.clone()))
-									.with_order(1)
-									.with_message(format!(
-										"Style cannot {} starts here",
-										style.name().fg(state.parser.colors().info)
-									))
-									.with_color(state.parser.colors().error),
-							)
-							.with_label(
-								Label::new((start_token.source(), start_token.range.clone()))
-									.with_order(2)
-									.with_message(format!(
-										"Style {} starts previously here",
-										style.name().fg(state.parser.colors().info)
-									))
-									.with_color(state.parser.colors().error),
-							)
-							.finish()],
+							reports
 						);
 					}
 
diff --git a/src/elements/elemstyle.rs b/src/elements/elemstyle.rs
index c5e7b40..b800824 100644
--- a/src/elements/elemstyle.rs
+++ b/src/elements/elemstyle.rs
@@ -6,9 +6,6 @@ use std::rc::Rc;
 use std::sync::Arc;
 
 use ariadne::Fmt;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use mlua::Error::BadArgument;
 use mlua::Function;
 use mlua::Lua;
@@ -18,9 +15,10 @@ use regex::Regex;
 use crate::document::document::Document;
 use crate::lua::kernel::CTX;
 use crate::parser::parser::ParserState;
+use crate::parser::reports::macros::*;
+use crate::parser::reports::*;
 use crate::parser::rule::Rule;
 use crate::parser::source::Cursor;
-use crate::parser::source::Source;
 
 #[auto_registry::auto_registry(registry = "rules", path = "crate::elements::elemstyle")]
 pub struct ElemStyleRule {
@@ -80,7 +78,7 @@ impl Rule for ElemStyleRule {
 		_document: &'a (dyn Document<'a> + 'a),
 		cursor: Cursor,
 		_match_data: Box<dyn Any>,
-	) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
+	) -> (Cursor, Vec<Report>) {
 		let mut reports = vec![];
 		let matches = self
 			.start_re
@@ -93,33 +91,28 @@ impl Rule for ElemStyleRule {
 
 			// Check if empty
 			if trimmed.is_empty() {
-				reports.push(
-					Report::build(ReportKind::Error, cursor.source.clone(), key.start())
-						.with_message("Empty Style Key")
-						.with_label(
-							Label::new((cursor.source.clone(), key.range()))
-								.with_message("Expected a non-empty style key".to_string())
-								.with_color(state.parser.colors().error),
-						)
-						.finish(),
+				report_err!(
+					&mut reports,
+					cursor.source.clone(),
+					"Empty Style Key".into(),
+					span(key.range(), "Expected a non-empty style key".into()),
 				);
 				return (cursor, reports);
 			}
 
 			// Check if key exists
 			if !state.shared.styles.borrow().is_registered(trimmed) {
-				reports.push(
-					Report::build(ReportKind::Error, cursor.source.clone(), key.start())
-						.with_message("Unknown Style Key")
-						.with_label(
-							Label::new((cursor.source.clone(), key.range()))
-								.with_message(format!(
-									"Could not find a style with key: {}",
-									trimmed.fg(state.parser.colors().info)
-								))
-								.with_color(state.parser.colors().error),
+				report_err!(
+					&mut reports,
+					cursor.source.clone(),
+					"Unknown Style Key".into(),
+					span(
+						key.range(),
+						format!(
+							"Could not find a style with key: {}",
+							trimmed.fg(state.parser.colors().info)
 						)
-						.finish(),
+					),
 				);
 
 				return (cursor, reports);
@@ -135,17 +128,14 @@ impl Rule for ElemStyleRule {
 			&cursor.source.clone().content().as_str()[cursor.pos..],
 		) {
 			None => {
-				reports.push(
-					Report::build(ReportKind::Error, cursor.source.clone(), cursor.pos)
-						.with_message("Invalid Style Value")
-						.with_label(
-							Label::new((cursor.source.clone(), matches.get(0).unwrap().range()))
-								.with_message(
-									"Unable to parse json string after style key".to_string(),
-								)
-								.with_color(state.parser.colors().error),
-						)
-						.finish(),
+				report_err!(
+					&mut reports,
+					cursor.source.clone(),
+					"Invalid Style Value".into(),
+					span(
+						matches.get(0).unwrap().range(),
+						"Unable to parse json string after style key".into()
+					)
 				);
 				return (cursor, reports);
 			}
@@ -155,22 +145,18 @@ impl Rule for ElemStyleRule {
 				// Attempt to deserialize
 				match style.from_json(json) {
 					Err(err) => {
-						reports.push(
-							Report::build(ReportKind::Error, cursor.source.clone(), cursor.pos)
-								.with_message("Invalid Style Value")
-								.with_label(
-									Label::new((
-										cursor.source.clone(),
-										cursor.pos..cursor.pos + json.len(),
-									))
-									.with_message(format!(
+						report_err!(
+							&mut reports,
+							cursor.source.clone(),
+							"Invalid Style Value".into(),
+							span(
+								cursor.pos..cursor.pos + json.len(),
+								format!(
 										"Failed to serialize `{}` into style with key `{}`: {err}",
 										json.fg(state.parser.colors().highlight),
 										style.key().fg(state.parser.colors().info)
-									))
-									.with_color(state.parser.colors().error),
 								)
-								.finish(),
+							)
 						);
 						return (cursor, reports);
 					}
diff --git a/src/elements/layout.rs b/src/elements/layout.rs
index daa7e12..d4cc825 100644
--- a/src/elements/layout.rs
+++ b/src/elements/layout.rs
@@ -17,9 +17,6 @@ use crate::parser::state::RuleState;
 use crate::parser::state::Scope;
 use crate::parser::util::escape_text;
 use ariadne::Fmt;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use mlua::Error::BadArgument;
 use mlua::Function;
 use mlua::Lua;
@@ -34,6 +31,8 @@ use std::ops::Range;
 use std::rc::Rc;
 use std::str::FromStr;
 use std::sync::Arc;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub(crate) enum LayoutToken {
@@ -253,11 +252,11 @@ struct LayoutState {
 impl RuleState for LayoutState {
 	fn scope(&self) -> Scope { Scope::DOCUMENT }
 
-	fn on_remove<'a>(
+	fn on_remove(
 		&self,
 		state: &ParserState,
 		document: &dyn Document,
-	) -> Vec<Report<'a, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let mut reports = vec![];
 
 		let doc_borrow = document.content().borrow();
@@ -265,25 +264,23 @@ impl RuleState for LayoutState {
 
 		for (tokens, layout_type) in &self.stack {
 			let start = tokens.first().unwrap();
-			reports.push(
-				Report::build(ReportKind::Error, start.source(), start.start())
-					.with_message("Unterminated Layout")
-					.with_label(
-						Label::new((start.source(), start.range.start + 1..start.range.end))
-							.with_order(1)
-							.with_message(format!(
-								"Layout {} stars here",
-								layout_type.name().fg(state.parser.colors().info)
-							))
-							.with_color(state.parser.colors().error),
+			report_err!(
+				&mut reports,
+				start.source(),
+				"Unterminated Layout".into(),
+				span(
+					start.source(),
+					start.range.start+1..start.range.end,
+					format!(
+						"Layout {} stars here",
+						layout_type.name().fg(state.parser.colors().info)
 					)
-					.with_label(
-						Label::new((at.source(), at.range.clone()))
-							.with_order(2)
-							.with_message("Document ends here".to_string())
-							.with_color(state.parser.colors().error),
-					)
-					.finish(),
+				),
+				span(
+					at.source(),
+					at.range.clone(),
+					"Document ends here".into()
+				)
 			);
 		}
 
@@ -340,24 +337,26 @@ impl LayoutRule {
 	}
 
 	pub fn parse_properties<'a>(
-		colors: &ReportColors,
+		mut reports: &mut Vec<Report>,
 		token: &Token,
 		layout_type: Rc<dyn LayoutType>,
 		properties: Option<Match>,
-	) -> Result<Option<Box<dyn Any>>, Report<'a, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Option<Box<dyn Any>> {
 		match properties {
 			None => match layout_type.parse_properties("") {
-				Ok(props) => Ok(props),
-				Err(err) => Err(
-					Report::build(ReportKind::Error, token.source(), token.start())
-						.with_message("Unable to parse layout properties")
-						.with_label(
-							Label::new((token.source(), token.range.clone()))
-								.with_message(err)
-								.with_color(colors.error),
+				Ok(props) => props,
+				Err(err) => {
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Layout Properties".into(),
+						span(
+							token.range.clone(),
+							format!("Layout is missing required property: {eee}")
 						)
-						.finish(),
-				),
+					);
+					None
+				}
 			},
 			Some(props) => {
 				let trimmed = props.as_str().trim_start().trim_end();
diff --git a/src/elements/link.rs b/src/elements/link.rs
index 74c6d1e..3fdacaa 100644
--- a/src/elements/link.rs
+++ b/src/elements/link.rs
@@ -14,9 +14,6 @@ use crate::parser::source::Token;
 use crate::parser::source::VirtualSource;
 use crate::parser::util;
 use ariadne::Fmt;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use mlua::Error::BadArgument;
 use mlua::Function;
 use mlua::Lua;
@@ -25,6 +22,8 @@ use regex::Regex;
 use std::ops::Range;
 use std::rc::Rc;
 use std::sync::Arc;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 
 #[derive(Debug)]
 pub struct Link {
@@ -109,39 +108,37 @@ impl RegexRule for LinkRule {
 		document: &'a (dyn Document<'a> + 'a),
 		token: Token,
 		matches: Captures,
-	) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let mut reports = vec![];
 
 		let link_display = match matches.get(1) {
 			Some(display) => {
 				if display.as_str().is_empty() {
-					reports.push(
-						Report::build(ReportKind::Error, token.source(), display.start())
-							.with_message("Empty link name")
-							.with_label(
-								Label::new((token.source().clone(), display.range()))
-									.with_message("Link name is empty")
-									.with_color(state.parser.colors().error),
-							)
-							.finish(),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Empty Link Display".into(),
+						span(
+							display.range(),
+							"Link display is empty".into()
+						)
 					);
 					return reports;
 				}
 				let display_source = util::escape_source(token.source(), display.range(), "Link Display".into(), '\\', "](");
 				if display_source.content().is_empty() {
-					reports.push(
-						Report::build(ReportKind::Error, token.source(), display.start())
-							.with_message("Empty link name")
-							.with_label(
-								Label::new((token.source(), display.range()))
-									.with_message(format!(
-										"Link name is empty. Once processed, `{}` yields `{}`",
-										display.as_str().fg(state.parser.colors().highlight),
-										display_source.fg(state.parser.colors().highlight),
-									))
-									.with_color(state.parser.colors().error),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Empty Link Display".into(),
+						span(
+							display.range(),
+							format!(
+								"Link name is empty. Once processed, `{}` yields `{}`",
+								display.as_str().fg(state.parser.colors().highlight),
+								display_source.fg(state.parser.colors().highlight),
 							)
-							.finish(),
+						)
 					);
 					return reports;
 				}
@@ -156,15 +153,14 @@ impl RegexRule for LinkRule {
 				}
 				match util::parse_paragraph(state, display_source, document) {
 					Err(err) => {
-						reports.push(
-							Report::build(ReportKind::Error, token.source(), display.start())
-								.with_message("Failed to parse link display")
-								.with_label(
-									Label::new((token.source(), display.range()))
-										.with_message(err.to_string())
-										.with_color(state.parser.colors().error),
-								)
-								.finish(),
+						report_err!(
+							&mut reports,
+							token.source(),
+							"Invalid Link Display".into(),
+							span(
+								display.range(),
+								format!("Failed to parse link display:\n{err}")
+							)
 						);
 						return reports;
 					}
@@ -177,34 +173,32 @@ impl RegexRule for LinkRule {
 		let link_url = match matches.get(2) {
 			Some(url) => {
 				if url.as_str().is_empty() {
-					reports.push(
-						Report::build(ReportKind::Error, token.source(), url.start())
-							.with_message("Empty link url")
-							.with_label(
-								Label::new((token.source(), url.range()))
-									.with_message("Link url is empty")
-									.with_color(state.parser.colors().error),
-							)
-							.finish(),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Empty Link URL".into(),
+						span(
+							url.range(),
+							"Link url is empty".into()
+						)
 					);
 					return reports;
 				}
 				let text_content = util::process_text(document, url.as_str());
 
 				if text_content.is_empty() {
-					reports.push(
-						Report::build(ReportKind::Error, token.source(), url.start())
-							.with_message("Empty link url")
-							.with_label(
-								Label::new((token.source(), url.range()))
-									.with_message(format!(
-										"Link url is empty. Once processed, `{}` yields `{}`",
-										url.as_str().fg(state.parser.colors().highlight),
-										text_content.as_str().fg(state.parser.colors().highlight),
-									))
-									.with_color(state.parser.colors().error),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Empty Link URL".into(),
+						span(
+							url.range(),
+							format!(
+								"Link url is empty. Once processed, `{}` yields `{}`",
+								url.as_str().fg(state.parser.colors().highlight),
+								text_content.as_str().fg(state.parser.colors().highlight),
 							)
-							.finish(),
+						)
 					);
 					return reports;
 				}
diff --git a/src/elements/list.rs b/src/elements/list.rs
index 538d3bd..045a591 100644
--- a/src/elements/list.rs
+++ b/src/elements/list.rs
@@ -14,9 +14,9 @@ use crate::document::element::Element;
 use crate::lsp::semantic::Semantics;
 use crate::parser::parser::ParseMode;
 use crate::parser::parser::ParserState;
+use crate::parser::reports::Report;
 use crate::parser::rule::Rule;
 use crate::parser::source::Cursor;
-use crate::parser::source::Source;
 use crate::parser::source::Token;
 use crate::parser::source::VirtualSource;
 use crate::parser::util;
@@ -24,9 +24,8 @@ use crate::parser::util::escape_text;
 use crate::parser::util::Property;
 use crate::parser::util::PropertyMapError;
 use crate::parser::util::PropertyParser;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 use regex::Match;
 use regex::Regex;
 
@@ -293,7 +292,7 @@ impl Rule for ListRule {
 		document: &'a dyn Document<'a>,
 		cursor: Cursor,
 		_match_data: Box<dyn Any>,
-	) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
+	) -> (Cursor, Vec<Report>) {
 		let mut reports = vec![];
 
 		let content = cursor.source.content();
@@ -312,19 +311,8 @@ impl Rule for ListRule {
 				if let Some(properties) = captures.get(2) {
 					match self.parse_properties(properties) {
 						Err(err) => {
-							reports.push(
-								Report::build(
-									ReportKind::Warning,
-									cursor.source.clone(),
-									properties.start(),
-								)
-								.with_message("Invalid List Entry Properties")
-								.with_label(
-									Label::new((cursor.source.clone(), properties.range()))
-										.with_message(err)
-										.with_color(state.parser.colors().warning),
-								)
-								.finish(),
+							report_err!(&mut reports, cursor.source.clone(), "Invalid List Entry Properties".into(),
+								span(properties.range(), err)
 							);
 							return (cursor.at(captures.get(0).unwrap().end()), reports);
 						}
@@ -391,15 +379,8 @@ impl Rule for ListRule {
 				));
 				let parsed_content = match util::parse_paragraph(state, entry_src, document) {
 					Err(err) => {
-						reports.push(
-							Report::build(ReportKind::Warning, token.source(), token.range.start)
-								.with_message("Unable to Parse List Entry")
-								.with_label(
-									Label::new((token.source(), token.range.clone()))
-										.with_message(err)
-										.with_color(state.parser.colors().warning),
-								)
-								.finish(),
+						report_warn!(&mut reports, token.source(), "Unable to parse List Entry".into(),
+							span(token.range.clone(), err.into())
 						);
 						// Return an empty paragraph
 						vec![]
diff --git a/src/elements/media.rs b/src/elements/media.rs
index d2cd47d..fdfab56 100644
--- a/src/elements/media.rs
+++ b/src/elements/media.rs
@@ -4,9 +4,6 @@ use std::rc::Rc;
 use std::str::FromStr;
 
 use ariadne::Fmt;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use regex::Captures;
 use regex::Match;
 use regex::Regex;
@@ -34,6 +31,8 @@ use crate::parser::util::Property;
 use crate::parser::util::PropertyMap;
 use crate::parser::util::PropertyMapError;
 use crate::parser::util::PropertyParser;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 
 use super::paragraph::Paragraph;
 use super::reference::InternalReference;
@@ -286,39 +285,43 @@ impl MediaRule {
 
 	fn parse_properties(
 		&self,
-		colors: &ReportColors,
+		mut reports: &mut Vec<Report>,
 		token: &Token,
 		m: &Option<Match>,
-	) -> Result<PropertyMap, Report<'_, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Option<PropertyMap> {
 		match m {
 			None => match self.properties.default() {
-				Ok(properties) => Ok(properties),
-				Err(e) => Err(
-					Report::build(ReportKind::Error, token.source(), token.start())
-						.with_message("Invalid Media Properties")
-						.with_label(
-							Label::new((token.source().clone(), token.range.clone()))
-								.with_message(format!("Media is missing required property: {e}"))
-								.with_color(colors.error),
+				Ok(properties) => Some(properties),
+				Err(e) => {
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Media Properties".into(),
+						span(
+							token.range.clone(),
+							format!("Media is missing required property: {e}")
 						)
-						.finish(),
-				),
+					);
+					None
+				}
 			},
 			Some(props) => {
 				let processed =
 					util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
 				match self.properties.parse(processed.as_str()) {
-					Err(e) => Err(
-						Report::build(ReportKind::Error, token.source(), props.start())
-							.with_message("Invalid Media Properties")
-							.with_label(
-								Label::new((token.source().clone(), props.range()))
-									.with_message(e)
-									.with_color(colors.error),
+					Err(e) => {
+						report_err!(
+							&mut reports,
+							token.source(),
+							"Invalid Media Properties".into(),
+							span(
+								props.range(),
+								e
 							)
-							.finish(),
-					),
-					Ok(properties) => Ok(properties),
+						);
+						None
+					},
+					Ok(properties) => Some(properties),
 				}
 			}
 		}
@@ -357,7 +360,7 @@ impl RegexRule for MediaRule {
 		document: &'a (dyn Document<'a> + 'a),
 		token: Token,
 		matches: Captures,
-	) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let mut reports = vec![];
 
 		let refname = match (
@@ -366,13 +369,14 @@ impl RegexRule for MediaRule {
 		) {
 			(_, Ok(refname)) => refname.to_string(),
 			(m, Err(err)) => {
-				reports.push(
-					Report::build(ReportKind::Error, token.source(), m.start())
-						.with_message("Invalid Media Refname")
-						.with_label(
-							Label::new((token.source().clone(), m.range())).with_message(err),
-						)
-						.finish(),
+				report_err!(
+					&mut reports,
+					token.source(),
+					"Invalid Media Refname".into(),
+					span(
+						m.range(),
+						err
+					)
 				);
 				return reports;
 			}
@@ -384,26 +388,24 @@ impl RegexRule for MediaRule {
 		) {
 			(_, Ok(uri)) => util::escape_text('\\', ")", uri),
 			(m, Err(err)) => {
-				reports.push(
-					Report::build(ReportKind::Error, token.source(), m.start())
-						.with_message("Invalid Media URI")
-						.with_label(
-							Label::new((token.source().clone(), m.range())).with_message(err),
-						)
-						.finish(),
+				report_err!(
+					&mut reports,
+					token.source(),
+					"Invalid Media URI".into(),
+					span(
+						m.range(),
+						err
+					)
 				);
 				return reports;
 			}
 		};
 
 		// Properties
-		let properties = match self.parse_properties(state.parser.colors(), &token, &matches.get(3))
+		let properties = match self.parse_properties(&mut reports, &token, &matches.get(3))
 		{
-			Ok(pm) => pm,
-			Err(report) => {
-				reports.push(report);
-				return reports;
-			}
+			Some(pm) => pm,
+			None => return reports,
 		};
 
 		let media_type =
@@ -415,36 +417,31 @@ impl RegexRule for MediaRule {
 					Ok((_prop, kind)) => kind,
 					Err(e) => match e {
 						PropertyMapError::ParseError((prop, err)) => {
-							reports.push(
-								Report::build(ReportKind::Error, token.source(), token.start())
-									.with_message("Invalid Media Property")
-									.with_label(
-										Label::new((token.source().clone(), token.range.clone()))
-											.with_message(format!(
-												"Property `type: {}` cannot be converted: {}",
-												prop.fg(state.parser.colors().info),
-												err.fg(state.parser.colors().error)
-											))
-											.with_color(state.parser.colors().warning),
+							report_err!(
+								&mut reports,
+								token.source(),
+								"Invalid Media Property".into(),
+								span(
+									token.start()+1..token.end(),
+									format!(
+										"Property `type: {}` cannot be converted: {}",
+										prop.fg(state.parser.colors().info),
+										err.fg(state.parser.colors().error)
 									)
-									.finish(),
+								)
 							);
 							return reports;
 						}
 						PropertyMapError::NotFoundError(err) => {
-							reports.push(
-							Report::build(ReportKind::Error, token.source(), token.start())
-							.with_message("Invalid Media Property")
-							.with_label(
-								Label::new((
-										token.source().clone(),
-										token.start() + 1..token.end(),
-								))
-								.with_message(format!("{err}. Required because mediatype could not be detected"))
-								.with_color(state.parser.colors().error),
-							)
-							.finish(),
-						);
+							report_err!(
+								&mut reports,
+								token.source(),
+								"Invalid Media Property".into(),
+								span(
+									token.start()+1..token.end(),
+									format!("{err}. Required because mediatype could not be detected")
+								)
+							);
 							return reports;
 						}
 					},
@@ -478,17 +475,16 @@ impl RegexRule for MediaRule {
 					match parse_paragraph(state, source, document) {
 						Ok(paragraph) => Some(*paragraph),
 						Err(err) => {
-							reports.push(
-								Report::build(ReportKind::Error, token.source(), content.start())
-									.with_message("Invalid Media Description")
-									.with_label(
-										Label::new((token.source().clone(), content.range()))
-											.with_message(format!(
-												"Could not parse description: {err}"
-											))
-											.with_color(state.parser.colors().error),
+							report_err!(
+								&mut reports,
+								token.source(),
+								"Invalid Media Description".into(),
+								span(
+									content.range(),
+									format!(
+										"Could not parse description: {err}"
 									)
-									.finish(),
+								)
 							);
 							return reports;
 						}
@@ -522,15 +518,15 @@ impl RegexRule for MediaRule {
 			caption,
 			description,
 		})) {
-			reports.push(
-				Report::build(ReportKind::Error, token.source(), token.start())
-					.with_message("Invalid Media")
-					.with_label(
-						Label::new((token.source().clone(), token.range.clone()))
-							.with_message(err)
-							.with_color(state.parser.colors().error),
-					)
-					.finish(),
+			report_err!(
+				&mut reports,
+				token.source(),
+				"Invalid Media".into(),
+				span(
+					token.range.clone(),
+					err
+
+				)
 			);
 		}
 
diff --git a/src/elements/raw.rs b/src/elements/raw.rs
index 23bc6b6..cfa190e 100644
--- a/src/elements/raw.rs
+++ b/src/elements/raw.rs
@@ -6,25 +6,21 @@ use crate::lsp::semantic::Semantics;
 use crate::lua::kernel::CTX;
 use crate::parser::parser::ParseMode;
 use crate::parser::parser::ParserState;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 use crate::parser::rule::RegexRule;
-use crate::parser::source::Source;
 use crate::parser::source::Token;
 use crate::parser::util::Property;
 use crate::parser::util::PropertyMapError;
 use crate::parser::util::PropertyParser;
 use crate::parser::util::{self};
 use ariadne::Fmt;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use mlua::Error::BadArgument;
 use mlua::Function;
 use mlua::Lua;
 use regex::Captures;
 use regex::Regex;
 use std::collections::HashMap;
-use std::ops::Range;
-use std::rc::Rc;
 use std::str::FromStr;
 use std::sync::Arc;
 
@@ -94,25 +90,24 @@ impl RegexRule for RawRule {
 		document: &dyn Document,
 		token: Token,
 		matches: Captures,
-	) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let mut reports = vec![];
 
 		let raw_content = match matches.get(2) {
 			// Unterminated
 			None => {
-				reports.push(
-					Report::build(ReportKind::Error, token.source(), token.start())
-						.with_message("Unterminated Raw Code")
-						.with_label(
-							Label::new((token.source().clone(), token.range.clone()))
-								.with_message(format!(
-									"Missing terminating `{}` after first `{}`",
-									"?}".fg(state.parser.colors().info),
-									"{?".fg(state.parser.colors().info)
-								))
-								.with_color(state.parser.colors().error),
+				report_err!(
+					&mut reports,
+					token.source(),
+					"Unterminated Raw Code".into(),
+					span(
+						token.range.clone(),
+						format!(
+							"Missing terminating `{}` after first `{}`",
+							"?}".fg(state.parser.colors().info),
+							"{?".fg(state.parser.colors().info)
 						)
-						.finish(),
+					)
 				);
 				return reports;
 			}
@@ -121,15 +116,11 @@ impl RegexRule for RawRule {
 					util::escape_text('\\', "?}", content.as_str().trim_start().trim_end());
 
 				if processed.is_empty() {
-					reports.push(
-						Report::build(ReportKind::Warning, token.source(), content.start())
-							.with_message("Empty Raw Code")
-							.with_label(
-								Label::new((token.source().clone(), content.range()))
-									.with_message("Raw code is empty")
-									.with_color(state.parser.colors().warning),
-							)
-							.finish(),
+					report_warn!(
+						&mut reports,
+						token.source(),
+						"Empty Raw Code".into(),
+						span(content.range(), "Raw code is empty".into())
 					);
 				}
 				processed
@@ -140,15 +131,14 @@ impl RegexRule for RawRule {
 			None => match self.properties.default() {
 				Ok(properties) => properties,
 				Err(e) => {
-					reports.push(
-						Report::build(ReportKind::Error, token.source(), token.start())
-							.with_message("Invalid Raw Code")
-							.with_label(
-								Label::new((token.source().clone(), token.range.clone()))
-									.with_message(format!("Raw code is missing properties: {e}"))
-									.with_color(state.parser.colors().error),
-							)
-							.finish(),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Raw Code".into(),
+						span(
+							token.range.clone(),
+							format!("Raw code is missing properties: {e}")
+						)
 					);
 					return reports;
 				}
@@ -158,15 +148,11 @@ impl RegexRule for RawRule {
 					util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
 				match self.properties.parse(processed.as_str()) {
 					Err(e) => {
-						reports.push(
-							Report::build(ReportKind::Error, token.source(), props.start())
-								.with_message("Invalid Raw Code Properties")
-								.with_label(
-									Label::new((token.source().clone(), props.range()))
-										.with_message(e)
-										.with_color(state.parser.colors().error),
-								)
-								.finish(),
+						report_err!(
+							&mut reports,
+							token.source(),
+							"Invalid Raw Code Properties".into(),
+							span(props.range(), e)
 						);
 						return reports;
 					}
@@ -181,38 +167,33 @@ impl RegexRule for RawRule {
 			Ok((_prop, kind)) => kind,
 			Err(e) => match e {
 				PropertyMapError::ParseError((prop, err)) => {
-					reports.push(
-						Report::build(ReportKind::Error, token.source(), token.start())
-							.with_message("Invalid Raw Code Property")
-							.with_label(
-								Label::new((token.source().clone(), token.range.clone()))
-									.with_message(format!(
-										"Property `kind: {}` cannot be converted: {}",
-										prop.fg(state.parser.colors().info),
-										err.fg(state.parser.colors().error)
-									))
-									.with_color(state.parser.colors().warning),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Raw Code Properties".into(),
+						span(
+							token.range.clone(),
+							format!(
+								"Property `kind: {}` cannot be converted: {}",
+								prop.fg(state.parser.colors().info),
+								err.fg(state.parser.colors().error)
 							)
-							.finish(),
+						)
 					);
 					return reports;
 				}
 				PropertyMapError::NotFoundError(err) => {
-					reports.push(
-						Report::build(ReportKind::Error, token.source(), token.start())
-							.with_message("Invalid Code Property")
-							.with_label(
-								Label::new((
-									token.source().clone(),
-									token.start() + 1..token.end(),
-								))
-								.with_message(format!(
-									"Property `{}` is missing",
-									err.fg(state.parser.colors().info)
-								))
-								.with_color(state.parser.colors().warning),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Raw Code Properties".into(),
+						span(
+							token.range.clone(),
+							format!(
+								"Property `{}` is missing",
+								err.fg(state.parser.colors().info)
 							)
-							.finish(),
+						)
 					);
 					return reports;
 				}
@@ -232,15 +213,14 @@ impl RegexRule for RawRule {
 			Semantics::from_source(token.source(), &state.shared.semantics)
 		{
 			let range = matches.get(0).unwrap().range();
-			sems.add(range.start..range.start+2, tokens.raw_sep);
-			if let Some(props) = matches.get(1).map(|m| m.range())
-			{
+			sems.add(range.start..range.start + 2, tokens.raw_sep);
+			if let Some(props) = matches.get(1).map(|m| m.range()) {
 				sems.add(props.start - 1..props.start, tokens.raw_props_sep);
 				sems.add(props.clone(), tokens.raw_props);
 				sems.add(props.end..props.end + 1, tokens.raw_props_sep);
 			}
 			sems.add(matches.get(2).unwrap().range(), tokens.raw_content);
-			sems.add(range.end-2..range.end, tokens.raw_sep);
+			sems.add(range.end - 2..range.end, tokens.raw_sep);
 		}
 
 		reports
@@ -297,7 +277,8 @@ mod tests {
 	use crate::parser::langparser::LangParser;
 	use crate::parser::parser::Parser;
 	use crate::parser::source::SourceFile;
-	use crate::{validate_document, validate_semantics};
+	use crate::validate_document;
+	use crate::validate_semantics;
 
 	#[test]
 	fn parser() {
diff --git a/src/elements/reference.rs b/src/elements/reference.rs
index 5b43a5a..e12db74 100644
--- a/src/elements/reference.rs
+++ b/src/elements/reference.rs
@@ -2,9 +2,6 @@ use std::collections::HashMap;
 use std::ops::Range;
 use std::rc::Rc;
 
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use reference_style::ExternalReferenceStyle;
 use regex::Captures;
 use regex::Match;
@@ -32,6 +29,8 @@ use crate::parser::util;
 use crate::parser::util::Property;
 use crate::parser::util::PropertyMap;
 use crate::parser::util::PropertyParser;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 
 #[derive(Debug)]
 pub struct InternalReference {
@@ -186,39 +185,43 @@ impl ReferenceRule {
 
 	fn parse_properties(
 		&self,
-		colors: &ReportColors,
+		mut reports: &mut Vec<Report>,
 		token: &Token,
 		m: &Option<Match>,
-	) -> Result<PropertyMap, Report<'_, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Option<PropertyMap> {
 		match m {
 			None => match self.properties.default() {
-				Ok(properties) => Ok(properties),
-				Err(e) => Err(
-					Report::build(ReportKind::Error, token.source(), token.start())
-						.with_message("Invalid Media Properties")
-						.with_label(
-							Label::new((token.source().clone(), token.range.clone()))
-								.with_message(format!("Media is missing required property: {e}"))
-								.with_color(colors.error),
+				Ok(properties) => Some(properties),
+				Err(e) => {
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Reference Properties".into(),
+						span(
+							token.range.clone(),
+							format!("Reference is missing required property: {e}")
 						)
-						.finish(),
-				),
+					);
+					None
+				}
 			},
 			Some(props) => {
 				let processed =
 					util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
 				match self.properties.parse(processed.as_str()) {
-					Err(e) => Err(
-						Report::build(ReportKind::Error, token.source(), props.start())
-							.with_message("Invalid Media Properties")
-							.with_label(
-								Label::new((token.source().clone(), props.range()))
-									.with_message(e)
-									.with_color(colors.error),
+					Err(e) => {
+						report_err!(
+							&mut reports,
+							token.source(),
+							"Invalid Reference Properties".into(),
+							span(
+								props.range(),
+								e
 							)
-							.finish(),
-					),
-					Ok(properties) => Ok(properties),
+						);
+						None
+					},
+					Ok(properties) => Some(properties),
 				}
 			}
 		}
@@ -241,7 +244,7 @@ impl RegexRule for ReferenceRule {
 		document: &'a (dyn Document<'a> + 'a),
 		token: Token,
 		matches: Captures,
-	) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let mut reports = vec![];
 
 		let (refdoc, refname) = if let Some(refname_match) = matches.get(1) {
@@ -252,14 +255,14 @@ impl RegexRule for ReferenceRule {
 				match validate_refname(document, refname_match.as_str().split_at(sep + 1).1, false)
 				{
 					Err(err) => {
-						reports.push(
-							Report::build(ReportKind::Error, token.source(), refname_match.start())
-								.with_message("Invalid Reference Refname")
-								.with_label(
-									Label::new((token.source().clone(), refname_match.range()))
-										.with_message(err),
-								)
-								.finish(),
+						report_err!(
+							&mut reports,
+							token.source(),
+							"Invalid Reference Refname".into(),
+							span(
+								refname_match.range(),
+								err
+							)
 						);
 						return reports;
 					}
@@ -270,14 +273,14 @@ impl RegexRule for ReferenceRule {
 			{
 				match validate_refname(document, refname_match.as_str(), false) {
 					Err(err) => {
-						reports.push(
-							Report::build(ReportKind::Error, token.source(), refname_match.start())
-								.with_message("Invalid Reference Refname")
-								.with_label(
-									Label::new((token.source().clone(), refname_match.range()))
-										.with_message(err),
-								)
-								.finish(),
+						report_err!(
+							&mut reports,
+							token.source(),
+							"Invalid Reference Refname".into(),
+							span(
+								refname_match.range(),
+								err
+							)
 						);
 						return reports;
 					}
@@ -289,13 +292,10 @@ impl RegexRule for ReferenceRule {
 		};
 
 		// Properties
-		let properties = match self.parse_properties(state.parser.colors(), &token, &matches.get(2))
+		let properties = match self.parse_properties(&mut reports, &token, &matches.get(2))
 		{
-			Ok(pm) => pm,
-			Err(report) => {
-				reports.push(report);
-				return reports;
-			}
+			Some(pm) => pm,
+			None => return reports,
 		};
 
 		let caption = properties
diff --git a/src/elements/script.rs b/src/elements/script.rs
index 7c24178..9a05338 100644
--- a/src/elements/script.rs
+++ b/src/elements/script.rs
@@ -12,14 +12,13 @@ use crate::parser::source::VirtualSource;
 use crate::parser::util;
 use crate::parser::util::escape_source;
 use ariadne::Fmt;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use mlua::Lua;
 use regex::Captures;
 use regex::Regex;
 use std::ops::Range;
 use std::rc::Rc;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 
 use super::text::Text;
 
@@ -95,7 +94,7 @@ impl RegexRule for ScriptRule {
 		document: &'a dyn Document<'a>,
 		token: Token,
 		matches: Captures,
-	) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let mut reports = vec![];
 
 		let kernel_name = match matches.get(1) {
@@ -104,15 +103,14 @@ impl RegexRule for ScriptRule {
 				match ScriptRule::validate_kernel_name(state.parser.colors(), name.as_str()) {
 					Ok(name) => name,
 					Err(e) => {
-						reports.push(
-							Report::build(ReportKind::Error, token.source(), name.start())
-								.with_message("Invalid kernel name")
-								.with_label(
-									Label::new((token.source(), name.range()))
-										.with_message(e)
-										.with_color(state.parser.colors().error),
-								)
-								.finish(),
+						report_err!(
+							&mut reports,
+							token.source(),
+							"Invalid Kernel Name".into(),
+							span(
+								name.range(),
+								e
+							)
 						);
 						return reports;
 					}
@@ -136,15 +134,14 @@ impl RegexRule for ScriptRule {
 			), '\\', ">@");
 		if source.content().is_empty()		
 		{
-			reports.push(
-				Report::build(ReportKind::Warning, token.source(), token.start())
-					.with_message("Invalid kernel code")
-					.with_label(
-						Label::new((token.source(), script_range))
-							.with_message("Kernel code is empty")
-							.with_color(state.parser.colors().warning),
-					)
-					.finish(),
+			report_warn!(
+				&mut reports,
+				token.source(),
+				"Invalid Kernel Code".into(),
+				span(
+					script_range,
+					"Kernel code is empty".into(),
+				)
 			);
 			return reports;
 		}
@@ -157,15 +154,14 @@ impl RegexRule for ScriptRule {
 			// Exec
 			{
 				if let Err(e) = chunk.exec() {
-					reports.push(
-						Report::build(ReportKind::Error, source.clone(), 0)
-							.with_message("Invalid kernel code")
-							.with_label(
-								Label::new((source.clone(), 0..source.content().len()))
-									.with_message(format!("Kernel execution failed:\n{}", e))
-									.with_color(state.parser.colors().error),
-							)
-							.finish(),
+					report_err!(
+						&mut reports,
+						source.clone(),
+						"Invalid Kernel Code".into(),
+						span(
+							0..source.content().len(),
+							format!("Kernel execution failed:\n{}", e)
+						)
 					);
 					return reports;
 				}
@@ -178,15 +174,14 @@ impl RegexRule for ScriptRule {
 					Some(kind) => match self.validate_kind(state.parser.colors(), kind.as_str()) {
 						Ok(kind) => kind,
 						Err(msg) => {
-							reports.push(
-								Report::build(ReportKind::Error, token.source(), kind.start())
-									.with_message("Invalid kernel code kind")
-									.with_label(
-										Label::new((token.source(), kind.range()))
-											.with_message(msg)
-											.with_color(state.parser.colors().error),
-									)
-									.finish(),
+							report_err!(
+								&mut reports,
+								token.source(),
+								"Invalid Kernel Code Kind".into(),
+								span(
+									kind.range(),
+									msg
+								)
 							);
 							return reports;
 						}
@@ -197,15 +192,14 @@ impl RegexRule for ScriptRule {
 				// Eval
 				{
 					if let Err(e) = chunk.eval::<()>() {
-						reports.push(
-							Report::build(ReportKind::Error, source.clone(), 0)
-								.with_message("Invalid kernel code")
-								.with_label(
-									Label::new((source.clone(), 0..source.content().len()))
-										.with_message(format!("Kernel evaluation failed:\n{}", e))
-										.with_color(state.parser.colors().error),
-								)
-								.finish(),
+						report_err!(
+							&mut reports,
+							source.clone(),
+							"Invalid Kernel Code".into(),
+							span(
+								0..source.content().len(),
+								format!("Kernel evaluation failed:\n{}", e)
+							)
 						);
 					}
 				} else
@@ -245,18 +239,14 @@ impl RegexRule for ScriptRule {
 							}
 						}
 						Err(e) => {
-							reports.push(
-								Report::build(ReportKind::Error, source.clone(), 0)
-									.with_message("Invalid kernel code")
-									.with_label(
-										Label::new((source.clone(), 0..source.content().len()))
-											.with_message(format!(
-												"Kernel evaluation failed:\n{}",
-												e
-											))
-											.with_color(state.parser.colors().error),
-									)
-									.finish(),
+							report_err!(
+								&mut reports,
+								source.clone(),
+								"Invalid Kernel Code".into(),
+								span(
+									0..source.content().len(),
+									format!("Kernel evaluation failed:\n{}", e)
+								)
 							);
 						}
 					}
diff --git a/src/elements/section.rs b/src/elements/section.rs
index 1db17b5..3da3b89 100644
--- a/src/elements/section.rs
+++ b/src/elements/section.rs
@@ -13,9 +13,6 @@ use crate::parser::source::Source;
 use crate::parser::source::Token;
 use crate::parser::style::StyleHolder;
 use ariadne::Fmt;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use mlua::Error::BadArgument;
 use mlua::Function;
 use mlua::Lua;
@@ -25,6 +22,8 @@ use section_style::SectionStyle;
 use std::ops::Range;
 use std::rc::Rc;
 use std::sync::Arc;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 
 use super::reference::InternalReference;
 
@@ -183,22 +182,22 @@ impl RegexRule for SectionRule {
 		document: &dyn Document,
 		token: Token,
 		matches: regex::Captures,
-	) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
-		let mut result = vec![];
+	) -> Vec<Report> {
+		let mut reports = vec![];
 		let section_depth = match matches.get(1) {
 			Some(depth) => {
 				if depth.len() > 6 {
-					result.push(
-					Report::build(ReportKind::Error, token.source(), depth.start())
-						.with_message("Invalid section depth")
-						.with_label(
-							Label::new((token.source(), depth.range()))
-							.with_message(format!("Section is of depth {}, which is greather than {} (maximum depth allowed)",
-                            depth.len().fg(state.parser.colors().info),
-                            6.fg(state.parser.colors().info)))
-							.with_color(state.parser.colors().error))
-						.finish());
-					return result;
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Section Depth".into(),
+						span(
+							depth.range(),
+							format!("Section is of depth {}, which is greather than {} (maximum depth allowed)",
+							depth.len().fg(state.parser.colors().info),
+							6.fg(state.parser.colors().info))
+						)
+					);
 				}
 
 				depth.len()
@@ -215,25 +214,27 @@ impl RegexRule for SectionRule {
 					if let Some(elem_reference) = document.get_reference(refname.as_str()) {
 						let elem = document.get_from_reference(&elem_reference).unwrap();
 
-						result.push(
-						Report::build(ReportKind::Warning, token.source(), refname.start())
-						.with_message("Duplicate reference name")
-						.with_label(
-							Label::new((token.source(), refname.range()))
-							.with_message(format!("Reference with name `{}` is already defined in `{}`",
+
+						report_warn!(
+							&mut reports,
+							token.source(),
+							"Duplicate Reference Name".into(),
+							span(
+								refname.range(),
+								format!("Reference with name `{}` is already defined in `{}`. `{}` conflicts with previously defined reference to {}",
 									refname.as_str().fg(state.parser.colors().highlight),
-									elem.location().source().name().as_str().fg(state.parser.colors().highlight)))
-							.with_message(format!("`{}` conflicts with previously defined reference to {}",
+									elem.location().source().name().as_str().fg(state.parser.colors().highlight),
 									refname.as_str().fg(state.parser.colors().highlight),
-									elem.element_name().fg(state.parser.colors().highlight)))
-							.with_color(state.parser.colors().warning))
-						.with_label(
-							Label::new((elem.location().source(), elem.location().start()..elem.location().end() ))
-							.with_message(format!("`{}` previously defined here",
-								refname.as_str().fg(state.parser.colors().highlight)))
-							.with_color(state.parser.colors().warning))
-						.with_note("Previous reference was overwritten".to_string())
-						.finish());
+									elem.element_name().fg(state.parser.colors().highlight))
+							),
+							span(
+								elem.location().source(),
+								elem.location().start()..elem.location().end(),
+								format!("`{}` previously defined here",
+									refname.as_str().fg(state.parser.colors().highlight))
+							),
+							note("Previous reference was overwritten".into())
+						);
 					}
 					Some(refname.as_str().to_string())
 				},
@@ -247,19 +248,20 @@ impl RegexRule for SectionRule {
 				"+" => section_kind::NO_TOC,
 				"" => section_kind::NONE,
 				_ => {
-					result.push(
-							Report::build(ReportKind::Error, token.source(), kind.start())
-							.with_message("Invalid section numbering kind")
-							.with_label(
-								Label::new((token.source(), kind.range()))
-								.with_message(format!("Section numbering kind must be a combination of `{}` for unnumbered, and `{}` for non-listing; got `{}`",
-										"*".fg(state.parser.colors().info),
-										"+".fg(state.parser.colors().info),
-										kind.as_str().fg(state.parser.colors().highlight)))
-								.with_color(state.parser.colors().error))
-								.with_help("Leave empty for a numbered listed section".to_string())
-							.finish());
-					return result;
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Section Numbering Kind".into(),
+						span(
+							kind.range(),
+							format!("Section numbering kind must be a combination of `{}` for unnumbered, and `{}` for non-listing; got `{}`",
+								"*".fg(state.parser.colors().info),
+								"+".fg(state.parser.colors().info),
+								kind.as_str().fg(state.parser.colors().highlight))
+						),
+						help("Leave empty for a numbered listed section".into())
+					);
+					return reports;
 				}
 			},
 			_ => section_kind::NONE,
@@ -278,31 +280,31 @@ impl RegexRule for SectionRule {
 				if section_name.is_empty()
 				// No name
 				{
-					result.push(
-						Report::build(ReportKind::Error, token.source(), name.start())
-							.with_message("Missing section name")
-							.with_label(
-								Label::new((token.source(), name.range()))
-									.with_message("Sections require a name before line end")
-									.with_color(state.parser.colors().error),
-							)
-							.finish(),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Missing Section Name".into(),
+						span(
+							name.range(),
+							"Section name must be specified before line end".into()
+						),
 					);
-					return result;
+					return reports;
 				}
 
 				// No spacing
 				if split == 0 {
-					result.push(
-						Report::build(ReportKind::Warning, token.source(), name.start())
-						.with_message("Missing section spacing")
-						.with_label(
-							Label::new((token.source(), name.range()))
-							.with_message("Sections require at least one whitespace before the section's name")
-							.with_color(state.parser.colors().warning))
-                        .with_help(format!("Add a space before `{}`", section_name.fg(state.parser.colors().highlight)))
-						.finish());
-					return result;
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Missing Section Spacing".into(),
+						span(
+							name.range(),
+							"Sections require at least one whitespace before the section's name".into()
+						),
+                        help(format!("Add a space before `{}`", section_name.fg(state.parser.colors().highlight)))
+					);
+					return reports;
 				}
 
 				section_name.to_string()
@@ -347,7 +349,7 @@ impl RegexRule for SectionRule {
 			sems.add(matches.get(5).unwrap().range(), tokens.section_name);
 		}
 
-		result
+		reports
 	}
 
 	fn register_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> {
diff --git a/src/elements/style.rs b/src/elements/style.rs
index 6bf7ebd..a4a2452 100644
--- a/src/elements/style.rs
+++ b/src/elements/style.rs
@@ -14,9 +14,6 @@ use crate::parser::source::Token;
 use crate::parser::state::RuleState;
 use crate::parser::state::Scope;
 use ariadne::Fmt;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use mlua::Function;
 use regex::Captures;
 use regex::Regex;
@@ -24,6 +21,8 @@ use std::cell::RefCell;
 use std::ops::Range;
 use std::rc::Rc;
 use std::sync::Arc;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 
 use super::paragraph::Paragraph;
 
@@ -87,11 +86,11 @@ impl StyleState {
 impl RuleState for StyleState {
 	fn scope(&self) -> Scope { Scope::PARAGRAPH }
 
-	fn on_remove<'a>(
+	fn on_remove(
 		&self,
 		state: &ParserState,
 		document: &dyn Document,
-	) -> Vec<Report<'a, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let mut reports = vec![];
 
 		self.toggled
@@ -114,27 +113,22 @@ impl RuleState for StyleState {
 						)
 					})
 					.unwrap();
-
-				reports.push(
-					Report::build(ReportKind::Error, token.source(), token.start())
-						.with_message("Unterminated Style")
-						.with_label(
-							Label::new((token.source(), token.range.clone()))
-								.with_order(1)
-								.with_message(format!(
-									"Style {} starts here",
-									name.fg(state.parser.colors().info)
-								))
-								.with_color(state.parser.colors().error),
+				report_err!(
+					&mut reports,
+					token.source(),
+					"Unterminated Style".into(),
+					span(
+						token.range.clone(),
+						format!(
+							"Style {} starts here",
+							name.fg(state.parser.colors().info)
 						)
-						.with_label(
-							Label::new(paragraph_end)
-								.with_order(1)
-								.with_message("Paragraph ends here".to_string())
-								.with_color(state.parser.colors().error),
-						)
-						.with_note("Styles cannot span multiple documents (i.e @import)")
-						.finish(),
+					),
+					span(
+						paragraph_end.1,
+						"Paragraph ends here".into()
+					),
+					note("Styles cannot span multiple documents (i.e @import)".into())
 				);
 			});
 
@@ -182,7 +176,7 @@ impl RegexRule for StyleRule {
 		document: &dyn Document,
 		token: Token,
 		_matches: Captures,
-	) -> Vec<Report<(Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let query = state.shared.rule_state.borrow().get(STATE_NAME);
 		let style_state = match query {
 			Some(state) => state,
diff --git a/src/elements/tex.rs b/src/elements/tex.rs
index 17b221e..476c2e3 100644
--- a/src/elements/tex.rs
+++ b/src/elements/tex.rs
@@ -10,9 +10,6 @@ use std::sync::Arc;
 use std::sync::Once;
 
 use ariadne::Fmt;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use crypto::digest::Digest;
 use crypto::sha2::Sha512;
 use mlua::Function;
@@ -41,6 +38,8 @@ use crate::parser::util::Property;
 use crate::parser::util::PropertyMap;
 use crate::parser::util::PropertyMapError;
 use crate::parser::util::PropertyParser;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 
 #[derive(Debug, PartialEq, Eq)]
 enum TexKind {
@@ -266,39 +265,45 @@ impl TexRule {
 
 	fn parse_properties(
 		&self,
+		mut reports: &mut Vec<Report>,
 		colors: &ReportColors,
 		token: &Token,
 		m: &Option<Match>,
-	) -> Result<PropertyMap, Report<'_, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Option<PropertyMap> {
 		match m {
 			None => match self.properties.default() {
-				Ok(properties) => Ok(properties),
-				Err(e) => Err(
-					Report::build(ReportKind::Error, token.source(), token.start())
-						.with_message("Invalid Tex Properties")
-						.with_label(
-							Label::new((token.source().clone(), token.range.clone()))
-								.with_message(format!("Tex is missing required property: {e}"))
-								.with_color(colors.error),
+				Ok(properties) => Some(properties),
+				Err(e) =>
+				{
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Tex Properties".into(),
+						span(
+							token.range.clone(),
+							format!("Tex is missing required property: {e}")
 						)
-						.finish(),
-				),
+					);
+					None
+				}
 			},
 			Some(props) => {
 				let processed =
 					util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
 				match self.properties.parse(processed.as_str()) {
-					Err(e) => Err(
-						Report::build(ReportKind::Error, token.source(), props.start())
-							.with_message("Invalid Tex Properties")
-							.with_label(
-								Label::new((token.source().clone(), props.range()))
-									.with_message(e)
-									.with_color(colors.error),
+					Err(e) => {
+						report_err!(
+							&mut reports,
+							token.source(),
+							"Invalid Tex Properties".into(),
+							span(
+								props.range(),
+								e
 							)
-							.finish(),
-					),
-					Ok(properties) => Ok(properties),
+						);
+						None
+					},
+					Ok(properties) => Some(properties),
 				}
 			}
 		}
@@ -321,25 +326,24 @@ impl RegexRule for TexRule {
 		document: &dyn Document,
 		token: Token,
 		matches: Captures,
-	) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let mut reports = vec![];
 
 		let tex_content = match matches.get(2) {
 			// Unterminated `$`
 			None => {
-				reports.push(
-					Report::build(ReportKind::Error, token.source(), token.start())
-						.with_message("Unterminated Tex Code")
-						.with_label(
-							Label::new((token.source().clone(), token.range.clone()))
-								.with_message(format!(
-									"Missing terminating `{}` after first `{}`",
-									["|$", "$"][index].fg(state.parser.colors().info),
-									["$|", "$"][index].fg(state.parser.colors().info)
-								))
-								.with_color(state.parser.colors().error),
+				report_err!(
+					&mut reports,
+					token.source(),
+					"Unterminated Tex Code".into(),
+					span(
+						token.range.clone(),
+						format!(
+							"Missing terminating `{}` after first `{}`",
+							["|$", "$"][index].fg(state.parser.colors().info),
+							["$|", "$"][index].fg(state.parser.colors().info)
 						)
-						.finish(),
+					)
 				);
 				return reports;
 			}
@@ -351,15 +355,14 @@ impl RegexRule for TexRule {
 				);
 
 				if processed.is_empty() {
-					reports.push(
-						Report::build(ReportKind::Warning, token.source(), content.start())
-							.with_message("Empty Tex Code")
-							.with_label(
-								Label::new((token.source().clone(), content.range()))
-									.with_message("Tex code is empty")
-									.with_color(state.parser.colors().warning),
-							)
-							.finish(),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Empty Tex Code".into(),
+						span(
+							content.range(),
+							"Tex code is empty".into()
+						)
 					);
 				}
 				processed
@@ -367,13 +370,10 @@ impl RegexRule for TexRule {
 		};
 
 		// Properties
-		let properties = match self.parse_properties(state.parser.colors(), &token, &matches.get(1))
+		let properties = match self.parse_properties(&mut reports, state.parser.colors(), &token, &matches.get(1))
 		{
-			Ok(pm) => pm,
-			Err(report) => {
-				reports.push(report);
-				return reports;
-			}
+			Some(pm) => pm,
+			None => return reports,
 		};
 
 		// Tex kind
@@ -383,19 +383,19 @@ impl RegexRule for TexRule {
 			Ok((_prop, kind)) => kind,
 			Err(e) => match e {
 				PropertyMapError::ParseError((prop, err)) => {
-					reports.push(
-						Report::build(ReportKind::Error, token.source(), token.start())
-							.with_message("Invalid Tex Property")
-							.with_label(
-								Label::new((token.source().clone(), token.range.clone()))
-									.with_message(format!(
-										"Property `kind: {}` cannot be converted: {}",
-										prop.fg(state.parser.colors().info),
-										err.fg(state.parser.colors().error)
-									))
-									.with_color(state.parser.colors().warning),
+
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Tex Property".into(),
+						span(
+							token.range.clone(),
+							format!(
+								"Property `kind: {}` cannot be converted: {}",
+								prop.fg(state.parser.colors().info),
+								err.fg(state.parser.colors().error)
 							)
-							.finish(),
+						)
 					);
 					return reports;
 				}
diff --git a/src/elements/text.rs b/src/elements/text.rs
index edc13ff..a04a966 100644
--- a/src/elements/text.rs
+++ b/src/elements/text.rs
@@ -2,7 +2,6 @@ use std::any::Any;
 use std::ops::Range;
 use std::rc::Rc;
 
-use ariadne::Report;
 use mlua::Function;
 use mlua::Lua;
 
@@ -17,6 +16,7 @@ use crate::parser::rule::Rule;
 use crate::parser::source::Cursor;
 use crate::parser::source::Source;
 use crate::parser::source::Token;
+use crate::parser::reports::*;
 
 #[derive(Debug)]
 pub struct Text {
@@ -70,7 +70,7 @@ impl Rule for TextRule {
 		_document: &dyn Document,
 		_cursor: Cursor,
 		_match_data: Box<dyn Any>,
-	) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
+	) -> (Cursor, Vec<Report>) {
 		panic!("Text cannot match");
 	}
 
diff --git a/src/elements/variable.rs b/src/elements/variable.rs
index 8ec6f8e..1fd7f7a 100644
--- a/src/elements/variable.rs
+++ b/src/elements/variable.rs
@@ -11,15 +11,14 @@ use crate::parser::rule::RegexRule;
 use crate::parser::source::Source;
 use crate::parser::source::Token;
 use ariadne::Fmt;
-use ariadne::Label;
-use ariadne::Report;
-use ariadne::ReportKind;
 use mlua::Function;
 use mlua::Lua;
 use regex::Regex;
 use std::ops::Range;
 use std::rc::Rc;
 use std::str::FromStr;
+use crate::parser::reports::*;
+use crate::parser::reports::macros::*;
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 enum VariableKind {
@@ -134,8 +133,8 @@ impl RegexRule for VariableRule {
 		document: &dyn Document,
 		token: Token,
 		matches: regex::Captures,
-	) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
-		let mut result = vec![];
+	) -> Vec<Report> {
+		let mut reports = vec![];
 		// [Optional] variable kind
 		let var_kind = match matches.get(1) {
 			Some(kind) => {
@@ -148,18 +147,18 @@ impl RegexRule for VariableRule {
 
 				// Unknown kind specified
 				if r.is_none() {
-					result.push(
-						Report::build(ReportKind::Error, token.source(), kind.start())
-							.with_message("Unknown variable kind")
-							.with_label(
-								Label::new((token.source(), kind.range()))
-									.with_message(format!(
-										"Variable kind `{}` is unknown",
-										kind.as_str().fg(state.parser.colors().highlight)
-									))
-									.with_color(state.parser.colors().error),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Unknowm Variable Kind".into(),
+						span(
+							kind.range(),
+							format!(
+								"Variable kind `{}` is unknown",
+								kind.as_str().fg(state.parser.colors().highlight)
 							)
-							.with_help(format!(
+						),
+						help(format!(
 								"Leave empty for regular variables. Available variable kinds:{}",
 								self.kinds.iter().skip(1).fold(
 									"".to_string(),
@@ -169,14 +168,12 @@ impl RegexRule for VariableRule {
 											char.fg(state.parser.colors().highlight),
 											name.fg(state.parser.colors().info)
 										)
-										.as_str()
+											.as_str()
 									}
 								)
-							))
-							.finish(),
+						))
 					);
-
-					return result;
+					return reports;
 				}
 
 				r.unwrap().0
@@ -188,21 +185,20 @@ impl RegexRule for VariableRule {
 			Some(name) => match VariableRule::validate_name(state.parser.colors(), name.as_str()) {
 				Ok(var_name) => var_name,
 				Err(msg) => {
-					result.push(
-						Report::build(ReportKind::Error, token.source(), name.start())
-							.with_message("Invalid variable name")
-							.with_label(
-								Label::new((token.source(), name.range()))
-									.with_message(format!(
-										"Variable name `{}` is not allowed. {msg}",
-										name.as_str().fg(state.parser.colors().highlight)
-									))
-									.with_color(state.parser.colors().error),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Variable Name".into(),
+						span(
+							name.range(),
+							format!(
+								"Variable name `{}` is not allowed. {msg}",
+								name.as_str().fg(state.parser.colors().highlight)
 							)
-							.finish(),
+						),
 					);
 
-					return result;
+					return reports;
 				}
 			},
 			_ => panic!("Unknown variable name"),
@@ -212,21 +208,20 @@ impl RegexRule for VariableRule {
 			Some(value) => match VariableRule::validate_value(value.as_str()) {
 				Ok(var_value) => var_value,
 				Err(msg) => {
-					result.push(
-						Report::build(ReportKind::Error, token.source(), value.start())
-							.with_message("Invalid variable value")
-							.with_label(
-								Label::new((token.source(), value.range()))
-									.with_message(format!(
-										"Variable value `{}` is not allowed. {msg}",
-										value.as_str().fg(state.parser.colors().highlight)
-									))
-									.with_color(state.parser.colors().error),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Variable Value".into(),
+						span(
+							value.range(),
+							format!(
+								"Variable value `{}` is not allowed. {msg}",
+								value.as_str().fg(state.parser.colors().highlight)
 							)
-							.finish(),
+						),
 					);
 
-					return result;
+					return reports;
 				}
 			},
 			_ => panic!("Invalid variable value"),
@@ -242,22 +237,21 @@ impl RegexRule for VariableRule {
 			Ok(variable) => document.add_variable(variable),
 			Err(msg) => {
 				let m = matches.get(0).unwrap();
-				result.push(
-					Report::build(ReportKind::Error, token.source(), m.start())
-						.with_message("Unable to create variable")
-						.with_label(
-							Label::new((token.source(), m.start() + 1..m.end()))
-								.with_message(format!(
+				report_err!(
+					&mut reports,
+					token.source(),
+					"Unable to Create Variable".into(),
+					span(
+						m.start() + 1..m.end(),
+							format!(
 									"Unable to create variable `{}`. {}",
 									var_name.fg(state.parser.colors().highlight),
 									msg
-								))
-								.with_color(state.parser.colors().error),
-						)
-						.finish(),
+								)
+					),
 				);
 
-				return result;
+				return reports;
 			}
 		}
 
@@ -277,7 +271,7 @@ impl RegexRule for VariableRule {
 			sems.add(value.clone(), tokens.variable_value);
 		}
 
-		result
+		reports
 	}
 
 	fn register_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> {
@@ -346,98 +340,87 @@ impl RegexRule for VariableSubstitutionRule {
 		document: &'a dyn Document<'a>,
 		token: Token,
 		matches: regex::Captures,
-	) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
-		let mut result = vec![];
+	) -> Vec<Report> {
+		let mut reports = vec![];
 
 		let variable = match matches.get(1) {
 			Some(name) => {
 				// Empty name
 				if name.as_str().is_empty() {
-					result.push(
-						Report::build(ReportKind::Error, token.source(), name.start())
-							.with_message("Empty variable name")
-							.with_label(
-								Label::new((token.source(), matches.get(0).unwrap().range()))
-									.with_message(
-										"Missing variable name for substitution".to_string(),
-									)
-									.with_color(state.parser.colors().error),
-							)
-							.finish(),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Empty Variable Name".into(),
+						span(
+							name.range(),
+							"Missing variable name for substitution".into(),
+						)
 					);
 
-					return result;
+					return reports;
 				}
 				// Leading spaces
 				else if name.as_str().trim_start() != name.as_str() {
-					result.push(
-						Report::build(ReportKind::Error, token.source(), name.start())
-							.with_message("Invalid variable name")
-							.with_label(
-								Label::new((token.source(), name.range()))
-									.with_message(
-										"Variable names contains leading spaces".to_string(),
-									)
-									.with_color(state.parser.colors().error),
-							)
-							.with_help("Remove leading spaces")
-							.finish(),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Variable Name".into(),
+						span(
+							name.range(),
+							"Variable names contains leading spaces".into(),
+						),
+						help("Remove leading spaces".into())
 					);
 
-					return result;
+					return reports;
 				}
 				// Trailing spaces
 				else if name.as_str().trim_end() != name.as_str() {
-					result.push(
-						Report::build(ReportKind::Error, token.source(), name.start())
-							.with_message("Invalid variable name")
-							.with_label(
-								Label::new((token.source(), name.range()))
-									.with_message(
-										"Variable names contains trailing spaces".to_string(),
-									)
-									.with_color(state.parser.colors().error),
-							)
-							.with_help("Remove trailing spaces")
-							.finish(),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Variable Name".into(),
+						span(
+							name.range(),
+							"Variable names contains trailing spaces".into(),
+						),
+						help("Remove trailing spaces".into())
 					);
 
-					return result;
+					return reports;
 				}
 				// Invalid name
 				if let Err(msg) = VariableRule::validate_name(state.parser.colors(), name.as_str())
 				{
-					result.push(
-						Report::build(ReportKind::Error, token.source(), name.start())
-							.with_message("Invalid variable name")
-							.with_label(
-								Label::new((token.source(), name.range()))
-									.with_message(msg)
-									.with_color(state.parser.colors().error),
-							)
-							.finish(),
+					report_err!(
+						&mut reports,
+						token.source(),
+						"Invalid Variable Name".into(),
+						span(
+							name.range(),
+							msg
+						)
 					);
 
-					return result;
+					return reports;
 				}
 
 				// Get variable
 				match document.get_variable(name.as_str()) {
 					None => {
-						result.push(
-							Report::build(ReportKind::Error, token.source(), name.start())
-								.with_message("Unknown variable name")
-								.with_label(
-									Label::new((token.source(), name.range()))
-										.with_message(format!(
-											"Unable to find variable with name: `{}`",
-											name.as_str().fg(state.parser.colors().highlight)
-										))
-										.with_color(state.parser.colors().error),
+						report_err!(
+							&mut reports,
+							token.source(),
+							"Unknown Variable Name".into(),
+							span(
+								name.range(),
+								format!(
+									"Unable to find variable with name: `{}`",
+									name.as_str().fg(state.parser.colors().highlight)
 								)
-								.finish(),
+							)
 						);
-						return result;
+						return reports;
 					}
 					Some(var) => var,
 				}
@@ -456,6 +439,6 @@ impl RegexRule for VariableSubstitutionRule {
 			sems.add(name.end..name.end + 1, tokens.variable_sub_sep);
 		}
 
-		result
+		reports
 	}
 }
diff --git a/src/parser/customstyle.rs b/src/parser/customstyle.rs
index 78e0712..2a90e43 100644
--- a/src/parser/customstyle.rs
+++ b/src/parser/customstyle.rs
@@ -1,14 +1,13 @@
 use std::collections::HashMap;
-use std::ops::Range;
 use std::rc::Rc;
 use std::ops::Deref;
 
-use ariadne::Report;
-
 use crate::document::document::Document;
-use crate::parser::source::Source;
 use crate::parser::source::Token;
 
+use crate::parser::reports::*;
+
+
 use super::parser::ParserState;
 
 #[derive(Debug, PartialEq, Eq)]
@@ -28,13 +27,13 @@ pub trait CustomStyle: core::fmt::Debug {
 		location: Token,
 		state: &ParserState,
 		document: &'a (dyn Document<'a> + 'a),
-	) -> Vec<Report<(Rc<dyn Source>, Range<usize>)>>;
+	) -> Vec<Report>;
 	fn on_end<'a>(
 		&self,
 		location: Token,
 		state: &ParserState,
 		document: &'a (dyn Document<'a> + 'a),
-	) -> Vec<Report<(Rc<dyn Source>, Range<usize>)>>;
+	) -> Vec<Report>;
 }
 
 #[derive(Default)]
diff --git a/src/parser/parser.rs b/src/parser/parser.rs
index 7cb3cf3..519af86 100644
--- a/src/parser/parser.rs
+++ b/src/parser/parser.rs
@@ -1,5 +1,3 @@
-use ariadne::Label;
-use ariadne::Report;
 use std::any::Any;
 use std::cell::RefCell;
 use std::collections::HashSet;
@@ -9,6 +7,7 @@ use unicode_segmentation::UnicodeSegmentation;
 
 use super::customstyle::CustomStyleHolder;
 use super::layout::LayoutHolder;
+use super::reports::Report;
 use super::rule::Rule;
 use super::source::Cursor;
 use super::source::Source;
@@ -434,7 +433,9 @@ pub trait Parser {
 
 	/// Handles the reports produced by parsing. The default is to output them
 	/// to stderr, but you are free to modify it.
-	fn handle_reports(&self, reports: Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
+	fn handle_reports(&self, reports: Vec<Report>) {
+		todo!(); // TODO
+		/*
 		for mut report in reports {
 			let mut sources: HashSet<Rc<dyn Source>> = HashSet::new();
 			fn recurse_source(sources: &mut HashSet<Rc<dyn Source>>, source: Rc<dyn Source>) {
@@ -483,5 +484,6 @@ pub trait Parser {
 			});
 			report.eprint(ariadne::sources(cache)).unwrap()
 		}
+		*/
 	}
 }
diff --git a/src/parser/reports.rs b/src/parser/reports.rs
index bea0f8e..419b19b 100644
--- a/src/parser/reports.rs
+++ b/src/parser/reports.rs
@@ -3,7 +3,7 @@ use std::{ops::Range, rc::Rc};
 use super::{parser::Parser, source::{Source, SourcePosition, Token}};
 
 #[derive(Debug)]
-enum ReportKind
+pub enum ReportKind
 {
 	Error,
 	Warning,
@@ -21,18 +21,20 @@ impl Into<ariadne::ReportKind<'static>> for &ReportKind
 }
 
 #[derive(Debug)]
-struct ReportSpan
+pub struct ReportSpan
 {
 	pub token: Token,
 	pub message: String
 }
 
 #[derive(Debug)]
-struct Report
+pub struct Report
 {
 	pub kind: ReportKind,
 	pub source: Rc<dyn Source>,
 	pub message: String,
+	pub note: Option<String>,
+	pub help: Option<String>,
 	pub spans: Vec<ReportSpan>,
 }
 
@@ -86,36 +88,69 @@ impl Report
 	}
 }
 
-macro_rules! report_label {
-	($spans:expr, $psource:expr,) => {{ }};
-	($spans:expr, $psource:expr, span($source:expr, $range:expr, $message:expr), $(, $($tail:tt)*)?) => {{
-		$spans.push(ReportSpan {
-			token: Token::new($range, $source),
-			message: $message,
-		});
-		report_label!($spans, $psource, $($($tail)*)?);
-	}};
-	($spans:expr, $psource:expr, span($range:expr, $message:expr) $(, $($tail:tt)*)?) => {{
-		$spans.push(ReportSpan {
-			token: Token::new($range, $psource),
-			message: $message,
-		});
-		report_label!($spans, $psource, $($($tail)*)?);
-	}}
-}
-
+pub mod macros
+{
+	pub use super::*;
 #[macro_export]
-macro_rules! report_err {
-	($reports:expr, $source:expr, $message:expr, $($tail:tt)*) => {{
-		let mut spans = Vec::new();
-		report_label!(spans, $source.clone(), $($tail)*);
-		$reports.push(Report {
-			kind: ReportKind::Error,
-			source: $source,
-			message: $message,
-			spans,
-		});
-	}}
+	macro_rules! report_label {
+		($r:expr,) => {{ }};
+		($r:expr, span($source:expr, $range:expr, $message:expr), $(, $($tail:tt)*)?) => {{
+			$r.spans.push(ReportSpan {
+				token: crate::parser::source::Token::Token::new($range, $source),
+				message: $message,
+			});
+			report_label!($r, $($($tail)*)?);
+		}};
+		($r:expr, span($range:expr, $message:expr) $(, $($tail:tt)*)?) => {{
+			$r.spans.push(ReportSpan {
+				token: crate::parser::source::Token::new($range, $r.source.clone()),
+				message: $message,
+			});
+			report_label!($r, $($($tail)*)?);
+		}};
+		($r:expr, note($message:expr) $(, $($tail:tt)*)?) => {{
+			$r.note = Some($message);
+			report_label!($r, $($($tail)*)?);
+		}};
+		($r:expr, help($message:expr) $(, $($tail:tt)*)?) => {{
+			$r.help = Some($message);
+			report_label!($r, $($($tail)*)?);
+		}}
+	}
+
+	#[macro_export]
+	macro_rules! report_err {
+		($reports:expr, $source:expr, $message:expr, $($tail:tt)*) => {{
+			let mut r = Report {
+				kind: ReportKind::Error,
+				source: $source,
+				message: $message,
+				note: None,
+				help: None,
+				spans: vec![],
+			};
+			report_label!(r, $($tail)*);
+			$reports.push(r);
+		}}
+	}
+
+	#[macro_export]
+	macro_rules! report_warn {
+		($reports:expr, $source:expr, $message:expr, $($tail:tt)*) => {{
+			let mut r = Report {
+				kind: ReportKind::Warning,
+				source: $source,
+				message: $message,
+				note: None,
+				help: None,
+				spans: vec![],
+			};
+			report_label!(r, $($tail)*);
+			$reports.push(r);
+		}}
+	}
+
+	pub use crate::*;
 }
 
 #[cfg(test)]
@@ -140,7 +175,6 @@ Dolor
 		));
 
 		let mut reports = vec![];
-
 		//let la = report_label!(source.clone(), 5..9, "Msg".into());
 		report_err!(&mut reports, source.clone(), "Some message".into(),
 			span(5..9, "Msg".into()),
diff --git a/src/parser/rule.rs b/src/parser/rule.rs
index f27ee2c..5759550 100644
--- a/src/parser/rule.rs
+++ b/src/parser/rule.rs
@@ -1,12 +1,12 @@
 use super::layout::LayoutHolder;
 use super::parser::ParseMode;
 use super::parser::ParserState;
+use super::reports::Report;
 use super::source::Cursor;
 use super::source::Source;
 use super::source::Token;
 use super::style::StyleHolder;
 use crate::document::document::Document;
-use ariadne::Report;
 use downcast_rs::impl_downcast;
 use downcast_rs::Downcast;
 use mlua::Function;
@@ -88,7 +88,7 @@ pub trait Rule: Downcast {
 		document: &'a (dyn Document<'a> + 'a),
 		cursor: Cursor,
 		match_data: Box<dyn Any>,
-	) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>);
+	) -> (Cursor, Vec<Report>);
 
 	/// Registers lua bindings
 	fn register_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
@@ -128,7 +128,7 @@ pub trait RegexRule {
 		document: &'a (dyn Document<'a> + 'a),
 		token: Token,
 		matches: regex::Captures,
-	) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>;
+	) -> Vec<Report>;
 
 	fn register_bindings<'lua>(&self, _lua: &'lua Lua) -> Vec<(String, Function<'lua>)> { vec![] }
 	fn register_styles(&self, _holder: &mut StyleHolder) {}
@@ -175,7 +175,7 @@ impl<T: RegexRule + 'static> Rule for T {
 		document: &'a (dyn Document<'a> + 'a),
 		cursor: Cursor,
 		match_data: Box<dyn Any>,
-	) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
+	) -> (Cursor, Vec<Report>) {
 		let content = cursor.source.content();
 		let index = match_data.downcast::<usize>().unwrap();
 		let re = &self.regexes()[*index];
diff --git a/src/parser/state.rs b/src/parser/state.rs
index 4476755..79707cc 100644
--- a/src/parser/state.rs
+++ b/src/parser/state.rs
@@ -1,16 +1,14 @@
 use std::cell::RefCell;
 use std::collections::HashMap;
-use std::ops::Range;
 use std::rc::Rc;
 
-use ariadne::Report;
 use downcast_rs::impl_downcast;
 use downcast_rs::Downcast;
 
 use crate::document::document::Document;
 
 use super::parser::ParserState;
-use super::source::Source;
+use super::reports::Report;
 
 /// Scope for state objects
 #[derive(PartialEq, PartialOrd, Debug)]
@@ -30,11 +28,11 @@ pub trait RuleState: Downcast {
 	fn scope(&self) -> Scope;
 
 	/// Callback called when state goes out of scope
-	fn on_remove<'a>(
+	fn on_remove(
 		&self,
 		state: &ParserState,
 		document: &dyn Document,
-	) -> Vec<Report<'a, (Rc<dyn Source>, Range<usize>)>>;
+	) -> Vec<Report>;
 }
 impl_downcast!(RuleState);
 
@@ -72,7 +70,7 @@ impl RuleStateHolder {
 		state: &ParserState,
 		document: &dyn Document,
 		scope: Scope,
-	) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
+	) -> Vec<Report> {
 		let mut reports = vec![];
 
 		self.states.retain(|_name, rule_state| {