Repository: huff-language/huff-rs (rust)

Issue:
feat: Optional Macro Stack Syntax
## Overview

Often-times, developers write macros that take/return no stack items.

This creates unnecessary boilerplate code required by the developer in the form of `= takes (0) returns (0)`.

We should make the `takes` and `returns` annotations optional (NOTE: Maybe also allow for just declaring one?) so `#define macro MAIN()` would be equivalent to `#define macro MAIN() = takes (0) returns (0)`. 

Pull request:
feat(huff_parser): macro optional takes/returns
## Overview

Adds support for optional takes / returns for macros by using `.map_or`, defaulting to 0.
Fixes #192 

Patch:
diff --git a/huff_parser/src/lib.rs b/huff_parser/src/lib.rs
index d9edec9..683e295 100644
--- a/huff_parser/src/lib.rs
+++ b/huff_parser/src/lib.rs
@@ -431,10 +431,12 @@ impl Parser {
 
         let macro_arguments = self.parse_args(true, false, false, false)?;
         self.match_kind(TokenKind::Assign)?;
-        self.match_kind(TokenKind::Takes)?;
-        let macro_takes: usize = self.parse_single_arg()?;
-        self.match_kind(TokenKind::Returns)?;
-        let macro_returns: usize = self.parse_single_arg()?;
+
+        let macro_takes =
+            self.match_kind(TokenKind::Takes).map_or(Ok(0), |_| self.parse_single_arg())?;
+        let macro_returns =
+            self.match_kind(TokenKind::Returns).map_or(Ok(0), |_| self.parse_single_arg())?;
+
         let macro_statements: Vec<Statement> = self.parse_body()?;
 
         Ok(MacroDefinition::new(


Test patch:
diff --git a/huff_parser/tests/macro.rs b/huff_parser/tests/macro.rs
index 9dd24f5..a36536b 100644
--- a/huff_parser/tests/macro.rs
+++ b/huff_parser/tests/macro.rs
@@ -42,6 +42,110 @@ fn empty_macro() {
     assert_eq!(parser.current_token.kind, TokenKind::Eof);
 }
 
+#[test]
+fn empty_macro_without_takes_returns() {
+    let source = "#define macro HELLO_WORLD() = {}";
+    let flattened_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(flattened_source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, None);
+
+    // Grab the first macro
+    let macro_definition = parser.parse().unwrap().macros[0].clone();
+    let expected = MacroDefinition {
+        name: "HELLO_WORLD".to_string(),
+        parameters: vec![],
+        statements: vec![],
+        takes: 0,
+        returns: 0,
+        span: AstSpan(vec![
+            Span { start: 0, end: 7, file: None },
+            Span { start: 8, end: 13, file: None },
+            Span { start: 14, end: 25, file: None },
+            Span { start: 25, end: 26, file: None },
+            Span { start: 26, end: 27, file: None },
+            Span { start: 28, end: 29, file: None },
+            Span { start: 30, end: 31, file: None },
+            Span { start: 31, end: 32, file: None },
+        ]),
+        outlined: false,
+    };
+    assert_eq!(macro_definition, expected);
+    assert_eq!(parser.current_token.kind, TokenKind::Eof);
+}
+
+#[test]
+fn empty_macro_only_takes() {
+    let source = "#define macro HELLO_WORLD() = takes(3) {}";
+    let flattened_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(flattened_source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, None);
+
+    // Grab the first macro
+    let macro_definition = parser.parse().unwrap().macros[0].clone();
+    let expected = MacroDefinition {
+        name: "HELLO_WORLD".to_string(),
+        parameters: vec![],
+        statements: vec![],
+        takes: 3,
+        returns: 0,
+        span: AstSpan(vec![
+            Span { start: 0, end: 7, file: None },
+            Span { start: 8, end: 13, file: None },
+            Span { start: 14, end: 25, file: None },
+            Span { start: 25, end: 26, file: None },
+            Span { start: 26, end: 27, file: None },
+            Span { start: 28, end: 29, file: None },
+            Span { start: 30, end: 35, file: None },
+            Span { start: 35, end: 36, file: None },
+            Span { start: 36, end: 37, file: None },
+            Span { start: 37, end: 38, file: None },
+            Span { start: 39, end: 40, file: None },
+            Span { start: 40, end: 41, file: None },
+        ]),
+        outlined: false,
+    };
+    assert_eq!(macro_definition, expected);
+    assert_eq!(parser.current_token.kind, TokenKind::Eof);
+}
+
+#[test]
+fn empty_macro_only_returns() {
+    let source = "#define macro HELLO_WORLD() = returns(10) {}";
+    let flattened_source = FullFileSource { source, file: None, spans: vec![] };
+    let lexer = Lexer::new(flattened_source);
+    let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
+    let mut parser = Parser::new(tokens, None);
+
+    // Grab the first macro
+    let macro_definition = parser.parse().unwrap().macros[0].clone();
+    let expected = MacroDefinition {
+        name: "HELLO_WORLD".to_string(),
+        parameters: vec![],
+        statements: vec![],
+        takes: 0,
+        returns: 10,
+        span: AstSpan(vec![
+            Span { start: 0, end: 7, file: None },
+            Span { start: 8, end: 13, file: None },
+            Span { start: 14, end: 25, file: None },
+            Span { start: 25, end: 26, file: None },
+            Span { start: 26, end: 27, file: None },
+            Span { start: 28, end: 29, file: None },
+            Span { start: 30, end: 37, file: None },
+            Span { start: 37, end: 38, file: None },
+            Span { start: 38, end: 40, file: None },
+            Span { start: 40, end: 41, file: None },
+            Span { start: 42, end: 43, file: None },
+            Span { start: 43, end: 44, file: None },
+        ]),
+        outlined: false,
+    };
+    assert_eq!(macro_definition, expected);
+    assert_eq!(parser.current_token.kind, TokenKind::Eof);
+}
+
 #[test]
 fn macro_with_simple_body() {
     let source =