1use proc_macro2::{
16 Ident,
17 TokenStream as TokenStream2,
18};
19use quote::ToTokens;
20use syn::{
21 ext::IdentExt as _,
22 parse::{
23 Parse,
24 ParseStream,
25 },
26 punctuated::Punctuated,
27 spanned::Spanned,
28 LitBool,
29 LitInt,
30 LitStr,
31 Token,
32};
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39pub enum Meta {
40 Path(syn::Path),
42 NameValue(MetaNameValue),
44}
45
46impl Parse for Meta {
47 fn parse(input: ParseStream) -> Result<Self, syn::Error> {
48 let path = input.call(parse_meta_path)?;
49 if input.peek(Token![=]) {
50 MetaNameValue::parse_meta_name_value_after_path(path, input)
51 .map(Meta::NameValue)
52 } else {
53 Ok(Meta::Path(path))
54 }
55 }
56}
57
58impl ToTokens for Meta {
59 fn to_tokens(&self, tokens: &mut TokenStream2) {
60 match self {
61 Self::Path(path) => path.to_tokens(tokens),
62 Self::NameValue(name_value) => name_value.to_tokens(tokens),
63 }
64 }
65}
66
67impl Meta {
68 pub fn name(&self) -> &syn::Path {
70 match self {
71 Meta::Path(path) => path,
72 Meta::NameValue(name_value) => &name_value.name,
73 }
74 }
75
76 pub fn value(&self) -> Option<&MetaValue> {
78 match self {
79 Meta::Path(_) => None,
80 Meta::NameValue(name_value) => Some(&name_value.value),
81 }
82 }
83
84 pub fn name_value(&self) -> Option<&MetaNameValue> {
86 match self {
87 Meta::NameValue(name_value) => Some(name_value),
88 Meta::Path(_) => None,
89 }
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Hash)]
98pub struct MetaNameValue {
99 pub name: syn::Path,
100 pub eq_token: syn::token::Eq,
101 pub value: MetaValue,
102}
103
104impl Parse for MetaNameValue {
105 fn parse(input: ParseStream) -> Result<Self, syn::Error> {
106 let path = input.call(parse_meta_path)?;
107 Self::parse_meta_name_value_after_path(path, input)
108 }
109}
110
111impl ToTokens for MetaNameValue {
112 fn to_tokens(&self, tokens: &mut TokenStream2) {
113 self.name.to_tokens(tokens);
114 self.eq_token.to_tokens(tokens);
115 self.value.to_tokens(tokens);
116 }
117}
118
119impl MetaNameValue {
120 fn parse_meta_name_value_after_path(
121 name: syn::Path,
122 input: ParseStream,
123 ) -> Result<MetaNameValue, syn::Error> {
124 let span = name.span();
125 Ok(MetaNameValue {
126 name,
127 eq_token: input.parse().map_err(|_error| {
128 format_err!(
129 span,
130 "ink! config options require an argument separated by '='",
131 )
132 })?,
133 value: input.parse()?,
134 })
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Hash)]
140pub enum MetaValue {
141 Path(syn::Path),
142 Lit(syn::Lit),
143 Symbol(Symbol),
144}
145
146impl Parse for MetaValue {
147 fn parse(input: ParseStream) -> Result<Self, syn::Error> {
148 if input.peek(Token![_]) || input.peek(Token![@]) {
149 return input.parse::<Symbol>().map(MetaValue::Symbol)
150 }
151 if input.fork().peek(syn::Lit) {
152 return input.parse::<syn::Lit>().map(MetaValue::Lit)
153 }
154 if input.fork().peek(Ident::peek_any) || input.fork().peek(Token![::]) {
155 return input.call(parse_meta_path).map(MetaValue::Path)
156 }
157 Err(input.error("expected a literal, a path or a punct for a meta value"))
158 }
159}
160
161impl ToTokens for MetaValue {
162 fn to_tokens(&self, tokens: &mut TokenStream2) {
163 match self {
164 Self::Lit(lit) => lit.to_tokens(tokens),
165 Self::Path(path) => path.to_tokens(tokens),
166 Self::Symbol(symbol) => symbol.to_tokens(tokens),
167 }
168 }
169}
170
171impl MetaValue {
172 pub fn as_bool(&self) -> Option<bool> {
174 match self {
175 Self::Lit(syn::Lit::Bool(lit_bool)) => Some(lit_bool.value),
176 _ => None,
177 }
178 }
179
180 pub fn as_string(&self) -> Option<String> {
182 match self {
183 Self::Lit(syn::Lit::Str(lit_str)) => Some(lit_str.value()),
184 _ => None,
185 }
186 }
187
188 pub fn as_lit_int(&self) -> Option<&LitInt> {
190 match self {
191 Self::Lit(syn::Lit::Int(lit_int)) => Some(lit_int),
192 _ => None,
193 }
194 }
195
196 pub fn as_lit_bool(&self) -> Option<&LitBool> {
198 match self {
199 Self::Lit(syn::Lit::Bool(lit_bool)) => Some(lit_bool),
200 _ => None,
201 }
202 }
203
204 pub fn as_lit_string(&self) -> Option<&LitStr> {
206 match self {
207 Self::Lit(syn::Lit::Str(lit_str)) => Some(lit_str),
208 _ => None,
209 }
210 }
211
212 pub fn as_path(&self) -> Option<&syn::Path> {
214 match self {
215 Self::Path(path) => Some(path),
216 _ => None,
217 }
218 }
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Hash)]
222pub enum Symbol {
223 Underscore(Token![_]),
224 AtSign(Token![@]),
225}
226
227impl Parse for Symbol {
228 fn parse(input: ParseStream) -> syn::Result<Self> {
229 if input.peek(Token![_]) {
230 Ok(Symbol::Underscore(input.parse()?))
231 } else if input.peek(Token![@]) {
232 Ok(Symbol::AtSign(input.parse()?))
233 } else {
234 Err(input.error("expected either a `_` or a `@` symbol"))
235 }
236 }
237}
238
239impl ToTokens for Symbol {
240 fn to_tokens(&self, tokens: &mut TokenStream2) {
241 match self {
242 Self::Underscore(underscore) => underscore.to_tokens(tokens),
243 Self::AtSign(at_sign) => at_sign.to_tokens(tokens),
244 }
245 }
246}
247
248fn parse_meta_path(input: ParseStream) -> Result<syn::Path, syn::Error> {
255 Ok(syn::Path {
256 leading_colon: input.parse()?,
257 segments: {
258 let mut segments = Punctuated::new();
259 while input.peek(Ident::peek_any) {
260 let ident = Ident::parse_any(input)?;
261 segments.push_value(syn::PathSegment::from(ident));
262 if !input.peek(syn::Token![::]) {
263 break
264 }
265 let punct = input.parse()?;
266 segments.push_punct(punct);
267 }
268 if segments.is_empty() {
269 return Err(input.error("expected path"))
270 } else if segments.trailing_punct() {
271 return Err(input.error("expected path segment"))
272 }
273 segments
274 },
275 })
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281 use crate::ast::{
282 MetaValue,
283 Symbol,
284 };
285 use quote::quote;
286
287 #[test]
288 fn underscore_token_works() {
289 assert_eq!(
290 syn::parse2::<Meta>(quote! { selector = _ }).unwrap(),
291 Meta::NameValue(MetaNameValue {
292 name: syn::parse_quote! { selector },
293 eq_token: syn::parse_quote! { = },
294 value: MetaValue::Symbol(Symbol::Underscore(syn::parse_quote! { _ })),
295 })
296 )
297 }
298
299 #[test]
300 fn at_token_works() {
301 assert_eq!(
302 syn::parse2::<Meta>(quote! { selector = @ }).unwrap(),
303 Meta::NameValue(MetaNameValue {
304 name: syn::parse_quote! { selector },
305 eq_token: syn::parse_quote! { = },
306 value: MetaValue::Symbol(Symbol::AtSign(syn::parse_quote! { @ })),
307 })
308 )
309 }
310}