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