Repository: huff-language/huff-rs
Pull request #145 (2022-06-29 03:17:48)

fix(errors): Propagation
#### Checklist

- [x] Error Propagation #143 
- [x] Loosened Keyword Requirements #44  

#### Reproducing

`import.huff`
```huff
#define macro SOME_RANDOM_MACRO() = takes(2) returns (1) {
    // Store the keys in memory
    dup1 0x00 mstore
    swap1 dup1 0x00 mstore

    // Hash the data, generating a key.
    0x40 sha3
}
```

`error.huff`
```huff
#include "./import.huff"

#define function addressGetter() internal returns (address)
```

**Prior to these fixes**, compiling `error.huff` produced the error:

<img width="453" alt="Screen Shot 2022-06-28 at 4 51 54 PM" src="https://user-images.githubusercontent.com/21288394/176285518-004ce963-e371-4176-ac76-dbb3d248a279.png">

**Now**, with this pr's source segment fixes, propagation, and hints, the error produced is:

<img width="449" alt="Screen Shot 2022-06-29 at 9 24 46 AM" src="https://user-images.githubusercontent.com/21288394/176447357-e9ae0f0c-236b-4880-9560-27100be0cbfa.png">




Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 97a939a..ee78d1b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -688,6 +688,7 @@ dependencies = [
  "huff_parser",
  "huff_utils",
  "proptest",
+ "rand",
  "rayon",
  "serde_json",
  "tracing",
diff --git a/huff_core/Cargo.toml b/huff_core/Cargo.toml
index 81d8838..c910371 100644
--- a/huff_core/Cargo.toml
+++ b/huff_core/Cargo.toml
@@ -28,6 +28,7 @@ walkdir = "2"
 
 [dev-dependencies]
 criterion = "0.3.5"
+rand = "0.8.5"
 
 [[bench]]
 name = "huff_benchmark"
diff --git a/huff_lexer/src/lib.rs b/huff_lexer/src/lib.rs
index f1c3a65..f2f19cc 100644
--- a/huff_lexer/src/lib.rs
+++ b/huff_lexer/src/lib.rs
@@ -185,6 +185,15 @@ impl<'a> Lexer<'a> {
         chars.iter().collect()
     }
 
+    /// Dynamically peeks until with last chec and checks
+    pub fn checked_lookforward(&mut self, ch: char) -> bool {
+        let mut current_pos = self.current_span().end;
+        while self.nth_peek(current_pos).map(|c| c.is_ascii_whitespace()).unwrap_or(false) {
+            current_pos += 1;
+        }
+        self.nth_peek(current_pos).map(|x| x == ch).unwrap_or(false)
+    }
+
     /// Try to peek at the nth character from the source
     pub fn nth_peek(&mut self, n: usize) -> Option<char> {
         self.reference_chars.clone().nth(n)
@@ -294,8 +303,8 @@ impl<'a> Lexer<'a> {
             Some(TokenKind::Takes) => self.checked_lookback(TokenKind::Assign),
             Some(TokenKind::Returns) => {
                 let cur_span_end = self.current_span().end;
-                // Allow for loose and tight syntax (e.g. `returns (0)` & `returns(0)`)
-                self.peek_n_chars_from(2, cur_span_end).trim().starts_with('(') &&
+                // Allow for loose and tight syntax (e.g. `returns   (0)`, `returns(0)`, ...)
+                self.checked_lookforward('(') &&
                     !self.checked_lookback(TokenKind::Function) &&
                     self.peek_n_chars_from(1, cur_span_end) != ":"
             }
@@ -678,6 +687,7 @@ impl<'a> Iterator for Lexer<'a> {
                 Some(s) => s,
                 None => {
                     tracing::warn!(target: "lexer", "UNABLE TO RELATIVIZE SPAN FOR \"{}\"", kind);
+                    tracing::warn!(target: "lexer", "Current Span: {:?}", self.current_span());
                     self.current_span().clone()
                 }
             };
diff --git a/huff_parser/src/lib.rs b/huff_parser/src/lib.rs
index 4c25c56..28e8175 100644
--- a/huff_parser/src/lib.rs
+++ b/huff_parser/src/lib.rs
@@ -102,8 +102,9 @@ impl Parser {
                         self.current_token.kind
                     );
                     return Err(ParserError {
-                        kind: ParserErrorKind::InvalidDefinition,
-                        spans: AstSpan(self.spans.clone()),
+                        kind: ParserErrorKind::InvalidDefinition(self.current_token.kind.clone()),
+                        hint: Some("Definition must be one of: `function`, `event`, `constant`, or `macro`.".to_string()),
+                        spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
             };
@@ -127,7 +128,8 @@ impl Parser {
                 let new_spans = self.spans.clone();
                 self.spans = vec![];
                 return Err(ParserError {
-                    kind: ParserErrorKind::InvalidName(tok),
+                    kind: ParserErrorKind::InvalidName(tok.clone()),
+                    hint: Some(format!("Expected import string. Got: \"{}\"", tok)),
                     spans: AstSpan(new_spans),
                 })
             }
@@ -153,6 +155,7 @@ impl Parser {
             self.spans = vec![];
             return Err(ParserError {
                 kind: ParserErrorKind::InvalidImportPath(p),
+                hint: None,
                 spans: AstSpan(new_spans),
             })
         }
@@ -169,7 +172,8 @@ impl Parser {
         } else {
             tracing::error!(target: "parser", "TOKEN MISMATCH - EXPECTED: {}, GOT: {}", kind, self.current_token.kind);
             Err(ParserError {
-                kind: ParserErrorKind::UnexpectedType(kind),
+                kind: ParserErrorKind::UnexpectedType(self.current_token.kind.clone()),
+                hint: Some(format!("Expected: \"{}\"", kind)),
                 spans: AstSpan(self.spans.clone()),
             })
         }
@@ -230,7 +234,8 @@ impl Parser {
             _ => {
                 tracing::error!(target: "parser", "TOKEN MISMATCH - EXPECTED IDENT, GOT: {}", tok);
                 return Err(ParserError {
-                    kind: ParserErrorKind::InvalidName(tok),
+                    kind: ParserErrorKind::InvalidName(tok.clone()),
+                    hint: Some(format!("Expected function name, found: \"{}\"", tok)),
                     spans: AstSpan(self.spans.clone()),
                 })
             }
@@ -247,7 +252,10 @@ impl Parser {
             tok => {
                 return Err(ParserError {
                     kind: ParserErrorKind::UnexpectedType(tok),
-                    spans: AstSpan(self.spans.clone()),
+                    hint: Some(
+                        "Expected one of: `view`, `pure`, `payable`, `nonpayable`.".to_string(),
+                    ),
+                    spans: AstSpan(vec![self.current_token.span.clone()]),
                 })
             }
         };
@@ -290,7 +298,8 @@ impl Parser {
             _ => {
                 tracing::error!(target: "parser", "TOKEN MISMATCH - EXPECTED IDENT, GOT: {}", tok);
                 return Err(ParserError {
-                    kind: ParserErrorKind::InvalidName(tok),
+                    kind: ParserErrorKind::InvalidName(tok.clone()),
+                    hint: Some(format!("Expected event name, found: \"{}\"", tok)),
                     spans: AstSpan(self.spans.clone()),
                 })
             }
@@ -318,6 +327,7 @@ impl Parser {
                 self.spans = vec![];
                 return Err(ParserError {
                     kind: ParserErrorKind::UnexpectedType(tok),
+                    hint: Some("Expected constant name.".to_string()),
                     spans: AstSpan(new_spans),
                 })
             }
@@ -337,11 +347,13 @@ impl Parser {
             }
             kind => {
                 tracing::error!(target: "parser", "TOKEN MISMATCH - EXPECTED FreeStoragePointer OR Literal, GOT: {}", self.current_token.kind);
-                let new_spans = self.spans.clone();
-                self.spans = vec![];
                 return Err(ParserError {
                     kind: ParserErrorKind::InvalidConstantValue(kind),
-                    spans: AstSpan(new_spans),
+                    hint: Some(
+                        "Expected constant value to be a literal or `FREE_STORAGE_POINTER()`"
+                            .to_string(),
+                    ),
+                    spans: AstSpan(vec![self.current_token.span.clone()]),
                 })
             }
         };
@@ -489,6 +501,7 @@ impl Parser {
                     tracing::error!(target: "parser", "TOKEN MISMATCH - MACRO BODY: {}", kind);
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidTokenInMacroBody(kind),
+                        hint: None,
                         spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
@@ -499,7 +512,6 @@ impl Parser {
         Ok(statements)
     }
 
-    // TODO: Better label scoping
     /// Parse the body of a label.
     ///
     /// ## Examples
@@ -584,11 +596,11 @@ impl Parser {
                     });
                 }
                 kind => {
-                    let curr_spans = vec![self.current_token.span.clone()];
                     tracing::error!(target: "parser", "TOKEN MISMATCH - LABEL BODY: {}", kind);
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidTokenInLabelDefinition(kind),
-                        spans: AstSpan(curr_spans),
+                        hint: None,
+                        spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
             };
@@ -665,6 +677,7 @@ impl Parser {
             _ => {
                 return Err(ParserError {
                     kind: ParserErrorKind::InvalidSingleArg(self.current_token.kind.clone()),
+                    hint: Some("Expected number representing stack item count.".to_string()),
                     spans: AstSpan(single_arg_span),
                 })
             }
@@ -712,6 +725,10 @@ impl Parser {
                     self.spans = vec![];
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidMacroArgs(arg),
+                        hint: Some(
+                            "Expected literal, identifier (string), or an argument call"
+                                .to_string(),
+                        ),
                         spans: AstSpan(new_spans),
                     })
                 }
@@ -794,6 +811,7 @@ impl Parser {
                     tracing::error!("Invalid Table Body Token: {:?}", self.current_token.kind);
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidTableBodyToken(kind.clone()),
+                        hint: Some("Expected an identifier string.".to_string()),
                         spans: AstSpan(new_spans),
                     })
                 }
@@ -820,6 +838,7 @@ impl Parser {
                 self.spans = vec![];
                 Err(ParserError {
                     kind: ParserErrorKind::InvalidConstant(kind),
+                    hint: None,
                     spans: AstSpan(new_spans),
                 })
             }
@@ -851,6 +870,7 @@ impl Parser {
                 self.spans = vec![];
                 Err(ParserError {
                     kind: ParserErrorKind::InvalidArgCallIdent(kind),
+                    hint: None,
                     spans: AstSpan(new_spans),
                 })
             }
@@ -879,6 +899,7 @@ impl Parser {
             }
             kind => Err(ParserError {
                 kind: ParserErrorKind::InvalidArgs(kind),
+                hint: None,
                 spans: AstSpan(vec![self.current_token.span.clone()]),
             }),
         }
