diff --git a/Cargo.toml b/Cargo.toml index fdda267..2301050 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ description = "Procedural macros for converting between enums and strings" proc-macro = true [dependencies] +proc-macro2 = "1.0" syn = "1.0" quote = "1.0" diff --git a/src/attribute.rs b/src/attribute.rs new file mode 100644 index 0000000..cea9060 --- /dev/null +++ b/src/attribute.rs @@ -0,0 +1,87 @@ +use std::collections::HashMap; + +use syn::{Ident, Lit, Token}; +use syn::parse::{Parse, ParseBuffer, ParseStream}; +use syn::parse::discouraged::Speculative; +use syn::token::Token; + +pub(crate) enum Value { + None, + Lit(Lit), + Ident(Ident), +} + +pub(crate) struct Dict { + pub(crate) inner: HashMap +} + +impl Dict { + fn require_keys(&self, keys: &[&str]) -> Result<(), String> { + match keys.iter().find(|key| !self.inner.contains_key(**key)) { + Some(absent_key) => Err(absent_key.to_string()), + None => Ok(()) + } + } + + fn allow_keys(&self, keys: &[&str]) -> Result<(), String> { + match self.inner.keys().find(|key| !keys.contains(&key.as_str())) { + Some(disallowed_key) => Err(disallowed_key.clone()), + None => Ok(()) + } + } +} + +impl Parse for Dict { + fn parse(input: ParseStream) -> syn::Result { + Ok(Dict { + inner: input + .parse_terminated::(KeyValPair::parse)? + .into_iter() + .map(|pair| (pair.key, pair.val)) + .collect::>() + }) + } +} + +struct KeyValPair { + key: String, + val: Value, +} + +impl Parse for KeyValPair { + fn parse(input: ParseStream) -> syn::Result { + let key = input + .parse::()? + .to_string(); + + let val = if input.peek(Token![=]) { + input.parse::()?; + if let Ok(lit) = speculative_parse::(input) { + Value::Lit(lit) + } else if let Ok(ident) = speculative_parse::(input) { + Value::Ident(ident) + } else { + return Err(input.error(format!("expected either a literal or identifier as the value corresponding to the key \"{}\", but found neither", key))); + } + } else { + Value::None + }; + + Ok(KeyValPair { key, val }) + } +} + +fn speculative_parse(input: ParseStream) -> syn::Result where T: Parse { + match fork_and_parse(input) { + Ok((fork, parsed)) => { + input.advance_to(&fork); + Ok(parsed) + } + Err(err) => Err(err) + } +} + +fn fork_and_parse(input: ParseStream) -> syn::Result<(ParseBuffer, T)> where T: Parse { + let fork = input.fork(); + T::parse(&fork).map(move |parsed| (fork, parsed)) +}