seam

Symbolic-Expressions As Markup.
git clone git://git.knutsen.co/seam
Log | Files | Refs | README | LICENSE

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:
MCargo.lock | 80++++++++++++++++++++++++++++++++++++++++----------------------------------------
MCargo.toml | 2++
Mcrates/seam/src/lib.rs | 13++++++++++++-
Mcrates/seam/src/parse/expander.rs | 37+++++++++++++++++++++++++++++--------
Mcrates/seam/src/parse/lexer.rs | 6+++---
Mcrates/seam/src/parse/macros.rs | 397+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mcrates/seam/src/parse/parser.rs | 65++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mcrates/seam_argparse_proc_macro/Cargo.toml | 3++-
Mcrates/seam_argparse_proc_macro/src/lib.rs | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Dsrc/seam_argparse_proc_macro/lib.rs | 6------
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! { [&params] mandatory(1): literal, - mandatory(2): number fn(_v: ParseNode) { true }, optional("trailing"): literal["true", "false"], rest: literal, - }; - let arg_parser = args.parser(&params); + }?; - 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! { [&params] /// 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() -}