1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | |
5 | use std::borrow::Cow; |
6 | use std::collections::hash_map::Entry; |
7 | use std::collections::HashMap; |
8 | use std::str::FromStr; |
9 | |
10 | use crate::bindgen::config::{Config, Language}; |
11 | use crate::bindgen::utilities::SynAttributeHelpers; |
12 | |
13 | // A system for specifying properties on items. Annotations are |
14 | // given through document comments and parsed by this code. |
15 | // |
16 | // An annotation is in the form cbindgen:PROPERTY=VALUE |
17 | // Where PROPERTY depends on the item |
18 | // Where VALUE can be |
19 | // * list - [Item1, Item2, Item3, ...] |
20 | // * atom - Foo |
21 | // * bool - true,false |
22 | // Examples: |
23 | // * cbindgen:field-names=[mHandle, mNamespace] |
24 | // * cbindgen:function-postfix=WR_DESTRUCTOR_SAFE |
25 | |
26 | /// A value specified by an annotation. |
27 | #[derive (Debug, Clone)] |
28 | pub enum AnnotationValue { |
29 | List(Vec<String>), |
30 | Atom(Option<String>), |
31 | Bool(bool), |
32 | } |
33 | |
34 | /// A set of annotations specified by a document comment. |
35 | #[derive (Debug, Default, Clone)] |
36 | pub struct AnnotationSet { |
37 | annotations: HashMap<String, AnnotationValue>, |
38 | pub must_use: bool, |
39 | pub deprecated: Option<String>, |
40 | } |
41 | |
42 | pub enum DeprecatedNoteKind { |
43 | Function, |
44 | Struct, |
45 | Enum, |
46 | } |
47 | |
48 | impl AnnotationSet { |
49 | pub fn new() -> AnnotationSet { |
50 | AnnotationSet { |
51 | annotations: HashMap::new(), |
52 | must_use: false, |
53 | deprecated: None, |
54 | } |
55 | } |
56 | |
57 | pub fn is_empty(&self) -> bool { |
58 | self.annotations.is_empty() && !self.must_use |
59 | } |
60 | |
61 | pub(crate) fn must_use(&self, config: &Config) -> bool { |
62 | self.must_use && config.language != Language::Cython |
63 | } |
64 | |
65 | pub(crate) fn deprecated_note<'c>( |
66 | &self, |
67 | config: &'c Config, |
68 | kind: DeprecatedNoteKind, |
69 | ) -> Option<Cow<'c, str>> { |
70 | let note = self.deprecated.as_deref()?; |
71 | |
72 | if config.language == Language::Cython { |
73 | return None; |
74 | } |
75 | |
76 | if note.is_empty() { |
77 | return Some(Cow::Borrowed(match kind { |
78 | DeprecatedNoteKind::Enum => config.enumeration.deprecated.as_deref()?, |
79 | DeprecatedNoteKind::Function => config.function.deprecated.as_deref()?, |
80 | DeprecatedNoteKind::Struct => config.structure.deprecated.as_deref()?, |
81 | })); |
82 | } |
83 | |
84 | let format = match kind { |
85 | DeprecatedNoteKind::Enum => &config.enumeration.deprecated_with_note, |
86 | DeprecatedNoteKind::Function => &config.function.deprecated_with_note, |
87 | DeprecatedNoteKind::Struct => &config.structure.deprecated_with_note, |
88 | } |
89 | .as_ref()?; |
90 | Some(Cow::Owned(format.replace("{}" , &format!(" {:?}" , note)))) |
91 | } |
92 | |
93 | pub fn load(attrs: &[syn::Attribute]) -> Result<AnnotationSet, String> { |
94 | let lines = attrs.get_comment_lines(); |
95 | let lines: Vec<&str> = lines |
96 | .iter() |
97 | .filter_map(|line| { |
98 | let line = line.trim_start(); |
99 | if !line.starts_with("cbindgen:" ) { |
100 | return None; |
101 | } |
102 | |
103 | Some(line) |
104 | }) |
105 | .collect(); |
106 | |
107 | let must_use = attrs.has_attr_word("must_use" ); |
108 | let deprecated = attrs.find_deprecated_note(); |
109 | let mut annotations = HashMap::new(); |
110 | |
111 | // Look at each line for an annotation |
112 | for line in lines { |
113 | debug_assert!(line.starts_with("cbindgen:" )); |
114 | |
115 | // Remove the "cbindgen:" prefix |
116 | let annotation = &line[9..]; |
117 | |
118 | // Split the annotation in two |
119 | let parts: Vec<&str> = annotation.split('=' ).map(|x| x.trim()).collect(); |
120 | |
121 | if parts.len() > 2 { |
122 | return Err(format!("Couldn't parse {}." , line)); |
123 | } |
124 | |
125 | // Grab the name that this annotation is modifying |
126 | let name = parts[0]; |
127 | |
128 | // If the annotation only has a name, assume it's setting a bool flag |
129 | if parts.len() == 1 { |
130 | annotations.insert(name.to_string(), AnnotationValue::Bool(true)); |
131 | continue; |
132 | } |
133 | |
134 | // Parse the value we're setting the name to |
135 | let value = parts[1]; |
136 | |
137 | if let Some(x) = parse_list(value) { |
138 | annotations.insert(name.to_string(), AnnotationValue::List(x)); |
139 | continue; |
140 | } |
141 | if let Ok(x) = value.parse::<bool>() { |
142 | annotations.insert(name.to_string(), AnnotationValue::Bool(x)); |
143 | continue; |
144 | } |
145 | annotations.insert( |
146 | name.to_string(), |
147 | if value.is_empty() { |
148 | AnnotationValue::Atom(None) |
149 | } else { |
150 | AnnotationValue::Atom(Some(value.to_string())) |
151 | }, |
152 | ); |
153 | } |
154 | |
155 | Ok(AnnotationSet { |
156 | annotations, |
157 | must_use, |
158 | deprecated, |
159 | }) |
160 | } |
161 | |
162 | /// Adds an annotation value if none is specified. |
163 | pub fn add_default(&mut self, name: &str, value: AnnotationValue) { |
164 | if let Entry::Vacant(e) = self.annotations.entry(name.to_string()) { |
165 | e.insert(value); |
166 | } |
167 | } |
168 | |
169 | pub fn list(&self, name: &str) -> Option<Vec<String>> { |
170 | match self.annotations.get(name) { |
171 | Some(AnnotationValue::List(x)) => Some(x.clone()), |
172 | _ => None, |
173 | } |
174 | } |
175 | pub fn atom(&self, name: &str) -> Option<Option<String>> { |
176 | match self.annotations.get(name) { |
177 | Some(AnnotationValue::Atom(x)) => Some(x.clone()), |
178 | _ => None, |
179 | } |
180 | } |
181 | pub fn bool(&self, name: &str) -> Option<bool> { |
182 | match self.annotations.get(name) { |
183 | Some(AnnotationValue::Bool(x)) => Some(*x), |
184 | _ => None, |
185 | } |
186 | } |
187 | |
188 | pub fn parse_atom<T>(&self, name: &str) -> Option<T> |
189 | where |
190 | T: Default + FromStr, |
191 | { |
192 | match self.annotations.get(name) { |
193 | Some(AnnotationValue::Atom(x)) => Some( |
194 | x.as_ref() |
195 | .map_or(T::default(), |y| y.parse::<T>().ok().unwrap()), |
196 | ), |
197 | _ => None, |
198 | } |
199 | } |
200 | } |
201 | |
202 | /// Parse lists like "[x, y, z]". This is not implemented efficiently or well. |
203 | fn parse_list(list: &str) -> Option<Vec<String>> { |
204 | if list.len() < 2 { |
205 | return None; |
206 | } |
207 | |
208 | match (list.chars().next(), list.chars().last()) { |
209 | (Some('[' ), Some(']' )) => Some( |
210 | listimpl Iterator [1..list.len() - 1] |
211 | .split(',' ) |
212 | .map(|x: &str| x.trim().to_string()) |
213 | .collect(), |
214 | ), |
215 | _ => None, |
216 | } |
217 | } |
218 | |