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 lines.extend(return_builder.build());
122
123 lines.push("}".to_string());
124 lines.push(String::new());
125 }
126
127 for ctx in parsed_impl.static_method_contexts() {
129 let method_name = ctx.method.r_method_name();
130 lines.push(ctx.source_comment(type_ident));
131 if !class_has_no_rd {
133 let method_doc =
134 MethodDocBuilder::new(&class_name, &method_name, type_ident, &ctx.method.doc_tags)
135 .with_name_prefix("$")
136 .with_params_as_details();
137 lines.extend(method_doc.build());
138 }
139
140 lines.push(format!(
141 "{}${} <- function({}) {{",
142 class_name, method_name, ctx.params
143 ));
144
145 let what = format!("{}${}", class_name, method_name);
146 ctx.emit_method_prelude(&mut lines, " ", &what);
147
148 let strategy = crate::ReturnStrategy::for_method(ctx.method);
149 let return_builder = crate::MethodReturnBuilder::new(ctx.static_call())
150 .with_strategy(strategy)
151 .with_class_name(class_name.clone());
152 lines.extend(return_builder.build());
153
154 lines.push("}".to_string());
155 lines.push(String::new());
156 }
157
158 let should_export = should_export_from_tags(
161 &parsed_impl.doc_tags,
162 parsed_impl.noexport || parsed_impl.internal,
163 );
164
165 if class_has_no_rd {
174 lines.push("#' @noRd".to_string());
175 lines.push("#' @export".to_string());
176 } else if !should_export {
177 lines.push("#' @export".to_string());
178 } else {
179 lines.push(format!("#' @rdname {}", class_name));
180 lines.push("#' @param self The object instance.".to_string());
181 lines.push("#' @param name Method name for dispatch.".to_string());
182 lines.push("#' @export".to_string());
183 }
184 lines.push(format!("`$.{}` <- function(self, name) {{", class_name));
185 lines.push(format!(" obj <- {}[[name]]", class_name));
186 lines.push(" if (is.environment(obj)) {".to_string());
187 lines.push(" # Trait namespace - wrap instance methods to prepend self".to_string());
188 lines.push(" bound <- new.env(parent = emptyenv())".to_string());
189 lines.push(" for (method_name in names(obj)) {".to_string());
190 lines.push(" method <- obj[[method_name]]".to_string());
191 lines.push(" if (is.function(method)) {".to_string());
192 lines.push(" if (isTRUE(attr(method, \".__mx_instance__\"))) {".to_string());
193 lines.push(" local({".to_string());
194 lines.push(" m <- method".to_string());
195 lines.push(" bound[[method_name]] <<- function(...) m(self, ...)".to_string());
196 lines.push(" })".to_string());
197 lines.push(" } else {".to_string());
198 lines.push(" bound[[method_name]] <- method".to_string());
199 lines.push(" }".to_string());
200 lines.push(" }".to_string());
201 lines.push(" }".to_string());
202 lines.push(" bound".to_string());
203 lines.push(" } else if (is.null(obj)) {".to_string());
204 lines.push(" # Not found at top level -- search trait namespace environments".to_string());
205 lines.push(format!(" for (ns_name in names({})) {{", class_name));
206 lines.push(format!(" ns <- {}[[ns_name]]", class_name));
207 lines.push(
208 " if (is.environment(ns) && exists(name, envir = ns, inherits = FALSE)) {".to_string(),
209 );
210 lines.push(" method <- ns[[name]]".to_string());
211 lines.push(
212 " if (is.function(method) && isTRUE(attr(method, \".__mx_instance__\"))) {"
213 .to_string(),
214 );
215 lines.push(" # Instance method -- bind self as first arg".to_string());
216 lines.push(" m <- method".to_string());
217 lines.push(" s <- self".to_string());
218 lines.push(" return(function(...) m(s, ...))".to_string());
219 lines.push(" } else if (is.function(method)) {".to_string());
220 lines.push(" return(method)".to_string());
221 lines.push(" }".to_string());
222 lines.push(" }".to_string());
223 lines.push(" }".to_string());
224 lines.push(" NULL".to_string());
225 lines.push(" } else {".to_string());
226 lines.push(" environment(obj) <- environment()".to_string());
227 lines.push(" obj".to_string());
228 lines.push(" }".to_string());
229 lines.push("}".to_string());
230 if class_has_no_rd {
231 lines.push("#' @noRd".to_string());
232 lines.push("#' @export".to_string());
233 } else if !should_export {
234 lines.push("#' @export".to_string());
235 } else {
236 lines.push(format!("#' @rdname {}", class_name));
237 lines.push("#' @export".to_string());
238 }
239 lines.push(format!("`[[.{}` <- `$.{}`", class_name, class_name));
240
241 lines.join("\n")
242}