@@ -895,6 +916,7 @@ impl Parser {
                 if !(8..=256).contains(&size) || size % 8 != 0 {
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidUint256(size),
+                        hint: None,
                         spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
@@ -904,6 +926,7 @@ impl Parser {
                 if !(1..=32).contains(&size) {
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidBytes(size),
+                        hint: None,
                         spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
@@ -917,6 +940,7 @@ impl Parser {
                 if !(8..=256).contains(&size) || size % 8 != 0 {
                     return Err(ParserError {
                         kind: ParserErrorKind::InvalidInt(size),
+                        hint: None,
                         spans: AstSpan(vec![self.current_token.span.clone()]),
                     })
                 }
diff --git a/huff_utils/src/abi.rs b/huff_utils/src/abi.rs
index a8caf45..ed28c43 100644
--- a/huff_utils/src/abi.rs
+++ b/huff_utils/src/abi.rs
@@ -32,7 +32,6 @@
 //!
 //! // Create an ABI using that generate contract
 //! let abi: Abi = contract.into();
-//! println!("Abi instant: {:?}", abi);
 //! ```
 
 use serde::{Deserialize, Serialize};
diff --git a/huff_utils/src/ast.rs b/huff_utils/src/ast.rs
index de818e5..2a3899d 100644
--- a/huff_utils/src/ast.rs
+++ b/huff_utils/src/ast.rs
@@ -28,7 +28,7 @@ pub struct AstSpan(pub Vec<Span>);
 
 impl AstSpan {
     /// Coalesce Multiple Spans Into an error string
-    pub fn error(&self) -> String {
+    pub fn error(&self, hint: Option<&String>) -> String {
         let file_to_source_map =
             self.0.iter().fold(BTreeMap::<String, Vec<&Span>>::new(), |mut m, s| {
                 let file_name =
@@ -38,29 +38,38 @@ impl AstSpan {
                 m.insert(file_name, new_vec);
                 m
             });
-        file_to_source_map.iter().filter(|fs| !fs.0.is_empty()).fold("".to_string(), |s, fs| {
-            let start = fs.1.iter().map(|fs2| fs2.start).min().unwrap_or(0);
-            let end = fs.1.iter().map(|fs2| fs2.end).max().unwrap_or(0);
-            let newline_s = if s.is_empty() { "".to_string() } else { format!("{}\n", s) };
-            if start.eq(&0) && end.eq(&0) {
-                format!("{}-> {}:{}\n   > 0|", newline_s, fs.0, start)
-            } else {
-                format!(
-                    "{}-> {}:{}-{}{}",
-                    newline_s,
-                    fs.0,
-                    start,
-                    end,
-                    fs.1.iter()
-                        .map(|sp| sp.source_seg())
-                        .filter(|ss| !ss.is_empty())
-                        .collect::<Vec<String>>()
-                        .into_iter()
-                        .unique()
-                        .fold("".to_string(), |acc, ss| { format!("{}{}", acc, ss) })
-                )
-            }
-        })
+        let source_str = file_to_source_map.iter().filter(|fs| !fs.0.is_empty()).fold(
+            "".to_string(),
+            |s, fs| {
+                let start = fs.1.iter().map(|fs2| fs2.start).min().unwrap_or(0);
+                let end = fs.1.iter().map(|fs2| fs2.end).max().unwrap_or(0);
+                let newline_s = if s.is_empty() { "".to_string() } else { format!("{}\n", s) };
+                if start.eq(&0) && end.eq(&0) {
+                    format!("{}-> {}:{}\n   > 0|", newline_s, fs.0, start)
+                } else {
+                    format!(
+                        "{}-> {}:{}-{}{}",
+                        newline_s,
+                        fs.0,
+                        start,
+                        end,
+                        fs.1.iter()
+                            .map(|sp| sp.source_seg())
+                            .filter(|ss| !ss.is_empty())
+                            .collect::<Vec<String>>()
+                            .into_iter()
+                            .unique()
+                            .fold("".to_string(), |acc, ss| { format!("{}{}", acc, ss) })
+                    )
+                }
+            },
+        );
+        // Add in optional hint message
+        format!(
+            "{}{}",
+            hint.map(|msg| format!("{}\n", /* " ".repeat(7), */ msg)).unwrap_or_default(),
+            source_str
+        )
     }
 
     /// Print just the file for missing
diff --git a/huff_utils/src/error.rs b/huff_utils/src/error.rs
index 4bbbfb9..253de72 100644
--- a/huff_utils/src/error.rs
+++ b/huff_utils/src/error.rs
@@ -12,6 +12,8 @@ use std::{ffi::OsString, fmt, io::Write};
 pub struct ParserError {
     /// The type of Parser Error
     pub kind: ParserErrorKind,
+    /// Hints about the error
+    pub hint: Option<String>,
     /// A collection of spans the Parser Error crosses
     pub spans: AstSpan,
 }
@@ -19,12 +21,10 @@ pub struct ParserError {
 /// A Type of Parser Error
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
 pub enum ParserErrorKind {
-    /// A general syntax error that accepts a message
-    SyntaxError(String),
     /// Unexpected type
     UnexpectedType(TokenKind),
     /// Invalid definition
-    InvalidDefinition,
+    InvalidDefinition(TokenKind),
     /// Invalid constant value
     InvalidConstantValue(TokenKind),
     /// Unexpected token in macro body
@@ -263,21 +263,28 @@ impl<'a> fmt::Display for CompilerError<'a> {
                 }
             },
             CompilerError::ParserError(pe) => match &pe.kind {
-                ParserErrorKind::SyntaxError(se) => {
-                    write!(f, "\nError: Syntax Error: \"{}\" \n{}\n", se, pe.spans.error())
-                }
                 ParserErrorKind::UnexpectedType(ut) => {
-                    write!(f, "\nError: Unexpected Type: \"{}\" \n{}\n", ut, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Unexpected Type: \"{}\" \n{}\n",
+                        ut,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
-                ParserErrorKind::InvalidDefinition => {
-                    write!(f, "\nError: Invalid Defintiion\n{}\n", pe.spans.error())
+                ParserErrorKind::InvalidDefinition(k) => {
+                    write!(
+                        f,
+                        "\nError: Invalid Defintion \"{}\"\n{}\n",
+                        k,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidConstantValue(cv) => {
                     write!(
                         f,
                         "\nError: Invalid Constant Value: \"{}\" \n{}\n",
                         cv,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidTokenInMacroBody(tmb) => {
@@ -285,7 +292,7 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         f,
                         "\nError: Invalid Token In Macro Body: \"{}\" \n{}\n",
                         tmb,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidTokenInLabelDefinition(tlb) => {
@@ -293,18 +300,23 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         f,
                         "\nError: Invalid Token In Label Defintiion: \"{}\" \n{}\n",
                         tlb,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidSingleArg(sa) => {
-                    write!(f, "\nError: Invalid Argument: \"{}\" \n{}\n", sa, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Argument: \"{}\" \n{}\n",
+                        sa,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidTableBodyToken(tbt) => {
                     write!(
                         f,
                         "\nError: Invalid Token In Table Body: \"{}\" \n{}\n",
                         tbt,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidConstant(constant) => {
@@ -312,7 +324,7 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         f,
                         "\nError: Invalid Constant: \"{}\" \n{}\n",
                         constant,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidArgCallIdent(aci) => {
@@ -320,37 +332,71 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         f,
                         "\nError: Invalid Argument Call Identifier: \"{}\" \n{}\n",
                         aci,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidName(name) => {
-                    write!(f, "\nError: Invalid Name: \"{}\" \n{}\n", name, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Name: \"{}\" \n{}\n",
+                        name,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidArgs(args) => {
-                    write!(f, "\nError: Invalid Arguments: \"{}\" \n{}\n", args, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Argument Type: \"{}\" \n{}\n",
+                        args,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidUint256(v) => {
-                    write!(f, "\nError: Invalid Uint256 Value: \"{}\" \n{}\n", v, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Uint256 Value: \"{}\" \n{}\n",
+                        v,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidBytes(b) => {
-                    write!(f, "\nError: Invalid Bytes Value: \"{}\" \n{}\n", b, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Bytes Value: \"{}\" \n{}\n",
+                        b,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidInt(i) => {
-                    write!(f, "\nError: Invalid Int Value: \"{}\" \n{}\n", i, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Int Value: \"{}\" \n{}\n",
+                        i,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidMacroArgs(ma) => {
                     write!(
                         f,
                         "\nError: Invalid Macro Arguments: \"{}\" \n{}\n",
                         ma,
-                        pe.spans.error()
+                        pe.spans.error(pe.hint.as_ref())
                     )
                 }
                 ParserErrorKind::InvalidReturnArgs => {
-                    write!(f, "\nError: Invalid Return Arguments\n{}\n", pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Return Arguments\n{}\n",
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
                 ParserErrorKind::InvalidImportPath(ip) => {
-                    write!(f, "\nError: Invalid Import Path: \"{}\" \n{}\n", ip, pe.spans.error())
+                    write!(
+                        f,
+                        "\nError: Invalid Import Path: \"{}\" \n{}\n",
+                        ip,
+                        pe.spans.error(pe.hint.as_ref())
+                    )
                 }
             },
             CompilerError::PathBufRead(os_str) => {
@@ -362,10 +408,10 @@ impl<'a> fmt::Display for CompilerError<'a> {
             }
             CompilerError::CodegenError(ce) => match &ce.kind {
                 CodegenErrorKind::StoragePointersNotDerived => {
-                    write!(f, "\nError: Storage Pointers Not Derived\n{}\n", ce.span.error())
+                    write!(f, "\nError: Storage Pointers Not Derived\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::InvalidMacroStatement => {
-                    write!(f, "\nError: Invalid Macro Statement\n{}\n", ce.span.error())
+                    write!(f, "\nError: Invalid Macro Statement\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::MissingMacroDefinition(md) => {
                     write!(
@@ -380,34 +426,34 @@ impl<'a> fmt::Display for CompilerError<'a> {
                         f,
                         "\nError: Missing Macro Definition For Invocation: \"{}\"\n{}\n",
                         mmi,
-                        ce.span.error()
+                        ce.span.error(None)
                     )
                 }
                 CodegenErrorKind::MissingConstantDefinition(_) => {
-                    write!(f, "\nError: Missing Constant Definition\n{}\n", ce.span.error())
+                    write!(f, "\nError: Missing Constant Definition\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::AbiGenerationFailure => {
-                    write!(f, "\nError: ABI Generation Failed\n{}\n", ce.span.error())
+                    write!(f, "\nError: ABI Generation Failed\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::IOError(ioe) => {
                     write!(f, "\nError: IO Error: {}\n{}", ioe, ce.span.file())
                 }
                 CodegenErrorKind::UnkownArgcallType => {
-                    write!(f, "\nError: Unknown Arg Call Type\n{}\n", ce.span.error())
+                    write!(f, "\nError: Unknown Arg Call Type\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::MissingMacroInvocation(mmi) => {
                     write!(
                         f,
                         "\nError: Missing Macro Invocation: \"{}\"\n{}\n",
                         mmi,
-                        ce.span.error()
+                        ce.span.error(None)
                     )
                 }
                 CodegenErrorKind::UnmatchedJumpLabel => {
-                    write!(f, "\nError: Unmatched Jump Label\n{}\n", ce.span.error())
+                    write!(f, "\nError: Unmatched Jump Label\n{}\n", ce.span.error(None))
                 }
                 CodegenErrorKind::UsizeConversion(_) => {
-                    write!(f, "\nError: Usize Conversion\n{}\n", ce.span.error())
+                    write!(f, "\nError: Usize Conversion\n{}\n", ce.span.error(None))
                 }
             },
             CompilerError::FailedCompiles(v) => {
diff --git a/huff_utils/src/files.rs b/huff_utils/src/files.rs
index 15ae0bc..ee7a09b 100644
--- a/huff_utils/src/files.rs
+++ b/huff_utils/src/files.rs
@@ -30,7 +30,8 @@ impl<'a> FullFileSource<'a> {
                 file: Some(s.0.clone()),
             })
             .collect::<Vec<Span>>()
-            .pop()
+            .into_iter()
+            .next()
     }
 }
 
@@ -193,7 +194,10 @@ impl Span {
                                 1;
                         let line_start = &s[0..self.start].rfind('\n').unwrap_or(0);
                         let line_end = self.end +
-                            s[self.end..s.len()].find('\n').unwrap_or(s.len()).to_owned();
+                            s[self.end..s.len()]
+                                .find('\n')
+                                .unwrap_or(s.len() - self.end)
+                                .to_owned();
                         let padding =
                             (0..line_num.to_string().len()).map(|_| " ").collect::<String>();
                         format!(


Test patch:
diff --git a/huff_core/tests/errors.rs b/huff_core/tests/codegen_errors.rs
similarity index 87%
rename from huff_core/tests/errors.rs
rename to huff_core/tests/codegen_errors.rs
index d647f47..d7364e8 100644
--- a/huff_core/tests/errors.rs
+++ b/huff_core/tests/codegen_errors.rs
@@ -116,51 +116,6 @@ fn test_invalid_constant_definition() {
     }
 }
 
-#[test]
-fn test_invalid_macro_statement() {
-    let source = r#"
-    #define macro CONSTRUCTOR() = takes(0) returns (0) {}
-
-    #define macro MINT() = takes(0) returns (0) {
-        0x04 calldataload   // [to]
-        0x00                // [from (0x00), to]
-        0x24 calldataload   // [value, from, to]
-
-        FREE_STORAGE_POINTER()
-    }
-
-    #define macro MAIN() = takes(0) returns (0) {
-        0x00 calldataload 0xE0 shr
-        dup1 0x40c10f19 eq mints jumpi
-
-        mints:
-            MINT()
-    }
-    "#;
-
-    let const_start = source.find("FREE_STORAGE_POINTER()").unwrap_or(0);
-    let const_end = const_start + "FREE_STORAGE_POINTER()".len();
-
-    let full_source = FullFileSource { source, file: None, spans: vec![] };
-    let lexer = Lexer::new(full_source);
-    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
-    let mut parser = Parser::new(tokens, Some("".to_string()));
-
-    // This should be caught before codegen invalid macro statement
-    match parser.parse() {
-        Ok(_) => panic!("moose"),
-        Err(e) => {
-            assert_eq!(
-                e,
-                ParserError {
-                    kind: ParserErrorKind::InvalidTokenInMacroBody(TokenKind::FreeStoragePointer),
-                    spans: AstSpan(vec![Span { start: const_start, end: const_end, file: None }]),
-                }
-            )
-        }
-    }
-}
-
 #[test]
 fn test_missing_constructor() {
     let source = r#"
diff --git a/huff_core/tests/parser_errors.rs b/huff_core/tests/parser_errors.rs
new file mode 100644
index 0000000..6dd8b47
--- /dev/null
+++ b/huff_core/tests/parser_errors.rs
@@ -0,0 +1,289 @@
+use huff_lexer::*;
+use huff_parser::*;
+use huff_utils::prelude::*;
+use rand::prelude::*;
+
+#[test]
+fn test_invalid_macro_statement() {
+    let source = r#"
+    #define macro CONSTRUCTOR() = takes(0) returns (0) {}
+
+    #define macro MINT() = takes(0) returns (0) {
+        0x04 calldataload   // [to]
+        0x00                // [from (0x00), to]
+        0x24 calldataload   // [value, from, to]
+
+        FREE_STORAGE_POINTER()
+    }
+
+    #define macro MAIN() = takes(0) returns (0) {
+        0x00 calldataload 0xE0 shr
+        dup1 0x40c10f19 eq mints jumpi
+
+        mints:
+            MINT()
+    }
+    "#;
+
+    let const_start = source.find("FREE_STORAGE_POINTER()").unwrap_or(0);
+    let const_end = const_start + "FREE_STORAGE_POINTER()".len();
+
+    let full_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(full_source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, Some("".to_string()));
+
+    // This should be caught before codegen invalid macro statement
+    match parser.parse() {
+        Ok(_) => panic!("moose"),
+        Err(e) => {
+            assert_eq!(
+                e,
+                ParserError {
+                    kind: ParserErrorKind::InvalidTokenInMacroBody(TokenKind::FreeStoragePointer),
+                    hint: None,
+                    spans: AstSpan(vec![Span { start: const_start, end: const_end, file: None }]),
+                }
+            )
+        }
+    }
+}
+
+#[test]
+fn test_unexpected_type() {
+    let source = "#define function func() internal returns ()";
+
+    let full_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(full_source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, Some("".to_string()));
+
+    match parser.parse() {
+        Ok(_) => panic!("moose"),
+        Err(e) => {
+            assert_eq!(
+                e,
+                ParserError {
+                    kind: ParserErrorKind::UnexpectedType(TokenKind::Ident("internal".to_string())),
+                    hint: Some(
+                        "Expected one of: `view`, `pure`, `payable`, `nonpayable`.".to_string(),
+                    ),
+                    spans: AstSpan(vec![Span {
+                        start: source.find("internal").unwrap_or(0),
+                        end: source.find("internal").unwrap_or(0) + "internal".len(),
+                        file: None
+                    }]),
+                }
+            )
+        }
+    }
+}
+
+#[test]
+fn test_invalid_definition() {
+    let source = "#define test func() returns ()";
+
+    let full_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(full_source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, Some("".to_string()));
+
+    match parser.parse() {
+        Ok(_) => panic!("moose"),
+        Err(e) => {
+            assert_eq!(
+                e,
+                ParserError {
+                    kind: ParserErrorKind::InvalidDefinition(TokenKind::Ident("test".to_string())),
+                    hint: Some(
+                        "Definition must be one of: `function`, `event`, `constant`, or `macro`."
+                            .to_string()
+                    ),
+                    spans: AstSpan(vec![Span {
+                        start: source.find("test").unwrap_or(0),
+                        end: source.find("test").unwrap_or(0) + "test".len(),
+                        file: None
+                    }]),
+                }
+            )
+        }
+    }
+}
+
+#[test]
+fn test_invalid_constant_value() {
+    let invalid_constant_values = vec![
+        ("ident", TokenKind::Ident("ident".to_string())),
+        ("<", TokenKind::LeftAngle),
+        ("{", TokenKind::OpenBrace),
+        ("[", TokenKind::OpenBracket),
+        ("(", TokenKind::OpenParen),
+        (":", TokenKind::Colon),
+        (",", TokenKind::Comma),
+        ("+", TokenKind::Add),
+        ("-", TokenKind::Sub),
+    ];
+
+    for (value, kind) in invalid_constant_values {
+        let source = &format!("#define constant CONSTANT = {}", value);
+
+        let full_source = FullFileSource { source, file: None, spans: vec![] };
+        let lexer = Lexer::new(full_source);
+        let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+        let mut parser = Parser::new(tokens, Some("".to_string()));
+
+        match parser.parse() {
+            Ok(_) => panic!("moose"),
+            Err(e) => {
+                assert_eq!(
+                    e,
+                    ParserError {
+                        kind: ParserErrorKind::InvalidConstantValue(kind),
+                        hint: Some(
+                            "Expected constant value to be a literal or `FREE_STORAGE_POINTER()`"
+                                .to_string()
+                        ),
+                        spans: AstSpan(vec![Span {
+                            start: source.find(value).unwrap_or(0),
+                            end: source.find(value).unwrap_or(0) + value.len(),
+                            file: None
+                        }]),
+                    }
+                )
+            }
+        }
+    }
+}
+
+#[test]
+fn test_invalid_token_in_macro_body() {
+    let invalids = vec![
+        ("{", TokenKind::OpenBrace),
+        ("(", TokenKind::OpenParen),
+        (":", TokenKind::Colon),
+        (",", TokenKind::Comma),
+        ("+", TokenKind::Add),
+        ("-", TokenKind::Sub),
+        ("/", TokenKind::Div),
+    ];
+
+    for (value, kind) in invalids {
+        let source = &format!(
+            r#"#define macro CONSTANT() = takes (0) returns (0) {{
+            {}
+        }}"#,
+            value
+        );
+
+        let full_source = FullFileSource { source, file: None, spans: vec![] };
+        let lexer = Lexer::new(full_source);
+        let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+        let mut parser = Parser::new(tokens, Some("".to_string()));
+
+        match parser.parse() {
+            Ok(_) => panic!("moose"),
+            Err(e) => {
+                assert_eq!(
+                    e,
+                    ParserError {
+                        kind: ParserErrorKind::InvalidTokenInMacroBody(kind),
+                        hint: None,
+                        spans: AstSpan(vec![Span {
+                            start: source.rfind(value).unwrap_or(0),
+                            end: source.rfind(value).unwrap_or(0) + value.len(),
+                            file: None
+                        }]),
+                    }
+                )
+            }
+        }
+    }
+}
+
+#[test]
+fn test_invalid_token_in_label_definition() {
+    let invalids = vec![
+        ("{", TokenKind::OpenBrace),
+        ("(", TokenKind::OpenParen),
+        (":", TokenKind::Colon),
+        (",", TokenKind::Comma),
+        ("+", TokenKind::Add),
+        ("-", TokenKind::Sub),
+        ("/", TokenKind::Div),
+    ];
+
+    for (value, kind) in invalids {
+        let source = &format!(
+            r#"#define macro CONSTANT() = takes (0) returns (0) {{
+            lab:
+                {}
+        }}"#,
+            value
+        );
+
+        let full_source = FullFileSource { source, file: None, spans: vec![] };
+        let lexer = Lexer::new(full_source);
+        let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+        let mut parser = Parser::new(tokens, Some("".to_string()));
+
+        match parser.parse() {
+            Ok(_) => panic!("moose"),
+            Err(e) => {
+                assert_eq!(
+                    e,
+                    ParserError {
+                        kind: ParserErrorKind::InvalidTokenInLabelDefinition(kind),
+                        hint: None,
+                        spans: AstSpan(vec![Span {
+                            start: source.rfind(value).unwrap_or(0),
+                            end: source.rfind(value).unwrap_or(0) + value.len(),
+                            file: None
+                        }]),
+                    }
+                )
+            }
+        }
+    }
+}
+
+#[test]
+fn test_invalid_single_arg() {
+    for _ in [0..10_000] {
+        let random_char = rand::random::<u8>() as char;
+        if random_char.is_numeric() || !random_char.is_alphabetic() {
+            continue
+        }
+        let source =
+            &format!("#define macro CONSTANT() = takes ({}) returns (0) {{}}", random_char);
+
+        let full_source = FullFileSource { source, file: None, spans: vec![] };
+        let lexer = Lexer::new(full_source);
+        let tokens = lexer
+            .into_iter()
+            .map(|x| match x {
+                Ok(t) => t,
+                Err(_) => {
+                    Token { kind: TokenKind::Add, span: Span { start: 0, end: 0, file: None } }
+                }
+            })
+            .collect::<Vec<Token>>();
+        let mut parser = Parser::new(tokens, Some("".to_string()));
+
+        match parser.parse() {
+            Ok(_) => panic!("moose"),
+            Err(e) => {
+                assert_eq!(
+                    e,
+                    ParserError {
+                        kind: ParserErrorKind::InvalidSingleArg(TokenKind::Ident(format!(
+                            "{}",
+                            random_char
+                        ))),
+                        hint: Some("Expected number representing stack item count.".to_string()),
+                        spans: AstSpan(vec![Span { start: 34, end: 35, file: None }]),
+                    }
+                )
+            }
+        }
+    }
+}
diff --git a/huff_lexer/tests/keywords.rs b/huff_lexer/tests/keywords.rs
index b2c4533..f235a66 100644
--- a/huff_lexer/tests/keywords.rs
+++ b/huff_lexer/tests/keywords.rs
@@ -138,7 +138,7 @@ fn parses_constant_keyword() {
 
 #[test]
 fn parses_takes_and_returns_keywords() {
-    let source = "#define macro TEST() = takes (0) returns (0)";
+    let source = "#define macro TEST() = takes   (0)   returns   (0)";
     let flattened_source = FullFileSource { source, file: None, spans: vec![] };
     let mut lexer = Lexer::new(flattened_source);
 
@@ -170,7 +170,7 @@ fn parses_takes_and_returns_keywords() {
     // Lex Returns
     let tok = lexer.next();
     let unwrapped = tok.unwrap().unwrap();
-    let returns_span = Span::new(33..40, None);
+    let returns_span = Span::new(37..44, None);
     assert_eq!(unwrapped, Token::new(TokenKind::Returns, returns_span.clone()));
     assert_eq!(lexer.current_span().deref(), &returns_span);
 
diff --git a/huff_utils/tests/files.rs b/huff_utils/tests/files.rs
index f5cffb2..edd3ef4 100644
--- a/huff_utils/tests/files.rs
+++ b/huff_utils/tests/files.rs
@@ -1,4 +1,40 @@
-use huff_utils::files::FileSource;
+use std::sync::Arc;
+
+use huff_utils::{files::FileSource, prelude::Span};
+
+#[test]
+fn test_source_seg() {
+    let span = Span {
+        start: 59,
+        end: 67,
+        file: Some(Arc::new(
+            FileSource {
+                id: uuid::Uuid::nil(),
+                path: "./huff-examples/errors/error.huff".to_string(),
+                source: Some("#include \"./import.huff\"\n\n#define function addressGetter() internal returns (address)".to_string()),
+                access: None,
+                dependencies: Some(vec![
+                    Arc::new(FileSource {
+                        id: uuid::Uuid::nil(),
+                        path: "./huff-examples/errors/import.huff".to_string(),
+                        source: Some("#define macro SOME_RANDOM_MACRO() = takes(2) returns (1) {\n    // Store the keys in memory\n    dup1 0x00 mstore\n    swap1 dup1 0x00 mstore\n\n    // Hash the data, generating a key.\n    0x40 sha3\n}\n".to_string()),
+                        access: None,
+                        dependencies: Some(vec![])
+                    })
+                ])
+            }
+        ))
+    };
+
+    let source_seg = span.source_seg();
+    assert_eq!(
+        source_seg,
+        format!(
+            "\n     {}|\n  > {} | {}\n     {}|",
+            " ", 3, "#define function addressGetter() internal returns (address)", " ",
+        )
+    );
+}
 
 #[test]
 fn test_derive_dir() {