commit 176485af79ac8b9c3eada7f4460e7f64035d4040
parent 53018a113f1cd202aa4a36d60bb97d3827f51934
Author: Demonstrandum <samuel@knutsen.co>
Date:   Mon,  2 Dec 2024 00:00:21 +0000
partial: implemented argument parser proc-macro.
seam macros may now be generated by a proc-macro with a special
syntax for specifying the argument schema/format.
this lets me focus on the seam-macro's logic instead of
having 90% of the effort be on parsing and verifying the argument list.
right now the arg-parser's runtime does not do all of the rules
checking for all the rules that it is provided with. will be added soon.
Merge branch 'master' of github.com:Demonstrandum/seam
Diffstat:
10 files changed, 542 insertions(+), 324 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "aho-corasick"
@@ -28,9 +28,9 @@ dependencies = [
 
 [[package]]
 name = "autocfg"
-version = "1.3.0"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 
 [[package]]
 name = "bumpalo"
@@ -40,9 +40,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
 
 [[package]]
 name = "cc"
-version = "1.1.13"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
+checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
 dependencies = [
  "shlex",
 ]
@@ -85,9 +85,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
 [[package]]
 name = "descape"
-version = "1.1.2"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "396a0a312bef78b5f62b0251d7162c4b8af162949b8b104d2967e41b26c1b68c"
+checksum = "214de5502a2fa31149b291f594c1cc0d3929e93c6f4be6842d3944a16a9ef336"
 
 [[package]]
 name = "formatx"
@@ -97,9 +97,9 @@ checksum = "db0f0c49aba98a3b2578315766960bd242885ff672fd62610c5557cd6c6efe03"
 
 [[package]]
 name = "iana-time-zone"
-version = "0.1.60"
+version = "0.1.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys",
@@ -120,9 +120,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.70"
+version = "0.3.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -135,9 +135,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 
 [[package]]
 name = "libc"
-version = "0.2.156"
+version = "0.2.166"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5f43f184355eefb8d17fc948dbecf6c13be3c141f20d834ae842193a448c72a"
+checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36"
 
 [[package]]
 name = "log"
@@ -162,15 +162,15 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.19.0"
+version = "1.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.86"
+version = "1.0.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
 dependencies = [
  "unicode-ident",
 ]
@@ -186,9 +186,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.10.6"
+version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -198,9 +198,9 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.4.7"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -209,9 +209,9 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.8.4"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
 
 [[package]]
 name = "seam"
@@ -228,7 +228,7 @@ dependencies = [
 
 [[package]]
 name = "seam_argparse_proc_macro"
-version = "0.0.0"
+version = "0.1.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -243,9 +243,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
 name = "syn"
-version = "2.0.77"
+version = "2.0.89"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
+checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -254,21 +254,21 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.12"
+version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.13"
+version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -277,9 +277,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
 dependencies = [
  "bumpalo",
  "log",
@@ -292,9 +292,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -302,9 +302,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -315,9 +315,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
 
 [[package]]
 name = "windows-core"
diff --git a/Cargo.toml b/Cargo.toml
@@ -1,2 +1,4 @@
 [workspace]
 members = ["crates/seam", "crates/seam_argparse_proc_macro"]
+resolver = "2"
+package.edition = "2021"
diff --git a/crates/seam/src/lib.rs b/crates/seam/src/lib.rs
@@ -10,7 +10,18 @@ use parse::{expander, parser, lexer};
 
 use std::{fs, io, path::Path};
 
-pub const VERSION: (u8, u8, u8) = (0, 3, 0);
+const fn parse_u8(s: &str) -> u8 {
+    match u8::from_str_radix(s, 10) {
+        Ok(n) => n,
+        Err(_) => panic!("is not a base 10 integer"),
+    }
+}
+
+pub const VERSION: (u8, u8, u8) = (
+    parse_u8(env!("CARGO_PKG_VERSION_MAJOR")),
+    parse_u8(env!("CARGO_PKG_VERSION_MINOR")),
+    parse_u8(env!("CARGO_PKG_VERSION_PATCH")),
+);
 
 /* Utilities. */
 
diff --git a/crates/seam/src/parse/expander.rs b/crates/seam/src/parse/expander.rs
@@ -1,4 +1,3 @@
-use super::macros::*;
 use super::parser::{Node, ParseNode, ParseTree, Parser};
 use super::tokens::Site;
 
@@ -20,14 +19,17 @@ use colored::*;
 use formatx;
 use unicode_width::UnicodeWidthStr;
 
+// proc macros for generating macros.
+use seam_argparse_proc_macro::arguments;
+
 /// Error type for errors while expanding macros.
 #[derive(Debug, Clone)]
 pub struct ExpansionError<'a>(pub String, pub Site<'a>);
 
 impl<'a> ExpansionError<'a> {
     /// Create a new error given the ML, the message, and the site.
-    pub fn new(msg: &str, site: &Site<'a>) -> Self {
-        Self(msg.to_owned(), site.to_owned())
+    pub fn new<S: Into<String>>(msg: S, site: &Site<'a>) -> Self {
+        Self(msg.into(), site.to_owned())
     }
 }
 
@@ -142,6 +144,12 @@ impl<'a> Expander<'a> {
         self.latest_context().unwrap()
     }
 
+    /// Delete a subcontext from the current context.
+    fn remove_subcontext(&self) -> () {
+        let mut contexts = self.subcontexts.borrow_mut();
+        contexts.pop();
+    }
+
     /// Get the latest subparser added.
     fn latest_context(&self) -> Option<&mut Self> {
         let contexts = self.subcontexts.as_ptr();
@@ -430,6 +438,7 @@ impl<'a> Expander<'a> {
 
     fn expand_os_env_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
     -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let params = self.expand_nodes(params)?;
         let [ref var] = *params else {
             return Err(ExpansionError::new(
                 "`%os/env' expects excatly one argument.",
@@ -453,6 +462,7 @@ impl<'a> Expander<'a> {
 
     fn expand_format_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
     -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let params = self.expand_nodes(params)?;
         let [format_str, ..] = &*params else {
             return Err(ExpansionError::new(
                 "`%format' expects at a format-string.",
@@ -593,15 +603,24 @@ impl<'a> Expander<'a> {
 
     fn expand_join_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
     -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        let args: ArgRules = arguments! {
+        let params = self.expand_nodes(params)?; // Eager.
+        let (_parser, args) = arguments! { [¶ms]
             mandatory(1): literal,
-            mandatory(2): number fn(_v: ParseNode) { true },
             optional("trailing"): literal["true", "false"],
             rest: literal,
-        };
-        let arg_parser = args.parser(¶ms);
+        }?;
 
-        todo!()
+        let sep = &args.number.1.value;
+        let trailing = args.trailing.map(|n| n.value == "true").unwrap_or(false);
+        let items: Vec<&str> = args.rest.iter().map(|n| n.value.as_str()).collect();
+        let joined = items.join(sep) + if trailing { sep } else { "" };
+        Ok(Box::new([
+            ParseNode::String(Node {
+                value: joined,
+                site: node.owned_site(),
+                leading_whitespace: node.leading_whitespace().to_owned(),
+            })
+        ]))
     }
 
     fn expand_macro(&self, name: &str, node: &ParseNode<'a>, params: ParseTree<'a>)
@@ -638,6 +657,8 @@ impl<'a> Expander<'a> {
         if let Some(first_node) = expanded.get_mut(0) {
             first_node.set_leading_whitespace(node.leading_whitespace().to_owned());
         }
+        // Finished expanding macro, delete the subcontext.
+        self.remove_subcontext();
         Ok(expanded.into_boxed_slice())
     }
 
diff --git a/crates/seam/src/parse/lexer.rs b/crates/seam/src/parse/lexer.rs
@@ -132,7 +132,7 @@ impl<'a> Lexer {
     }
 
     /// Check if source-code at current possition starts with a pattern.
-    fn starts_with<P>(&'a self, pat: P) -> bool where P: Pattern<'a> {
+    fn starts_with<P>(&self, pat: P) -> bool where P: Pattern {
         self.source[self.byte_offset.get()..].starts_with(pat)
     }
 
@@ -242,7 +242,7 @@ impl<'a> Lexer {
                 } else {
                     reading_escape = false;
                 }
-                self.increment_byte_offsets(1);
+                let _ = self.consume_char();
             }
             let end_of_string = self.byte_offset.get();
             self.increment_byte_offsets(3);
@@ -273,7 +273,7 @@ impl<'a> Lexer {
                 } else {
                     reading_escape = false;
                 }
-                self.increment_byte_offsets(1);
+                let _ = self.consume_char();
             }
             let end_of_string = self.byte_offset.get();
             self.increment_byte_offsets(1);
diff --git a/crates/seam/src/parse/macros.rs b/crates/seam/src/parse/macros.rs
@@ -1,17 +1,27 @@
 //! Expander macros argument parsing utilities.
-use std::{borrow::Borrow, collections::HashMap};
+use std::{borrow::Borrow, collections::HashMap, iter::zip};
 
 use regex::Regex;
 
 use super::{
     expander::ExpansionError,
-    parser::{Node, ParseNode, ParseTree},
+    parser::{Node, ParseNode, ParseTree}, tokens::Site,
 };
 
+#[derive(Debug, Clone)]
 pub enum ArgPredicate {
     Exactly(String),
     Matching(Regex),
-    Satisfying(Box<dyn Fn(ParseNode) -> bool>),
+    Satisfying(fn(ParseNode) -> bool),
+}
+
+impl ArgPredicate {
+    pub fn check_node<'tree>(&self, node: &Node<'tree>) -> Result<(), ExpansionError<'tree>> {
+        Ok(())
+    }
+    pub fn check<'tree>(&self, node: &ParseNode<'tree>) -> Result<(), ExpansionError<'tree>> {
+        Ok(())
+    }
 }
 
 /// Type of argument, and what kind of
@@ -22,6 +32,7 @@ pub enum ArgPredicate {
 ///     Number ⊆ Symbolic;
 ///     Symbolic ⊆ Literal;
 ///     * ⊆ Any.
+#[derive(Debug, Clone)]
 pub enum ArgType {
     Literal(Vec<ArgPredicate>),
     String(Vec<ArgPredicate>),
@@ -29,16 +40,120 @@ pub enum ArgType {
     Number(Vec<ArgPredicate>),
     Symbolic(Vec<ArgPredicate>),
     List(Vec<ArgType>),
-    Any(Vec<ArgType>),
+    Any(Vec<ArgPredicate>),
+}
+
+fn check_all_node<'tree>(preds: &Vec<ArgPredicate>, node: &Node<'tree>) -> Result<(), ExpansionError<'tree>> {
+    if preds.is_empty() { return Ok(()); }
+    let mut issues = vec![];
+    for pred in preds {
+        match pred.check_node(node) {
+            Ok(()) => return Ok(()),
+            Err(err) => issues.push(err),
+        }
+    }
+    if issues.is_empty() { return Ok(()); }
+    // Amalgamate errors.
+    let mut error = String::from("This argument's value did not satisfy one of the follwining:\n");
+    for issue in issues {
+        error += &format!(" * {}", issue.0);
+    }
+    Err(ExpansionError(error, node.site.clone()))
 }
 
-/// Kind of arguemnt type (optional, mandatory).
+fn check_all<'tree>(preds: &Vec<ArgPredicate>, node: &ParseNode<'tree>) -> Result<(), ExpansionError<'tree>> {
+    if preds.is_empty() { return Ok(()); }
+    let mut issues = vec![];
+    for pred in preds {
+        match pred.check(node) {
+            Ok(()) => return Ok(()),
+            Err(err) => issues.push(err),
+        }
+    }
+    if issues.is_empty() { return Ok(()); }
+    // Amalgamate errors.
+    let mut error = String::from("This argument's value did not satisfy one of the follwining:\n");
+    for issue in issues {
+        error += &format!(" * {}", issue.0);
+    }
+    Err(ExpansionError(error, node.owned_site()))
+}
+
+impl ArgType {
+    pub fn name(&self) -> &'static str {
+        use ArgType::*;
+        match self {
+            Literal(..) => "literal",
+            String(..) => "string",
+            Symbol(..) => "symbol",
+            Number(..) => "number",
+            Symbolic(..) => "symbolic",
+            List(..) => "list",
+            Any(..) => "any",
+        }
+    }
+
+    pub fn check<'tree>(&self, node: &ParseNode<'tree>) -> Result<(), ExpansionError<'tree>> {
+        use ArgType::*;
+        // Compute the generic type-mismatch error beforehand, even if not used.
+        let mismatch = ExpansionError(
+            format!("Expected a {} node, got a {} instead.", self.name(), node.node_type()),
+            node.owned_site());
+        match node {
+            ParseNode::Symbol(v) => match self {
+                Literal(pred) | Symbol(pred) | Symbolic(pred) | Any(pred) => check_all_node(pred, v),
+                _ => Err(mismatch),
+            },
+            ParseNode::String(v) | ParseNode::Raw(v) => match self {
+                Literal(pred) | String(pred) | Any(pred) => check_all_node(pred, v),
+                _ => Err(mismatch),
+            },
+            ParseNode::Number(v) => match self {
+                Literal(pred) | Symbolic(pred) | Number(pred) | Any(pred) => check_all_node(pred, v),
+                _ => Err(mismatch),
+            },
+            ParseNode::List { nodes, .. } => match self {
+                List(arg_types) => {
+                    if nodes.len() != arg_types.len() {
+                        return Err(ExpansionError(
+                            format!("Unexpected number of items in list, expected {} items, got {}.",
+                                arg_types.len(), nodes.len()),
+                            node.owned_site()
+                        ));
+                    }
+                    for (arg_type, node) in zip(arg_types, nodes) {
+                        arg_type.check(node)?;
+                    }
+                    Ok(())
+                },
+                Any(preds) => check_all(preds, node),
+                _ => Err(mismatch),
+            },
+            ParseNode::Attribute { keyword, .. } => match self {
+                Any(pred) => check_all(pred, node),
+                _ => Err(ExpansionError(format!("Unknown attribute `:{}`.", keyword), node.owned_site()))
+            },
+        }
+    }
+}
+
+/// Kind of arguemnt (optional, mandatory).
+#[derive(Debug, Clone)]
 pub enum Arg {
     Mandatory(ArgType),
     Optional(ArgType),
 }
 
