Skip to main content

opencv_binding_generator/
generator_env.rs

1use std::cmp::Reverse;
2use std::collections::{HashMap, HashSet};
3use std::convert::TryFrom;
4use std::fmt;
5use std::fs::File;
6use std::io::{BufRead, BufReader, Read, Seek, SeekFrom};
7use std::ops::ControlFlow;
8use std::path::{Path, PathBuf};
9use std::rc::Rc;
10
11use clang::{Entity, EntityKind, EntityVisitResult};
12use semver::Version;
13
14use crate::class::ClassKind;
15use crate::settings::Settings;
16use crate::type_ref::CppNameStyle;
17use crate::{
18	Class, Element, EntityWalkerExt, EntityWalkerVisitor, MemoizeMap, MemoizeMapExt, SupportedModule, is_opencv_path,
19	opencv_module_from_path, settings,
20};
21
22#[derive(Copy, Clone, Debug)]
23pub enum ClassKindOverride {
24	Boxed,
25	Simple,
26	BoxedForced,
27	System,
28}
29
30impl ClassKindOverride {
31	pub fn is_boxed(self) -> bool {
32		match self {
33			ClassKindOverride::Boxed | ClassKindOverride::BoxedForced => true,
34			ClassKindOverride::Simple | ClassKindOverride::System => false,
35		}
36	}
37}
38
39#[derive(Clone, Debug)]
40pub struct ExportConfig {
41	pub class_kind_override: ClassKindOverride,
42	pub deprecated: bool,
43	pub no_return: bool,
44	pub no_except: bool,
45	pub no_discard: bool,
46	// the function is used to generate only the helper types using OCVRS_ONLY_DEPENDENT_TYPES
47	pub only_generated_types: bool,
48}
49
50impl Default for ExportConfig {
51	fn default() -> Self {
52		Self {
53			class_kind_override: ClassKindOverride::Boxed,
54			deprecated: false,
55			no_return: false,
56			no_except: false,
57			no_discard: false,
58			only_generated_types: false,
59		}
60	}
61}
62
63impl ExportConfig {
64	/// Doesn't change export config, but putting it into `ELEMENT_EXPORT_TWEAK` will force the creation of the default `ExportConfig`
65	pub fn export(src: ExportConfig) -> Option<ExportConfig> {
66		Some(src)
67	}
68
69	pub fn no_export(_src: ExportConfig) -> Option<ExportConfig> {
70		None
71	}
72
73	pub fn override_boxed(mut src: ExportConfig) -> Option<ExportConfig> {
74		src.class_kind_override = ClassKindOverride::Boxed;
75		Some(src)
76	}
77
78	pub fn force_boxed(mut src: ExportConfig) -> Option<ExportConfig> {
79		src.class_kind_override = ClassKindOverride::BoxedForced;
80		Some(src)
81	}
82
83	pub fn simple(mut src: ExportConfig) -> Option<ExportConfig> {
84		src.class_kind_override = ClassKindOverride::Simple;
85		Some(src)
86	}
87
88	pub fn system(mut src: ExportConfig) -> Option<ExportConfig> {
89		src.class_kind_override = ClassKindOverride::System;
90		Some(src)
91	}
92}
93
94pub struct RenameConfig {
95	pub rename: Rc<str>,
96}
97
98#[derive(Eq, PartialEq, Hash)]
99struct ExportIdx {
100	path: PathBuf,
101	line: u32,
102	line_offset: usize,
103}
104
105/// Populates different fields of [GeneratorEnv] to be used later for binding generation.
106///
107/// This is 1st pass of the analysis. It performs the collection of the necessary auxiliary data like which descendants a class has.
108struct GeneratorEnvPopulator<'tu, 'ge> {
109	module: SupportedModule,
110	gen_env: &'ge mut GeneratorEnv<'tu>,
111}
112
113impl<'tu> GeneratorEnvPopulator<'tu, '_> {
114	fn add_func_comment(&mut self, entity: Entity) {
115		let raw_comment = entity.doc_comment();
116		// Note to future: str::contains is very fast, no sense in trying to avoid going over string multiple times
117		if !raw_comment.is_empty() && !raw_comment.contains("@overload") && !raw_comment.contains("@copybrief") {
118			let name = entity.cpp_name(CppNameStyle::Reference).into_owned();
119			let line = entity.get_location().map_or(0, |l| l.get_file_location().line);
120			let defs = self.gen_env.func_comments.entry(name).or_default();
121			defs.push((line, raw_comment.into_owned()));
122			// reverse sort due to how we're querying this; the amount of elements in this Vec doesn't go above 7
123			defs.sort_unstable_by_key(|(line, _)| Reverse(*line));
124		}
125	}
126
127	fn add_descendant(&mut self, base_class: Entity, descendant: Entity<'tu>) {
128		self
129			.gen_env
130			.descendants
131			.entry(base_class.cpp_name(CppNameStyle::Reference).into_owned())
132			.or_insert_with(|| HashSet::with_capacity(4))
133			.insert(descendant);
134	}
135}
136
137impl<'tu> EntityWalkerVisitor<'tu> for GeneratorEnvPopulator<'tu, '_> {
138	fn wants_file(&mut self, path: &Path) -> bool {
139		is_opencv_path(path) || opencv_module_from_path(path) == Some(self.module)
140	}
141
142	fn visit_entity(&mut self, entity: Entity<'tu>) -> ControlFlow<()> {
143		match entity.get_kind() {
144			EntityKind::ClassDecl | EntityKind::StructDecl => {
145				entity.visit_children(|child, _| {
146					match child.get_kind() {
147						EntityKind::BaseSpecifier => {
148							self.add_descendant(child.get_definition().expect("Can't get base class definition"), entity);
149						}
150						EntityKind::Constructor
151						| EntityKind::Method
152						| EntityKind::FunctionTemplate
153						| EntityKind::ConversionFunction => {
154							self.add_func_comment(child);
155						}
156						_ => {}
157					}
158					EntityVisitResult::Continue
159				});
160			}
161			EntityKind::FunctionDecl => {
162				self.add_func_comment(entity);
163			}
164			_ => {}
165		}
166		ControlFlow::Continue(())
167	}
168}
169
170/// Generator environment or context, contains a global data (passed by immutable reference) for the binding generation
171///
172/// This is partially pre-populated in an additional pass before the generation to provide some necessary data that's not available
173/// at the generation moment. E.g. list of descendants of a particular class.
174pub struct GeneratorEnv<'tu> {
175	export_map: HashMap<ExportIdx, ExportConfig>,
176	rename_map: HashMap<ExportIdx, RenameConfig>,
177	/// Collection of function comments to be able to replace `@overload` and `@copybrief` comment markers
178	func_comments: HashMap<String, Vec<(u32, String)>>,
179	/// Cache of the calculated [ClassKind]s
180	class_kind_cache: MemoizeMap<String, Option<ClassKind>>,
181	descendants: HashMap<String, HashSet<Entity<'tu>>>,
182	pub settings: Settings,
183}
184
185impl<'tu> GeneratorEnv<'tu> {
186	pub fn empty() -> Self {
187		Self {
188			export_map: HashMap::new(),
189			rename_map: HashMap::new(),
190			func_comments: HashMap::new(),
191			class_kind_cache: MemoizeMap::new(HashMap::new()),
192			descendants: HashMap::new(),
193			settings: Settings::empty(),
194		}
195	}
196
197	/// [GeneratorEnv] with the global settings for the regular working mode
198	pub fn global(module: SupportedModule, root_entity: Entity<'tu>, opencv_version: &Version) -> Self {
199		let mut out = Self {
200			export_map: HashMap::with_capacity(1024),
201			rename_map: HashMap::with_capacity(64),
202			func_comments: HashMap::with_capacity(2048),
203			class_kind_cache: MemoizeMap::new(HashMap::with_capacity(32)),
204			descendants: HashMap::with_capacity(16),
205			settings: Settings::for_module(module, opencv_version),
206		};
207		root_entity.walk_opencv_entities(GeneratorEnvPopulator {
208			module,
209			gen_env: &mut out,
210		});
211		out
212	}
213
214	fn key(entity: Entity) -> ExportIdx {
215		let (loc, line_offset) = if entity.get_kind() == EntityKind::MacroExpansion {
216			// sometimes CV_EXPORT macros are located on a separate line so for those we compensate the offset
217			let l = entity
218				.get_range()
219				.expect("Can't get exported macro range")
220				.get_end()
221				.get_spelling_location();
222			let path = l.file.expect("Can't get exported macro file").get_path();
223			let mut f = BufReader::new(File::open(path).expect("Can't open export macro file"));
224			f.seek(SeekFrom::Start(u64::from(l.offset)))
225				.expect("Can't seek export macro file");
226			let mut line_offset = 0;
227			let mut line = String::with_capacity(8);
228			while f.read_line(&mut line).is_ok() {
229				if line.trim().is_empty() {
230					line_offset += 1;
231				} else {
232					break;
233				}
234			}
235			if line_offset > 1 {
236				panic!("Line offset more than 1 is not supported, modify fuzzy_key in get_export_config() to support higher values");
237			}
238			(l, line_offset)
239		} else {
240			let loc = entity
241				.get_definition()
242				.unwrap_or(entity)
243				.get_range()
244				.map_or_else(
245					// for some reason Apple libclang on macos has problems with get_range() on FacemarkLBF::Params::pupils
246					// see https://github.com/twistedfall/opencv-rust/issues/159#issuecomment-668234058
247					|| entity.get_location().expect("Can't get entity location"),
248					|range| range.get_start(),
249				)
250				.get_expansion_location();
251			(loc, 0)
252		};
253		ExportIdx {
254			path: loc.file.expect("Can't get exported entry file").get_path(),
255			line: loc.line,
256			line_offset,
257		}
258	}
259
260	pub fn make_export_config(&mut self, entity: Entity) -> &mut ExportConfig {
261		let key = Self::key(entity);
262		self.export_map.entry(key).or_default()
263	}
264
265	#[inline]
266	fn get_with_fuzzy_key<T>(entity: Entity, getter: impl Fn(&ExportIdx) -> Option<T>) -> Option<T> {
267		let key = Self::key(entity);
268		getter(&key).or_else(|| {
269			// for cases where CV_EXPORTS is on the separate line but entity.get_range() spans into it
270			let fuzzy_key = ExportIdx { line_offset: 1, ..key };
271			getter(&fuzzy_key).or_else(|| {
272				if fuzzy_key.line >= 1 {
273					// for cases where CV_EXPORTS is on the separate line but entity.get_range() starts on the next line
274					let fuzzy_key = ExportIdx {
275						line: fuzzy_key.line - 1,
276						..fuzzy_key
277					};
278					getter(&fuzzy_key)
279				} else {
280					None
281				}
282			})
283		})
284	}
285
286	pub fn get_export_config(&self, entity: Entity) -> Option<ExportConfig> {
287		let out = Self::get_with_fuzzy_key(entity, |key| self.export_map.get(key)).cloned();
288		let cpp_refname = entity.cpp_name(CppNameStyle::Reference);
289		if let Some(tweak) = settings::ELEMENT_EXPORT_TWEAK.get(cpp_refname.as_ref()) {
290			tweak(out.unwrap_or_default())
291		} else {
292			out
293		}
294	}
295
296	pub fn make_rename_config(&mut self, entity: Entity) -> &mut RenameConfig {
297		let key = Self::key(entity);
298		self
299			.rename_map
300			.entry(key)
301			.or_insert_with(|| RenameConfig { rename: Rc::from("") })
302	}
303
304	pub fn get_rename_config(&self, entity: Entity) -> Option<&RenameConfig> {
305		Self::get_with_fuzzy_key(entity, |key| self.rename_map.get(key))
306	}
307
308	pub fn get_func_comment(&self, line: u32, cpp_refname: &str) -> Option<&str> {
309		self
310			.func_comments
311			.get(cpp_refname)
312			.and_then(|comments| {
313				comments.iter()
314				// try to find the source function comment that is closest to the requested
315				.find(|(source_line, _)| *source_line <= line)
316				// if it fails return at least something
317				.or_else(|| comments.last())
318			})
319			.map(|(_, comment)| comment.as_str())
320	}
321
322	/// Calculates the [ClassKind] of the class `entity` based on the macros connected to its declaration and whether it can be
323	/// expressed as simple in Rust
324	pub fn get_class_kind(&self, entity: Entity<'tu>) -> Option<ClassKind> {
325		let id = entity.cpp_name(CppNameStyle::Reference);
326		self.class_kind_cache.memo_get(id.as_ref(), || {
327			let entity = entity.get_template().unwrap_or(entity);
328			if let Some(range) = entity.get_range() {
329				let name_ranges = entity.get_name_ranges();
330				if !name_ranges.is_empty() {
331					let file_location = range.get_start().get_file_location();
332					if let Some(file) = file_location.file {
333						let start = u64::from(file_location.offset);
334						let end = u64::from(name_ranges[0].get_start().get_file_location().offset);
335						let len = usize::try_from(end - start).expect("Buffer length doesn't fit usize");
336						let mut buf = vec![0; len];
337						if let Ok(mut f) = File::open(file.get_path()) {
338							f.seek(SeekFrom::Start(start)).expect("Can't seek");
339							f.read_exact(buf.as_mut_slice()).expect("Can't read");
340						}
341						let export_decl = String::from_utf8(buf).expect("Not a valid UTF-8");
342						if export_decl.contains("CV_EXPORTS_W_SIMPLE") || export_decl.contains("CV_EXPORTS_W_MAP") {
343							return Some(ClassKind::Simple);
344						} else if export_decl.contains("CV_EXPORTS") || export_decl.contains("GAPI_EXPORTS") {
345							return Some(ClassKind::Boxed);
346						}
347					}
348				}
349			}
350			let cls = Class::new(entity, self);
351			if cls.can_be_simple() {
352				return Some(ClassKind::Simple);
353			}
354			None
355		})
356	}
357
358	/// Returns the descendants of the specified class
359	pub fn descendants_of(&self, cpp_refname: &str) -> Option<&HashSet<Entity<'tu>>> {
360		self.descendants.get(cpp_refname)
361	}
362}
363
364impl fmt::Debug for GeneratorEnv<'_> {
365	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
366		f.debug_struct("GeneratorEnv")
367			.field("export_map", &format!("{} elements", self.export_map.len()))
368			.field("rename_map", &format!("{} elements", self.rename_map.len()))
369			.field("func_comments", &format!("{} elements", self.func_comments.len()))
370			.field(
371				"class_kind_cache",
372				&format!("{} elements", self.class_kind_cache.borrow().len()),
373			)
374			.field("descendants", &format!("{} elements", self.descendants.len()))
375			.field("settings", &self.settings)
376			.finish()
377	}
378}