miniextendr_macros/miniextendr_impl/
r6_class.rs1use super::ParsedImpl;
4use crate::r_class_formatter::class_ref_or_verbatim;
5
6pub fn generate_r6_r_wrapper(parsed_impl: &ParsedImpl) -> String {
23 use crate::r_class_formatter::{ClassDocBuilder, MethodDocBuilder, ParsedImplExt};
24
25 let class_name = parsed_impl.class_name();
26 let type_ident = &parsed_impl.type_ident;
27 let class_doc_tags = &parsed_impl.doc_tags;
28
29 let has_self_returning_methods = parsed_impl
31 .methods
32 .iter()
33 .filter(|m| m.should_include())
34 .any(|m| m.returns_self());
35
36 let mut lines = Vec::new();
37
38 lines.extend(
40 ClassDocBuilder::new(&class_name, type_ident, class_doc_tags, "R6")
41 .with_imports("@importFrom R6 R6Class")
42 .with_export_control(parsed_impl.internal, parsed_impl.noexport)
43 .build(),
44 );
45 if let Some(lc_import) = crate::lifecycle::collect_lifecycle_imports(
47 parsed_impl
48 .methods
49 .iter()
50 .filter_map(|m| m.method_attrs.lifecycle.as_ref()),
51 ) {
52 let insert_pos = lines.len().saturating_sub(1);
54 lines.insert(insert_pos, format!("#' {}", lc_import));
55 }
56
57 if has_self_returning_methods && !crate::roxygen::has_roxygen_tag(class_doc_tags, "param .ptr")
59 {
60 let insert_pos = lines.len().saturating_sub(1);
62 lines.insert(
63 insert_pos,
64 "#' @param .ptr Internal pointer (used by static methods, not for direct use)."
65 .to_string(),
66 );
67 }
68 if let Some(ref parent) = parsed_impl.r6_inherit {
72 let parent_ref = class_ref_or_verbatim(parent);
73 lines.push(format!(
74 "{} <- R6::R6Class(\"{}\", inherit = {},",
75 class_name, class_name, parent_ref
76 ));
77 } else {
78 lines.push(format!("{} <- R6::R6Class(\"{}\",", class_name, class_name));
79 }
80
81 if parsed_impl.r6_portable == Some(false) {
83 lines.push(" portable = FALSE,".to_string());
84 }
85
86 lines.push(" public = list(".to_string());
88
89 let public_method_contexts: Vec<_> = parsed_impl.public_instance_method_contexts().collect();
91 let has_public_methods = !public_method_contexts.is_empty();
92
93 if let Some(ctx) = parsed_impl.constructor_context() {
97 lines.push(format!(" {}", ctx.source_comment(type_ident)));
98 let has_description = ctx
101 .method
102 .doc_tags
103 .iter()
104 .any(|t| t.starts_with("@description ") || t.starts_with("@title "));
105 if !has_description {
106 lines.push(format!(
107 " #' @description Create a new `{}`.",
108 class_name
109 ));
110 }
111 for tag in &ctx.method.doc_tags {
112 for line in tag.lines() {
113 let line = if line.starts_with("@title ") {
114 line.replacen("@title ", "@description ", 1)
115 } else {
116 line.to_string()
117 };
118 lines.push(format!(" #' {}", line));
119 }
120 }
121 let ctor_mx_doc = ctx.match_arg_doc_placeholders();
123 for param in ctx.params.split(", ").filter(|p| !p.is_empty()) {
124 let param_name = param.split('=').next().unwrap_or(param).trim();
125 if param_name == ".ptr" {
126 continue;
127 }
128 let already_documented = ctx
129 .method
130 .doc_tags
131 .iter()
132 .any(|t| t.starts_with(&format!("@param {}", param_name)));
133 if !already_documented {
134 let body = ctor_mx_doc
137 .get(param_name)
138 .map(String::as_str)
139 .unwrap_or("(no documentation available)");
140 lines.push(format!(" #' @param {} {}", param_name, body));
141 }
142 }
143
144 let comma = if has_public_methods { "," } else { "" };
146
147 let ctor_preconditions = ctx.precondition_checks();
149
150 let ctor_missing = ctx.missing_prelude();
152
153 let ctor_match_arg = ctx.match_arg_prelude();
154
155 if has_self_returning_methods {
156 let full_params = if ctx.params.is_empty() {
157 ".ptr = NULL".to_string()
158 } else {
159 format!("{}, .ptr = NULL", ctx.params)
160 };
161 lines.push(format!(" initialize = function({}) {{", full_params));
162 if !ctor_missing.is_empty()
164 || !ctor_preconditions.is_empty()
165 || !ctor_match_arg.is_empty()
166 {
167 lines.push(" if (is.null(.ptr)) {".to_string());
168 for line in &ctor_missing {
169 lines.push(format!(" {}", line));
170 }
171 for check in &ctor_preconditions {
172 lines.push(format!(" {}", check));
173 }
174 for line in &ctor_match_arg {
175 lines.push(format!(" {}", line));
176 }
177 lines.push(" }".to_string());
178 }
179 lines.push(" if (!is.null(.ptr)) {".to_string());
180 lines.push(" private$.ptr <- .ptr".to_string());
181 lines.push(" } else {".to_string());
182 lines.push(format!(" .val <- {}", ctx.static_call()));
183 for check_line in crate::method_return_builder::condition_check_lines(" ") {
185 lines.push(check_line);
186 }
187 lines.push(" private$.ptr <- .val".to_string());
188 lines.push(" }".to_string());
189 lines.push(format!(" }}{}", comma));
190 } else {
191 lines.push(format!(" initialize = function({}) {{", ctx.params));
192 for line in &ctor_missing {
193 lines.push(format!(" {}", line));
194 }
195 for check in &ctor_preconditions {
196 lines.push(format!(" {}", check));
197 }
198 for line in &ctor_match_arg {
199 lines.push(format!(" {}", line));
200 }
201 lines.push(format!(" .val <- {}", ctx.static_call()));
202 lines.extend(crate::method_return_builder::condition_check_lines(
203 " ",
204 ));
205 lines.push(" private$.ptr <- .val".to_string());
206 lines.push(format!(" }}{}", comma));
207 }
208 } else if has_self_returning_methods {
209 let comma = if has_public_methods { "," } else { "" };
212 lines.push(format!(
213 " #' @description Create a new `{}`.",
214 class_name
215 ));
216 lines.push(" initialize = function(.ptr = NULL) {".to_string());
217 lines.push(" if (!is.null(.ptr)) {".to_string());
218 lines.push(" private$.ptr <- .ptr".to_string());
219 lines.push(" }".to_string());
220 lines.push(format!(" }}{}", comma));
221 }
222
223 for (i, ctx) in public_method_contexts.iter().enumerate() {
225 let comma = if i < public_method_contexts.len() - 1 {
226 ","
227 } else {
228 ""
229 };
230
231 lines.push(format!(" {}", ctx.source_comment(type_ident)));
232 let r_name = ctx.method.r_method_name();
235 let has_description = ctx
236 .method
237 .doc_tags
238 .iter()
239 .any(|t| t.starts_with("@description ") || t.starts_with("@title "));
240 if !has_description {
241 lines.push(format!(" #' @description Method `{}`.", r_name));
242 }
243 for tag in &ctx.method.doc_tags {
244 for line in tag.lines() {
245 let line = if line.starts_with("@title ") {
246 line.replacen("@title ", "@description ", 1)
247 } else {
248 line.to_string()
249 };
250 lines.push(format!(" #' {}", line));
251 }
252 }
253 let method_mx_doc = ctx.match_arg_doc_placeholders();
255 for param in ctx.params.split(", ").filter(|p| !p.is_empty()) {
256 let param_name = param.split('=').next().unwrap_or(param).trim();
257 let already_documented = ctx
258 .method
259 .doc_tags
260 .iter()
261 .any(|t| t.starts_with(&format!("@param {}", param_name)));
262 if !already_documented {
263 let body = method_mx_doc
264 .get(param_name)
265 .map(String::as_str)
266 .unwrap_or("(no documentation available)");
267 lines.push(format!(" #' @param {} {}", param_name, body));
268 }
269 }
270 lines.push(format!(" {} = function({}) {{", r_name, ctx.params));
271
272 let what = format!("{}${}", class_name, r_name);
273 ctx.emit_method_prelude(&mut lines, " ", &what);
274
275 let call = ctx.instance_call("private$.ptr");
276 let strategy = crate::ReturnStrategy::for_method(ctx.method);
277 let return_builder = crate::MethodReturnBuilder::new(call)
278 .with_strategy(strategy)
279 .with_class_name(class_name.clone())
280 .with_indent(6); lines.extend(return_builder.build_r6_body());
282
283 lines.push(format!(" }}{}", comma));
284 }
285
286 lines.push(" ),".to_string());
287
288 lines.push(" private = list(".to_string());
290
291 for ctx in parsed_impl.private_instance_method_contexts() {
293 lines.push(format!(" {}", ctx.source_comment(type_ident)));
294 lines.push(format!(
295 " {} = function({}) {{",
296 ctx.method.r_method_name(),
297 ctx.params
298 ));
299
300 if let Some(ref entry) = ctx.method.method_attrs.r_entry {
302 for line in entry.lines() {
303 lines.push(format!(" {}", line));
304 }
305 }
306 if let Some(ref on_exit) = ctx.method.method_attrs.r_on_exit {
308 lines.push(format!(" {}", on_exit.to_r_code()));
309 }
310 for line in ctx.missing_prelude() {
312 lines.push(format!(" {}", line));
313 }
314 for line in ctx.match_arg_prelude() {
316 lines.push(format!(" {}", line));
317 }
318 if let Some(ref post) = ctx.method.method_attrs.r_post_checks {
320 for line in post.lines() {
321 lines.push(format!(" {}", line));
322 }
323 }
324
325 let call = ctx.instance_call("private$.ptr");
326 let strategy = crate::ReturnStrategy::for_method(ctx.method);
327 let return_builder = crate::MethodReturnBuilder::new(call)
328 .with_strategy(strategy)
329 .with_class_name(class_name.clone())
330 .with_indent(6);
331 lines.extend(return_builder.build_r6_body());
332
333 lines.push(" },".to_string());
334 }
335
336 if let Some(finalizer) = parsed_impl.finalizer() {
338 let c_ident = finalizer
339 .c_wrapper_ident(type_ident, parsed_impl.label())
340 .to_string();
341 let finalize_call = crate::r_wrapper_builder::DotCallBuilder::new(&c_ident)
342 .null_call_attribution()
343 .with_self("private$.ptr")
344 .build();
345 lines.push(format!(" finalize = function() {finalize_call},"));
346 }
347
348 if let Some(dc_method) = parsed_impl
350 .methods
351 .iter()
352 .find(|m| m.method_attrs.r6.deep_clone && m.should_include())
353 {
354 let c_ident = dc_method
355 .c_wrapper_ident(type_ident, parsed_impl.label())
356 .to_string();
357 let deep_clone_call = crate::r_wrapper_builder::DotCallBuilder::new(&c_ident)
358 .null_call_attribution()
359 .with_self("private$.ptr")
360 .with_args(&["name", "value"])
361 .build();
362 lines.push(format!(
363 " deep_clone = function(name, value) {deep_clone_call},"
364 ));
365 }
366
367 lines.push(" .ptr = NULL".to_string());
369 lines.push(" ),".to_string());
370
371 let active_method_contexts: Vec<_> = parsed_impl.active_instance_method_contexts().collect();
373 if !active_method_contexts.is_empty() {
374 lines.push(" active = list(".to_string());
375
376 for (i, ctx) in active_method_contexts.iter().enumerate() {
377 let comma = if i < active_method_contexts.len() - 1 {
378 ","
379 } else {
380 ""
381 };
382
383 let method_name = ctx.method.r_method_name();
386 let method_noexport =
387 ctx.method.method_attrs.noexport || ctx.method.method_attrs.internal;
388 if method_noexport {
389 lines.push(format!(" #' @field {} (internal)", method_name));
397 } else if ctx.method.doc_tags.is_empty() {
398 lines.push(format!(" #' @field {} Active binding.", method_name));
399 } else {
400 for tag in &ctx.method.doc_tags {
401 for (line_idx, line) in tag.lines().enumerate() {
402 let line = if line_idx == 0 {
404 if let Some(desc) = line.strip_prefix("@description ") {
405 format!("@field {} {}", method_name, desc)
406 } else if let Some(desc) = line.strip_prefix("@title ") {
407 format!("@field {} {}", method_name, desc)
408 } else if !line.starts_with('@') {
409 format!("@field {} {}", method_name, line)
411 } else {
412 line.to_string()
413 }
414 } else {
415 line.to_string()
417 };
418 lines.push(format!(" #' {}", line));
419 }
420 }
421 }
422
423 let prop_name = ctx
425 .method
426 .method_attrs
427 .r6
428 .prop
429 .clone()
430 .unwrap_or_else(|| ctx.method.r_method_name());
431
432 let setter = parsed_impl.find_setter_for_prop(&prop_name);
434
435 if let Some(setter_method) = setter {
436 lines.push(format!(" {} = function(value) {{", prop_name));
439 lines.push(" if (missing(value)) {".to_string());
440
441 let getter_call = ctx.instance_call("private$.ptr");
443 lines.push(format!(" {}", getter_call));
444
445 lines.push(" } else {".to_string());
446
447 let setter_c_ident = setter_method
449 .c_wrapper_ident(type_ident, parsed_impl.label.as_deref())
450 .to_string();
451 let setter_call = crate::r_wrapper_builder::DotCallBuilder::new(&setter_c_ident)
452 .with_self("private$.ptr")
453 .with_args(&["value"])
454 .build();
455 lines.push(format!(" {}", setter_call));
456 lines.push(" invisible(self)".to_string());
457
458 lines.push(" }".to_string());
459 lines.push(format!(" }}{}", comma));
460 } else {
461 lines.push(format!(" {} = function() {{", prop_name));
464
465 let call = ctx.instance_call("private$.ptr");
466 let strategy = crate::ReturnStrategy::for_method(ctx.method);
467 let return_builder = crate::MethodReturnBuilder::new(call)
468 .with_strategy(strategy)
469 .with_class_name(class_name.clone())
470 .with_indent(6); lines.extend(return_builder.build_r6_body());
472
473 lines.push(format!(" }}{}", comma));
474 }
475 }
476
477 lines.push(" ),".to_string());
478 }
479
480 let lock_objects = parsed_impl.r6_lock_objects.unwrap_or(true);
482 let lock_class = parsed_impl.r6_lock_class.unwrap_or(false);
483 let cloneable = parsed_impl.r6_cloneable.unwrap_or(false);
484 lines.push(format!(
485 " lock_objects = {},",
486 if lock_objects { "TRUE" } else { "FALSE" }
487 ));
488 lines.push(format!(
489 " lock_class = {},",
490 if lock_class { "TRUE" } else { "FALSE" }
491 ));
492 lines.push(format!(
493 " cloneable = {}",
494 if cloneable { "TRUE" } else { "FALSE" }
495 ));
496 lines.push(")".to_string());
497
498 if parsed_impl.r_data_accessors {
500 let type_name = type_ident.to_string();
501 lines.push(format!(
502 ".rdata_active_bindings_{}({})",
503 type_name, class_name
504 ));
505 }
506
507 let class_has_no_rd = crate::roxygen::has_roxygen_tag(class_doc_tags, "noRd");
509
510 for ctx in parsed_impl.static_method_contexts() {
512 let method_name = ctx.method.r_method_name();
513 let static_method_name = format!("{}${}", class_name, method_name);
514 lines.push(String::new());
515
516 lines.push(ctx.source_comment(type_ident));
517 let method_doc =
518 MethodDocBuilder::new(&class_name, &method_name, type_ident, &ctx.method.doc_tags)
519 .with_name_prefix("$")
520 .with_class_no_rd(class_has_no_rd);
521 lines.extend(method_doc.build());
522
523 lines.push(format!(
524 "{} <- function({}) {{",
525 static_method_name, ctx.params
526 ));
527
528 let what = format!("{}${}", class_name, method_name);
529 ctx.emit_method_prelude(&mut lines, " ", &what);
530
531 let strategy = crate::ReturnStrategy::for_method(ctx.method);
532 let return_builder = crate::MethodReturnBuilder::new(ctx.static_call())
533 .with_strategy(strategy)
534 .with_class_name(class_name.clone());
535 lines.extend(return_builder.build_r6_body());
536
537 lines.push("}".to_string());
538 }
539
540 lines.join("\n")
541}