+impl Arg {
+    pub fn argtype(&self) -> &ArgType {
+        match self {
+           Arg::Mandatory(typ) | Arg::Optional(typ) => typ
+        }
+    }
+}
+
 /// Positonal or named argument position.
+#[derive(Debug, Clone)]
 enum ArgPos<'a> { Int(usize), Str(&'a str) }
 /// What kind of types can be matched against
 /// when determining an arguments positionality.
@@ -51,8 +166,8 @@ impl ArgMatcher for usize {
 impl ArgMatcher for &str {
     fn unwrap(&self) -> ArgPos { ArgPos::Str(self) }
 }
-impl From<&Box<dyn ArgMatcher>> for Option<usize> {
-    fn from(value: &Box<dyn ArgMatcher>) -> Option<usize> {
+impl<'a> From<&'a Box<dyn ArgMatcher + 'a>> for Option<usize> {
+    fn from(value: &'a Box<dyn ArgMatcher + 'a>) -> Option<usize> {
         match value.unwrap() {
             ArgPos::Int(int) => Some(int),
             _ => None,
@@ -67,8 +182,8 @@ impl<'a> From<&'a Box<dyn ArgMatcher + 'a>> for Option<&'a str> {
         }
     }
 }
-impl From<usize> for Box<dyn ArgMatcher> {
-    fn from(value: usize) -> Box<dyn ArgMatcher> { Box::new(value) }
+impl<'a> From<usize> for Box<dyn ArgMatcher + 'a> {
+    fn from(value: usize) -> Box<dyn ArgMatcher + 'a> { Box::new(value) }
 }
 impl<'a> From<&'a str> for Box<dyn ArgMatcher + 'a> {
     fn from(value: &'a str) -> Box<dyn ArgMatcher + 'a> { Box::new(value) }
@@ -82,13 +197,15 @@ impl<'a> From<&'a String> for Box<dyn ArgMatcher + 'a> {
 /// position.
 /// Pattern pertains to how to argument sits
 /// in the macro-call's argument list.
+#[derive(Debug, Clone)]
 struct ArgPattern<'a> {
     argument: Arg,
-    pattern: Box<dyn Fn(&Box<dyn ArgMatcher + 'a>) -> bool>,
+    pattern: fn(&Box<dyn ArgMatcher + 'a>) -> bool,
 }
 
 /// A complete description of how a macro's arguments
 /// should be parsed.
+#[derive(Debug, Clone)]
 pub struct ArgRules<'a> {
     patterns: Vec<ArgPattern<'a>>,
     trailing: Option<ArgType>,
@@ -99,12 +216,11 @@ impl<'a> ArgRules<'a> {
         Self { patterns: Vec::new(), trailing: None }
     }
     /// Register a pattern to match.
-    pub fn register<F>(&mut self, matcher: F, arg: Arg)
-        where F: 'static + Fn(&Box<dyn ArgMatcher + 'a>) -> bool
+    pub fn register(&mut self, matcher: fn(&Box<dyn ArgMatcher + 'a>) -> bool, arg: Arg)
     {
         self.patterns.push(ArgPattern {
             argument: arg,
-            pattern: Box::new(matcher),
+            pattern: matcher,
         });
     }
     /// Register matching on all remaining arguments.
@@ -113,135 +229,27 @@ impl<'a> ArgRules<'a> {
     }
     /// Turn this structure into a parser.
     pub fn parser<'params, 'tree>(self, params: &'params Box<[ParseNode<'tree>]>) -> ArgParser<'params, 'a, 'tree> {
-        ArgParser::new(self, params)
+        ArgParser::new(self, params).unwrap()
     }
-}
-
-/// Turns a pattern into a argument matching predicate.
-macro_rules! predicate {
-    // A literals which represent a potential exact match of the string values.
-    ($lit:literal) => { ArgPredicate::Exactly(String::from($lit)) };
-    // A pattern which can match against the argument.
-    ($pat:pat) => {{
-        fn matcher(arg: ParseNode) -> bool {
-            use super::parser::IntoValue;
-            match arg.into_value() {
-                Some($pat) => true,
-                _ => false,
+    /// Count how many mandatory arguments there are.
+    pub fn count_mandatory(&self) -> usize {
+        let mut count = 0;
+        for pattern in &self.patterns {
+            match pattern.argument {
+                Arg::Mandatory(..) => count += 1,
+                _ => {}
             }
         }
-        ArgPredicate::Satisfying(Box::new(matcher))
-    }};
-}
-
-macro_rules! arg_type {
-    (literal)  => { ArgType::Literal };
-    (string)   => { ArgType::String };
-    (symbol)   => { ArgType::Symbol };
-    (number)   => { ArgType::Number };
-    (symbolic) => { ArgType::Symbolic };
-    (list)     => { ArgType::List };
-    (any)      => { ArgType::Any };
-}
-
-macro_rules! argument_type {
-    ($typ:ident) => {{ ArgType::Literal(vec![]) }};
-    ($typ:ident[ $($preds:literal),+ ]) => {{
-        arg_type!($typ)(vec![ $( predicate!($preds) ),+ ])
-    }};
-    ($typ:ident ( $($preds:pat),+ )) => {{
-        arg_type!($typ)(vec![ $( predicate!($preds) ),+ ])
-    }};
-    ($typ:ident fn($($var:tt)+) { $($body:tt)* }) => {{
-        fn predicate($($var)+) -> bool { $($body)* }
-        let arg_pred = ArgPredicate::Satisfying(Box::new(predicate));
-        arg_type!($typ)(vec![arg_pred])
-    }};
-}
-
-macro_rules! register_position_pattern {
-    ($ctx:expr, $n:pat, $arg:expr) => {
-        fn position_matcher(pattern: &Box<dyn ArgMatcher>) -> bool {
-            match pattern.into() {
-                Some($n) => true,
-                _ => false,
-            }
-        }
-        let ctx: &mut ArgRules = $ctx;
-        let arg: Arg = $arg;
-        ctx.register(position_matcher, arg);
-    };
-}
-
-macro_rules! _argument {
-    // The pattern for a mandatory argument.
-    ($ctx:expr => mandatory($n:pat): $($kind:tt)+) => {
-        {
-            let arg_type = argument_type!($($kind)+);
-            let arg = Arg::Mandatory(arg_type);
-            let ctx: &mut ArgRules = $ctx;
-            register_position_pattern!(ctx, $n, arg);
-        }
-    };
-    // The pattern for an optional argument.
-    ($ctx:expr => optional($n:pat): $($kind:tt)+) => {
-        {
-            let arg_type = argument_type!($($kind)+);
-            let arg = Arg::Optional(arg_type);
-            let ctx: &mut ArgRules = $ctx;
-            register_position_pattern!(ctx, $n, arg);
-        }
-    };
-    // The pattern for an any remaining argument.
-    ($ctx:expr => rest: $($kind:tt)+) => {
-        {
-            let arg_type = argument_type!($($kind)+);
-            let ctx: &mut ArgRules = $ctx;
-            ctx.register_remaining(arg_type);
-        }
-    };
-}
-
-/// See <https://stackoverflow.com/a/74971086/13162100>.
-#[macro_export]
-macro_rules! arguments {
-    ($ctx:expr => @accumulate [ $($accumulated:tt)* ] [ ]) => { [ $($accumulated)* ] };
-    ($ctx:expr => @accumulate [ $($accumulated:tt)* ] [ $($final_line:tt)* ]) => {
-        [ $($accumulated)* _argument!( $ctx => $($final_line)+ ) ]
-    };
-    ($ctx:expr => @accumulate [ $($accumulated:tt)* ] [ $($this_line:tt)* ] , $($rest:tt)* ) => {
-        arguments! {
-            $ctx => @accumulate
-                [ $($accumulated)* _argument!( $ctx => $($this_line)* ), ]
-                [ ] $($rest)*
-        }
-    };
-    ($ctx:expr => @accumulate [ $($accumulated:tt)* ] [ $($this_line:tt)* ] $current:tt $($rest:tt)* ) => {
-        arguments! {
-            $ctx => @accumulate
-                [ $($accumulated)* ]
-                [ $($this_line)* $current ]
-                $($rest)*
-        }
-    };
-    ( $($t:tt)* ) => {{
-        let mut ctx = ArgRules::new();
-        arguments! { &mut ctx => @accumulate [ ] [ ] $($t)* };
-        ctx
-    }}
+        count
+    }
 }
 
-
-// --- Proc Macro
-use seam_argparse_proc_macro::*;
-
-
-// ---
-
+#[derive(Debug, Clone)]
 pub struct ArgParser<'params: 'rules, 'rules, 'tree> {
-    rules: ArgRules<'rules>,
-    positional: HashMap<usize, &'params ParseNode<'tree>>,
-    named: HashMap<String, &'params ParseNode<'tree>>,
+    pub rules: ArgRules<'rules>,
+    pub positional: HashMap<usize, &'params ParseNode<'tree>>,
+    pub named: HashMap<String, &'params ParseNode<'tree>>,
+    pub trailing: Vec<&'params ParseNode<'tree>>
 }
 
 impl<'params, 'rules, 'tree> ArgParser<'params, 'rules, 'tree> {
@@ -251,77 +259,106 @@ impl<'params, 'rules, 'tree> ArgParser<'params, 'rules, 'tree> {
         let mut position = 0;
         let mut positional = HashMap::with_capacity(params.len());
         let mut named = HashMap::with_capacity(params.len());
+        let mut trailing = vec![];
+        let mut mandatory_count: usize = 0;
+        println!("going through params: {:?}", params);
+
         for param in params {
-            let matcher: Box<dyn ArgMatcher>;
+            let matcher: Box<dyn ArgMatcher + 'rules>;
             // Register each argument with the parser.
+            let param_node: &'params ParseNode;
             if let ParseNode::Attribute { keyword, node, .. } = param {
-                named.insert(keyword.to_owned(), node.borrow());
                 matcher = keyword.into();
+                param_node = node;
             } else {
-                positional.insert(position, param);
                 position += 1;
                 matcher = position.into();
+                param_node = param;
             }
             // Check if they do actually match with any of the rules.
             let mut arg_rule = None;
             for rule in &rules.patterns {
+                println!("calling matcher");
                 // First check that there is a valid place for this argument.
                 let is_valid_argument = (rule.pattern)(&matcher);
-                if !is_valid_argument {
+                println!("checked pattern {:?} against {:?} and got {:?}", rule.pattern, matcher.unwrap(), is_valid_argument);
+                if is_valid_argument {
                     arg_rule = Some(rule);
                     break;
                 }
             }
-            let Some(rule) = arg_rule else {
-                // Error on fact that an errenous positional or named argument
-                // has been given. Only error on additional errenous named
-                // arguemnts if trailing argument capture is enabled.
-                todo!()
-            };
-            // Now check that the types are satisfied.
-            let arg = &rule.argument;
-            // TODO: throw error when mismatched.
+            // If the position rule does not match any specified argument,
+            // check if it can be given as trailing argument.
+            match arg_rule {
+                Some(rule) => {
+                    println!("matched param against rule: {:?}", rule);
+                    // Now check that the types are satisfied.
+                    let argtype = rule.argument.argtype();
+                    argtype.check(param_node)?;
+                    // If so, insert the parameter.
+                    match matcher.unwrap() {
+                        ArgPos::Int(i) => positional.insert(i, param_node),
+                        ArgPos::Str(k) => named.insert(k.to_owned(), param_node),
+                    };
+                    // Register if a mandatory argument was consumed.
+                    match rule.argument {
+                        Arg::Mandatory(..) => {
+                            println!("found mand");
+                            mandatory_count += 1
+                        },
+                        _ => {},
+                    };
+                },
+                None => match &rules.trailing {
+                    Some(trailing_rule) => {
+                        // Check that the trailing type is satisfied.
+                        trailing_rule.check(param)?;
+                        // If so, push the argument.
+                        trailing.push(param);
+                    },
+                    None => {
+                        // Error on fact that an errenous positional or named argument
+                        // has been given. Only error on additional errenous named
+                        // arguemnts if trailing argument capture is enabled.
+                        return Err(ExpansionError(if let ParseNode::Attribute { keyword, .. } = param {
+                            format!("Unexpected named argument `:{}`.", keyword)
+                        } else {
+                            format!("Unexpected positional argument in position {}.", position)
+                        }, param.owned_site()));
+                    }
+                }
+            }
         }
         // After checking all the arguments are *valid*, now check
         // that all mandatory arguments are given.
-        "todo";
-        // Now check if trailing (variadic) arguments are permitted
-        // (otherwise error on unexpected additional arguments).
-        // And if so, that they all satisfy the trailing argument rule.
-        "todo";
+        let needed_count =  rules.count_mandatory();
+        // TODO: pass in site of macro-call
+        let last_site = params.last().map(|node| node.owned_site()).unwrap_or(Site::unknown());
+        if mandatory_count < needed_count {
+            return Err(ExpansionError(
+                format!("Missing {} non-optional arguments from macro call.", needed_count - mandatory_count),
+                last_site
+            ));
+        }
 
-        Ok(Self { rules, positional, named, })
+        Ok(Self { rules, positional, named, trailing })
     }
 
-    pub fn get<P>(&mut self, key: P) -> Result<ParseNode<'tree>, ExpansionError<'tree>>
-        where P: Into<Box<dyn ArgMatcher>>
+    pub fn get_optional<P>(&self, key: P) -> Option<&&ParseNode<'tree>>
+        where P: Into<Box<dyn ArgMatcher + 'rules>>
     {
-        let matcher: &Box<dyn ArgMatcher> = &key.into();
-        // Go through every pattern that could match against the argument
-        // position given and check if they match.
-        for argpat in &self.rules.patterns {
-            let pat = &argpat.pattern;
-            let did_match = pat(matcher);
-            if did_match {
-                match matcher.unwrap() {
-                    ArgPos::Int(i) => {},
-                    ArgPos::Str(k) => {},
-                }
-            }
+        let matcher: &Box<dyn ArgMatcher + 'rules> = &key.into();
+        match matcher.unwrap() {
+            ArgPos::Int(i) => self.positional.get(&i),
+            ArgPos::Str(k) => self.named.get(k),
         }
-
-        todo!()
     }
-}
 
-pub enum _ArgType {
-    Literal(Vec<ArgPredicate>),
-    String(Vec<ArgPredicate>),
-    Symbol(Vec<ArgPredicate>),
-    Number(Vec<ArgPredicate>),
-    Symbolic(Vec<ArgPredicate>),
-    List(Vec<ArgType>),
-    Any(Vec<ArgType>),
+    pub fn get<P>(&self, key: P) -> Result<&&ParseNode<'tree>, ExpansionError<'tree>>
+        where P: Into<Box<dyn ArgMatcher + 'rules>>
+    {
+        Ok(self.get_optional(key).unwrap())
+    }
 }
 
 pub fn extract_literal<'a>(node: ParseNode<'a>) -> Result<Node<'a>, ExpansionError<'a>> {
diff --git a/crates/seam/src/parse/parser.rs b/crates/seam/src/parse/parser.rs
@@ -82,7 +82,8 @@ impl<'a> ParseNode<'a> {
         match self {
             Self::Symbol(node)
             | Self::Number(node)
-            | Self::String(node) => Some(node),
+            | Self::String(node)
+            | Self::Raw(node) => Some(node),
             _ => None,
         }
     }
@@ -147,6 +148,56 @@ impl<'a> ParseNode<'a> {
             Self::Attribute { .. } => "attribute",
         }
     }
+
+    pub fn is_atomic(&self) -> bool { self.atomic().is_some() }
+
+    pub fn is_list(&self) -> bool {
+        match self {
+            Self::List { .. } => true,
+            _ => false,
+        }
+    }
+
+    pub fn is_attribute(&self) -> bool {
+        match self {
+            Self::Attribute { .. } => true,
+            _ => false,
+        }
+    }
+}
+
+// Try to convert a [`ParseNode`] enum value into
+// its underlying type (e.g. [`Node`], `Box<[Node]>`, etc.).
+
+impl<'a> TryFrom<ParseNode<'a>> for Node<'a> {
+    type Error = ();
+
+    fn try_from(value: ParseNode<'a>) -> Result<Self, Self::Error> {
+        match value.into_atomic() {
+            Some(node) => Ok(node),
+            None => Err(()),
+        }
+    }
+}
+
+impl<'a> TryFrom<ParseNode<'a>> for Box<[ParseNode<'a>]> {
+    type Error = ();
+
+    fn try_from(value: ParseNode<'a>) -> Result<Self, Self::Error> {
+        match value {
+            ParseNode::List { nodes, .. } => Ok(nodes),
+            _ => Err(()),
+        }
+    }
+}
+
+impl<'a> TryFrom<ParseNode<'a>> for Vec<ParseNode<'a>> {
+    type Error = ();
+
+    fn try_from(value: ParseNode<'a>) -> Result<Self, Self::Error> {
+        let into: Result<Box<[ParseNode<'a>]>, Self::Error> = value.try_into();
+        into.map(|b| b.to_vec())
+    }
 }
 
 /// Trait determining if a [`ParseNode`] can be converted into
@@ -257,7 +308,7 @@ impl<'a> Parser {
     }
 
     /// Parse whole source code, finishing off the lexer.
-    pub fn parse(&'a self) -> Result<ParseTree, Box<dyn Error + 'a>> {
+    pub fn parse(&'a self) -> Result<ParseTree<'a>, Box<dyn Error + 'a>> {
         let mut root: Vec<ParseNode> = Vec::new();
         while !self.lexer.eof() {
             let expr = self.parse_expr()?;
@@ -267,7 +318,7 @@ impl<'a> Parser {
     }
 
     /// Produce a parse node from the current position in the lexer.
-    pub fn parse_expr(&'a self) -> Result<ParseNode, Box<dyn Error + 'a>> {
+    pub fn parse_expr(&'a self) -> Result<ParseNode<'a>, Box<dyn Error + 'a>> {
         let token = self.lexer.peek()?;
         match token.kind {
             Kind::LParen => self.parse_list(),
@@ -283,7 +334,7 @@ impl<'a> Parser {
     }
 
     /// Parse keyword-attribute pair.
-    fn parse_keyword(&'a self) -> Result<ParseNode, Box<dyn Error + 'a>> {
+    fn parse_keyword(&'a self) -> Result<ParseNode<'a>, Box<dyn Error + 'a>> {
         // Consume :keyword token.
         let token = self.lexer.consume()?;
         assert_eq!(token.kind, Kind::Keyword);
@@ -386,7 +437,7 @@ pub trait SearchTree<'a> {
     fn search_node(&'a self, kind: SearchType,
                    value: &str,
                    case_insensitive: bool,
-                   level: usize) -> Option<&ParseNode<'a>>;
+                   level: usize) -> Option<&'a ParseNode<'a>>;
 }
 
 #[derive(Clone, Copy, PartialEq)]
@@ -405,7 +456,7 @@ impl SearchType {
 
 impl<'a> SearchTree<'a> for ParseNode<'a> {
     fn search_node(&'a self, kind: SearchType, value: &str,
-                   insensitive: bool, level: usize) -> Option<&ParseNode<'a>> {
+                   insensitive: bool, level: usize) -> Option<&'a ParseNode<'a>> {
         if level == 0 {
             return None;
         }
@@ -462,7 +513,7 @@ impl<'a> SearchTree<'a> for ParseNode<'a> {
 
 impl<'a> SearchTree<'a> for ParseTree<'a> {
     fn search_node(&'a self, kind: SearchType, value: &str,
-                   insensitive: bool, level: usize) -> Option<&ParseNode<'a>> {
+                   insensitive: bool, level: usize) -> Option<&'a ParseNode<'a>> {
         if level == 0 {
             return None;
         }
diff --git a/crates/seam_argparse_proc_macro/Cargo.toml b/crates/seam_argparse_proc_macro/Cargo.toml
@@ -3,6 +3,7 @@ name = "seam_argparse_proc_macro"
 license-file = "../../LICENSE"
 authors = ["Demonstrandum <samuel@knutsen.co>"]
 edition = "2021"
+version = "0.1.0"
 
 [lib]
 proc-macro = true
@@ -12,4 +13,4 @@ path = "src/lib.rs"
 [dependencies]
 proc-macro2 = "1.0.86"
 quote = "1.0.37"
-syn = "2.0.77"
+syn = { version = "2.0.89", features = ["full"] }
diff --git a/crates/seam_argparse_proc_macro/src/lib.rs b/crates/seam_argparse_proc_macro/src/lib.rs
@@ -1,12 +1,13 @@
 //! Procedural macro for the `arguments! { ... }`
 //! macro-argument parser for seam macros.
 //! TODO: Convert all `panic!(..)` calls to actual compiler errors.
+#![feature(proc_macro_diagnostic)]
 
 use std::{collections::{HashMap, HashSet}, iter::Peekable};
 
-use proc_macro;
+use proc_macro::{self, Diagnostic, Span};
 use proc_macro2::{token_stream::IntoIter, Delimiter, TokenStream, TokenTree};
-use quote::quote;
+use quote::{quote, ToTokens};
 use syn::{self,
     Expr, ExprRange, ExprLit,
     Lit, Pat, PatOr,
@@ -53,14 +54,24 @@ struct ArgumentStructTypes {
 ///     ```
 ///     let (parser, args) = arguments! { [¶ms]
 ///         mandatory(1..=3): literal,
-///         mandatory(4): number fn(_v: ParseNode) { true },
+///         mandatory(4): number fn(n: ParseNode) {
+///             let n = extract_number(n)?;
+///             let Ok(n): u32 = n.value.parse() else {
+///                 return Err("Argument must be an integer.");
+///             }
+///             if n % 2 == 0 {
+///                 Ok(())
+///             } else {
+///                 Err("Integer must be even.")
+///             }
+///         },
 ///         optional("trailing"): literal["true", "false"],
 ///         rest: number
 ///     }?;
 ///     println!("first  arg {:?}", args.number.1); // a literal (Node<'a>).
 ///     println!("second arg {:?}", args.number.2); // a literal (Node<'a>).
 ///     println!("third  arg {:?}", args.number.3); // a literal (Node<'a>).
-///     println!("fourth arg {:?}", args.number.4); // a number of any kind (Node<'a>).
+///     println!("fourth arg {:?}", args.number.4); // an even integer (Node<'a>).
 ///     if let Some(named) = args.trailing {
 ///         println!("named arg {:?}", named);  // the literal "true" or "false".
 ///     }
@@ -76,25 +87,22 @@ pub fn arguments(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
 
     // Parse the provided runtime argument vector.
     let Some(args_vec) = stream.next().and_then(|tokens| match tokens {
-        TokenTree::Group(group) => match
-            group.stream()
-                .into_iter()
-                .collect::<Vec<TokenTree>>()
-                .as_slice()
-        {
-            [params,] => Some(params.clone()),
-            _ => None,
-        },
+        TokenTree::Group(group) => Some(group
+            .stream()
+            .into_iter()
+            .collect::<Vec<TokenTree>>()
+            .as_slice()
+            .to_vec()),
         _ => None,
     }) else {
-        panic!("Vector of arguments not given.");
+        panic!("Argument vector not given.");
     };
+    let params: TokenStream = quote! { #(#args_vec)* };
 
     // Start building final source-code output.
     let mut out: TokenStream = TokenStream::new();
     out.extend(quote! {
         let mut rules = crate::parse::macros::ArgRules::new();
-        let params: Vec<crate::parse::parser::ParseNode> = #args_vec;
     });
     // Initialize keeping track of the custom argument struct types.
     let mut arg_struct = ArgumentStructTypes {
@@ -115,12 +123,15 @@ pub fn arguments(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
                     match ident.to_string().as_str() {
                         "mandatory" => {
                             parse_state = ParseState::PositionPattern(PositionTypes::Mandatroy);
+                            continue;
                         },
                         "optional" => {
                             parse_state = ParseState::PositionPattern(PositionTypes::Optional);
+                            continue;
                         },
                         "rest" => {
                             parse_state = ParseState::PositionPattern(PositionTypes::Rest);
+                            continue;
                         },
                         _ => panic!("Invalid token: `{}`", ident.to_string()),
                     }
@@ -136,21 +147,27 @@ pub fn arguments(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
                 }
                 let argument_type = parse_argument_type(&mut stream, PositionTypes::Rest);
                 let arg_type = argument_type.source_code;
-                let code = quote! {{
-                    let arg_type = #arg_type;
-                    rules.register_remaining(arg_type);
-                }};
+                let code = quote! {
+                    {
+                        let arg_type = #arg_type;
+                        rules.register_remaining(arg_type);
+                    };
+                };
                 out.extend(code);
                 // Register argument struct type.
                 let rust_type = argument_type.properties.rust_type;
                 arg_struct.rest.kind = argument_type.properties.kind;
-                arg_struct.rest.rust_type = quote! { Vec<#rust_type> };
+                arg_struct.rest.rust_type = rust_type;
             },
             ParseState::PositionPattern(pos@PositionTypes::Mandatroy | pos@PositionTypes::Optional) => {
                 // Parse the pattern for matching argument positions.
                 let position_pattern = match token {
                     TokenTree::Group(group) => group.stream(),
-                    _ => panic!("Unexpected token"),
+                    t => {
+                        let span: proc_macro::Span = t.span().unwrap();
+                        Diagnostic::spanned(span, proc_macro::Level::Error, "expected a paranthesised pattern matching the argument position here.").emit();
+                        panic!("failed to parse.");
+                    },
                 };
                 // Skip `:`
                 let token = stream.next();
@@ -166,17 +183,19 @@ pub fn arguments(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
                     PositionTypes::Optional  => quote! { crate::parse::macros::Arg::Optional  },
                     _ => unreachable!(),
                 };
-                let code = quote! {{
-                    let arg_type = #arg_type;
-                    let arg_pos = #arg_pos;
-                    fn position_matcher(pattern: &Box<dyn crate::parse::macros::ArgMatcher>) -> bool {
-                        match pattern.into() {
-                            Some(#position_pattern) => true,
-                            _ => false,
+                let code = quote! {
+                    {
+                        let arg_type = #arg_type;
+                        let arg = #arg_pos(arg_type);
+                        fn position_matcher<'b>(pattern: &Box<dyn crate::parse::macros::ArgMatcher + 'b>) -> bool {
+                            match pattern.into() {
+                                Some(#position_pattern) => true,
+                                _ => false,
+                            }
                         }
-                    }
-                    rules.register(position_matcher, arg);
-                }};
+                        rules.register(position_matcher, arg);
+                    };
+                };
                 out.extend(code);
                 // Register argument struct type.
                 let rust_type = argument_type.properties.rust_type;
@@ -189,88 +208,163 @@ pub fn arguments(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
                 for position in parse_finite_pattern(position_pattern) {
                     match position {
                         StringOrInt::String(name) => arg_struct.named.insert(name, ArgumentProperties {
-                            kind: argument_type.properties.kind,
+                            kind: argument_type.properties.kind.clone(),
                             position_type: pos,
-                            rust_type,
+                            rust_type: rust_type.clone(),
                         }),
                         StringOrInt::Int(offset)  => arg_struct.positional.insert(offset, ArgumentProperties {
-                            kind: argument_type.properties.kind,
+                            kind: argument_type.properties.kind.clone(),
                             position_type: pos,
-                            rust_type,
+                            rust_type: rust_type.clone(),
                         }),
                     };
                 }
             },
         };
+        // Handle switching back states, and any additional delimiting tokens.
+        match parse_state {
+            ParseState::PositionPattern(_) => {
+                // Finished parsing a pattern, skip the comma delimiting the next rule.
+                let token = stream.next();
+                // Expecting to find ',' at this point.
+                match match token {
+                    Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => Ok(()),
+                    Some(t) => Err(t.span().unwrap()),
+                    None => Err(Span::call_site()),
+                } {
+                    Ok(()) => {},
+                    Err(span) => {
+                        Diagnostic::spanned(span, proc_macro::Level::Error,
+                            "Expected a comma after defining an argument rule.").emit();
+                        panic!("failed to parse");
+                    }
+                };
+                // Otherwise, switch back to trying tp parse a new rule.
+                parse_state = ParseState::ArgumentPosition;
+            },
+            _ => {},
+        };
     }
 
     // Build tuple type for arguments structure.
     let tuple_len = *arg_struct.positional.keys().max().unwrap_or(&0);
-    let mut tuple_types = vec![quote! { () }; tuple_len];
-    for i in 0..tuple_len {
-        tuple_types.push(match arg_struct.positional.remove(&i) {
-            Some(props) => props.rust_type,
-            None            => quote! { () },
-        });
+    let mut tuple_types = vec![quote! { () }; tuple_len + 1];
+    for i in 1..=tuple_len {
+        let props = arg_struct.positional.get(&i);
+        tuple_types[i] = match props {
+            Some(props) => props.rust_type.clone(),
+            None        => quote! { () },
+        };
     }
     // Build named arguments struct fields.
     let mut named_arguments: Vec<TokenStream> = vec![];
+    let mut named_types: Vec<TokenStream> = vec![];
+    let mut named_values: Vec<TokenStream> = vec![];
     for (name, props) in arg_struct.named.iter() {
-        let rust_type = props.rust_type;
-        named_arguments.push(quote! {
-            #name: #rust_type
-        });
+        let rust_type = props.rust_type.clone();
+        let variable: proc_macro2::TokenStream = name.parse().unwrap();
+        named_types.push(rust_type);
+        named_arguments.push(variable);
+        match props.position_type {
+            PositionTypes::Mandatroy => named_values.push(quote! {{
+                let retrieved = *parser.get(#name)?;
+                retrieved
+                    .clone()
+                    .try_into()
+                    .expect("node type-checked but unwrap failed")
+            }}),
+            PositionTypes::Optional => named_values.push(quote! {{
+                parser.get_optional(#name).map(|retrieved|
+                    (*retrieved)
+                        .clone()
+                        .try_into()
+                        .expect("node type-checked but unwrap failed"))
+            }}),
+            _ => unreachable!(),
+        }
     }
 
-    // TODO: need to iterate the runtime-provided params and extract
-    // them into the correct type depending on expected type
-    // (i.e. literal => Node<'a>; list => Vec<ParseNode<'a>; etc.)
-    // A failure of the above extraction should nicely hand back a
-    // custom Err() message.
-    // Optional nodes do not fail, they just get a `None` value.
-    // While doing this, each extracted argument should be checked
-    // that it satisfies the supplemental conditions in the schema (predicates).
-    // Again, if it fails the check, default on an Err() describing in the
-    // most informative possible way why it failed.
-    // Finally, no failures should mean a fully populated arguments struct
-    // can be constructed from the previous arguments, and can be returned.
-
-    // TODO: write reusable `extract_literal` function
-    // (signature: ParseNode<'a> -> Result<Node<'a>, ExpansionError<'a>>)
-    // that will give a custom error message for failing to extract.
-    // e.g. "expected a literal, got a {insert_node_kind}".
-
+    // Generate code for extracting the values of the positional arguments.
+    let mut tuple_variables = vec![quote! { () }];
+    let mut tuple_variable_initializations = vec![];
     for i in 1..=tuple_len {
         let arg_num_name: TokenStream = format!("arg_num_{}", i).parse().unwrap();
+        let arg_type = tuple_types[i].clone();
 
-        let code = quote! {
-            let #arg_num_name = parser.positional.get();
-        };
+        tuple_variables.push(arg_num_name.clone());
+        let Some(props) = arg_struct.positional.get(&i) else { continue };
+        match props.position_type {
+            PositionTypes::Mandatroy => {
+                tuple_variable_initializations.push(quote! {
+                    let #arg_num_name: #arg_type = {
+                        let retrieved = *parser.get(#i)?;
+                        retrieved
+                            .clone()
+                            .try_into()
+                            .expect("node type-checked but unwrap failed")
+                    };
+                });
+            },
+            PositionTypes::Optional => {
+                tuple_variable_initializations.push(quote! {
+                    let #arg_num_name: #arg_type = parser
+                        .get_optional(#i)
+                        .map(|retrieved|
+                            (*retrieved)
+                                .clone()
+                                .try_into()
+                                .expect("node type-checked but unwrap failed"));
+                });
+            },
+            _ => unreachable!(),
+        }
     }
 
+    // Generate code for extracting the trailing arguments.
+    let rest_rust_type = arg_struct.rest.rust_type;
+    let trailing_arguments = quote! {
+        {
+            parser.trailing
+                .iter()
+                .map(|arg| {
+                    let arg: crate::parse::parser::ParseNode = (*arg).clone();
+                    let retrieved: #rest_rust_type = arg.try_into().expect("node type-checked but unwrap failed");
+                    retrieved
+                })
+                .collect()
+        }
+    };
+
     // Assemble code that builds argument parser context and argument struct.
-    let rest_rust_type = arg_struct.rest;
     let out = out.into_iter();
-    quote! {
+    let out = quote! {
         {
             #(#out)*;
-            struct MyArguments {
-                number: #(#tuple_types),*,
-                #(#named_arguments),*,
-                rest: #rest_rust_type,
+            #[derive(Debug, Clone)]
+            struct MyArguments<'a> {
+                number: (#(#tuple_types),*,),
+                #(#named_arguments: #named_types),*,
+                rest: Vec<#rest_rust_type>,
             }
-            let rules = rules.clone();
-            match crate::parse::macros::ArgParser::new(rules, params) {
+            let parser_result = crate::parse::macros::ArgParser::new(rules, #params);
+            match parser_result {
                 Ok(parser) => {
+                    #(#tuple_variable_initializations)*
+                    #(let #named_arguments: #named_types = #named_values;)*
+                    let rest = #trailing_arguments;
                     let args_struct = MyArguments {
-                        ...
+                        number: (#(#tuple_variables),*,),
+                        #(#named_arguments),*,
+                        rest,
                     };
                     Ok((parser, args_struct))  // Returns the parser and args from the scope.
                 },
-                Err(e) => e,
+                Err(e) => Err(e),
             }
         }
-    }.into()
+    }.into();
+    println!("{}", out);
+    out
 }
 
 #[derive(Clone, PartialEq, Eq, Hash)]
@@ -382,7 +476,14 @@ fn parse_argument_type(stream: &mut Peekable<IntoIter>, position_type: PositionT
             Delimiter::Bracket | Delimiter::Parenthesis => {
                 stream.next(); // Consume the list.
                 let group = group.stream().into_iter();
-                quote! { #arg_type(vec![ #(#group),* ]) }
+                // TODO: generate predicates based on syntax: "exact", /match/.
+                let predicates = group.map(|predicate| match predicate {
+                    TokenTree::Literal(literal) => quote! {
+                        crate::parse::macros::ArgPredicate::Exactly(String::from(#literal))
+                    },
+                    token => token.into_token_stream(),
+                });
+                quote! { #arg_type(vec![ #(#predicates)* ]) }
             },
             _ => panic!("Unexpected list delimiter"),
         },
diff --git a/src/seam_argparse_proc_macro/lib.rs b/src/seam_argparse_proc_macro/lib.rs
@@ -1,6 +0,0 @@
-use proc_macro::TokenStream;
-
-#[proc_macro]
-pub fn make_answer(stream: TokenStream) -> TokenStream {
-    "fn answer() -> u32 { 42 }".parse().unwrap()
-}