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