1use std::path::PathBuf;
6
7use crate::interpreter::value::*;
8use crate::interpreter::BuiltinContext;
9use minir_macros::interpreter_builtin;
10
11#[interpreter_builtin(name = ".Call")]
23fn builtin_dot_call(
24 args: &[RValue],
25 _named: &[(String, RValue)],
26 ctx: &BuiltinContext,
27) -> Result<RValue, RError> {
28 if args.is_empty() {
29 return Err(RError::new(
30 RErrorKind::Argument,
31 ".Call requires at least one argument (the function name)".to_string(),
32 ));
33 }
34
35 let symbol_name = match &args[0] {
38 RValue::Vector(rv) => rv.as_character_scalar().ok_or_else(|| {
39 RError::new(
40 RErrorKind::Argument,
41 ".Call: first argument must be a character string or native symbol reference"
42 .to_string(),
43 )
44 })?,
45 RValue::List(list) => {
46 list.values
48 .iter()
49 .find(|(k, _)| k.as_deref() == Some("name"))
50 .and_then(|(_, v)| v.as_vector()?.as_character_scalar())
51 .ok_or_else(|| {
52 RError::new(
53 RErrorKind::Argument,
54 ".Call: native symbol reference must have a $name field".to_string(),
55 )
56 })?
57 }
58 _ => {
59 return Err(RError::new(
60 RErrorKind::Argument,
61 ".Call: first argument must be a character string or native symbol reference"
62 .to_string(),
63 ))
64 }
65 };
66
67 let native_args = &args[1..];
69
70 if let Some(result) = crate::interpreter::builtins::rlang_ffi::try_dispatch(
73 &symbol_name,
74 native_args,
75 _named,
76 ctx.env(),
77 ) {
78 return result;
79 }
80
81 ctx.interpreter().dot_call(&symbol_name, native_args)
82}
83
84#[interpreter_builtin(name = ".External")]
98fn builtin_dot_external(
99 args: &[RValue],
100 _named: &[(String, RValue)],
101 ctx: &BuiltinContext,
102) -> Result<RValue, RError> {
103 if args.is_empty() {
104 return Err(RError::new(
105 RErrorKind::Argument,
106 ".External requires at least one argument (the function name)".to_string(),
107 ));
108 }
109
110 let symbol_name =
111 match &args[0] {
112 RValue::Vector(rv) => rv.as_character_scalar().ok_or_else(|| {
113 RError::new(
114 RErrorKind::Argument,
115 ".External: first argument must be a character string".to_string(),
116 )
117 })?,
118 RValue::List(list) => list
119 .values
120 .iter()
121 .find(|(k, _)| k.as_deref() == Some("name"))
122 .and_then(|(_, v)| v.as_vector()?.as_character_scalar())
123 .ok_or_else(|| {
124 RError::new(
125 RErrorKind::Argument,
126 ".External: native symbol reference must have a $name field".to_string(),
127 )
128 })?,
129 _ => return Err(RError::new(
130 RErrorKind::Argument,
131 ".External: first argument must be a character string or native symbol reference"
132 .to_string(),
133 )),
134 };
135
136 let native_args = &args[1..];
137 ctx.interpreter().dot_external(&symbol_name, native_args)
138}
139
140#[interpreter_builtin(name = ".External2")]
154fn builtin_dot_external2(
155 args: &[RValue],
156 _named: &[(String, RValue)],
157 ctx: &BuiltinContext,
158) -> Result<RValue, RError> {
159 if args.is_empty() {
160 return Err(RError::new(
161 RErrorKind::Argument,
162 ".External2 requires at least one argument (the function name)".to_string(),
163 ));
164 }
165
166 let symbol_name = match &args[0] {
167 RValue::Vector(rv) => rv.as_character_scalar().ok_or_else(|| {
168 RError::new(
169 RErrorKind::Argument,
170 ".External2: first argument must be a character string".to_string(),
171 )
172 })?,
173 RValue::List(list) => list
174 .values
175 .iter()
176 .find(|(k, _)| k.as_deref() == Some("name"))
177 .and_then(|(_, v)| v.as_vector()?.as_character_scalar())
178 .ok_or_else(|| {
179 RError::new(
180 RErrorKind::Argument,
181 ".External2: native symbol reference must have a $name field".to_string(),
182 )
183 })?,
184 _ => {
185 return Err(RError::new(
186 RErrorKind::Argument,
187 ".External2: first argument must be a character string".to_string(),
188 ))
189 }
190 };
191
192 let native_args = &args[1..];
194 if let Some(result) = crate::interpreter::builtins::rlang_ffi::try_dispatch(
195 &symbol_name,
196 native_args,
197 _named,
198 ctx.env(),
199 ) {
200 return result;
201 }
202
203 ctx.interpreter().dot_external(&symbol_name, native_args)
205}
206
207#[interpreter_builtin(name = ".C")]
223fn builtin_dot_c(
224 args: &[RValue],
225 named: &[(String, RValue)],
226 ctx: &BuiltinContext,
227) -> Result<RValue, RError> {
228 if args.is_empty() {
229 return Err(RError::new(
230 RErrorKind::Argument,
231 ".C requires at least one argument (the function name)".to_string(),
232 ));
233 }
234
235 let symbol_name = match &args[0] {
238 RValue::Vector(rv) => rv.as_character_scalar().ok_or_else(|| {
239 RError::new(
240 RErrorKind::Argument,
241 ".C: first argument must be a character string or native symbol reference"
242 .to_string(),
243 )
244 })?,
245 RValue::List(list) => {
246 list.values
248 .iter()
249 .find(|(k, _)| k.as_deref() == Some("name"))
250 .and_then(|(_, v)| v.as_vector()?.as_character_scalar())
251 .ok_or_else(|| {
252 RError::new(
253 RErrorKind::Argument,
254 ".C: native symbol reference must have a $name field".to_string(),
255 )
256 })?
257 }
258 _ => {
259 return Err(RError::new(
260 RErrorKind::Argument,
261 ".C: first argument must be a character string or native symbol reference"
262 .to_string(),
263 ))
264 }
265 };
266
267 let native_args = &args[1..];
269
270 let mut all_args: Vec<RValue> = native_args.to_vec();
273 let mut arg_names: Vec<Option<String>> = vec![None; native_args.len()];
274
275 for (name, val) in named {
276 if name == "PACKAGE" {
278 continue;
279 }
280 arg_names.push(Some(name.clone()));
281 all_args.push(val.clone());
282 }
283
284 ctx.interpreter().dot_c(&symbol_name, &all_args, &arg_names)
285}
286
287#[interpreter_builtin(name = "dyn.load", min_args = 1)]
300fn builtin_dyn_load(
301 args: &[RValue],
302 _named: &[(String, RValue)],
303 ctx: &BuiltinContext,
304) -> Result<RValue, RError> {
305 tracing::debug!("dyn.load called");
306 let path = args[0]
307 .as_vector()
308 .and_then(|v| v.as_character_scalar())
309 .ok_or_else(|| {
310 RError::new(
311 RErrorKind::Argument,
312 "dyn.load: argument must be a file path (character string)".to_string(),
313 )
314 })?;
315
316 let dll_path = PathBuf::from(&path);
317 ctx.interpreter().dyn_load(&dll_path)?;
318 Ok(RValue::Null)
319}
320
321#[interpreter_builtin(name = "dyn.unload", min_args = 1)]
327fn builtin_dyn_unload(
328 args: &[RValue],
329 _named: &[(String, RValue)],
330 ctx: &BuiltinContext,
331) -> Result<RValue, RError> {
332 let path = args[0]
333 .as_vector()
334 .and_then(|v| v.as_character_scalar())
335 .ok_or_else(|| {
336 RError::new(
337 RErrorKind::Argument,
338 "dyn.unload: argument must be a file path (character string)".to_string(),
339 )
340 })?;
341
342 let name = std::path::Path::new(&path)
344 .file_stem()
345 .and_then(|s| s.to_str())
346 .unwrap_or(&path);
347
348 ctx.interpreter().dyn_unload(name)?;
349 Ok(RValue::Null)
350}
351
352#[interpreter_builtin(name = "library.dynam", min_args = 1)]
363fn builtin_library_dynam(
364 args: &[RValue],
365 _named: &[(String, RValue)],
366 ctx: &BuiltinContext,
367) -> Result<RValue, RError> {
368 let chname = args[0]
369 .as_vector()
370 .and_then(|v| v.as_character_scalar())
371 .ok_or_else(|| {
372 RError::new(
373 RErrorKind::Argument,
374 "library.dynam: 'chname' must be a character string".to_string(),
375 )
376 })?;
377
378 let ext = if cfg!(target_os = "macos") {
380 "dylib"
381 } else {
382 "so"
383 };
384
385 let namespaces = ctx.interpreter().loaded_namespaces.borrow();
387 if let Some(ns) = namespaces.get(&chname) {
388 let lib_path = ns.lib_path.join("libs").join(format!("{chname}.{ext}"));
389 if lib_path.is_file() {
390 drop(namespaces);
391 ctx.interpreter().dyn_load(&lib_path)?;
392 return Ok(RValue::Null);
393 }
394 }
395 drop(namespaces);
396
397 let direct = PathBuf::from(format!("{chname}.{ext}"));
399 if direct.is_file() {
400 ctx.interpreter().dyn_load(&direct)?;
401 }
402
403 Ok(RValue::Null)
404}
405
406#[interpreter_builtin(name = "library.dynam.unload", min_args = 1)]
409fn builtin_library_dynam_unload(
410 args: &[RValue],
411 _named: &[(String, RValue)],
412 ctx: &BuiltinContext,
413) -> Result<RValue, RError> {
414 let name = args[0]
415 .as_vector()
416 .and_then(|v| v.as_character_scalar())
417 .unwrap_or_default();
418 ctx.interpreter().dyn_unload(&name)?;
419 Ok(RValue::Null)
420}
421
422#[interpreter_builtin(name = "is.loaded", min_args = 1)]
432fn builtin_is_loaded(
433 args: &[RValue],
434 _named: &[(String, RValue)],
435 ctx: &BuiltinContext,
436) -> Result<RValue, RError> {
437 let name = args[0]
438 .as_vector()
439 .and_then(|v| v.as_character_scalar())
440 .unwrap_or_default();
441 let loaded = ctx.interpreter().is_symbol_loaded(&name);
442 Ok(RValue::vec(Vector::Logical(vec![Some(loaded)].into())))
443}
444
445#[interpreter_builtin(name = "getNativeSymbolInfo", min_args = 1)]
448fn builtin_get_native_symbol_info(
449 args: &[RValue],
450 _named: &[(String, RValue)],
451 ctx: &BuiltinContext,
452) -> Result<RValue, RError> {
453 let name = args[0]
454 .as_vector()
455 .and_then(|v| v.as_character_scalar())
456 .unwrap_or_default();
457
458 match ctx.interpreter().find_native_symbol(&name) {
460 Ok(_) => {
461 Ok(RValue::List(RList::new(vec![(
464 Some("name".to_string()),
465 RValue::vec(Vector::Character(vec![Some(name)].into())),
466 )])))
467 }
468 Err(e) => Err(e),
469 }
470}
471
472