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