commit 53018a113f1cd202aa4a36d60bb97d3827f51934
parent 5be4f703571f29d8cdc06540dba0bfb32b3d005a
Author: Demonstrandum <samuel@knutsen.co>
Date:   Thu, 28 Nov 2024 17:27:15 +0000
Partially implemented proc macro for parsing macro arguments.
Diffstat:
28 files changed, 3055 insertions(+), 1598 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3,6 +3,15 @@
 version = 3
 
 [[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
 name = "android-tzdata"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -31,9 +40,12 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
 
 [[package]]
 name = "cc"
-version = "1.0.98"
+version = "1.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
+checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
+dependencies = [
+ "shlex",
+]
 
 [[package]]
 name = "cfg-if"
@@ -52,7 +64,7 @@ dependencies = [
  "js-sys",
  "num-traits",
  "wasm-bindgen",
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -67,9 +79,9 @@ dependencies = [
 
 [[package]]
 name = "core-foundation-sys"
-version = "0.8.6"
+version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
 [[package]]
 name = "descape"
@@ -108,30 +120,36 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.69"
+version = "0.3.70"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
 dependencies = [
  "wasm-bindgen",
 ]
 
 [[package]]
 name = "lazy_static"
-version = "1.4.0"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 
 [[package]]
 name = "libc"
-version = "0.2.155"
+version = "0.2.156"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+checksum = "a5f43f184355eefb8d17fc948dbecf6c13be3c141f20d834ae842193a448c72a"
 
 [[package]]
 name = "log"
-version = "0.4.21"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
 name = "num-traits"
@@ -150,23 +168,52 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.85"
+version = "1.0.86"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.36"
+version = "1.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
+name = "regex"
+version = "1.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
+[[package]]
 name = "seam"
 version = "0.3.0"
 dependencies = [
@@ -174,14 +221,31 @@ dependencies = [
  "colored",
  "descape",
  "formatx",
+ "regex",
+ "seam_argparse_proc_macro",
  "unicode-width",
 ]
 
 [[package]]
+name = "seam_argparse_proc_macro"
+version = "0.0.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
 name = "syn"
-version = "2.0.66"
+version = "2.0.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
+checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -196,25 +260,26 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.12"
+version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
+checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.92"
+version = "0.2.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
 dependencies = [
  "cfg-if",
+ "once_cell",
  "wasm-bindgen-macro",
 ]
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.92"
+version = "0.2.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
 dependencies = [
  "bumpalo",
  "log",
@@ -227,9 +292,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.92"
+version = "0.2.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -237,9 +302,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.92"
+version = "0.2.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -250,9 +315,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.92"
+version = "0.2.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
 
 [[package]]
 name = "windows-core"
@@ -260,7 +325,7 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
 dependencies = [
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -289,18 +354,18 @@ dependencies = [
 
 [[package]]
 name = "windows-targets"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.5",
- "windows_aarch64_msvc 0.52.5",
- "windows_i686_gnu 0.52.5",
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
  "windows_i686_gnullvm",
- "windows_i686_msvc 0.52.5",
- "windows_x86_64_gnu 0.52.5",
- "windows_x86_64_gnullvm 0.52.5",
- "windows_x86_64_msvc 0.52.5",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
 ]
 
 [[package]]
@@ -311,9 +376,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
 [[package]]
 name = "windows_aarch64_msvc"
@@ -323,9 +388,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
 [[package]]
 name = "windows_i686_gnu"
@@ -335,15 +400,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
 
 [[package]]
 name = "windows_i686_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
 [[package]]
 name = "windows_i686_msvc"
@@ -353,9 +418,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -365,9 +430,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
@@ -377,9 +442,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
 
 [[package]]
 name = "windows_x86_64_msvc"
@@ -389,6 +454,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/Cargo.toml b/Cargo.toml
@@ -1,28 +1,2 @@
-[package]
-name = "seam"
-description = "Symbolic Expressions As Markup."
-keywords = ["markup", "lisp", "macro", "symbolic-expression", "sexp"]
-license-file = "LICENSE"
-homepage = "https://git.knutsen.co/seam"
-version = "0.3.0"
-authors = ["Demonstrandum <samuel@knutsen.co>"]
-edition = "2021"
-
-[features]
-# default = ["debug"]
-debug = []
-
-[lib]
-name = "seam"
-path = "src/lib.rs"
-
-[[bin]]
-name = "seam"
-path = "src/bin.rs"
-
-[dependencies]
-colored = "2.1"
-chrono = "0.4"
-unicode-width = "0.1.12"
-descape = "1.1.2"
-formatx = "0.2.2"
+[workspace]
+members = ["crates/seam", "crates/seam_argparse_proc_macro"]
diff --git a/README.md b/README.md
@@ -91,6 +91,7 @@ seam --sexp <<< '(hello (%define subject world) %subject)'
 ```
 
 ## Checklist
+ - [ ] A *splat* operation: `(%splat (a b c))` becomes `a b c`.
  - [x] `(%define x %body)` evaluates `%body` eagerly (at definition),
        while `(%define (y) %body)` only evaluates `%body` per call-site `(%y)`.
  - [x] Namespace macro `(%namespace ns (%include "file.sex"))` will prefix all definitions in its body with `ns/`, e.g. `%ns/defn`.
@@ -109,6 +110,7 @@ seam --sexp <<< '(hello (%define subject world) %subject)'
  - [ ] Implement lexical scope by letting macros store a copy of the scope they were defined in (or a reference?).
  - [x] `(%embed "/path")` macro, like `%include`, but just returns the file contents as a string.
  - [ ] Variadic arguments via `&rest` syntax.
+ - [ ] Type-checking facilities for user macros (?).
  - [ ] Delayed evaluation of macros by `%(...)` syntax.
    [ ] For example `%(f x y)` is the same as `(%f x y)`, so you can have `(%define uneval f x)` and then write `%(%uneval y)`.
  - [ ] `%list` macro which expands from `(p (%list a b c))` to `(p a b c)`.
diff --git a/crates/seam/Cargo.lock b/crates/seam/Cargo.lock
@@ -0,0 +1,439 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "cc"
+version = "1.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "colored"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
+dependencies = [
+ "lazy_static",
+ "windows-sys",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "descape"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "396a0a312bef78b5f62b0251d7162c4b8af162949b8b104d2967e41b26c1b68c"
+
+[[package]]
+name = "formatx"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db0f0c49aba98a3b2578315766960bd242885ff672fd62610c5557cd6c6efe03"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
+[[package]]
+name = "seam"
+version = "0.3.0"
+dependencies = [
+ "chrono",
+ "colored",
+ "descape",
+ "formatx",
+ "regex",
+ "unicode-width",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.5",
+ "windows_aarch64_msvc 0.52.5",
+ "windows_i686_gnu 0.52.5",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.5",
+ "windows_x86_64_gnu 0.52.5",
+ "windows_x86_64_gnullvm 0.52.5",
+ "windows_x86_64_msvc 0.52.5",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
diff --git a/crates/seam/Cargo.toml b/crates/seam/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "seam"
+description = "Symbolic Expressions As Markup."
+keywords = ["markup", "lisp", "macro", "symbolic-expression", "sexp"]
+license-file = "LICENSE"
+homepage = "https://git.knutsen.co/seam"
+version = "0.3.0"
+authors = ["Demonstrandum <samuel@knutsen.co>"]
+edition = "2021"
+
+[features]
+# default = ["debug"]
+debug = []
+
+[lib]
+name = "seam"
+path = "src/lib.rs"
+
+[[bin]]
+name = "seam"
+path = "src/bin.rs"
+
+[dependencies]
+seam_argparse_proc_macro = { path = "../seam_argparse_proc_macro" }
+colored = "2.1"
+chrono = "0.4"
+unicode-width = "0.1.12"
+descape = "1.1.2"
+formatx = "0.2.2"
+regex = "1.10.5"
diff --git a/src/assemble/css.rs b/crates/seam/src/assemble/css.rs
diff --git a/src/assemble/html.rs b/crates/seam/src/assemble/html.rs
diff --git a/crates/seam/src/assemble/mod.rs b/crates/seam/src/assemble/mod.rs
@@ -0,0 +1,143 @@
+use crate::{impl_clone_box, CloneBox, parse::tokens::Site};
+use std::{convert, error::Error, fmt::{self, Debug}};
+
+use colored::*;
+use unicode_width::UnicodeWidthStr;
+
+/// Error type for specific errors with generating
+/// each type of markup.
+#[derive(Debug, Clone)]
+pub struct GenerationError<'a> {
+    pub markup: &'static str,
+    pub message: String,
+    pub site: Site<'a>,
+}
+
+impl<'a> GenerationError<'a> {
+    /// Create a new error given the ML, the message, and the site.
+    pub fn new(ml: &'static str, msg: &str, site: &Site<'a>) -> Self {
+        Self {
+            markup: ml,
+            message: msg.to_owned(),
+            site: site.to_owned(),
+        }
+    }
+}
+
+/// Implement fmt::Display for user-facing error output.
+impl<'a> fmt::Display for GenerationError<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let line_prefix = format!("  {} |", self.site.line);
+        let line_view = self.site.line_slice();
+        writeln!(f, "{} {}", line_prefix, line_view)?;
+        writeln!(f, "{:>prefix_offset$} {:~>text_offset$}{:^>length$}", "|", "", "",
+            prefix_offset=UnicodeWidthStr::width(line_prefix.as_str()),
+            text_offset=self.site.line_column() - 1,
+            length=self.site.width())?;
+        write!(f, "{}: {}",
+            format!("[{}] Error Generating {} ({}:{}:{})",
+                "**".red().bold(),
+                self.markup.bold(),
+                self.site.source,
+                self.site.line,
+                self.site.line_column(),
+            ).black(),
+            self.message)
+    }
+}
+
+/// Implements std::error::Error.
+impl<'a> Error for GenerationError<'a> { }
+
+/// Convert from an io::Error to a generation error.
+impl<'a> From<std::io::Error> for GenerationError<'a> {
+    fn from(e: std::io::Error) -> Self {
+        Self {
+            markup: "<markup>",
+            message: format!("IO error: {}", e),
+            site: Site::unknown(),
+        }
+    }
+}
+
+/// An fmt::Error can be cast to an equally horribly
+/// ambiguous GenerationError.
+impl<'a> convert::From<fmt::Error> for GenerationError<'a> {
+    fn from(e: fmt::Error) -> Self {
+        Self {
+            markup: "<markup>",
+            message: format!("Format buffer error: {}", e),
+            site: Site::unknown(),
+        }
+    }
+}
+
+pub type Formatter<'a> = &'a mut dyn fmt::Write;
+
+/// Trait for all structs that can generate specific markup
+/// for the s-expression tree.
+pub trait MarkupFormatter: Debug + CloneBox {
+    // Required definitions:
+    /// Similar to fmt in Display/Debug traits, takes in a
+    /// mutable writable buffer, returns success or a specifc
+    /// error while generating the markup.
+    fn generate(&self, buf: Formatter) -> Result<(), GenerationError>;
+    /// Documentises the input, that's to say, it adds any
+    /// extra meta-information to the generated markup, if
+    /// the s-expressions your wrote ommited it.
+    /// e.g. All XML gets a `<?xml ... ?>` tag added to it.
+    fn document(&self) -> Result<String, GenerationError>;
+    // Default definitions:
+    /// Directly converts the s-expressions into a string
+    /// containing the markup, unless there was an error.
+    fn display(&self) -> Result<String, GenerationError> {
+        let mut buf = String::new();
+        self.generate(&mut buf)?;
+        Ok(buf)
+    }
+}
+
+impl_clone_box! { 'a; dyn MarkupFormatter + 'a}
+
+/// Automatically implement fmt::Display as a wrapper around
+/// MarkupFormatter::generate, but throws away the useful error message.
+impl fmt::Display for dyn MarkupFormatter {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.generate(f).map_err(|_| fmt::Error)
+    }
+}
+
+/// Parforms the following escapes:
+/// - `<` → `<`
+/// - `>` → `>`
+/// - `"` → `"`
+/// - `'` → `'`
+/// - `&` → `&`
+pub fn escape_xml(string: &str) -> String {
+    let mut bytes = string.bytes();
+    let mut byte_builder: Vec<u8> = Vec::with_capacity(bytes.len());
+    while let Some(byte) = bytes.next() {
+        match byte {
+            b'<'  => byte_builder.extend(b"<"),
+            b'>'  => byte_builder.extend(b">"),
+            b'"'  => byte_builder.extend(b"""),
+            b'\'' => byte_builder.extend(b"'"),
+            b'&'  => byte_builder.extend(b"&"),
+            _ => byte_builder.push(byte)
+        }
+    }
+    unsafe {
+        String::from_utf8_unchecked(byte_builder)
+    }
+}
+
+/// Re-constitute original S-expressions.
+pub mod sexp;
+/// Converts source into expanded plain-text.
+pub mod text;
+/// XML generation.
+pub mod xml;
+/// HTML5 CSS generation.
+pub mod css;
+/// HTML5 HTML generation.
+pub mod html;
diff --git a/src/assemble/sexp.rs b/crates/seam/src/assemble/sexp.rs
diff --git a/src/assemble/text.rs b/crates/seam/src/assemble/text.rs
diff --git a/src/assemble/xml.rs b/crates/seam/src/assemble/xml.rs
diff --git a/src/bin.rs b/crates/seam/src/bin.rs
diff --git a/crates/seam/src/lib.rs b/crates/seam/src/lib.rs
@@ -0,0 +1,72 @@
+#![allow(incomplete_features)]
+#![feature(pattern)]
+#![feature(box_patterns)]
+#![feature(associated_type_defaults)]
+
+pub mod parse;
+pub mod assemble;
+
+use parse::{expander, parser, lexer};
+
+use std::{fs, io, path::Path};
+
+pub const VERSION: (u8, u8, u8) = (0, 3, 0);
+
+/* Utilities. */
+
+/// See: <https://stackoverflow.com/a/30353928>
+pub trait CloneBox {
+    fn clone_box(&self) -> *mut ();
+}
+
+impl<'a, T> CloneBox for T where T: Clone + 'a {
+    fn clone_box(&self) -> *mut () {
+        Box::<T>::into_raw(Box::new(self.clone())) as *mut ()
+    }
+}
+
+#[macro_export]
+macro_rules! impl_clone_box {
+    ($($lif:tt),* ; $tra:ty) => {
+        impl< $($lif),* > Clone for Box< $tra > {
+            fn clone(&self) -> Box< $tra > {
+                unsafe {
+                    *Box::from_raw(self.clone_box() as *mut Self)
+                }
+            }
+        }
+    };
+    ($($lif:tt),* ; $($gen:tt),* ; $tra:ty) => {
+        impl< $($lif),* , $($gen),* > Clone for Box< $tra > {
+            fn clone(&self) -> Box< $tra > {
+                unsafe {
+                    *Box::from_raw(self.clone_box() as *mut Self)
+                }
+            }
+        }
+    };
+}
+
+/* Library helpers. */
+
+pub fn tree_builder<'a, P: AsRef<Path>>(source_path: Option<P>, string: String)
+    -> expander::Expander<'a> {
+    let path = source_path.map_or("<stdin>".to_string(),
+        |s| s.as_ref().to_string_lossy().to_string());
+    let tokenizer = lexer::Lexer::new(path, string);
+    let builder = parser::Parser::new(tokenizer);
+    expander::Expander::new(builder)
+}
+
+pub fn tree_builder_file<'a>(path: &Path)
+    -> io::Result<expander::Expander<'a>> {
+    let contents = fs::read_to_string(&path)?;
+    Ok(tree_builder(Some(path), contents))
+}
+
+pub fn tree_builder_stream(stream: &mut impl io::Read)
+    -> io::Result<expander::Expander> {
+    let mut contents = String::new();
+    stream.read_to_string(&mut contents)?;
+    Ok(tree_builder(Option::<&Path>::None, contents))
+}
diff --git a/crates/seam/src/parse/expander.rs b/crates/seam/src/parse/expander.rs
@@ -0,0 +1,760 @@
+use super::macros::*;
+use super::parser::{Node, ParseNode, ParseTree, Parser};
+use super::tokens::Site;
+
+use std::fmt::Display;
+use std::{
+    fmt,
+    cell::RefCell,
+    path::PathBuf,
+    ffi::OsString,
+    error::Error,
+    rc::Rc,
+    collections::{
+        HashMap,
+        BTreeSet,
+    },
+};
+
+use colored::*;
+use formatx;
+use unicode_width::UnicodeWidthStr;
+
+/// 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())
+    }
+}
+
+/// Implement fmt::Display for user-facing error output.
+impl<'a> fmt::Display for ExpansionError<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let ExpansionError(msg, site) = self;
+        let line_prefix = format!("  {} |", site.line);
+        let line_view = site.line_slice();
+        writeln!(f, "{} {}", line_prefix, line_view)?;
+        writeln!(f, "{:>prefix_offset$} {:~>text_offset$}{:^>length$}", "|", "", "",
+            prefix_offset=UnicodeWidthStr::width(line_prefix.as_str()),
+            text_offset=site.line_column() - 1,
+            length=site.width())?;
+        write!(f, "[{}] Error Expanding Macro {}: {}",
+            "**".red().bold(), site, msg)
+    }
+}
+
+/// Implements std::error::Error for macro expansion error.
+impl<'a> Error for ExpansionError<'a> { }
+
+/// A macro consists of:
+/// - its name;
+/// - its argument list (if any);
+/// - and its defintion (i.e. *body*).
+#[derive(Debug, Clone)]
+pub struct Macro<'a> {
+    name: String,
+    params: Box<[String]>,
+    body: Box<[ParseNode<'a>]>
+}
+// TODO: Macro to also store its own scope (at place of definition)
+// in order to implement lexical scoping.
+
+impl<'a> Macro<'a> {
+    pub fn new(name: &str) -> Macro {
+        Macro {
+            name: name.to_string(),
+            params: Box::new([]),
+            body:   Box::new([]),
+        }
+    }
+}
+
+/// Type of variable scope owned by an `Expander` instance.
+pub type Scope<'a> = RefCell<HashMap<String, Rc<Macro<'a>>>>; // Can you believe this type?
+
+/// Macro expansion context, takes a parser and expands
+/// any macro calls found in the generated parse-tree.
+#[derive(Debug, Clone)]
+pub struct Expander<'a> {
+    parser: Parser,
+    /// Include directories, in order of search.
+    includes: BTreeSet<PathBuf>,
+    subparsers: RefCell<Vec<Parser>>,
+    subcontexts: RefCell<Vec<Self>>,
+    invocations: RefCell<Vec<ParseNode<'a>>>,
+    definitions: Scope<'a>,
+}
+
+impl<'a> Expander<'a> {
+    pub fn new(parser: Parser) -> Self {
+        Self {
+            parser,
+            includes: BTreeSet::from([PathBuf::from(".")]),
+            subparsers: RefCell::new(Vec::new()),
+            subcontexts: RefCell::new(Vec::new()),
+            invocations: RefCell::new(Vec::new()),
+            definitions: RefCell::new(HashMap::new()),
+        }
+    }
+
+    /// Get underlying source-code of the active parser for current unit.
+    pub fn get_source(&self) -> &str {
+        self.parser.get_source()
+    }
+
+    /// Supply additonal include-directories for the macros
+    /// to use when searching for files to include/emebed.
+    /// Files are searched for in the order that of the directories.
+    pub fn add_includes<T: Iterator>(&mut self, dirs: T)
+        where T::Item: Into<PathBuf>
+    {
+        for dir in dirs {
+            self.includes.insert(dir.into());
+        }
+    }
+
+    /// Add a subparser owned by the expander context.
+    fn register_parser(&self, parser: Parser) -> &'a Parser {
+        {
+            let mut parsers = self.subparsers.borrow_mut();
+            parsers.push(parser);
+        }
+        self.latest_parser().unwrap()
+    }
+
+    /// Get the latest subparser added.
+    fn latest_parser(&self) -> Option<&'a Parser> {
+        let p = self.subparsers.as_ptr();
+        unsafe { (*p).last() }
+    }
+
+    /// Create and register a subcontext built from the current context.
+    fn create_subcontext(&self) -> &mut Self {
+        {
+            let copy = self.clone();
+            let mut contexts = self.subcontexts.borrow_mut();
+            contexts.push(copy);
+        }
+        self.latest_context().unwrap()
+    }
+
+    /// Get the latest subparser added.
+    fn latest_context(&self) -> Option<&mut Self> {
+        let contexts = self.subcontexts.as_ptr();
+        unsafe { (*contexts).last_mut() }
+    }
+
+    fn register_invocation(&self, node: ParseNode<'a>) -> &ParseNode<'a> {
+        let invocations = self.invocations.as_ptr();
+        unsafe {
+            (*invocations).push(node);
+            (*invocations).last().unwrap()
+        }
+    }
+
+    /// Update variable (macro) for this scope.
+    fn insert_variable(&self, name: String, var: Rc<Macro<'a>>) {
+        let mut defs = self.definitions.borrow_mut();
+        defs.insert(name, var);
+    }
+
+    /// Check if macro exists in this scope.
+    fn has_variable(&self, name: &str) -> bool {
+        let defs = self.definitions.borrow();
+        defs.contains_key(name)
+    }
+
+    fn get_variable(&self, name: &str) -> Option<Rc<Macro<'a>>> {
+        self.definitions.borrow().get(name).map(|m| m.clone())
+    }
+
+    /// Define a macro with `(%define a b)` --- `a` is a symbol or a list `(c ...)` where `c` is a symbol.
+    /// macro definitions will eliminate any preceding whitespace, so make sure trailing whitespace provides
+    /// the whitespace you need.
+    fn expand_define_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let [head, nodes@..] = &*params else {
+            return Err(ExpansionError(
+                format!("`%define` macro takes at least \
+                    two (2) arguments ({} were given.", params.len()),
+                node.owned_site()));
+        };
+
+        // If head is atomic, we assign to a 'variable'.
+        // Aditionally, we evaluate its body *eagerly*.
+        let def_macro = if let Some(variable) = head.atomic() {
+            let nodes = nodes.to_owned().into_boxed_slice();
+            let body = self.expand_nodes(nodes)?;
+            Rc::new(Macro {
+                name: variable.value.clone(),
+                params: Box::new([]),
+                body,
+            })
+        } else {  // Otherwise, we are assigning to a 'function'.
+            let ParseNode::List { nodes: defn_nodes, .. } = head else {
+                return Err(ExpansionError(
+                    "First argument of `%define` macro must be a list \
+                        or variable name/identifier.".to_owned(),
+                    node.site().to_owned()));
+            };
+            let [name, params@..] = &**defn_nodes else {
+                return Err(ExpansionError(
+                    "`%define` macro definition must at \
+                        least have a name.".to_owned(),
+                    node.site().to_owned()));
+            };
+            let mut arguments: Vec<String> = Vec::with_capacity(params.len());
+            for param_node in params {  // Verify arguments are symbols.
+                if let ParseNode::Symbol(param) = param_node {
+                    arguments.push(param.value.clone());
+                } else {
+                    return Err(ExpansionError(
+                        "`define` function arguments must be \
+                            symbols/identifers.".to_owned(),
+                        node.site().to_owned()));
+                };
+            }
+            let ParseNode::Symbol(name_node) = name else {
+                return Err(ExpansionError(
+                    "`define` function name must be \
+                        a symbol/identifier.".to_owned(),
+                    node.site().to_owned()));
+            };
+            let name = name_node.value.clone();
+
+            Rc::new(Macro {
+                name,
+                params: arguments.into_boxed_slice(),
+                body: nodes.to_owned().into_boxed_slice(),
+            })
+        };
+
+        self.insert_variable(def_macro.name.to_owned(), def_macro);
+        Ok(Box::new([]))
+    }
+
+    /// `(%ifdef symbol a b)` --- `b` is optional, however, if not provided *and*
+    /// the symbol is not defined, it will erase the whole expression, and whitespace will not
+    /// be preseved before it. If that's a concern, provide `b` as the empty string `""`.
+    fn expand_ifdef_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        if params.len() < 2 || params.len() > 3 {
+            return Err(ExpansionError(format!("`ifdef` takes one (1) \
+                    condition and one (1) consequent, a third optional \
+                    alternative expression may also be provided, but \
+                    `ifdef` was given {} arguments.", params.len()),
+                node.site().to_owned()));
+        }
+        let symbol = if let Some(node) = params[0].atomic() {
+            node.value.to_owned()
+        } else {
+            // FIXME: Borrow-checker won't let me use params[0].site() as site!
+            return Err(ExpansionError(
+                "The first argument to `ifdef` must be a symbol/name.".to_string(),
+                node.site().clone()));
+        };
+
+        let mut expanded = if self.has_variable(&symbol) {
+            self.expand_node(params[1].clone())?
+        } else {
+            if let Some(alt) = params.get(2) {
+                self.expand_node(alt.clone())?
+            } else {
+                Box::new([])
+            }
+        };
+        if let Some(first_node) = expanded.get_mut(0) {
+            first_node.set_leading_whitespace(node.leading_whitespace().to_owned());
+        }
+        Ok(expanded)
+    }
+
+    fn expand_include_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let params: Box<[ParseNode<'a>]> = self.expand_nodes(params)?;
+        let [path_node] = &*params else {
+            return Err(ExpansionError(
+                format!("Incorrect number of arguments \
+                    to `%include' macro. Got {}, expected {}.",
+                    params.len(), 1),
+                node.site().to_owned()));
+        };
+
+        let Some(Node { value: path, site, .. }) = path_node.atomic()  else {
+            return Err(ExpansionError(
+                "Bad argument to `%include' macro.\n\
+                    Expected a path, but did not get any value
+                    that could be interpreted as a path.".to_string(),
+                path_node.site().to_owned()))
+        };
+
+        // Open file, and parse contents!
+        let include_error = |error: Box<dyn Display>| ExpansionError(
+            format!("{}", error), site.to_owned());
+        let mut parser: Result<Parser, ExpansionError> = Err(
+            include_error(Box::new("No path tested.")));
+        // Try all include directories until one is succesful.
+        for include_dir in &self.includes {
+            let path = include_dir.join(path);
+            parser = super::parser_for_file(&path)
+                .or_else(|err| {
+                    let err = Box::new(err);
+                    // Try with `.sex` extensions appended.
+                    let mut with_ext = PathBuf::from(&path);
+                    let filename = path.file_name()
+                        .ok_or(include_error(err))?;
+                    with_ext.pop();  // Remove old filename.
+                    // Build new filename with `.sex` appended.
+                    let mut new_filename = OsString::new();
+                    new_filename.push(filename);
+                    new_filename.push(".sex");
+                    with_ext.push(new_filename); // Replace with new filename.
+                    match super::parser_for_file(&with_ext) {
+                        Ok(parser) => Ok(parser),
+                        Err(err)   => Err(include_error(Box::new(err)))
+                    }
+                });
+            if parser.is_ok() { break; }
+        }
+        // Register the parser for the found file.
+        let parser = self.register_parser(parser?);
+        let tree = match parser.parse() {
+            Ok(tree) => tree,
+            Err(error) => return Err(ExpansionError(
+                format!("{}", error), node.site().to_owned()))
+        };
+
+        // Build new (expanded) tree, with result of previous
+        // parse, while recursively expanding each branch in the
+        // tree too, as they are added.
+        let mut expanded_tree = Vec::with_capacity(tree.len());
+        for branch in tree {
+            expanded_tree.extend(self.expand_node(branch)?);
+        }
+        // First node should inherit leading whitespace from (%include ...) list.
+        if expanded_tree.len() != 0 {
+            expanded_tree[0].set_leading_whitespace(node.leading_whitespace().to_owned());
+        }
+        Ok(expanded_tree.into_boxed_slice())
+    }
+
+    fn expand_embed_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let params: Box<[ParseNode<'a>]> = self.expand_nodes(params)?;
+        let [path_node] = &*params else {
+            return Err(ExpansionError(
+                format!("Incorrect number of arguments \
+                    to `%embed' macro. Got {}, expected {}.",
+                    params.len(), 1),
+                node.site().to_owned()));
+        };
+
+        let Some(Node { value: path, site, .. }) = path_node.atomic()  else {
+            return Err(ExpansionError(
+                "Bad argument to `%embed' macro.\n\
+                    Expected a path, but did not get any value
+                    that could be interpreted as a path.".to_string(),
+                path_node.site().to_owned()))
+        };
+
+        // Open file, and read contents!
+        let embed_error = |error: Box<dyn Display>| ExpansionError(
+            format!("{}", error), site.to_owned());
+        let mut value: Result<String, ExpansionError> = Err(
+            embed_error(Box::new("No path tested.")));
+        // Try all include directories until one is succesful.
+        for include_dir in &self.includes {
+            let path = include_dir.join(path);
+            value = std::fs::read_to_string(path)
+                .map_err(|err| embed_error(Box::new(err)));
+            if value.is_ok() { break; }
+        }
+        let value = value?;
+        Ok(Box::new([
+            ParseNode::String(Node {
+                value,
+                site: node.owned_site(),
+                leading_whitespace: node.leading_whitespace().to_owned(),
+            }),
+        ]))
+    }
+
+    fn expand_date_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let params = self.expand_nodes(params)?;
+        let [date_format] = &*params else {
+            return Err(ExpansionError::new(
+                "`%date' macro only expects one formatting argument.",
+                node.site()))
+        };
+
+        let Some(Node { value: date_format, .. }) = date_format.atomic() else {
+            return Err(ExpansionError::new(
+                "`%date' macro needs string (or atomic) \
+                formatting argument.", node.site()))
+        };
+
+        let now = chrono::Local::now();
+        let formatted = now.format(&date_format).to_string();
+        let date_string_node = ParseNode::String(Node {
+            value: formatted,
+            site: node.site().clone(),
+            leading_whitespace: node.leading_whitespace().to_string(),
+        });
+        Ok(Box::new([date_string_node]))
+    }
+
+    /// `(%log ...)` logs to `STDERR` when called and leaves *no* node behind.
+    /// This means whitespace preceeding `(%log ...)` will be removed!
+    fn expand_log_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let mut words = Vec::with_capacity(params.len());
+        for param in self.expand_nodes(params)? {
+            if let Some(word) = param.atomic() {
+                words.push(word.value.clone());
+            } else {
+                return Err(ExpansionError::new("`log` should only take \
+                    arguments that are either symbols, strings or numbers.",
+                    node.site()));
+            }
+        }
+
+        eprintln!("{} {} {}: {}", "[#]".bold(), "log".bold().yellow(),
+            node.site(), words.join(" "));
+        Ok(Box::new([]))
+    }
+
+    fn expand_os_env_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let [ref var] = *params else {
+            return Err(ExpansionError::new(
+                "`%os/env' expects excatly one argument.",
+                node.site()));
+        };
+        let Some(var) = var.atomic() else {
+            return Err(ExpansionError::new(
+                "`%os/env' argument must be atomic (not a list).",
+                var.site()));
+        };
+        let Node { site, leading_whitespace, .. } = var.clone();
+        let Ok(value) = std::env::var(&var.value) else {
+            return Err(ExpansionError(
+                format!("No such environment variable ($`{}') visible.", &var.value),
+                site));
+        };
+        Ok(Box::new([
+            ParseNode::String(Node { value, site, leading_whitespace }),
+        ]))
+    }
+
+    fn expand_format_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let [format_str, ..] = &*params else {
+            return Err(ExpansionError::new(
+                "`%format' expects at a format-string.",
+                node.site()));
+        };
+        let ParseNode::String(format_str) = format_str else {
+            return Err(ExpansionError::new(
+                "First argument to `%format' must be a string.",
+                format_str.site()));
+        };
+        // Iterate and collect format arguments.
+        let mut arguments = params.iter();
+        let _ = arguments.next();  // Skip the format-string.
+        let Ok(mut template) = formatx::Template::new(&format_str.value) else {
+            return Err(ExpansionError::new(
+                "Invalid format string.",
+                &format_str.site));
+        };
+        for mut var in arguments {
+            // Check if we're replacing a named or positional placeholder.
+            let mut named: Option<&str> = None;
+            if let ParseNode::Attribute { keyword, node, .. } = var {
+                named = Some(keyword.as_str());
+                var = node;
+            }
+            // TODO: Somehow let non-atomic values be formattable?
+            let Some(Node { value, .. }) = var.atomic() else {
+                return Err(ExpansionError(
+                    format!("In `%format', the compound {} type is not formattable.",
+                        var.node_type()),
+                    var.site().clone()));
+            };
+            // Replace the placeholder.
+            match named {
+                Some(name) => template.replace(name, value),
+                None       => template.replace_positional(value),
+            }
+        }
+        // Template has been constructed, so now attempt to do subsitituions and
+        // render the formatted string.
+        match template.text() {
+            Ok(value) => Ok(Box::new([
+                ParseNode::String(Node {
+                    value,
+                    site: node.owned_site(),
+                    leading_whitespace: node.leading_whitespace().to_owned(),
+                })
+            ])),
+            Err(err) => Err(ExpansionError(
+                format!("Failed to format string: {}", err.message()),
+                format_str.site.clone()))
+        }
+    }
+
+    fn expand_namespace_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        // Start evaluating all the arguments to the macro in a separate context.
+        let context = self.clone();
+        let params =  context.expand_nodes(params)?;
+        let mut args = params.iter().peekable();
+        let Some(namespace) = args.next().and_then(ParseNode::atomic) else {
+            return Err(ExpansionError::new("Expected a namespace name.", node.site()));
+        };
+        // Parse options to macro.
+        let mut seperator = "/";  // Default namespace seperator is `/`.
+        while let Some(ParseNode::Attribute { keyword, node, site, .. }) = args.peek() {
+            let _ = args.next();
+            match keyword.as_str() {
+                "separator" => match node.atomic() {
+                    Some(Node { value, .. }) => seperator = &value,
+                    None => return Err(ExpansionError(
+                        format!("`%namespace' separator must be a symbol, got a {}.", node.node_type()),
+                        node.owned_site())),
+                },
+                opt => return Err(ExpansionError(
+                    format!("Unknown option `:{}' to `%namespace' macro.", opt),
+                    site.clone())),
+            }
+        }
+        // Find all the definitions made within the context of the
+        // `%namespace` macro and include the defintion prefixed by
+        // the namespace in the *current* scope.
+        {
+            let mut self_defs = self.definitions.borrow_mut();
+            let defs = context.definitions.borrow();
+            for (key, value) in defs.iter() {
+                let new_key = format!("{}{}{}", namespace.value, seperator, key);
+                self_defs.insert(new_key, value.clone());
+            }
+        }
+        // Return remaining body of the macro.
+        Ok(args.cloned().collect())
+    }
+
+    fn expand_raw_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let mut builder = String::new();
+        let args = self.expand_nodes(params)?;
+        for arg in args {
+            let Some(Node { value, leading_whitespace, .. }) = arg.atomic() else {
+                return Err(ExpansionError(
+                    format!("Expected a literal, found a {} node instead.", arg.node_type()),
+                    arg.owned_site()));
+            };
+            builder += leading_whitespace;
+            builder += value;
+        }
+        Ok(Box::new([
+            ParseNode::Raw(Node {
+                value: builder,
+                site: node.owned_site(),
+                leading_whitespace: node.leading_whitespace().to_owned(),
+            })
+        ]))
+    }
+
+    fn expand_string_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let mut builder = String::new();
+        let args = self.expand_nodes(params)?;
+        for arg in args {
+            let Some(Node { value, leading_whitespace, .. }) = arg.atomic() else {
+                return Err(ExpansionError(
+                    format!("Expected a literal, found a {} node instead.", arg.node_type()),
+                    arg.owned_site()));
+            };
+            builder += leading_whitespace;
+            builder += value;
+        }
+        Ok(Box::new([
+            ParseNode::String(Node {
+                value: builder,
+                site: node.owned_site(),
+                leading_whitespace: node.leading_whitespace().to_owned(),
+            })
+        ]))
+    }
+
+    fn expand_join_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let args: ArgRules = arguments! {
+            mandatory(1): literal,
+            mandatory(2): number fn(_v: ParseNode) { true },
+            optional("trailing"): literal["true", "false"],
+            rest: literal,
+        };
+        let arg_parser = args.parser(¶ms);
+
+        todo!()
+    }
+
+    fn expand_macro(&self, name: &str, node: &ParseNode<'a>, params: ParseTree<'a>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        // Eagerly evaluate parameters passed to macro invocation.
+        let params = self.expand_nodes(params)?;
+
+        let Some(mac) = self.get_variable(name) else {
+            return Err(ExpansionError::new(
+                &format!("Macro not found (`{}').", name), &node.owned_site()))
+        };
+
+        // Instance of expansion subcontext.
+        let subcontext = self.create_subcontext();
+        // Check enough arguments were given.
+        if params.len() != mac.params.len() {
+            return Err(ExpansionError(
+                format!("`%{}` macro expects {} arguments, \
+                        but {} were given.", &mac.name, mac.params.len(),
+                        params.len()), node.site().to_owned()));
+        }
+        // Define arguments for body.
+        for i in 0..params.len() {
+            let arg_macro = Macro {
+                name: mac.params[i].to_owned(),
+                params: Box::new([]),
+                body: Box::new([params[i].clone()]), //< Argument as evaluated at call-site.
+            };
+            subcontext.insert_variable(mac.params[i].to_string(), Rc::new(arg_macro));
+        }
+        // Expand body.
+        let mut expanded = subcontext.expand_nodes(mac.body.clone())?.to_vec();
+        // Inherit leading whitespace of invocation.
+        if let Some(first_node) = expanded.get_mut(0) {
+            first_node.set_leading_whitespace(node.leading_whitespace().to_owned());
+        }
+        Ok(expanded.into_boxed_slice())
+    }
+
+    fn expand_invocation(&self,
+                         name: &str, //< Name of macro (e.g. %define).
+                         node: &ParseNode<'a>, //< Node for `%'-macro invocation.
+                         params: Box<[ParseNode<'a>]> //< Passed in arguments.
+    ) -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        // Some macros are lazy (e.g. `ifdef`), so each macro has to
+        //   expand the macros in its arguments individually.
+        match name {
+            "define"    => self.expand_define_macro(node, params),
+            "ifdef"     => self.expand_ifdef_macro(node, params),
+            "raw"       => self.expand_raw_macro(node, params),
+            "string"    => self.expand_string_macro(node, params),
+            "include"   => self.expand_include_macro(node, params),
+            "embed"     => self.expand_embed_macro(node, params),
+            "namespace" => self.expand_namespace_macro(node, params),
+            "date"      => self.expand_date_macro(node, params),
+            "join"      => self.expand_join_macro(node, params),
+            "log"       => self.expand_log_macro(node, params),
+            "format"    => self.expand_format_macro(node, params),
+            "os/env"    => self.expand_os_env_macro(node, params),
+            _           => self.expand_macro(name, node, params),
+        }
+    }
+
+    pub fn expand_node(&self, node: ParseNode<'a>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        match node {
+            ParseNode::Symbol(ref sym) => {
+                // Check if symbol starts with %... and replace it
+                // with it's defined value.
+                if sym.value.starts_with("%") {
+                    let name = &sym.value[1..];
+                    if let Some(def) = self.get_variable(name) {
+                        if !def.params.is_empty() {  // Should not be a function.
+                            return Err(ExpansionError::new(
+                                &format!("`{}` is a macro that takes arguments, \
+                                    and cannot be used as a variable.", name),
+                                &sym.site))
+                        }
+                        Ok(def.body.clone())
+                    } else {  // Not found.
+                        Err(ExpansionError(
+                            format!("No such macro, `{}`.", name),
+                            sym.site.to_owned()))
+                    }
+                } else {
+                    Ok(Box::new([node]))
+                }
+            },
+            ParseNode::List { ref nodes, ref site, ref end_token, ref leading_whitespace } => {
+                // Check for macro invocation (%_ _ _ _).
+                // Recurse over every element.
+                let len = nodes.len();
+                let mut call = nodes.to_vec().into_iter();
+                let head = call.next();
+
+                // Pathway: (%_ _ _) macro invocation.
+                if let Some(ref symbol@ParseNode::Symbol(..)) = head {
+                    let node = self.register_invocation(node.clone());
+                    let name = symbol.atomic().unwrap().value.clone();
+                    if name.starts_with("%") {
+                        // Rebuild node...
+                        let name = &name[1..];
+                        let mut params: Vec<ParseNode> = call.collect();
+                        // Delete leading whitespace of leading argument.
+                        if let Some(leading) = params.first_mut() {
+                            if !leading.leading_whitespace().contains('\n') {
+                                leading.set_leading_whitespace(String::from(""));
+                            }
+                        }
+                        return self.expand_invocation(name, node, params.into_boxed_slice());
+                    }
+                }
+                // Otherwise, if not a macro, just expand child nodes incase they are macros.
+                let mut expanded_list = Vec::with_capacity(len);
+                expanded_list.extend(self.expand_node(head.unwrap().clone())?);
+                for elem in call {
+                    expanded_list.extend(self.expand_node(elem)?);
+                }
+
+                Ok(Box::new([ParseNode::List {
+                    nodes: expanded_list.into_boxed_slice(),
+                    site: site.clone(),
+                    end_token: end_token.clone(),
+                    leading_whitespace: leading_whitespace.clone(),
+                }]))
+            },
+            ParseNode::Attribute { keyword, node, site, leading_whitespace } => {
+                let mut expanded_nodes = self.expand_node(*node)?;
+                let new_node = Box::new(expanded_nodes[0].clone());
+                expanded_nodes[0] = ParseNode::Attribute {
+                    keyword: keyword.clone(),
+                    node: new_node,
+                    site: site.clone(),
+                    leading_whitespace: leading_whitespace.clone(),
+                };
+                Ok(expanded_nodes)
+            },
+            _ => Ok(Box::new([node]))
+        }
+    }
+
+    pub fn expand_nodes(&self, tree: Box<[ParseNode<'a>]>)
+    -> Result<ParseTree<'a>, ExpansionError<'a>> {
+        let mut expanded = Vec::with_capacity(tree.len());
+        for branch in tree {
+            expanded.extend(self.expand_node(branch)?);
+        }
+        Ok(expanded.into_boxed_slice())
+    }
+
+    pub fn expand(&'a self) -> Result<ParseTree<'a>, Box<dyn 'a + std::error::Error>> {
+        let tree = self.parser.parse()?;
+        let expanded = self.expand_nodes(tree)?;
+        Ok(expanded)
+    }
+}
diff --git a/src/parse/lexer.rs b/crates/seam/src/parse/lexer.rs
diff --git a/crates/seam/src/parse/macros.rs b/crates/seam/src/parse/macros.rs
@@ -0,0 +1,394 @@
+//! Expander macros argument parsing utilities.
+use std::{borrow::Borrow, collections::HashMap};
+
+use regex::Regex;
+
+use super::{
+    expander::ExpansionError,
+    parser::{Node, ParseNode, ParseTree},
+};
+
+pub enum ArgPredicate {
+    Exactly(String),
+    Matching(Regex),
+    Satisfying(Box<dyn Fn(ParseNode) -> bool>),
+}
+
+/// Type of argument, and what kind of
+/// conditions they have to satisfy.
+///     Number ⊆ Literal;
+///     String ⊆ Literal;
+///     Symbol ⊆ Symbolic;
+///     Number ⊆ Symbolic;
+///     Symbolic ⊆ Literal;
+///     * ⊆ Any.
+pub enum ArgType {
+    Literal(Vec<ArgPredicate>),
+    String(Vec<ArgPredicate>),
+    Symbol(Vec<ArgPredicate>),
+    Number(Vec<ArgPredicate>),
+    Symbolic(Vec<ArgPredicate>),
+    List(Vec<ArgType>),
+    Any(Vec<ArgType>),
+}
+
+/// Kind of arguemnt type (optional, mandatory).
+pub enum Arg {
+    Mandatory(ArgType),
+    Optional(ArgType),
+}
+
+/// Positonal or named argument position.
+enum ArgPos<'a> { Int(usize), Str(&'a str) }
+/// What kind of types can be matched against
+/// when determining an arguments positionality.
+pub trait ArgMatcher {
+    fn unwrap(&self) -> ArgPos;
+}
+impl ArgMatcher for usize {
+    fn unwrap(&self) -> ArgPos { ArgPos::Int(*self) }
+}
+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> {
+        match value.unwrap() {
+            ArgPos::Int(int) => Some(int),
+            _ => None,
+        }
+    }
+}
+impl<'a> From<&'a Box<dyn ArgMatcher + 'a>> for Option<&'a str> {
+    fn from(value: &'a Box<dyn ArgMatcher + 'a>) -> Option<&'a str> {
+        match value.unwrap() {
+            ArgPos::Str(str) => Some(str),
+            _ => None,
+        }
+    }
+}
+impl From<usize> for Box<dyn ArgMatcher> {
+    fn from(value: usize) -> Box<dyn ArgMatcher> { 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) }
+}
+impl<'a> From<&'a String> for Box<dyn ArgMatcher + 'a> {
+    fn from(value: &'a String) -> Box<dyn ArgMatcher + 'a> { Box::new(value.as_ref()) }
+}
+
+/// Holds information as to what kind rules
+/// must be satsified for an argument's given
+/// position.
+/// Pattern pertains to how to argument sits
+/// in the macro-call's argument list.
+struct ArgPattern<'a> {
+    argument: Arg,
+    pattern: Box<dyn Fn(&Box<dyn ArgMatcher + 'a>) -> bool>,
+}
+
+/// A complete description of how a macro's arguments
+/// should be parsed.
+pub struct ArgRules<'a> {
+    patterns: Vec<ArgPattern<'a>>,
+    trailing: Option<ArgType>,
+}
+
+impl<'a> ArgRules<'a> {
+    pub fn new() -> Self {
+        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
+    {
+        self.patterns.push(ArgPattern {
+            argument: arg,
+            pattern: Box::new(matcher),
+        });
+    }
+    /// Register matching on all remaining arguments.
+    pub fn register_remaining(&mut self, arg_type: ArgType) {
+        self.trailing = Some(arg_type);
+    }
+    /// 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)
+    }
+}
+
+/// 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,
+            }
+        }
+        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
+    }}
+}
+
+
+// --- Proc Macro
+use seam_argparse_proc_macro::*;
+
+
+// ---
+
+pub struct ArgParser<'params: 'rules, 'rules, 'tree> {
+    rules: ArgRules<'rules>,
+    positional: HashMap<usize, &'params ParseNode<'tree>>,
+    named: HashMap<String, &'params ParseNode<'tree>>,
+}
+
+impl<'params, 'rules, 'tree> ArgParser<'params, 'rules, 'tree> {
+    pub fn new(rules: ArgRules<'rules>,
+               params: &'params ParseTree<'tree>)
+    -> Result<Self, ExpansionError<'tree>>  {
+        let mut position = 0;
+        let mut positional = HashMap::with_capacity(params.len());
+        let mut named = HashMap::with_capacity(params.len());
+        for param in params {
+            let matcher: Box<dyn ArgMatcher>;
+            // Register each argument with the parser.
+            if let ParseNode::Attribute { keyword, node, .. } = param {
+                named.insert(keyword.to_owned(), node.borrow());
+                matcher = keyword.into();
+            } else {
+                positional.insert(position, param);
+                position += 1;
+                matcher = position.into();
+            }
+            // Check if they do actually match with any of the rules.
+            let mut arg_rule = None;
+            for rule in &rules.patterns {
+                // First check that there is a valid place for this argument.
+                let is_valid_argument = (rule.pattern)(&matcher);
+                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.
+        }
+        // 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";
+
+        Ok(Self { rules, positional, named, })
+    }
+
+    pub fn get<P>(&mut self, key: P) -> Result<ParseNode<'tree>, ExpansionError<'tree>>
+        where P: Into<Box<dyn ArgMatcher>>
+    {
+        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) => {},
+                }
+            }
+        }
+
+        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 extract_literal<'a>(node: ParseNode<'a>) -> Result<Node<'a>, ExpansionError<'a>> {
+    match node {
+        ParseNode::Symbol(lit)
+      | ParseNode::Number(lit)
+      | ParseNode::String(lit)
+      | ParseNode::Raw(lit) => Ok(lit),
+        _ => Err(ExpansionError(
+            format!("Expected a literal, got a {} instead.", node.node_type()),
+            node.owned_site()
+        ))
+    }
+}
+
+pub fn extract_string<'a>(node: ParseNode<'a>) -> Result<Node<'a>, ExpansionError<'a>> {
+    match node {
+        ParseNode::String(string)
+      | ParseNode::Raw(string) => Ok(string),
+        _ => Err(ExpansionError(
+            format!("Expected a string, got a {} instead.", node.node_type()),
+            node.owned_site()
+        ))
+    }
+}
+
+pub fn extract_symbol<'a>(node: ParseNode<'a>) -> Result<Node<'a>, ExpansionError<'a>> {
+    match node {
+        ParseNode::Symbol(sym) => Ok(sym),
+        _ => Err(ExpansionError(
+            format!("Expected a symbol, got a {} instead.", node.node_type()),
+            node.owned_site()
+        ))
+    }
+}
+
+pub fn extract_number<'a>(node: ParseNode<'a>) -> Result<Node<'a>, ExpansionError<'a>> {
+    match node {
+        ParseNode::Number(lit) => Ok(lit),
+        _ => Err(ExpansionError(
+            format!("Expected a number, got a {} instead.", node.node_type()),
+            node.owned_site()
+        ))
+    }
+}
+
+pub fn extract_symbolic<'a>(node: ParseNode<'a>) -> Result<Node<'a>, ExpansionError<'a>> {
+    match node {
+        ParseNode::Symbol(sym)
+      | ParseNode::Number(sym) => Ok(sym),
+        _ => Err(ExpansionError(
+            format!("Expected a symbolic literal, got a {} instead.", node.node_type()),
+            node.owned_site()
+        ))
+    }
+}
+
+pub fn extract_list<'a>(node: ParseNode<'a>) -> Result<Vec<ParseNode<'a>>, ExpansionError<'a>> {
+    match node {
+        ParseNode::List { nodes, .. } => Ok(nodes.to_vec()),
+        _ => Err(ExpansionError(
+            format!("Expected a list, got a {} instead.", node.node_type()),
+            node.owned_site()
+        ))
+    }
+}
+
+pub fn extract_any<'a>(node: ParseNode<'a>) -> Result<ParseNode<'a>, ExpansionError<'a>> {
+    Ok(node)
+}
diff --git a/crates/seam/src/parse/mod.rs b/crates/seam/src/parse/mod.rs
@@ -0,0 +1,17 @@
+pub mod tokens;
+pub mod lexer;
+pub mod parser;
+#[macro_use]
+mod macros;
+pub mod expander;
+
+pub use parser::ParseTree;
+use std::{fs, path::Path, error::Error};
+
+/// Build a parser for a file without expanding macros.
+pub fn parser_for_file(path: &Path) -> Result<parser::Parser, Box<dyn Error>> {
+    let contents = fs::read_to_string(&path)?;
+    let tokenizer = lexer::Lexer::new(path.to_string_lossy().to_string(), contents);
+    let builder = parser::Parser::new(tokenizer);
+    Ok(builder)
+}
diff --git a/crates/seam/src/parse/parser.rs b/crates/seam/src/parse/parser.rs
@@ -0,0 +1,512 @@
+use std::{error::Error, fmt, str::FromStr};
+use unicode_width::UnicodeWidthStr;
+use descape::UnescapeExt;
+
+use super::{lexer::{LexError, Lexer}, tokens::{Kind, Site, Token}};
+
+/// The [`Node`] type represents what atomic/literals are parsed
+/// into; i.e. not compound types (e.g. lists, attributes).
+/// These are just a common storage for the literals in [`ParseNode`].
+#[derive(Debug, Clone)]
+pub struct Node<'a> {
+    pub value: String,
+    pub site: Site<'a>,
+    pub leading_whitespace: String,
+}
+
+impl<'a> Node<'a> {
+    pub fn new(value: &str, site: &Site<'a>, leading_whitespace: &str) -> Self {
+        Self {
+            site: site.to_owned(),
+            value: value.to_owned(),
+            leading_whitespace: leading_whitespace.to_owned(),
+        }
+    }
+}
+
+/// Parse nodes are the components of the syntax tree that
+/// the source code is translated into.
+/// These nodes are also produced at compile-time by the macro expander.
+#[derive(Debug, Clone)]
+pub enum ParseNode<'a> {
+    Symbol(Node<'a>),
+    Number(Node<'a>),
+    String(Node<'a>),
+    Raw(Node<'a>), //< Raw-content strings are not parsed, only expanded by macros.
+    List {
+        nodes: Box<[ParseNode<'a>]>,
+        site: Site<'a>,
+        end_token: Token<'a>,
+        leading_whitespace: String,
+    },
+    Attribute {
+        keyword: String,
+        node: Box<ParseNode<'a>>,
+        site: Site<'a>,
+        leading_whitespace: String,
+    },
+}
+
+impl<'a> ParseNode<'a> {
+    /// Unwrap a literal node if it is a symbol or number.
+    pub fn symbolic(&self) -> Option<&Node<'a>> {
+        match self {
+            Self::Symbol(ref node)
+            | Self::Number(ref node) => Some(node),
+            _ => None,
+        }
+    }
+
+    /// Unwrap string-like nodes.
+    pub fn string(&self) -> Option<&Node<'a>> {
+        match self {
+            Self::String(ref node) | Self::Raw(ref node) => Some(node),
+            _ => None,
+        }
+    }
+
+    /// Unwrap literal (atomic) nodes into their underlying [`Node`].
+    pub fn atomic(&self) -> Option<&Node<'a>> {
+        match self {
+            Self::Symbol(ref node)
+            | Self::Number(ref node)
+            | Self::String(ref node)
+            | Self::Raw(ref node) => Some(node),
+            _ => None,
+        }
+    }
+
+    /// Same as [`Self::atomic`], but consumes the node,
+    /// returning an owned [`Node`].
+    pub fn into_atomic(self) -> Option<Node<'a>> {
+        match self {
+            Self::Symbol(node)
+            | Self::Number(node)
+            | Self::String(node) => Some(node),
+            _ => None,
+        }
+    }
+
+    /// Get a reference to the parse node's underlying [`Site`].
+    pub fn site(&self) -> &Site<'a> {
+        match self {
+            Self::Symbol(ref node)
+            | Self::Number(ref node)
+            | Self::String(ref node)
+            | Self::Raw(ref node) => &node.site,
+            Self::List { ref site, .. } => site,
+            Self::Attribute { ref site, .. } => site,
+        }
+    }
+
+    /// Clone the underlying [`Site`] of this parse node.
+    pub fn owned_site(&self) -> Site<'a> {
+        match self {
+            Self::Symbol(node)
+            | Self::Number(node)
+            | Self::String(node)
+            | Self::Raw(node) => node.site.clone(),
+            Self::List { site, .. } => site.clone(),
+            Self::Attribute { site, .. } => site.clone(),
+        }
+    }
+
+    /// Get a reference to the underlying leading whitespace string
+    /// of this parse node.
+    pub fn leading_whitespace(&self) -> &str {
+        match self {
+            Self::Symbol(ref node)
+            | Self::Number(ref node)
+            | Self::String(ref node)
+            | Self::Raw(ref node) => &node.leading_whitespace,
+            Self::List { ref leading_whitespace, .. } => leading_whitespace,
+            Self::Attribute { ref leading_whitespace, .. } => leading_whitespace,
+        }
+    }
+
+    /// Modify the underlying leading whitespace stored for this parse node.
+    pub fn set_leading_whitespace(&mut self, whitespace: String) {
+        match self {
+            Self::Symbol(ref mut node)
+            | Self::Number(ref mut node)
+            | Self::String(ref mut node)
+            | Self::Raw(ref mut node) => node.leading_whitespace = whitespace,
+            Self::List { ref mut leading_whitespace, .. } => *leading_whitespace = whitespace,
+            Self::Attribute { ref mut leading_whitespace, .. } => *leading_whitespace = whitespace,
+        };
+    }
+
+    /// Get a `&'static str` string name of what type of parse node this is.
+    pub fn node_type(&self) -> &'static str {
+        match self {
+            Self::Symbol(..) => "symbol",
+            Self::Number(..) => "number",
+            Self::String(..) => "string",
+            Self::Raw(..) => "raw-content string",
+            Self::List { .. } => "list",
+            Self::Attribute { .. } => "attribute",
+        }
+    }
+}
+
+/// Trait determining if a [`ParseNode`] can be converted into
+/// a value of a given (usually inferred) type.
+pub trait IntoValue<'a, T>: Sized {
+    fn into_value(&'a self) -> Option<T> { None }
+}
+
+/// A number type.
+trait Num<Rhs = Self, Output = Self>:
+    std::ops::Add<Rhs, Output = Output>
+  + std::ops::Sub<Rhs, Output = Output>
+  + std::ops::Mul<Rhs, Output = Output>
+  + std::ops::Div<Rhs, Output = Output>
+  + std::ops::Rem<Rhs, Output = Output> { }
+impl Num for usize { }
+impl Num for isize { }
+impl Num for u32 { }
+impl Num for i32 { }
+impl Num for u64 { }
+impl Num for i64 { }
+impl Num for f32 { }
+impl Num for f64 { }
+
+/// Convert parse-node into value if said value is a number type.
+impl<'a, T: Num + FromStr> IntoValue<'a, T> for ParseNode<'a> {
+    fn into_value(&self) -> Option<T> {
+        match self {
+            ParseNode::Number(node) => node.value.parse().ok(),
+            _ => None,
+        }
+    }
+}
+
+/// Convert parse-node into value if said value is a symbol/string type.
+impl<'a> IntoValue<'a, &'a str> for ParseNode<'a> {
+    fn into_value(&'a self) -> Option<&'a str> {
+        match self {
+            ParseNode::Symbol(node)
+          | ParseNode::String(node)
+          | ParseNode::Raw(node) => Some(node.value.as_ref()),
+            _ => None,
+        }
+    }
+}
+
+/// TODO: Convert parse-node into value if said value is a list type.
+/*
+impl<'a, V> IntoValue<'a, &'a [V]> for ParseNode<'a> {
+    fn into_value(&'a self) -> Option<&'a [V]> {
+        match self {
+            ParseNode::List { nodes, .. } => {
+                let mut values = Vec::with_capacity(nodes.len());
+                for node in nodes {
+                    let Some(value) = node.into_value() else {
+                        return None;
+                    };
+                    let value: V = value;
+                    values.push(value)
+                }
+                // TODO: fix this.
+                let values: &[V] = &*Box::leak(values.into_boxed_slice());
+                Some(values)
+            },
+            _ => None,
+        }
+    }
+}
+*/
+
+/// An array of parse nodes, like in a [`ParseNode::List`], never grows.
+/// Hence we prefer the `Box<[...]>` representation over a `Vec<...>`.
+pub type ParseTree<'a> = Box<[ParseNode<'a>]>;
+
+#[derive(Debug, Clone)]
+pub struct ParseError<'a>(pub String, pub Site<'a>);
+
+impl<'a> fmt::Display for ParseError<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let ParseError(msg, site) = self;
+        let line_prefix = format!("  {} |", site.line);
+        let line_view = site.line_slice();
+        writeln!(f, "{} {}", line_prefix, line_view)?;
+        writeln!(f, "{:>prefix_offset$} {:~>text_offset$}{:^>length$}", "|", "", "",
+            prefix_offset=UnicodeWidthStr::width(line_prefix.as_str()),
+            text_offset=site.line_column() - 1,
+            length=site.width())?;
+        write!(f, "[**] Parse Error ({}:{}:{}): {}",
+            site.source, site.line, site.line_column(), msg)
+    }
+}
+
+impl<'a> Error for ParseError<'a> { }
+
+/// Parser structure walks through source using lexer.
+#[derive(Debug, Clone)]
+pub struct Parser {
+    lexer: Lexer, //< Parser owns a lexer.
+}
+
+impl<'a> Parser {
+    pub fn new(lexer: Lexer) -> Self {
+        Self { lexer }
+    }
+
+    pub fn get_source(&self) -> &str {
+        self.lexer.get_source()
+    }
+
+    /// Parse whole source code, finishing off the lexer.
+    pub fn parse(&'a self) -> Result<ParseTree, Box<dyn Error + 'a>> {
+        let mut root: Vec<ParseNode> = Vec::new();
+        while !self.lexer.eof() {
+            let expr = self.parse_expr()?;
+            root.push(expr);
+        }
+        return Ok(root.into_boxed_slice());
+    }
+
+    /// Produce a parse node from the current position in the lexer.
+    pub fn parse_expr(&'a self) -> Result<ParseNode, Box<dyn Error + 'a>> {
+        let token = self.lexer.peek()?;
+        match token.kind {
+            Kind::LParen => self.parse_list(),
+            Kind::RParen => Err(ParseError(
+                "Unexpected `)' closing parenthesis.".to_owned(),
+                token.site.to_owned()))?,
+            Kind::Keyword => self.parse_keyword(),
+            Kind::Symbol => Ok(ParseNode::Symbol(self.parse_atomic()?)),
+            // TODO: Parse (escpae) string-literals.
+            Kind::String => Ok(ParseNode::String(self.parse_atomic()?)),
+            Kind::Number => Ok(ParseNode::Number(self.parse_atomic()?)),
+        }
+    }
+
+    /// Parse keyword-attribute pair.
+    fn parse_keyword(&'a self) -> Result<ParseNode, Box<dyn Error + 'a>> {
+        // Consume :keyword token.
+        let token = self.lexer.consume()?;
+        assert_eq!(token.kind, Kind::Keyword);
+        // Check we are able to consume next expression for keyword's value.
+        {
+            let no_expr_error = ParseError(
+                format!("Keyword `:{}' expects an expression follwing it.", token.value),
+                token.site.to_owned());
+            if self.lexer.eof() { Err(no_expr_error.clone())? ;}
+            match self.lexer.peek()? {
+                Token { kind: Kind::RParen, .. } => Err(no_expr_error)?,
+                _ => ()
+            }
+        }
+        // Otherwise, parse the value and combine the node.
+        let value = self.parse_expr()?;
+        Ok(ParseNode::Attribute {
+            keyword: token.value.to_owned(),
+            node: Box::new(value),
+            site: token.site.to_owned(),
+            leading_whitespace: token.leading_whitespace.to_owned(),
+        })
+    }
+
+    /// Parse a literal node.
+    /// This is where escapes in symbols and strings are handled.
+    fn parse_atomic(&'a self) -> Result<Node<'a>, LexError<'a>> {
+        let token = self.lexer.consume()?;
+        let value = match token.kind {
+            Kind::Symbol | Kind::Number | Kind::Keyword => escape_sanitize(token.value),
+            Kind::String => escape_string(token.value, &token.site)?,
+            _ => unreachable!("called `parse_atomic` on non-atomic token."),
+        };
+        Ok(Node {
+            value,
+            site: token.site.clone(),
+            leading_whitespace: token.leading_whitespace.to_string(),
+        })
+    }
+
+    /// Parse a list `( [...] )'.
+    fn parse_list(&'a self) -> Result<ParseNode<'a>, Box<dyn Error + 'a>> {
+        // Consumed the `(' token.
+        let lparen = self.lexer.consume()?;
+        assert_eq!(lparen.kind, Kind::LParen);
+        // Collect list elements.
+        let mut elements = Vec::new();
+        let mut rparen: Option<Token> = None;
+        while !self.lexer.eof() {
+            // Keep parsing expressions until `)' is reached.
+            let token = self.lexer.peek()?;
+            if token.kind == Kind::RParen {
+                rparen = Some(self.lexer.consume()?); // Swallow up `)'.
+                break;
+            }
+            let expr = self.parse_expr()?;
+            elements.push(expr);
+        }
+        // Closing parenthesis was never found.
+        let Some(rparen) = rparen else {
+            return Err(ParseError(
+                "Expected `)' closing parenthesis.".to_owned(),
+                lparen.site.to_owned()))?;
+        };
+        Ok(ParseNode::List {
+            nodes: elements.into_boxed_slice(),
+            site: lparen.site.to_owned(),
+            end_token: rparen.to_owned(),
+            leading_whitespace: lparen.leading_whitespace.to_owned(),
+        })
+    }
+}
+
+/// Santize any escaped characters by removing their leading backslash.
+fn escape_sanitize(string: &str) -> String {
+    let mut builder = String::with_capacity(string.len());
+    let mut chars = string.chars();
+    while let Some(c) = chars.next() {
+        if c == '\\' { continue; }
+        builder.push(c)
+    }
+    builder
+}
+
+/// Parse a string with its escapes.
+/// **Note:** Uses the `descape` crate for now.
+fn escape_string<'a>(string: &'a str, site: &Site<'a>) -> Result<String, LexError<'a>> {
+    string.to_unescaped()
+        .map(|s| s.to_string())
+        .map_err(|index| {
+            LexError(
+                format!("Invalid escape `\\{}' at byte-index {}.",
+                    string.chars().nth(index).unwrap_or('?'), index),
+                site.clone())
+        })
+}
+
+pub trait SearchTree<'a> {
+    /// Search the parse-tree for a specific node with a specific value.
+    fn search_node(&'a self, kind: SearchType,
+                   value: &str,
+                   case_insensitive: bool,
+                   level: usize) -> Option<&ParseNode<'a>>;
+}
+
+#[derive(Clone, Copy, PartialEq)]
+pub enum SearchType {
+    ListHead, ListMember,
+    Symbol, Number, String,
+    Attribute,
+    Any,
+}
+
+impl SearchType {
+    pub fn is_a(self, kind: SearchType) -> bool {
+        self == SearchType::Any || self == kind
+    }
+}
+
+impl<'a> SearchTree<'a> for ParseNode<'a> {
+    fn search_node(&'a self, kind: SearchType, value: &str,
+                   insensitive: bool, level: usize) -> Option<&ParseNode<'a>> {
+        if level == 0 {
+            return None;
+        }
+
+        let is_equal = |string: &str| if insensitive {
+            string.to_lowercase() == value.to_lowercase()
+        } else {
+            string == value
+        };
+
+        match self {
+            ParseNode::List { nodes, .. } => {
+                if kind.is_a(SearchType::ListHead) {
+                    if let Some(Some(caller)) = nodes.get(0).map(ParseNode::atomic) {
+                        if is_equal(&caller.value) {
+                            return Some(self);
+                        }
+                    }
+                }
+                nodes.search_node(kind, value, insensitive, level - 1)
+            },
+            ParseNode::Symbol(name) => {
+                if kind.is_a(SearchType::Symbol) && is_equal(&name.value) {
+                    Some(self)
+                } else {
+                    None
+                }
+            },
+            ParseNode::String(name) | ParseNode::Raw(name) => {
+                if kind.is_a(SearchType::String) && is_equal(&name.value) {
+                    Some(self)
+                } else {
+                    None
+                }
+            },
+            ParseNode::Number(name) => {
+                if kind.is_a(SearchType::Number) && is_equal(&name.value) {
+                    Some(self)
+                } else {
+                    None
+                }
+            },
+            ParseNode::Attribute { node, ref keyword, .. } => {
+                if kind.is_a(SearchType::Attribute) {
+                    if is_equal(keyword) {
+                        return Some(node);
+                    }
+                }
+                node.search_node(kind, value, insensitive, level - 1)
+            },
+        }
+    }
+}
+
+impl<'a> SearchTree<'a> for ParseTree<'a> {
+    fn search_node(&'a self, kind: SearchType, value: &str,
+                   insensitive: bool, level: usize) -> Option<&ParseNode<'a>> {
+        if level == 0 {
+            return None;
+        }
+
+        for node in self {
+            let found = node.search_node(kind, value, insensitive, level);
+            if found.is_some() {
+                return found;
+            }
+        }
+
+        None
+    }
+}
+
+/// Pretty printing for parse nodes.
+#[cfg(feature="debug")]
+impl<'a> fmt::Display for ParseNode<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ParseNode::Symbol(node)
+            | ParseNode::Number(node)  => write!(f, "{}", &node.value),
+            ParseNode::String(node)    => {
+                if node.value.trim().is_empty() {
+                    write!(f, "")
+                } else {
+                    write!(f, "\"{}\"", &node.value)
+                }
+            },
+            ParseNode::Attribute { keyword, node, .. } => write!(f, ":{} {}",
+                &keyword, &*node),
+            ParseNode::List { nodes, .. } => if nodes.len() == 0 {
+                write!(f, "()")
+            } else if let [single] = &**nodes {
+                write!(f, "({})", single)
+            } else {
+                write!(f, "({}{})", nodes[0],
+                nodes[1..].iter().fold(String::new(), |acc, elem| {
+                    let nested = elem.to_string().split('\n')
+                        .fold(String::new(), |acc, e|
+                            acc + "\n  " + &e);
+                    acc + &nested
+                }))
+            }
+        }
+    }
+}
diff --git a/crates/seam/src/parse/tokens.rs b/crates/seam/src/parse/tokens.rs
@@ -0,0 +1,126 @@
+use std::fmt::{self, Display};
+use unicode_width::UnicodeWidthStr;
+
+/// Precise source-code location a parsed (or lexed) node (or token).
+/// Including references to the source-code and path, line number, bytes offsets
+/// within the file, including from start of line, and the number of
+/// bytes it occupies in the source.
+#[derive(Debug, Clone)]
+pub struct Site<'a> {
+    pub source: &'a str,
+    pub source_code: &'a str,
+    pub line: usize,
+    pub bytes_from_start: usize,
+    pub bytes_from_start_of_line: usize,
+    pub bytes_span: usize,
+}
+
+/// Dummy (unknown) site.
+pub const UNKNOWN_SITE: Site<'static> = Site {
+    source: "<unknwon>",
+    source_code: "",
+    line: 0,
+    bytes_from_start: 0,
+    bytes_from_start_of_line: 0,
+    bytes_span: 0,
+};
+
+impl<'a> Site<'a> {
+    pub fn new(source: &'a str,
+               source_code: &'a str,
+               line: usize,
+               bytes_from_start: usize,
+               bytes_from_start_of_line: usize,
+               bytes_span: usize) -> Self {
+        Self {
+            source,
+            source_code,
+            line,
+            bytes_from_start,
+            bytes_from_start_of_line,
+            bytes_span,
+        }
+    }
+
+    pub const fn unknown() -> Self { UNKNOWN_SITE }
+
+    /// Byte-offset in source code for start-of-line where this site is.
+    pub fn start_of_line(&self) -> usize {
+        self.bytes_from_start - self.bytes_from_start_of_line
+    }
+
+    /// Find byte-offset in source code of end-of-line where this site is.
+    pub fn end_of_line(&self) -> usize {
+        let mut i = self.bytes_from_start;
+        let bytes = self.source_code.as_bytes();
+        while i < self.source_code.len() {
+            if bytes[i] == '\n' as u8 {
+                return i;
+            }
+            i += 1;
+        }
+        return i;
+    }
+
+    /// Get a string slice into the part of the source-code
+    /// which occupies the location this site references.
+    pub fn view(&'a self) -> &'a str {
+        let start = self.bytes_from_start;
+        let end = start + self.bytes_span;
+        &self.source_code[start..end]
+    }
+
+    /// Get string view into whole line that this site is referencing.
+    pub fn line_slice(&self) -> &'a str {
+        &self.source_code[self.start_of_line()..self.end_of_line()]
+    }
+
+    /// Compute (monospace, terminal) column width of piece of text
+    /// referenced by this site in the source code.
+    pub fn width(&self) -> usize {
+        let text = &self.source_code[self.bytes_from_start..self.bytes_from_start + self.bytes_span];
+        UnicodeWidthStr::width(text)
+    }
+
+    /// Compute which column the site starts at on the line,
+    /// accounting for the rendered number of columns for each character
+    /// in a terminal, according to the same procedure as [`Self::width`].
+    pub fn line_column(&self) -> usize {
+        let preceeding = &self.source_code[self.start_of_line()..self.bytes_from_start];
+        UnicodeWidthStr::width(preceeding) + 1
+    }
+}
+
+impl<'a> Display for Site<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "(")?;
+        write!(f, "{}:", self.source)?;
+        write!(f, "{}:{}", self.line, self.line_column())?;
+        write!(f, ")")
+    }
+}
+
+/// Kinds of possible tokens.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Kind {
+    LParen,
+    RParen,
+    Symbol,
+    String,
+    Number,
+    Keyword,
+}
+
+#[derive(Debug, Clone)]
+pub struct Token<'a> {
+    pub kind: Kind,
+    pub value: &'a str,
+    pub leading_whitespace: &'a str,
+    pub site: Site<'a>,
+}
+
+impl<'a> Token<'a> {
+    pub fn new(kind: Kind, value: &'a str, leading_whitespace: &'a str, site: Site<'a>) -> Self {
+        Self { kind, value, leading_whitespace, site }
+    }
+}
diff --git a/crates/seam_argparse_proc_macro/Cargo.toml b/crates/seam_argparse_proc_macro/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "seam_argparse_proc_macro"
+license-file = "../../LICENSE"
+authors = ["Demonstrandum <samuel@knutsen.co>"]
+edition = "2021"
+
+[lib]
+proc-macro = true
+name = "seam_argparse_proc_macro"
+path = "src/lib.rs"
+
+[dependencies]
+proc-macro2 = "1.0.86"
+quote = "1.0.37"
+syn = "2.0.77"
diff --git a/crates/seam_argparse_proc_macro/src/lib.rs b/crates/seam_argparse_proc_macro/src/lib.rs
@@ -0,0 +1,415 @@
+//! Procedural macro for the `arguments! { ... }`
+//! macro-argument parser for seam macros.
+//! TODO: Convert all `panic!(..)` calls to actual compiler errors.
+
+use std::{collections::{HashMap, HashSet}, iter::Peekable};
+
+use proc_macro;
+use proc_macro2::{token_stream::IntoIter, Delimiter, TokenStream, TokenTree};
+use quote::quote;
+use syn::{self,
+    Expr, ExprRange, ExprLit,
+    Lit, Pat, PatOr,
+    RangeLimits,
+};
+
+#[derive(Clone, Copy)]
+enum PositionTypes { Mandatroy, Optional, Rest }
+
+#[derive(Clone, Copy)]
+enum ParseState {
+    ArgumentPosition, //< `mandatory: ...', `optional: ...', `rest: ...'.
+    PositionPattern(PositionTypes),  //< pattern for position or name.
+}
+
+#[derive(Clone)]
+enum ArgumentKind {
+    Literal,
+    String,
+    Symbol,
+    Number,
+    Symbolic,
+    List,
+    Any,
+    None
+}
+
+#[derive(Clone)]
+struct ArgumentProperties {
+    kind: ArgumentKind,
+    position_type: PositionTypes,
+    rust_type: TokenStream,
+}
+
+struct ArgumentStructTypes {
+    positional: HashMap<usize, ArgumentProperties>,
+    named: HashMap<String, ArgumentProperties>,
+    rest: ArgumentProperties,
+}
+
+/// Macro that generates an argument parser and builds a custom struct
+/// holding provided arguments, given a schema and the list of arguments.
+/// Example:
+///     ```
+///     let (parser, args) = arguments! { [¶ms]
+///         mandatory(1..=3): literal,
+///         mandatory(4): number fn(_v: ParseNode) { true },
+///         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>).
+///     if let Some(named) = args.trailing {
+///         println!("named arg {:?}", named);  // the literal "true" or "false".
+///     }
+///     for arg in args.rest {
+///         println!("trailing arg: {:?}", arg);  // trailing number args.
+///     }
+///     ```
+#[proc_macro]
+pub fn arguments(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    let stream: TokenStream = stream.into();
+    let stream = stream.into_iter().peekable();
+    let mut stream = stream.into_iter();
+
+    // 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,
+        },
+        _ => None,
+    }) else {
+        panic!("Vector of arguments not given.");
+    };
+
+    // 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 {
+        positional: HashMap::new(),
+        named: HashMap::new(),
+        rest: ArgumentProperties {
+            kind: ArgumentKind::None,
+            position_type: PositionTypes::Rest,
+            rust_type: quote! { () },
+        },
+    };
+    // Parse the argument schema.
+    let mut parse_state = ParseState::ArgumentPosition;
+    while let Some(token) = stream.next() {
+        match parse_state {
+            ParseState::ArgumentPosition => match token {
+                TokenTree::Ident(ident) => {
+                    match ident.to_string().as_str() {
+                        "mandatory" => {
+                            parse_state = ParseState::PositionPattern(PositionTypes::Mandatroy);
+                        },
+                        "optional" => {
+                            parse_state = ParseState::PositionPattern(PositionTypes::Optional);
+                        },
+                        "rest" => {
+                            parse_state = ParseState::PositionPattern(PositionTypes::Rest);
+                        },
+                        _ => panic!("Invalid token: `{}`", ident.to_string()),
+                    }
+                },
+                _ => panic!("Invalid token: `{}`", token),
+            },
+            // Parse `rest: ...`
+            ParseState::PositionPattern(PositionTypes::Rest) => {
+                // Check we consumed `:`
+                match token {
+                    TokenTree::Punct(punct) => assert!(punct.as_char() == ':'),
+                    _ => panic!("Invalid token: `{}`", token),
+                }
+                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);
+                }};
+                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> };
+            },
+            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"),
+                };
+                // Skip `:`
+                let token = stream.next();
+                match token {
+                    Some(TokenTree::Punct(punct)) => assert!(punct.as_char() == ':'),
+                    _ => panic!("Invalid token: `{:?}`", token),
+                }
+                // Register the argument-position matcher.
+                let argument_type = parse_argument_type(&mut stream, pos);
+                let arg_type = argument_type.source_code;
+                let arg_pos = match pos {
+                    PositionTypes::Mandatroy => quote! { crate::parse::macros::Arg::Mandatory },
+                    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,
+                        }
+                    }
+                    rules.register(position_matcher, arg);
+                }};
+                out.extend(code);
+                // Register argument struct type.
+                let rust_type = argument_type.properties.rust_type;
+                let rust_type = match pos {
+                    PositionTypes::Mandatroy => quote! { #rust_type },
+                    PositionTypes::Optional  => quote! { Option<#rust_type> },
+                    _ => unreachable!(),
+                };
+                // Take each possible argument position and register the type.
+                for position in parse_finite_pattern(position_pattern) {
+                    match position {
+                        StringOrInt::String(name) => arg_struct.named.insert(name, ArgumentProperties {
+                            kind: argument_type.properties.kind,
+                            position_type: pos,
+                            rust_type,
+                        }),
+                        StringOrInt::Int(offset)  => arg_struct.positional.insert(offset, ArgumentProperties {
+                            kind: argument_type.properties.kind,
+                            position_type: pos,
+                            rust_type,
+                        }),
+                    };
+                }
+            },
+        };
+    }
+
+    // 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! { () },
+        });
+    }
+    // Build named arguments struct fields.
+    let mut named_arguments: Vec<TokenStream> = vec![];
+    for (name, props) in arg_struct.named.iter() {
+        let rust_type = props.rust_type;
+        named_arguments.push(quote! {
+            #name: #rust_type
+        });
+    }
+
+    // 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}".
+
+    for i in 1..=tuple_len {
+        let arg_num_name: TokenStream = format!("arg_num_{}", i).parse().unwrap();
+
+        let code = quote! {
+            let #arg_num_name = parser.positional.get();
+        };
+    }
+
+    // Assemble code that builds argument parser context and argument struct.
+    let rest_rust_type = arg_struct.rest;
+    let out = out.into_iter();
+    quote! {
+        {
+            #(#out)*;
+            struct MyArguments {
+                number: #(#tuple_types),*,
+                #(#named_arguments),*,
+                rest: #rest_rust_type,
+            }
+            let rules = rules.clone();
+            match crate::parse::macros::ArgParser::new(rules, params) {
+                Ok(parser) => {
+                    let args_struct = MyArguments {
+                        ...
+                    };
+                    Ok((parser, args_struct))  // Returns the parser and args from the scope.
+                },
+                Err(e) => e,
+            }
+        }
+    }.into()
+}
+
+#[derive(Clone, PartialEq, Eq, Hash)]
+enum StringOrInt { String(String), Int(usize) }
+
+/// Parse a subset of rust "patterns" that have a finite set of
+/// values which will satisfy said pattern.
+/// Returns a list of all values which may satisfy it.
+/// Restrictions:
+///  - exact match (`a`)
+///  - ranges (`a..b`, `a..=b`);
+///  - or-patterns (`expr1 | expr2`);
+///  - values are strings or integers (`a: &str` or `a: usize`).
+/// TODO: Re-emit the pattern converted as such:
+///   `2..=4 | "hello" | 5` => `Int(2..=4) | String(&'static "hello") | Int(5)`
+fn parse_finite_pattern(pat: TokenStream) -> HashSet<StringOrInt> {
+    let mut set = HashSet::new();
+    // Parse the input TokenStream into a syn::Pat
+    let expr = syn::parse::Parser::parse2(|input: syn::parse::ParseStream| {
+        Pat::parse_multi(input)
+    }, pat.into()).expect("failed");
+
+    // Recursively parse patterns.
+    fn parse_expr(expr: &Pat, set: &mut HashSet<StringOrInt>) {
+        match expr {
+            // Handle literals (integers or strings)
+            Pat::Lit(ExprLit { lit, .. }) => {
+                match lit {
+                    Lit::Int(lit_int) => {
+                        set.insert(StringOrInt::Int(lit_int.base10_parse::<usize>().unwrap()));
+                    }
+                    Lit::Str(lit_str) => {
+                        set.insert(StringOrInt::String(lit_str.value()));
+                    }
+                    _ => {}
+                }
+            }
+            // Handle ranges
+            Pat::Range(ExprRange {
+                start: Some(start),
+                end: Some(end),
+                limits,
+                ..
+            }) => {
+                // Parse the start and end as integers
+                if let (
+                    Expr::Lit(ExprLit { lit: Lit::Int(start_lit), .. }),
+                     Expr::Lit(ExprLit { lit: Lit::Int(end_lit), .. }),
+                ) = (&**start, &**end) {
+                    let start_val = start_lit.base10_parse::<usize>().unwrap();
+                    let end_val = end_lit.base10_parse::<usize>().unwrap();
+                    // Enumerate inclusive (`..=`) or exclusive (`..`) range.
+                    match limits {
+                        RangeLimits::HalfOpen(_) => {
+                            for i in start_val..end_val {
+                                set.insert(StringOrInt::Int(i));
+                            }
+                        },
+                        RangeLimits::Closed(_) => {
+                            for i in start_val..=end_val {
+                                set.insert(StringOrInt::Int(i));
+                            }
+                        },
+                    };
+                }
+            }
+            // Handle or-patterns
+            Pat::Or(PatOr { cases, .. }) => {
+                // For or-patterns, parse both the left and right expressions recursively
+                for case in cases {
+                    parse_expr(case, set);
+                }
+            }
+            _ => panic!("Unsupported pattern.")
+        }
+    }
+
+    parse_expr(&expr, &mut set);
+    set
+}
+
+
+struct ArgumentType {
+    source_code: TokenStream,
+    properties: ArgumentProperties,
+}
+
+fn parse_argument_type(stream: &mut Peekable<IntoIter>, position_type: PositionTypes) -> ArgumentType {
+    use ArgumentKind as AK;
+    let (kind, rust_type, arg_type) = match stream.next() {
+        Some(TokenTree::Ident(ident)) => match ident.to_string().as_str() {
+            "literal"  => (AK::Literal,  quote! { crate::parse::parser::Node<'a> }, quote! { crate::parse::macros::ArgType::Literal  }),
+            "string"   => (AK::String,   quote! { crate::parse::parser::Node<'a> }, quote! { crate::parse::macros::ArgType::String   }),
+            "symbol"   => (AK::Symbol,   quote! { crate::parse::parser::Node<'a> }, quote! { crate::parse::macros::ArgType::Symbol   }),
+            "number"   => (AK::Number,   quote! { crate::parse::parser::Node<'a> }, quote! { crate::parse::macros::ArgType::Number   }),
+            "symbolic" => (AK::Symbolic, quote! { crate::parse::parser::Node<'a> }, quote! { crate::parse::macros::ArgType::Symbolic }),
+            "list" => (AK::List, quote! { Vec<crate::parse::parser::Node<'a>> }, quote! { crate::parse::macros::ArgType::List }),
+            "any" => (AK::Any, quote! { crate::parse::parser::ParseNode<'a> }, quote! { crate::parse::macros::ArgType::Any }),
+            _ => panic!("Invalid argument type: `{}`", ident),
+        },
+        None => panic!("Unexpected EOF"),
+        _ => panic!("Invalid token type"),
+    };
+
+    let token = stream.peek().map(|token| token.clone());
+    let source_code = match token {
+        // Parse a list of potential pattern matches for argument.
+        Some(TokenTree::Group(group)) => match group.delimiter() {
+            Delimiter::Bracket | Delimiter::Parenthesis => {
+                stream.next(); // Consume the list.
+                let group = group.stream().into_iter();
+                quote! { #arg_type(vec![ #(#group),* ]) }
+            },
+            _ => panic!("Unexpected list delimiter"),
+        },
+        // Parse a function which matches the arguemnt.
+        Some(TokenTree::Ident(ident)) if ident.to_string() == "fn" => {
+            stream.next(); // Consume the `fn` keyword.
+            let fn_arguments = match stream.next() {
+                Some(TokenTree::Group(group)) => group.stream().into_iter(),
+                None => panic!("Unexpected EOF"),
+                _ => panic!("Unexpected token"),
+            };
+            let Some(fn_body) = stream.next() else { panic!("Unexpected EOF") };
+            quote! {{
+                fn predicate(#(#fn_arguments),*) -> bool { #fn_body }
+                let arg_pred = crate::parse::macros::ArgPredicate::Satisfying(Box::new(predicate));
+                #arg_type(vec![arg_pred])
+            }}
+        }
+        _ => quote! { #arg_type(vec![]) },
+    };
+
+    ArgumentType {
+        source_code,
+        properties: ArgumentProperties {
+            kind,
+            position_type,
+            rust_type,
+        },
+    }
+}
diff --git a/src/assemble/mod.rs b/src/assemble/mod.rs
@@ -1,160 +0,0 @@
-use crate::parse::tokens::Site;
-use std::{convert, error::Error, fmt::{self, Debug}};
-
-use colored::*;
-use unicode_width::UnicodeWidthStr;
-
-/// Error type for specific errors with generating
-/// each type of markup.
-#[derive(Debug, Clone)]
-pub struct GenerationError<'a> {
-    pub markup: &'static str,
-    pub message: String,
-    pub site: Site<'a>,
-}
-
-impl<'a> GenerationError<'a> {
-    /// Create a new error given the ML, the message, and the site.
-    pub fn new(ml: &'static str, msg: &str, site: &Site<'a>) -> Self {
-        Self {
-            markup: ml,
-            message: msg.to_owned(),
-            site: site.to_owned(),
-        }
-    }
-}
-
-/// Implement fmt::Display for user-facing error output.
-impl<'a> fmt::Display for GenerationError<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let line_prefix = format!("  {} |", self.site.line);
-        let line_view = self.site.line_slice();
-        writeln!(f, "{} {}", line_prefix, line_view)?;
-        writeln!(f, "{:>prefix_offset$} {:~>text_offset$}{:^>length$}", "|", "", "",
-            prefix_offset=UnicodeWidthStr::width(line_prefix.as_str()),
-            text_offset=self.site.line_column() - 1,
-            length=self.site.width())?;
-        write!(f, "{}: {}",
-            format!("[{}] Error Generating {} ({}:{}:{})",
-                "**".red().bold(),
-                self.markup.bold(),
-                self.site.source,
-                self.site.line,
-                self.site.line_column(),
-            ).black(),
-            self.message)
-    }
-}
-
-/// Implements std::error::Error.
-impl<'a> Error for GenerationError<'a> { }
-
-/// Convert from an io::Error to a generation error.
-impl<'a> From<std::io::Error> for GenerationError<'a> {
-    fn from(e: std::io::Error) -> Self {
-        Self {
-            markup: "<markup>",
-            message: format!("IO error: {}", e),
-            site: Site::unknown(),
-        }
-    }
-}
-
-/// An fmt::Error can be cast to an equally horribly
-/// ambiguous GenerationError.
-impl<'a> convert::From<fmt::Error> for GenerationError<'a> {
-    fn from(e: fmt::Error) -> Self {
-        Self {
-            markup: "<markup>",
-            message: format!("Format buffer error: {}", e),
-            site: Site::unknown(),
-        }
-    }
-}
-
-pub type Formatter<'a> = &'a mut dyn fmt::Write;
-
-/// Trait for all structs that can generate specific markup
-/// for the s-expression tree.
-pub trait MarkupFormatter: Debug + CloneBox {
-    // Required definitions:
-    /// Similar to fmt in Display/Debug traits, takes in a
-    /// mutable writable buffer, returns success or a specifc
-    /// error while generating the markup.
-    fn generate(&self, buf: Formatter) -> Result<(), GenerationError>;
-    /// Documentises the input, that's to say, it adds any
-    /// extra meta-information to the generated markup, if
-    /// the s-expressions your wrote ommited it.
-    /// e.g. All XML gets a `<?xml ... ?>` tag added to it.
-    fn document(&self) -> Result<String, GenerationError>;
-    // Default definitions:
-    /// Directly converts the s-expressions into a string
-    /// containing the markup, unless there was an error.
-    fn display(&self) -> Result<String, GenerationError> {
-        let mut buf = String::new();
-        self.generate(&mut buf)?;
-        Ok(buf)
-    }
-}
-
-/// See: https://stackoverflow.com/a/30353928
-pub trait CloneBox {
-    fn clone_box(&self) -> *mut ();
-}
-
-impl<'a, T> CloneBox for T where T: Clone + 'a {
-    fn clone_box(&self) -> *mut () {
-        Box::<T>::into_raw(Box::new(self.clone())) as *mut ()
-    }
-}
-
-impl<'a> Clone for Box<dyn MarkupFormatter + 'a> {
-    fn clone(&self) -> Box<dyn MarkupFormatter + 'a> {
-        unsafe {
-            *Box::from_raw(self.clone_box() as *mut Self)
-        }
-    }
-}
-
-/// Automatically implement fmt::Display as a wrapper around
-/// MarkupFormatter::generate, but throws away the useful error message.
-impl fmt::Display for dyn MarkupFormatter {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        self.generate(f).map_err(|_| fmt::Error)
-    }
-}
-
-/// Parforms the following escapes:
-/// - `<` → `<`
-/// - `>` → `>`
-/// - `"` → `"`
-/// - `'` → `'`
-/// - `&` → `&`
-pub fn escape_xml(string: &str) -> String {
-    let mut bytes = string.bytes();
-    let mut byte_builder: Vec<u8> = Vec::with_capacity(bytes.len());
-    while let Some(byte) = bytes.next() {
-        match byte {
-            b'<'  => byte_builder.extend(b"<"),
-            b'>'  => byte_builder.extend(b">"),
-            b'"'  => byte_builder.extend(b"""),
-            b'\'' => byte_builder.extend(b"'"),
-            b'&'  => byte_builder.extend(b"&"),
-            _ => byte_builder.push(byte)
-        }
-    }
-    unsafe {
-        String::from_utf8_unchecked(byte_builder)
-    }
-}
-
-/// Re-constitute original S-expressions.
-pub mod sexp;
-/// Converts source into expanded plain-text.
-pub mod text;
-/// XML generation.
-pub mod xml;
-/// HTML5 CSS generation.
-pub mod css;
-/// HTML5 HTML generation.
-pub mod html;
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,39 +0,0 @@
-#![allow(incomplete_features)]
-#![feature(pattern)]
-#![feature(associated_type_defaults)]
-
-pub mod parse;
-pub mod assemble;
-
-use parse::{expander, parser, lexer};
-
-use std::{fs, io, path::Path};
-
-pub const VERSION: (u8, u8, u8) = (0, 3, 0);
-
-pub fn tree_builder<'a, P: AsRef<Path>>(source_path: Option<P>, string: String)
-    -> expander::Expander<'a> {
-    let path = source_path.map_or("<stdin>".to_string(),
-        |s| s.as_ref().to_string_lossy().to_string());
-    let tokenizer = lexer::Lexer::new(path, string);
-    let builder = parser::Parser::new(tokenizer);
-    expander::Expander::new(builder)
-}
-
-pub fn tree_builder_file<'a>(path: &Path)
-    -> io::Result<expander::Expander<'a>> {
-    let contents = fs::read_to_string(&path)?;
-    Ok(tree_builder(Some(path), contents))
-}
-
-pub fn tree_builder_stream(stream: &mut impl io::Read)
-    -> io::Result<expander::Expander> {
-    let mut contents = String::new();
-    stream.read_to_string(&mut contents)?;
-    Ok(tree_builder(Option::<&Path>::None, contents))
-}
-
-pub fn main() {
-    eprintln!("Library main function should not be used.");
-    std::process::exit(1);
-}
diff --git a/src/parse/expander.rs b/src/parse/expander.rs
@@ -1,739 +0,0 @@
-use super::parser::{Node, ParseNode, ParseTree, Parser};
-use super::tokens::Site;
-
-use std::fmt::Display;
-use std::{
-    fmt,
-    cell::RefCell,
-    path::PathBuf,
-    ffi::OsString,
-    error::Error,
-    rc::Rc,
-    collections::{
-        HashMap,
-        BTreeSet,
-    },
-};
-
-use colored::*;
-use formatx;
-use unicode_width::UnicodeWidthStr;
-
-/// 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())
-    }
-}
-
-/// Implement fmt::Display for user-facing error output.
-impl<'a> fmt::Display for ExpansionError<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let ExpansionError(msg, site) = self;
-        let line_prefix = format!("  {} |", site.line);
-        let line_view = site.line_slice();
-        writeln!(f, "{} {}", line_prefix, line_view)?;
-        writeln!(f, "{:>prefix_offset$} {:~>text_offset$}{:^>length$}", "|", "", "",
-            prefix_offset=UnicodeWidthStr::width(line_prefix.as_str()),
-            text_offset=site.line_column() - 1,
-            length=site.width())?;
-        write!(f, "[{}] Error Expanding Macro {}: {}",
-            "**".red().bold(), site, msg)
-    }
-}
-
-/// Implements std::error::Error for macro expansion error.
-impl<'a> Error for ExpansionError<'a> { }
-
-/// A macro consists of:
-/// - its name;
-/// - its argument list (if any);
-/// - and its defintion (i.e. *body*).
-#[derive(Debug, Clone)]
-pub struct Macro<'a> {
-    name: String,
-    params: Box<[String]>,
-    body: Box<[ParseNode<'a>]>
-}
-// TODO: Macro to also store its own scope (at place of definition)
-// in order to implement lexical scoping.
-
-impl<'a> Macro<'a> {
-    pub fn new(name: &str) -> Macro {
-        Macro {
-            name: name.to_string(),
-            params: Box::new([]),
-            body:   Box::new([]),
-        }
-    }
-}
-
-/// Type of variable scope owned by an `Expander` instance.
-pub type Scope<'a> = RefCell<HashMap<String, Rc<Macro<'a>>>>; // Can you believe this type?
-
-#[derive(Debug, Clone)]
-pub struct Expander<'a> {
-    parser: Parser,
-    includes: BTreeSet<PathBuf>,
-    subparsers: RefCell<Vec<Parser>>,
-    subcontexts: RefCell<Vec<Self>>,
-    invocations: RefCell<Vec<ParseNode<'a>>>,
-    definitions: Scope<'a>,
-}
-
-impl<'a> Expander<'a> {
-    pub fn new(parser: Parser) -> Self {
-        Self {
-            parser,
-            includes: BTreeSet::from([PathBuf::from(".")]),
-            subparsers: RefCell::new(Vec::new()),
-            subcontexts: RefCell::new(Vec::new()),
-            invocations: RefCell::new(Vec::new()),
-            definitions: RefCell::new(HashMap::new()),
-        }
-    }
-
-    /// Get underlying source-code of the active parser for current unit.
-    pub fn get_source(&self) -> &str {
-        self.parser.get_source()
-    }
-
-    pub fn add_includes<T: Iterator>(&mut self, dirs: T)
-        where T::Item: Into<PathBuf>
-    {
-        for dir in dirs {
-            self.includes.insert(dir.into());
-        }
-    }
-
-    /// Add a subparser owned by the expander context.
-    fn register_parser(&self, parser: Parser) -> &'a Parser {
-        {
-            let mut parsers = self.subparsers.borrow_mut();
-            parsers.push(parser);
-        }
-        self.latest_parser().unwrap()
-    }
-
-    /// Get the latest subparser added.
-    fn latest_parser(&self) -> Option<&'a Parser> {
-        let p = self.subparsers.as_ptr();
-        unsafe { (*p).last() }
-    }
-
-    /// Create and register a subcontext built from the current context.
-    fn create_subcontext(&self) -> &mut Self {
-        {
-            let copy = self.clone();
-            let mut contexts = self.subcontexts.borrow_mut();
-            contexts.push(copy);
-        }
-        self.latest_context().unwrap()
-    }
-
-    /// Get the latest subparser added.
-    fn latest_context(&self) -> Option<&mut Self> {
-        let contexts = self.subcontexts.as_ptr();
-        unsafe { (*contexts).last_mut() }
-    }
-
-    fn register_invocation(&self, node: ParseNode<'a>) -> &ParseNode<'a> {
-        let invocations = self.invocations.as_ptr();
-        unsafe {
-            (*invocations).push(node);
-            (*invocations).last().unwrap()
-        }
-    }
-
-    /// Update variable (macro) for this scope.
-    fn insert_variable(&self, name: String, var: Rc<Macro<'a>>) {
-        let mut defs = self.definitions.borrow_mut();
-        defs.insert(name, var);
-    }
-
-    /// Check if macro exists in this scope.
-    fn has_variable(&self, name: &str) -> bool {
-        let defs = self.definitions.borrow();
-        defs.contains_key(name)
-    }
-
-    fn get_variable(&self, name: &str) -> Option<Rc<Macro<'a>>> {
-        self.definitions.borrow().get(name).map(|m| m.clone())
-    }
-
-    /// Define a macro with `(%define a b)` --- `a` is a symbol or a list `(c ...)` where `c` is a symbol.
-    /// macro definitions will eliminate any preceding whitespace, so make sure trailing whitespace provides
-    /// the whitespace you need.
-    fn expand_define_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        let [head, nodes@..] = &*params else {
-            return Err(ExpansionError(
-                format!("`%define` macro takes at least \
-                    two (2) arguments ({} were given.", params.len()),
-                node.owned_site()));
-        };
-
-        // If head is atomic, we assign to a 'variable'.
-        // Aditionally, we evaluate its body *eagerly*.
-        let def_macro = if let Some(variable) = head.atomic() {
-            let nodes = nodes.to_owned().into_boxed_slice();
-            let body = self.expand_nodes(nodes)?;
-            Rc::new(Macro {
-                name: variable.value.clone(),
-                params: Box::new([]),
-                body,
-            })
-        } else {  // Otherwise, we are assigning to a 'function'.
-            let ParseNode::List { nodes: defn_nodes, .. } = head else {
-                return Err(ExpansionError(
-                    "First argument of `%define` macro must be a list \
-                        or variable name/identifier.".to_owned(),
-                    node.site().to_owned()));
-            };
-            let [name, params@..] = &**defn_nodes else {
-                return Err(ExpansionError(
-                    "`%define` macro definition must at \
-                        least have a name.".to_owned(),
-                    node.site().to_owned()));
-            };
-            let mut arguments: Vec<String> = Vec::with_capacity(params.len());
-            for param_node in params {  // Verify arguments are symbols.
-                if let ParseNode::Symbol(param) = param_node {
-                    arguments.push(param.value.clone());
-                } else {
-                    return Err(ExpansionError(
-                        "`define` function arguments must be \
-                            symbols/identifers.".to_owned(),
-                        node.site().to_owned()));
-                };
-            }
-            let ParseNode::Symbol(name_node) = name else {
-                return Err(ExpansionError(
-                    "`define` function name must be \
-                        a symbol/identifier.".to_owned(),
-                    node.site().to_owned()));
-            };
-            let name = name_node.value.clone();
-
-            Rc::new(Macro {
-                name,
-                params: arguments.into_boxed_slice(),
-                body: nodes.to_owned().into_boxed_slice(),
-            })
-        };
-
-        self.insert_variable(def_macro.name.to_owned(), def_macro);
-        Ok(Box::new([]))
-    }
-
-    /// `(%ifdef symbol a b)` --- `b` is optional, however, if not provided *and*
-    /// the symbol is not defined, it will erase the whole expression, and whitespace will not
-    /// be preseved before it. If that's a concern, provide `b` as the empty string `""`.
-    fn expand_ifdef_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        if params.len() < 2 || params.len() > 3 {
-            return Err(ExpansionError(format!("`ifdef` takes one (1) \
-                    condition and one (1) consequent, a third optional \
-                    alternative expression may also be provided, but \
-                    `ifdef` was given {} arguments.", params.len()),
-                node.site().to_owned()));
-        }
-        let symbol = if let Some(node) = params[0].atomic() {
-            node.value.to_owned()
-        } else {
-            // FIXME: Borrow-checker won't let me use params[0].site() as site!
-            return Err(ExpansionError(
-                "The first argument to `ifdef` must be a symbol/name.".to_string(),
-                node.site().clone()));
-        };
-
-        let mut expanded = if self.has_variable(&symbol) {
-            self.expand_node(params[1].clone())?
-        } else {
-            if let Some(alt) = params.get(2) {
-                self.expand_node(alt.clone())?
-            } else {
-                Box::new([])
-            }
-        };
-        if let Some(first_node) = expanded.get_mut(0) {
-            first_node.set_leading_whitespace(node.leading_whitespace().to_owned());
-        }
-        Ok(expanded)
-    }
-
-    fn expand_include_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        let params: Box<[ParseNode<'a>]> = self.expand_nodes(params)?;
-        let [path_node] = &*params else {
-            return Err(ExpansionError(
-                format!("Incorrect number of arguments \
-                    to `%include' macro. Got {}, expected {}.",
-                    params.len(), 1),
-                node.site().to_owned()));
-        };
-
-        let Some(Node { value: path, site, .. }) = path_node.atomic()  else {
-            return Err(ExpansionError(
-                "Bad argument to `%include' macro.\n\
-                    Expected a path, but did not get any value
-                    that could be interpreted as a path.".to_string(),
-                path_node.site().to_owned()))
-        };
-
-        // Open file, and parse contents!
-        let include_error = |error: Box<dyn Display>| ExpansionError(
-            format!("{}", error), site.to_owned());
-        let mut parser: Result<Parser, ExpansionError> = Err(
-            include_error(Box::new("No path tested.")));
-        // Try all include directories until one is succesful.
-        for include_dir in &self.includes {
-            let path = include_dir.join(path);
-            parser = super::parser_for_file(&path)
-                .or_else(|err| {
-                    let err = Box::new(err);
-                    // Try with `.sex` extensions appended.
-                    let mut with_ext = PathBuf::from(&path);
-                    let filename = path.file_name()
-                        .ok_or(include_error(err))?;
-                    with_ext.pop();  // Remove old filename.
-                    // Build new filename with `.sex` appended.
-                    let mut new_filename = OsString::new();
-                    new_filename.push(filename);
-                    new_filename.push(".sex");
-                    with_ext.push(new_filename); // Replace with new filename.
-                    match super::parser_for_file(&with_ext) {
-                        Ok(parser) => Ok(parser),
-                        Err(err)   => Err(include_error(Box::new(err)))
-                    }
-                });
-            if parser.is_ok() { break; }
-        }
-        // Register the parser for the found file.
-        let parser = self.register_parser(parser?);
-        let tree = match parser.parse() {
-            Ok(tree) => tree,
-            Err(error) => return Err(ExpansionError(
-                format!("{}", error), node.site().to_owned()))
-        };
-
-        // Build new (expanded) tree, with result of previous
-        // parse, while recursively expanding each branch in the
-        // tree too, as they are added.
-        let mut expanded_tree = Vec::with_capacity(tree.len());
-        for branch in tree {
-            expanded_tree.extend(self.expand_node(branch)?);
-        }
-        // First node should inherit leading whitespace from (%include ...) list.
-        if expanded_tree.len() != 0 {
-            expanded_tree[0].set_leading_whitespace(node.leading_whitespace().to_owned());
-        }
-        Ok(expanded_tree.into_boxed_slice())
-    }
-
-    fn expand_embed_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        let params: Box<[ParseNode<'a>]> = self.expand_nodes(params)?;
-        let [path_node] = &*params else {
-            return Err(ExpansionError(
-                format!("Incorrect number of arguments \
-                    to `%embed' macro. Got {}, expected {}.",
-                    params.len(), 1),
-                node.site().to_owned()));
-        };
-
-        let Some(Node { value: path, site, .. }) = path_node.atomic()  else {
-            return Err(ExpansionError(
-                "Bad argument to `%embed' macro.\n\
-                    Expected a path, but did not get any value
-                    that could be interpreted as a path.".to_string(),
-                path_node.site().to_owned()))
-        };
-
-        // Open file, and read contents!
-        let embed_error = |error: Box<dyn Display>| ExpansionError(
-            format!("{}", error), site.to_owned());
-        let mut value: Result<String, ExpansionError> = Err(
-            embed_error(Box::new("No path tested.")));
-        // Try all include directories until one is succesful.
-        for include_dir in &self.includes {
-            let path = include_dir.join(path);
-            value = std::fs::read_to_string(path)
-                .map_err(|err| embed_error(Box::new(err)));
-            if value.is_ok() { break; }
-        }
-        let value = value?;
-        Ok(Box::new([
-            ParseNode::String(Node {
-                value,
-                site: node.owned_site(),
-                leading_whitespace: node.leading_whitespace().to_owned(),
-            }),
-        ]))
-    }
-
-    fn expand_date_macro(&self, node: &ParseNode<'a>, params: Box<[ParseNode<'a>]>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        let params = self.expand_nodes(params)?;
-        let [date_format] = &*params else {
-            return Err(ExpansionError::new(
-                "`%date' macro only expects one formatting argument.",
-                node.site()))
-        };
-
-        let Some(Node { value: date_format, .. }) = date_format.atomic() else {
-            return Err(ExpansionError::new(
-                "`%date' macro needs string (or atomic) \
-                formatting argument.", node.site()))
-        };
-
-        let now = chrono::Local::now();
-        let formatted = now.format(&date_format).to_string();
-        let date_string_node = ParseNode::String(Node {
-            value: formatted,
-            site: node.site().clone(),
-            leading_whitespace: node.leading_whitespace().to_string(),
-        });
-        Ok(Box::new([date_string_node]))
-    }
-
-    /// `(%log ...)` logs to `STDERR` when called and leaves *no* node behind.
-    /// This means whitespace preceeding `(%log ...)` will be removed!
-    fn expand_log_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        let mut words = Vec::with_capacity(params.len());
-        for param in self.expand_nodes(params)? {
-            if let Some(word) = param.atomic() {
-                words.push(word.value.clone());
-            } else {
-                return Err(ExpansionError::new("`log` should only take \
-                    arguments that are either symbols, strings or numbers.",
-                    node.site()));
-            }
-        }
-
-        eprintln!("{} {} {}: {}", "[#]".bold(), "log".bold().yellow(),
-            node.site(), words.join(" "));
-        Ok(Box::new([]))
-    }
-
-    fn expand_os_env_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        let [ref var] = *params else {
-            return Err(ExpansionError::new(
-                "`%os/env' expects excatly one argument.",
-                node.site()));
-        };
-        let Some(var) = var.atomic() else {
-            return Err(ExpansionError::new(
-                "`%os/env' argument must be atomic (not a list).",
-                var.site()));
-        };
-        let Node { site, leading_whitespace, .. } = var.clone();
-        let Ok(value) = std::env::var(&var.value) else {
-            return Err(ExpansionError(
-                format!("No such environment variable ($`{}') visible.", &var.value),
-                site));
-        };
-        Ok(Box::new([
-            ParseNode::String(Node { value, site, leading_whitespace }),
-        ]))
-    }
-
-    fn expand_format_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        let [format_str, ..] = &*params else {
-            return Err(ExpansionError::new(
-                "`%format' expects at a format-string.",
-                node.site()));
-        };
-        let ParseNode::String(format_str) = format_str else {
-            return Err(ExpansionError::new(
-                "First argument to `%format' must be a string.",
-                format_str.site()));
-        };
-        // Iterate and collect format arguments.
-        let mut arguments = params.iter();
-        let _ = arguments.next();  // Skip the format-string.
-        let Ok(mut template) = formatx::Template::new(&format_str.value) else {
-            return Err(ExpansionError::new(
-                "Invalid format string.",
-                &format_str.site));
-        };
-        for mut var in arguments {
-            // Check if we're replacing a named or positional placeholder.
-            let mut named: Option<&str> = None;
-            if let ParseNode::Attribute { keyword, node, .. } = var {
-                named = Some(keyword.as_str());
-                var = node;
-            }
-            // TODO: Somehow let non-atomic values be formattable?
-            let Some(Node { value, .. }) = var.atomic() else {
-                return Err(ExpansionError(
-                    format!("In `%format', the compound {} type is not formattable.",
-                        var.node_type()),
-                    var.site().clone()));
-            };
-            // Replace the placeholder.
-            match named {
-                Some(name) => template.replace(name, value),
-                None       => template.replace_positional(value),
-            }
-        }
-        // Template has been constructed, so now attempt to do subsitituions and
-        // render the formatted string.
-        match template.text() {
-            Ok(value) => Ok(Box::new([
-                ParseNode::String(Node {
-                    value,
-                    site: node.owned_site(),
-                    leading_whitespace: node.leading_whitespace().to_owned(),
-                })
-            ])),
-            Err(err) => Err(ExpansionError(
-                format!("Failed to format string: {}", err.message()),
-                format_str.site.clone()))
-        }
-    }
-
-    fn expand_namespace_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        // Start evaluating all the arguments to the macro in a separate context.
-        let context = self.clone();
-        let params =  context.expand_nodes(params)?;
-        let mut args = params.iter().peekable();
-        let Some(namespace) = args.next().and_then(ParseNode::atomic) else {
-            return Err(ExpansionError::new("Expected a namespace name.", node.site()));
-        };
-        // Parse options to macro.
-        let mut seperator = "/";  // Default namespace seperator is `/`.
-        while let Some(ParseNode::Attribute { keyword, node, site, .. }) = args.peek() {
-            let _ = args.next();
-            match keyword.as_str() {
-                "separator" => match node.atomic() {
-                    Some(Node { value, .. }) => seperator = &value,
-                    None => return Err(ExpansionError(
-                        format!("`%namespace' separator must be a symbol, got a {}.", node.node_type()),
-                        node.owned_site())),
-                },
-                opt => return Err(ExpansionError(
-                    format!("Unknown option `:{}' to `%namespace' macro.", opt),
-                    site.clone())),
-            }
-        }
-        // Find all the definitions made within the context of the
-        // `%namespace` macro and include the defintion prefixed by
-        // the namespace in the *current* scope.
-        {
-            let mut self_defs = self.definitions.borrow_mut();
-            let defs = context.definitions.borrow();
-            for (key, value) in defs.iter() {
-                let new_key = format!("{}{}{}", namespace.value, seperator, key);
-                self_defs.insert(new_key, value.clone());
-            }
-        }
-        // Return remaining body of the macro.
-        Ok(args.cloned().collect())
-    }
-
-    fn expand_raw_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        let mut builder = String::new();
-        let args = self.expand_nodes(params)?;
-        for arg in args {
-            let Some(Node { value, leading_whitespace, .. }) = arg.atomic() else {
-                return Err(ExpansionError(
-                    format!("Expected a literal, found a {} node instead.", arg.node_type()),
-                    arg.owned_site()));
-            };
-            builder += leading_whitespace;
-            builder += value;
-        }
-        Ok(Box::new([
-            ParseNode::Raw(Node {
-                value: builder,
-                site: node.owned_site(),
-                leading_whitespace: node.leading_whitespace().to_owned(),
-            })
-        ]))
-    }
-
-    fn expand_string_macro(&self, node: &ParseNode<'a>, params: ParseTree<'a>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        let mut builder = String::new();
-        let args = self.expand_nodes(params)?;
-        for arg in args {
-            let Some(Node { value, leading_whitespace, .. }) = arg.atomic() else {
-                return Err(ExpansionError(
-                    format!("Expected a literal, found a {} node instead.", arg.node_type()),
-                    arg.owned_site()));
-            };
-            builder += leading_whitespace;
-            builder += value;
-        }
-        Ok(Box::new([
-            ParseNode::String(Node {
-                value: builder,
-                site: node.owned_site(),
-                leading_whitespace: node.leading_whitespace().to_owned(),
-            })
-        ]))
-    }
-
-    fn expand_macro(&self, name: &str, node: &ParseNode<'a>, params: ParseTree<'a>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        // Eagerly evaluate parameters passed to macro invocation.
-        let params = self.expand_nodes(params)?;
-
-        let Some(mac) = self.get_variable(name) else {
-            return Err(ExpansionError::new(
-                &format!("Macro not found (`{}').", name), &node.owned_site()))
-        };
-
-        // Instance of expansion subcontext.
-        let subcontext = self.create_subcontext();
-        // Check enough arguments were given.
-        if params.len() != mac.params.len() {
-            return Err(ExpansionError(
-                format!("`%{}` macro expects {} arguments, \
-                        but {} were given.", &mac.name, mac.params.len(),
-                        params.len()), node.site().to_owned()));
-        }
-        // Define arguments for body.
-        for i in 0..params.len() {
-            let arg_macro = Macro {
-                name: mac.params[i].to_owned(),
-                params: Box::new([]),
-                body: Box::new([params[i].clone()]), //< Argument as evaluated at call-site.
-            };
-            subcontext.insert_variable(mac.params[i].to_string(), Rc::new(arg_macro));
-        }
-        // Expand body.
-        let mut expanded = subcontext.expand_nodes(mac.body.clone())?.to_vec();
-        // Inherit leading whitespace of invocation.
-        if let Some(first_node) = expanded.get_mut(0) {
-            first_node.set_leading_whitespace(node.leading_whitespace().to_owned());
-        }
-        Ok(expanded.into_boxed_slice())
-    }
-
-    fn expand_invocation(&self,
-                         name: &str, //< Name of macro (e.g. %define).
-                         node: &ParseNode<'a>, //< Node for `%'-macro invocation.
-                         params: Box<[ParseNode<'a>]> //< Passed in arguments.
-    ) -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        // Some macros are lazy (e.g. `ifdef`), so each macro has to
-        //   expand the macros in its arguments individually.
-        match name {
-            "define"    => self.expand_define_macro(node, params),
-            "ifdef"     => self.expand_ifdef_macro(node, params),
-            "raw"       => self.expand_raw_macro(node, params),
-            "string"    => self.expand_string_macro(node, params),
-            "include"   => self.expand_include_macro(node, params),
-            "embed"     => self.expand_embed_macro(node, params),
-            "namespace" => self.expand_namespace_macro(node, params),
-            "date"      => self.expand_date_macro(node, params),
-            "log"       => self.expand_log_macro(node, params),
-            "format"    => self.expand_format_macro(node, params),
-            "os/env"    => self.expand_os_env_macro(node, params),
-            _           => self.expand_macro(name, node, params),
-        }
-    }
-
-    pub fn expand_node(&self, node: ParseNode<'a>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        match node {
-            ParseNode::Symbol(ref sym) => {
-                // Check if symbol starts with %... and replace it
-                // with it's defined value.
-                if sym.value.starts_with("%") {
-                    let name = &sym.value[1..];
-                    if let Some(def) = self.get_variable(name) {
-                        if !def.params.is_empty() {  // Should not be a function.
-                            return Err(ExpansionError::new(
-                                &format!("`{}` is a macro that takes arguments, \
-                                    and cannot be used as a variable.", name),
-                                &sym.site))
-                        }
-                        Ok(def.body.clone())
-                    } else {  // Not found.
-                        Err(ExpansionError(
-                            format!("No such macro, `{}`.", name),
-                            sym.site.to_owned()))
-                    }
-                } else {
-                    Ok(Box::new([node]))
-                }
-            },
-            ParseNode::List { ref nodes, ref site, ref end_token, ref leading_whitespace } => {
-                // Check for macro invocation (%_ _ _ _).
-                // Recurse over every element.
-                let len = nodes.len();
-                let mut call = nodes.to_vec().into_iter();
-                let head = call.next();
-
-                // Pathway: (%_ _ _) macro invocation.
-                if let Some(ref symbol@ParseNode::Symbol(..)) = head {
-                    let node = self.register_invocation(node.clone());
-                    let name = symbol.atomic().unwrap().value.clone();
-                    if name.starts_with("%") {
-                        // Rebuild node...
-                        let name = &name[1..];
-                        let mut params: Vec<ParseNode> = call.collect();
-                        // Delete leading whitespace of leading argument.
-                        if let Some(leading) = params.first_mut() {
-                            if !leading.leading_whitespace().contains('\n') {
-                                leading.set_leading_whitespace(String::from(""));
-                            }
-                        }
-                        return self.expand_invocation(name, node, params.into_boxed_slice());
-                    }
-                }
-                // Otherwise, if not a macro, just expand child nodes incase they are macros.
-                let mut expanded_list = Vec::with_capacity(len);
-                expanded_list.extend(self.expand_node(head.unwrap().clone())?);
-                for elem in call {
-                    expanded_list.extend(self.expand_node(elem)?);
-                }
-
-                Ok(Box::new([ParseNode::List {
-                    nodes: expanded_list.into_boxed_slice(),
-                    site: site.clone(),
-                    end_token: end_token.clone(),
-                    leading_whitespace: leading_whitespace.clone(),
-                }]))
-            },
-            ParseNode::Attribute { keyword, node, site, leading_whitespace } => {
-                let mut expanded_nodes = self.expand_node(*node)?;
-                let new_node = Box::new(expanded_nodes[0].clone());
-                expanded_nodes[0] = ParseNode::Attribute {
-                    keyword: keyword.clone(),
-                    node: new_node,
-                    site: site.clone(),
-                    leading_whitespace: leading_whitespace.clone(),
-                };
-                Ok(expanded_nodes)
-            },
-            _ => Ok(Box::new([node]))
-        }
-    }
-
-    pub fn expand_nodes(&self, tree: Box<[ParseNode<'a>]>)
-    -> Result<ParseTree<'a>, ExpansionError<'a>> {
-        let mut expanded = Vec::with_capacity(tree.len());
-        for branch in tree {
-            expanded.extend(self.expand_node(branch)?);
-        }
-        Ok(expanded.into_boxed_slice())
-    }
-
-    pub fn expand(&'a self) -> Result<ParseTree<'a>, Box<dyn 'a + std::error::Error>> {
-        let tree = self.parser.parse()?;
-        let expanded = self.expand_nodes(tree)?;
-        Ok(expanded)
-    }
-}
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
@@ -1,15 +0,0 @@
-pub mod tokens;
-pub mod lexer;
-pub mod parser;
-pub mod expander;
-
-pub use parser::ParseTree;
-use std::{fs, path::Path, error::Error};
-
-/// Build a parser for a file without expanding macros.
-pub fn parser_for_file(path: &Path) -> Result<parser::Parser, Box<dyn Error>> {
-    let contents = fs::read_to_string(&path)?;
-    let tokenizer = lexer::Lexer::new(path.to_string_lossy().to_string(), contents);
-    let builder = parser::Parser::new(tokenizer);
-    Ok(builder)
-}
diff --git a/src/parse/parser.rs b/src/parse/parser.rs
@@ -1,444 +0,0 @@
-use std::{fmt, error::Error};
-use unicode_width::UnicodeWidthStr;
-use descape::UnescapeExt;
-
-use super::{lexer::{LexError, Lexer}, tokens::{Kind, Site, Token}};
-
-/// The [`Node`] type represents what atomic/literals are parsed
-/// into; i.e. not compound types (e.g. lists, attributes).
-/// These are just a common storage for the literals in [`ParseNode`].
-#[derive(Debug, Clone)]
-pub struct Node<'a> {
-    pub value: String,
-    pub site: Site<'a>,
-    pub leading_whitespace: String,
-}
-
-impl<'a> Node<'a> {
-    pub fn new(value: &str, site: &Site<'a>, leading_whitespace: &str) -> Self {
-        Self {
-            site: site.to_owned(),
-            value: value.to_owned(),
-            leading_whitespace: leading_whitespace.to_owned(),
-        }
-    }
-}
-
-/// Parse nodes are the components of the syntax tree that
-/// the source code is translated into.
-/// These nodes are also produced at compile-time by the macro expander.
-#[derive(Debug, Clone)]
-pub enum ParseNode<'a> {
-    Symbol(Node<'a>),
-    Number(Node<'a>),
-    String(Node<'a>),
-    Raw(Node<'a>), //< Raw-content strings are not parsed, only expanded by macros.
-    List {
-        nodes: Box<[ParseNode<'a>]>,
-        site: Site<'a>,
-        end_token: Token<'a>,
-        leading_whitespace: String,
-    },
-    Attribute {
-        keyword: String,
-        node: Box<ParseNode<'a>>,
-        site: Site<'a>,
-        leading_whitespace: String,
-    },
-}
-
-impl<'a> ParseNode<'a> {
-    /// Unwrap a literal node if it is a symbol or number.
-    pub fn symbolic(&self) -> Option<&Node<'a>> {
-        match self {
-            Self::Symbol(ref node)
-            | Self::Number(ref node) => Some(node),
-            _ => None,
-        }
-    }
-
-    /// Unwrap string-like nodes.
-    pub fn string(&self) -> Option<&Node<'a>> {
-        match self {
-            Self::String(ref node) | Self::Raw(ref node) => Some(node),
-            _ => None,
-        }
-    }
-
-    /// Unwrap literal (atomic) nodes into their underlying [`Node`].
-    pub fn atomic(&self) -> Option<&Node<'a>> {
-        match self {
-            Self::Symbol(ref node)
-            | Self::Number(ref node)
-            | Self::String(ref node)
-            | Self::Raw(ref node) => Some(node),
-            _ => None,
-        }
-    }
-
-    /// Same as [`Self::atomic`], but consumes the node,
-    /// returning an owned [`Node`].
-    pub fn into_atomic(self) -> Option<Node<'a>> {
-        match self {
-            Self::Symbol(node)
-            | Self::Number(node)
-            | Self::String(node) => Some(node),
-            _ => None,
-        }
-    }
-
-    /// Get a reference to the parse node's underlying [`Site`].
-    pub fn site(&self) -> &Site<'a> {
-        match self {
-            Self::Symbol(ref node)
-            | Self::Number(ref node)
-            | Self::String(ref node)
-            | Self::Raw(ref node) => &node.site,
-            Self::List { ref site, .. } => site,
-            Self::Attribute { ref site, .. } => site,
-        }
-    }
-
-    /// Clone the underlying [`Site`] of this parse node.
-    pub fn owned_site(&self) -> Site<'a> {
-        match self {
-            Self::Symbol(node)
-            | Self::Number(node)
-            | Self::String(node)
-            | Self::Raw(node) => node.site.clone(),
-            Self::List { site, .. } => site.clone(),
-            Self::Attribute { site, .. } => site.clone(),
-        }
-    }
-
-    /// Get a reference to the underlying leading whitespace string
-    /// of this parse node.
-    pub fn leading_whitespace(&self) -> &str {
-        match self {
-            Self::Symbol(ref node)
-            | Self::Number(ref node)
-            | Self::String(ref node)
-            | Self::Raw(ref node) => &node.leading_whitespace,
-            Self::List { ref leading_whitespace, .. } => leading_whitespace,
-            Self::Attribute { ref leading_whitespace, .. } => leading_whitespace,
-        }
-    }
-
-    /// Modify the underlying leading whitespace stored for this parse node.
-    pub fn set_leading_whitespace(&mut self, whitespace: String) {
-        match self {
-            Self::Symbol(ref mut node)
-            | Self::Number(ref mut node)
-            | Self::String(ref mut node)
-            | Self::Raw(ref mut node) => node.leading_whitespace = whitespace,
-            Self::List { ref mut leading_whitespace, .. } => *leading_whitespace = whitespace,
-            Self::Attribute { ref mut leading_whitespace, .. } => *leading_whitespace = whitespace,
-        };
-    }
-
-    /// Get a `&'static str` string name of what type of parse node this is.
-    pub fn node_type(&self) -> &'static str {
-        match self {
-            Self::Symbol(..) => "symbol",
-            Self::Number(..) => "number",
-            Self::String(..) => "string",
-            Self::Raw(..) => "raw-content string",
-            Self::List { .. } => "list",
-            Self::Attribute { .. } => "attribute",
-        }
-    }
-}
-
-/// An array of parse nodes, like in a [`ParseNode::List`], never grows.
-/// Hence we prefer the `Box<[...]>` representation over a `Vec<...>`.
-pub type ParseTree<'a> = Box<[ParseNode<'a>]>;
-
-#[derive(Debug, Clone)]
-pub struct ParseError<'a>(pub String, pub Site<'a>);
-
-impl<'a> fmt::Display for ParseError<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let ParseError(msg, site) = self;
-        let line_prefix = format!("  {} |", site.line);
-        let line_view = site.line_slice();
-        writeln!(f, "{} {}", line_prefix, line_view)?;
-        writeln!(f, "{:>prefix_offset$} {:~>text_offset$}{:^>length$}", "|", "", "",
-            prefix_offset=UnicodeWidthStr::width(line_prefix.as_str()),
-            text_offset=site.line_column() - 1,
-            length=site.width())?;
-        write!(f, "[**] Parse Error ({}:{}:{}): {}",
-            site.source, site.line, site.line_column(), msg)
-    }
-}
-
-impl<'a> Error for ParseError<'a> { }
-
-/// Parser structure walks through source using lexer.
-#[derive(Debug, Clone)]
-pub struct Parser {
-    lexer: Lexer, //< Parser owns a lexer.
-}
-
-impl<'a> Parser {
-    pub fn new(lexer: Lexer) -> Self {
-        Self { lexer }
-    }
-
-    pub fn get_source(&self) -> &str {
-        self.lexer.get_source()
-    }
-
-    /// Parse whole source code, finishing off the lexer.
-    pub fn parse(&'a self) -> Result<ParseTree, Box<dyn Error + 'a>> {
-        let mut root: Vec<ParseNode> = Vec::new();
-        while !self.lexer.eof() {
-            let expr = self.parse_expr()?;
-            root.push(expr);
-        }
-        return Ok(root.into_boxed_slice());
-    }
-
-    /// Produce a parse node from the current position in the lexer.
-    pub fn parse_expr(&'a self) -> Result<ParseNode, Box<dyn Error + 'a>> {
-        let token = self.lexer.peek()?;
-        match token.kind {
-            Kind::LParen => self.parse_list(),
-            Kind::RParen => Err(ParseError(
-                "Unexpected `)' closing parenthesis.".to_owned(),
-                token.site.to_owned()))?,
-            Kind::Keyword => self.parse_keyword(),
-            Kind::Symbol => Ok(ParseNode::Symbol(self.parse_atomic()?)),
-            // TODO: Parse (escpae) string-literals.
-            Kind::String => Ok(ParseNode::String(self.parse_atomic()?)),
-            Kind::Number => Ok(ParseNode::Number(self.parse_atomic()?)),
-        }
-    }
-
-    /// Parse keyword-attribute pair.
-    fn parse_keyword(&'a self) -> Result<ParseNode, Box<dyn Error + 'a>> {
-        // Consume :keyword token.
-        let token = self.lexer.consume()?;
-        assert_eq!(token.kind, Kind::Keyword);
-        // Check we are able to consume next expression for keyword's value.
-        {
-            let no_expr_error = ParseError(
-                format!("Keyword `:{}' expects an expression follwing it.", token.value),
-                token.site.to_owned());
-            if self.lexer.eof() { Err(no_expr_error.clone())? ;}
-            match self.lexer.peek()? {
-                Token { kind: Kind::RParen, .. } => Err(no_expr_error)?,
-                _ => ()
-            }
-        }
-        // Otherwise, parse the value and combine the node.
-        let value = self.parse_expr()?;
-        Ok(ParseNode::Attribute {
-            keyword: token.value.to_owned(),
-            node: Box::new(value),
-            site: token.site.to_owned(),
-            leading_whitespace: token.leading_whitespace.to_owned(),
-        })
-    }
-
-    /// Parse a literal node.
-    /// This is where escapes in symbols and strings are handled.
-    fn parse_atomic(&'a self) -> Result<Node<'a>, LexError<'a>> {
-        let token = self.lexer.consume()?;
-        let value = match token.kind {
-            Kind::Symbol | Kind::Number | Kind::Keyword => escape_sanitize(token.value),
-            Kind::String => escape_string(token.value, &token.site)?,
-            _ => unreachable!("called `parse_atomic` on non-atomic token."),
-        };
-        Ok(Node {
-            value,
-            site: token.site.clone(),
-            leading_whitespace: token.leading_whitespace.to_string(),
-        })
-    }
-
-    /// Parse a list `( [...] )'.
-    fn parse_list(&'a self) -> Result<ParseNode<'a>, Box<dyn Error + 'a>> {
-        // Consumed the `(' token.
-        let lparen = self.lexer.consume()?;
-        assert_eq!(lparen.kind, Kind::LParen);
-        // Collect list elements.
-        let mut elements = Vec::new();
-        let mut rparen: Option<Token> = None;
-        while !self.lexer.eof() {
-            // Keep parsing expressions until `)' is reached.
-            let token = self.lexer.peek()?;
-            if token.kind == Kind::RParen {
-                rparen = Some(self.lexer.consume()?); // Swallow up `)'.
-                break;
-            }
-            let expr = self.parse_expr()?;
-            elements.push(expr);
-        }
-        // Closing parenthesis was never found.
-        let Some(rparen) = rparen else {
-            return Err(ParseError(
-                "Expected `)' closing parenthesis.".to_owned(),
-                lparen.site.to_owned()))?;
-        };
-        Ok(ParseNode::List {
-            nodes: elements.into_boxed_slice(),
-            site: lparen.site.to_owned(),
-            end_token: rparen.to_owned(),
-            leading_whitespace: lparen.leading_whitespace.to_owned(),
-        })
-    }
-}
-
-/// Santize any escaped characters by removing their leading backslash.
-fn escape_sanitize(string: &str) -> String {
-    let mut builder = String::with_capacity(string.len());
-    let mut chars = string.chars();
-    while let Some(c) = chars.next() {
-        if c == '\\' { continue; }
-        builder.push(c)
-    }
-    builder
-}
-
-/// Parse a string with its escapes.
-/// **Note:** Uses the `descape` crate for now.
-fn escape_string<'a>(string: &'a str, site: &Site<'a>) -> Result<String, LexError<'a>> {
-    string.to_unescaped()
-        .map(|s| s.to_string())
-        .map_err(|index| {
-            LexError(
-                format!("Invalid escape `\\{}' at byte-index {}.",
-                    string.chars().nth(index).unwrap_or('?'), index),
-                site.clone())
-        })
-}
-
-pub trait SearchTree<'a> {
-    /// Search the parse-tree for a specific node with a specific value.
-    fn search_node(&'a self, kind: SearchType,
-                   value: &str,
-                   case_insensitive: bool,
-                   level: usize) -> Option<&ParseNode<'a>>;
-}
-
-#[derive(Clone, Copy, PartialEq)]
-pub enum SearchType {
-    ListHead, ListMember,
-    Symbol, Number, String,
-    Attribute,
-    Any,
-}
-
-impl SearchType {
-    pub fn is_a(self, kind: SearchType) -> bool {
-        self == SearchType::Any || self == kind
-    }
-}
-
-impl<'a> SearchTree<'a> for ParseNode<'a> {
-    fn search_node(&'a self, kind: SearchType, value: &str,
-                   insensitive: bool, level: usize) -> Option<&ParseNode<'a>> {
-        if level == 0 {
-            return None;
-        }
-
-        let is_equal = |string: &str| if insensitive {
-            string.to_lowercase() == value.to_lowercase()
-        } else {
-            string == value
-        };
-
-        match self {
-            ParseNode::List { nodes, .. } => {
-                if kind.is_a(SearchType::ListHead) {
-                    if let Some(Some(caller)) = nodes.get(0).map(ParseNode::atomic) {
-                        if is_equal(&caller.value) {
-                            return Some(self);
-                        }
-                    }
-                }
-                nodes.search_node(kind, value, insensitive, level - 1)
-            },
-            ParseNode::Symbol(name) => {
-                if kind.is_a(SearchType::Symbol) && is_equal(&name.value) {
-                    Some(self)
-                } else {
-                    None
-                }
-            },
-            ParseNode::String(name) | ParseNode::Raw(name) => {
-                if kind.is_a(SearchType::String) && is_equal(&name.value) {
-                    Some(self)
-                } else {
-                    None
-                }
-            },
-            ParseNode::Number(name) => {
-                if kind.is_a(SearchType::Number) && is_equal(&name.value) {
-                    Some(self)
-                } else {
-                    None
-                }
-            },
-            ParseNode::Attribute { node, ref keyword, .. } => {
-                if kind.is_a(SearchType::Attribute) {
-                    if is_equal(keyword) {
-                        return Some(node);
-                    }
-                }
-                node.search_node(kind, value, insensitive, level - 1)
-            },
-        }
-    }
-}
-
-impl<'a> SearchTree<'a> for ParseTree<'a> {
-    fn search_node(&'a self, kind: SearchType, value: &str,
-                   insensitive: bool, level: usize) -> Option<&ParseNode<'a>> {
-        if level == 0 {
-            return None;
-        }
-
-        for node in self {
-            let found = node.search_node(kind, value, insensitive, level);
-            if found.is_some() {
-                return found;
-            }
-        }
-
-        None
-    }
-}
-
-/// Pretty printing for parse nodes.
-#[cfg(feature="debug")]
-impl<'a> fmt::Display for ParseNode<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            ParseNode::Symbol(node)
-            | ParseNode::Number(node)  => write!(f, "{}", &node.value),
-            ParseNode::String(node)    => {
-                if node.value.trim().is_empty() {
-                    write!(f, "")
-                } else {
-                    write!(f, "\"{}\"", &node.value)
-                }
-            },
-            ParseNode::Attribute { keyword, node, .. } => write!(f, ":{} {}",
-                &keyword, &*node),
-            ParseNode::List { nodes, .. } => if nodes.len() == 0 {
-                write!(f, "()")
-            } else if let [single] = &**nodes {
-                write!(f, "({})", single)
-            } else {
-                write!(f, "({}{})", nodes[0],
-                nodes[1..].iter().fold(String::new(), |acc, elem| {
-                    let nested = elem.to_string().split('\n')
-                        .fold(String::new(), |acc, e|
-                            acc + "\n  " + &e);
-                    acc + &nested
-                }))
-            }
-        }
-    }
-}
diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs
@@ -1,116 +0,0 @@
-use std::fmt::{self, Display};
-use unicode_width::UnicodeWidthStr;
-
-#[derive(Debug, Clone)]
-pub struct Site<'a> {
-    pub source: &'a str,
-    pub source_code: &'a str,
-    pub line: usize,
-    pub bytes_from_start: usize,
-    pub bytes_from_start_of_line: usize,
-    pub bytes_span: usize,
-}
-
-pub const UNKNOWN_SITE: Site<'static> = Site {
-    source: "<unknwon>",
-    source_code: "",
-    line: 0,
-    bytes_from_start: 0,
-    bytes_from_start_of_line: 0,
-    bytes_span: 0,
-};
-
-impl<'a> Site<'a> {
-    pub fn new(source: &'a str,
-               source_code: &'a str,
-               line: usize,
-               bytes_from_start: usize,
-               bytes_from_start_of_line: usize,
-               bytes_span: usize) -> Self {
-        Self {
-            source,
-            source_code,
-            line,
-            bytes_from_start,
-            bytes_from_start_of_line,
-            bytes_span,
-        }
-    }
-
-    pub const fn unknown() -> Self { UNKNOWN_SITE }
-
-    /// Byte-offset in source code for start-of-line where this site is.
-    pub fn start_of_line(&self) -> usize {
-        self.bytes_from_start - self.bytes_from_start_of_line
-    }
-
-    /// Find byte-offset in source code of end-of-line where this site is.
-    pub fn end_of_line(&self) -> usize {
-        let mut i = self.bytes_from_start;
-        let bytes = self.source_code.as_bytes();
-        while i < self.source_code.len() {
-            if bytes[i] == '\n' as u8 {
-                return i;
-            }
-            i += 1;
-        }
-        return i;
-    }
-
-    pub fn view(&'a self) -> &'a str {
-        let start = self.bytes_from_start;
-        let end = start + self.bytes_span;
-        &self.source_code[start..end]
-    }
-
-    /// Get string view into whole line that site is referencing.
-    pub fn line_slice(&self) -> &'a str {
-        &self.source_code[self.start_of_line()..self.end_of_line()]
-    }
-
-    /// Compute (monospace, terminal) column width of piece of text
-    /// referenced by this site in the source code.
-    pub fn width(&self) -> usize {
-        let text = &self.source_code[self.bytes_from_start..self.bytes_from_start + self.bytes_span];
-        UnicodeWidthStr::width(text)
-    }
-
-    /// Compute which column the site starts at on the line.
-    pub fn line_column(&self) -> usize {
-        let preceeding = &self.source_code[self.start_of_line()..self.bytes_from_start];
-        UnicodeWidthStr::width(preceeding) + 1
-    }
-}
-
-impl<'a> Display for Site<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "(")?;
-        write!(f, "{}:", self.source)?;
-        write!(f, "{}:{}", self.line, self.line_column())?;
-        write!(f, ")")
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Kind {
-    LParen,
-    RParen,
-    Symbol,
-    String,
-    Number,
-    Keyword,
-}
-
-#[derive(Debug, Clone)]
-pub struct Token<'a> {
-    pub kind: Kind,
-    pub value: &'a str,
-    pub leading_whitespace: &'a str,
-    pub site: Site<'a>,
-}
-
-impl<'a> Token<'a> {
-    pub fn new(kind: Kind, value: &'a str, leading_whitespace: &'a str, site: Site<'a>) -> Self {
-        Self { kind, value, leading_whitespace, site }
-    }
-}
diff --git a/src/seam_argparse_proc_macro/lib.rs b/src/seam_argparse_proc_macro/lib.rs
@@ -0,0 +1,6 @@
+use proc_macro::TokenStream;
+
+#[proc_macro]
+pub fn make_answer(stream: TokenStream) -> TokenStream {
+    "fn answer() -> u32 { 42 }".parse().unwrap()
+}