miniextendr_macros/miniextendr_impl/
env_class.rs1use super::ParsedImpl;
4
5pub fn generate_env_r_wrapper(parsed_impl: &ParsedImpl) -> String {
26 use crate::r_class_formatter::{
27 ClassDocBuilder, MethodDocBuilder, ParsedImplExt, should_export_from_tags,
28 };
29
30 let class_name = parsed_impl.class_name();
31 let type_ident = &parsed_impl.type_ident;
32 let class_has_no_rd = crate::roxygen::has_roxygen_tag(&parsed_impl.doc_tags, "noRd");
34
35 let mut lines = Vec::new();
36
37 lines.extend(
39 ClassDocBuilder::new(&class_name, type_ident, &parsed_impl.doc_tags, "")
40 .with_export_control(parsed_impl.internal, parsed_impl.noexport)
41 .build(),
42 );
43 if let Some(lc_import) = crate::lifecycle::collect_lifecycle_imports(
45 parsed_impl
46 .methods
47 .iter()
48 .filter_map(|m| m.method_attrs.lifecycle.as_ref()),
49 ) {
50 let insert_pos = lines.len().saturating_sub(1);
51 lines.insert(insert_pos, format!("#' {}", lc_import));
52 }
53 lines.push(format!("{} <- new.env(parent = emptyenv())", class_name));
54 lines.push(String::new());
55
56 if let Some(ctx) = parsed_impl.constructor_context() {
58 lines.push(ctx.source_comment(type_ident));
59 if !class_has_no_rd {
61 let method_doc =
62 MethodDocBuilder::new(&class_name, "new", type_ident, &ctx.method.doc_tags)
63 .with_name_prefix("$")
64 .with_params_as_details();
65 lines.extend(method_doc.build());
66 }
67 lines.push(format!("{}$new <- function({}) {{", class_name, ctx.params));
68 for line in ctx.missing_prelude() {
69 lines.push(format!(" {}", line));
70 }
71 for check in ctx.precondition_checks() {
72 lines.push(format!(" {}", check));
73 }
74 for line in ctx.match_arg_prelude() {
76 lines.push(format!(" {}", line));
77 }
78 lines.push(format!(" .val <- {}", ctx.static_call()));
79 lines.extend(crate::method_return_builder::condition_check_lines(" "));
80 lines.push(" self <- .val".to_string());
81 lines.push(format!(" class(self) <- \"{}\"", class_name));
82 lines.push(" self".to_string());
83 lines.push("}".to_string());
84 lines.push(String::new());
85 }
86
87 for ctx in parsed_impl.instance_method_contexts() {
89 let method_name = ctx.method.r_method_name();
90 lines.push(ctx.source_comment(type_ident));
91 if !class_has_no_rd {
93 let method_doc =
94 MethodDocBuilder::new(&class_name, &method_name, type_ident, &ctx.method.doc_tags)
95 .with_name_prefix("$")
96 .with_params_as_details();
97 lines.extend(method_doc.build());
98 }
99
100 lines.push(format!(
101 "{}${} <- function({}) {{",
102 class_name, method_name, ctx.params
103 ));
104
105 let what = format!("{}${}", class_name, method_name);
106 ctx.emit_method_prelude(&mut lines, " ", &what);
107
108 let call = ctx.instance_call("self");
109 let strategy = crate::ReturnStrategy::for_method(ctx.method);
110 let return_builder = crate::MethodReturnBuilder::new(call)
111 .with_strategy(strategy)
112 .with_class_name(class_name.clone());
113 lines.extend(return_builder.build());
114
115 lines.push("}".to_string());
116 lines.push(String::new());
117 }
118
119 for ctx in parsed_impl.static_method_contexts() {
121 let method_name = ctx.method.r_method_name();
122 lines.push(ctx.source_comment(type_ident));
123 if !class_has_no_rd {
125 let method_doc =
126 MethodDocBuilder::new(&class_name, &method_name, type_ident, &ctx.method.doc_tags)
127 .with_name_prefix("$")
128 .with_params_as_details();
129 lines.extend(method_doc.build());
130 }
131
132 lines.push(format!(
133 "{}${} <- function({}) {{",
134 class_name, method_name, ctx.params
135 ));
136
137 let what = format!("{}${}", class_name, method_name);
138 ctx.emit_method_prelude(&mut lines, " ", &what);
139
140 let strategy = crate::ReturnStrategy::for_method(ctx.method);
141 let return_builder = crate::MethodReturnBuilder::new(ctx.static_call())
142 .with_strategy(strategy)
143 .with_class_name(class_name.clone());
144 lines.extend(return_builder.build());
145
146 lines.push("}".to_string());
147 lines.push(String::new());
148 }
149
150 let should_export = should_export_from_tags(
153 &parsed_impl.doc_tags,
154 parsed_impl.noexport || parsed_impl.internal,
155 );
156
157 if class_has_no_rd {
166 lines.push("#' @noRd".to_string());
167 lines.push("#' @export".to_string());
168 } else if !should_export {
169 lines.push("#' @export".to_string());
170 } else {
171 lines.push(format!("#' @rdname {}", class_name));
172 lines.push("#' @param self The object instance.".to_string());
173 lines.push("#' @param name Method name for dispatch.".to_string());
174 lines.push("#' @export".to_string());
175 }
176 lines.push(format!("`$.{}` <- function(self, name) {{", class_name));
177 lines.push(format!(" obj <- {}[[name]]", class_name));
178 lines.push(" if (is.environment(obj)) {".to_string());
179 lines.push(" # Trait namespace - wrap instance methods to prepend self".to_string());
180 lines.push(" bound <- new.env(parent = emptyenv())".to_string());
181 lines.push(" for (method_name in names(obj)) {".to_string());
182 lines.push(" method <- obj[[method_name]]".to_string());
183 lines.push(" if (is.function(method)) {".to_string());
184 lines.push(" if (isTRUE(attr(method, \".__mx_instance__\"))) {".to_string());
185 lines.push(" local({".to_string());
186 lines.push(" m <- method".to_string());
187 lines.push(" bound[[method_name]] <<- function(...) m(self, ...)".to_string());
188 lines.push(" })".to_string());
189 lines.push(" } else {".to_string());
190 lines.push(" bound[[method_name]] <- method".to_string());
191 lines.push(" }".to_string());
192 lines.push(" }".to_string());
193 lines.push(" }".to_string());
194 lines.push(" bound".to_string());
195 lines.push(" } else if (is.null(obj)) {".to_string());
196 lines.push(" # Not found at top level -- search trait namespace environments".to_string());
197 lines.push(format!(" for (ns_name in names({})) {{", class_name));
198 lines.push(format!(" ns <- {}[[ns_name]]", class_name));
199 lines.push(
200 " if (is.environment(ns) && exists(name, envir = ns, inherits = FALSE)) {".to_string(),
201 );
202 lines.push(" method <- ns[[name]]".to_string());
203 lines.push(
204 " if (is.function(method) && isTRUE(attr(method, \".__mx_instance__\"))) {"
205 .to_string(),
206 );
207 lines.push(" # Instance method -- bind self as first arg".to_string());
208 lines.push(" m <- method".to_string());
209 lines.push(" s <- self".to_string());
210 lines.push(" return(function(...) m(s, ...))".to_string());
211 lines.push(" } else if (is.function(method)) {".to_string());
212 lines.push(" return(method)".to_string());
213 lines.push(" }".to_string());
214 lines.push(" }".to_string());
215 lines.push(" }".to_string());
216 lines.push(" NULL".to_string());
217 lines.push(" } else {".to_string());
218 lines.push(" environment(obj) <- environment()".to_string());
219 lines.push(" obj".to_string());
220 lines.push(" }".to_string());
221 lines.push("}".to_string());
222 if class_has_no_rd {
223 lines.push("#' @noRd".to_string());
224 lines.push("#' @export".to_string());
225 } else if !should_export {
226 lines.push("#' @export".to_string());
227 } else {
228 lines.push(format!("#' @rdname {}", class_name));
229 lines.push("#' @export".to_string());
230 }
231 lines.push(format!("`[[.{}` <- `$.{}`", class_name, class_name));
232
233 lines.join("\n")
234}