You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
241 lines
7.0 KiB
Rust
241 lines
7.0 KiB
Rust
use std::collections::HashMap;
|
|
use std::fmt;
|
|
|
|
use proc_macro2::Span;
|
|
use syn::parse::discouraged::Speculative;
|
|
use syn::parse::{Parse, ParseBuffer, ParseStream};
|
|
use syn::{Attribute, Ident, Lit, Token};
|
|
|
|
use crate::error::{MacroError, MacroResult, ValueTypeError, ValueTypeResult};
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) enum Value {
|
|
None,
|
|
Lit(Lit),
|
|
Ident(Ident),
|
|
}
|
|
|
|
impl Value {
|
|
pub(crate) fn type_name(&self) -> &'static str {
|
|
match self {
|
|
Value::None => "nothing",
|
|
Value::Lit(lit) => match lit {
|
|
Lit::Str(_) => "string",
|
|
Lit::ByteStr(_) => "byte string",
|
|
Lit::Byte(_) => "byte",
|
|
Lit::Char(_) => "character",
|
|
Lit::Int(_) => "integer",
|
|
Lit::Float(_) => "float",
|
|
Lit::Bool(_) => "boolean",
|
|
Lit::Verbatim(_) => "verbatim literal",
|
|
},
|
|
Value::Ident(_) => "identifier",
|
|
}
|
|
}
|
|
|
|
/// Gets the boolean value associated with this Value. `Value::None` value is considered to
|
|
/// be true. If this value cannot represent a boolean, a `ValueTypeError` will be returned.
|
|
pub(crate) fn value_bool(&self) -> ValueTypeResult<bool> {
|
|
match self {
|
|
Value::None => Ok(true),
|
|
Value::Lit(Lit::Bool(lit_bool)) => Ok(lit_bool.value),
|
|
val => Err(ValueTypeError {
|
|
message: format!("expected boolean but found {}", val.type_name()).into(),
|
|
}),
|
|
}
|
|
}
|
|
|
|
/// Gets the string value associated with this Value. If this value cannot represent a string,
|
|
/// a `ValueTypeError` will be returned.
|
|
pub(crate) fn value_string(&self) -> ValueTypeResult<String> {
|
|
match self {
|
|
Value::Lit(Lit::Str(lit_str)) => Ok(lit_str.value()),
|
|
val => Err(ValueTypeError {
|
|
message: format!("expected string but found {}", val.type_name()).into(),
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Value {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Value::None => write!(f, "ε"),
|
|
Value::Lit(lit) => match lit {
|
|
Lit::Str(lit_str) => lit_str.value().fmt(f),
|
|
Lit::ByteStr(lit_byte_str) => lit_byte_str.value().fmt(f),
|
|
Lit::Byte(lit_byte) => lit_byte.value().fmt(f),
|
|
Lit::Char(lit_char) => lit_char.value().fmt(f),
|
|
Lit::Int(lit_int) => write!(f, "{}", lit_int.base10_digits()),
|
|
Lit::Float(lit_float) => write!(f, "{}", lit_float.base10_digits()),
|
|
Lit::Bool(lit_bool) => lit_bool.value.fmt(f),
|
|
Lit::Verbatim(lit_verbatim) => lit_verbatim.fmt(f),
|
|
},
|
|
Value::Ident(ident) => ident.fmt(f),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct Dict {
|
|
pub(crate) inner: HashMap<String, (Value, Span)>,
|
|
}
|
|
|
|
/// Represents the contents of a single `#[tag(...)]`.
|
|
/// The contents are parsed from `key = value` pairs, separated by commas.
|
|
#[derive(Clone, Debug)]
|
|
struct AttributeTag {
|
|
inner: Vec<(String, Value, Span)>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct KeyValPair {
|
|
key: String,
|
|
val: Value,
|
|
span: Span,
|
|
}
|
|
|
|
impl Dict {
|
|
pub(crate) fn new() -> Self {
|
|
Dict {
|
|
inner: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn from_attrs(name: &str, attrs: &[Attribute]) -> MacroResult<Self> {
|
|
let mut dict = Dict::new();
|
|
|
|
let attribute_tags = attrs
|
|
.iter()
|
|
.filter(|attr| attr.path.is_ident(name))
|
|
.map(|attr| attr.parse_args::<AttributeTag>());
|
|
|
|
for tag in attribute_tags {
|
|
let tag = tag.map_err(MacroError::from)?;
|
|
|
|
for (key, val, span) in tag.inner {
|
|
if dict.inner.contains_key(&key) {
|
|
return Err(MacroError::new(
|
|
format!("key appears more than once: {}", key),
|
|
span,
|
|
));
|
|
}
|
|
|
|
dict.inner.insert(key, (val, span));
|
|
}
|
|
}
|
|
|
|
Ok(dict)
|
|
}
|
|
|
|
pub(crate) fn remove_typed<T, F>(
|
|
&mut self,
|
|
key: &str,
|
|
converter: F,
|
|
) -> MacroResult<Option<(T, Span)>>
|
|
where
|
|
F: Fn(&Value) -> ValueTypeResult<T>,
|
|
{
|
|
match self.inner.remove(key) {
|
|
None => Ok(None),
|
|
Some((val, span)) => match converter(&val) {
|
|
Ok(converted) => Ok(Some((converted, span))),
|
|
Err(ValueTypeError { message }) => Err(MacroError::new(
|
|
format!("{} for key: {}", message, key),
|
|
span,
|
|
)),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub(crate) fn remove_typed_or_default<T, F>(
|
|
&mut self,
|
|
key: &str,
|
|
default: (T, Span),
|
|
converter: F,
|
|
) -> MacroResult<(T, Span)>
|
|
where
|
|
F: Fn(&Value) -> ValueTypeResult<T>,
|
|
{
|
|
match self.remove_typed(key, converter) {
|
|
Ok(Some(value)) => Ok(value),
|
|
Ok(None) => Ok(default),
|
|
Err(err) => Err(err),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn assert_empty(&self) -> MacroResult<()> {
|
|
match self.inner.iter().next() {
|
|
Some((unexpected_key, (_, unexpected_span))) => {
|
|
Err(MacroError::new(
|
|
format!("unexpected key: {}", unexpected_key),
|
|
*unexpected_span,
|
|
))
|
|
},
|
|
None => Ok(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Parse for AttributeTag {
|
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
Ok(AttributeTag {
|
|
inner: input
|
|
.parse_terminated::<KeyValPair, Token![,]>(KeyValPair::parse)?
|
|
.into_iter()
|
|
.map(|pair| (pair.key, pair.val, pair.span))
|
|
.collect::<Vec<_>>(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Parse for KeyValPair {
|
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
let key = input.parse::<Ident>()?;
|
|
|
|
let val = if input.peek(Token![=]) {
|
|
input.parse::<Token![=]>()?;
|
|
|
|
if let Ok(lit) = speculative_parse::<Lit>(input) {
|
|
Value::Lit(lit)
|
|
} else if let Ok(ident) = speculative_parse::<Ident>(input) {
|
|
Value::Ident(ident)
|
|
} else {
|
|
return Err(input.error(format!(
|
|
"could not parse value corresponding to key: {}",
|
|
key
|
|
)));
|
|
}
|
|
} else {
|
|
Value::None
|
|
};
|
|
|
|
Ok(KeyValPair {
|
|
key: key.to_string(),
|
|
val,
|
|
span: key.span(),
|
|
})
|
|
}
|
|
}
|
|
|
|
fn speculative_parse<T>(input: ParseStream) -> syn::Result<T>
|
|
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<T>(input: ParseStream) -> syn::Result<(ParseBuffer, T)>
|
|
where
|
|
T: Parse,
|
|
{
|
|
let fork = input.fork();
|
|
T::parse(&fork).map(move |parsed| (fork, parsed))
|
|
}
|