1use crate::convert::{FromValue, IntoValue, ValueConversionError};
7use crate::engine::ExecutionContext;
8use crate::error::{Error, Result};
9use crate::value::Value;
10
11pub trait HostArg: Sized {
13 fn extract(args: &[Value], position: usize) -> std::result::Result<Self, ArgError>;
15
16 fn is_optional() -> bool {
18 false
19 }
20}
21
22#[derive(Debug, Clone)]
24pub struct ArgError {
25 pub position: usize,
27 pub expected: &'static str,
29 pub message: String,
31}
32
33impl std::fmt::Display for ArgError {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 write!(
36 f,
37 "argument {}: expected {}, {}",
38 self.position, self.expected, self.message
39 )
40 }
41}
42
43impl std::error::Error for ArgError {}
44
45impl From<ArgError> for Error {
46 fn from(err: ArgError) -> Self {
47 Error::HostFunction(err.to_string())
48 }
49}
50
51impl<T: FromValue> HostArg for T {
54 fn extract(args: &[Value], position: usize) -> std::result::Result<Self, ArgError> {
55 let value = args.get(position).cloned().ok_or_else(|| ArgError {
56 position,
57 expected: std::any::type_name::<T>(),
58 message: "missing argument".to_string(),
59 })?;
60
61 T::from_value(value).map_err(|e| ArgError {
62 position,
63 expected: std::any::type_name::<T>(),
64 message: e.to_string(),
65 })
66 }
67}
68
69pub struct Optional<T>(pub Option<T>);
71
72impl<T: FromValue> HostArg for Optional<T> {
73 fn extract(args: &[Value], position: usize) -> std::result::Result<Self, ArgError> {
74 match args.get(position) {
75 None | Some(Value::Null) => Ok(Optional(None)),
76 Some(value) => {
77 let converted = T::from_value(value.clone()).map_err(|e| ArgError {
78 position,
79 expected: std::any::type_name::<T>(),
80 message: e.to_string(),
81 })?;
82 Ok(Optional(Some(converted)))
83 }
84 }
85 }
86
87 fn is_optional() -> bool {
88 true
89 }
90}
91
92pub struct Rest<T>(pub Vec<T>);
94
95impl<T: FromValue> HostArg for Rest<T> {
96 fn extract(args: &[Value], position: usize) -> std::result::Result<Self, ArgError> {
97 let rest: std::result::Result<Vec<T>, _> = args[position..]
98 .iter()
99 .enumerate()
100 .map(|(i, v)| {
101 T::from_value(v.clone()).map_err(|e| ArgError {
102 position: position + i,
103 expected: std::any::type_name::<T>(),
104 message: e.to_string(),
105 })
106 })
107 .collect();
108 rest.map(Rest)
109 }
110
111 fn is_optional() -> bool {
112 true
113 }
114}
115
116pub trait HostReturn {
118 fn into_result(self) -> Result<Value>;
120}
121
122impl<T: IntoValue> HostReturn for T {
124 fn into_result(self) -> Result<Value> {
125 Ok(self.into_value())
126 }
127}
128
129impl<T: IntoValue, E: std::fmt::Display> HostReturn for std::result::Result<T, E> {
130 fn into_result(self) -> Result<Value> {
131 match self {
132 Ok(v) => Ok(v.into_value()),
133 Err(e) => Err(Error::HostFunction(e.to_string())),
134 }
135 }
136}
137
138pub fn wrap_host_fn<F, R>(f: F) -> impl Fn(&[Value], &ExecutionContext) -> Result<Value>
142where
143 F: Fn(&[Value], &ExecutionContext) -> R,
144 R: HostReturn,
145{
146 move |args: &[Value], ctx: &ExecutionContext| f(args, ctx).into_result()
147}
148
149pub fn typed_host_fn_0<F, R>(f: F) -> impl Fn(&[Value], &ExecutionContext) -> Result<Value>
159where
160 F: Fn() -> R,
161 R: HostReturn,
162{
163 move |_args: &[Value], _ctx: &ExecutionContext| f().into_result()
164}
165
166pub fn typed_host_fn_1<F, A, R>(f: F) -> impl Fn(&[Value], &ExecutionContext) -> Result<Value>
168where
169 F: Fn(A) -> R,
170 A: HostArg,
171 R: HostReturn,
172{
173 move |args: &[Value], _ctx: &ExecutionContext| {
174 let a = A::extract(args, 0)?;
175 f(a).into_result()
176 }
177}
178
179pub fn typed_host_fn_2<F, A, B, R>(f: F) -> impl Fn(&[Value], &ExecutionContext) -> Result<Value>
181where
182 F: Fn(A, B) -> R,
183 A: HostArg,
184 B: HostArg,
185 R: HostReturn,
186{
187 move |args: &[Value], _ctx: &ExecutionContext| {
188 let a = A::extract(args, 0)?;
189 let b = B::extract(args, 1)?;
190 f(a, b).into_result()
191 }
192}
193
194pub fn typed_host_fn_3<F, A, B, C, R>(
196 f: F,
197) -> impl Fn(&[Value], &ExecutionContext) -> Result<Value>
198where
199 F: Fn(A, B, C) -> R,
200 A: HostArg,
201 B: HostArg,
202 C: HostArg,
203 R: HostReturn,
204{
205 move |args: &[Value], _ctx: &ExecutionContext| {
206 let a = A::extract(args, 0)?;
207 let b = B::extract(args, 1)?;
208 let c = C::extract(args, 2)?;
209 f(a, b, c).into_result()
210 }
211}
212
213pub fn typed_host_fn_4<F, A, B, C, D, R>(
215 f: F,
216) -> impl Fn(&[Value], &ExecutionContext) -> Result<Value>
217where
218 F: Fn(A, B, C, D) -> R,
219 A: HostArg,
220 B: HostArg,
221 C: HostArg,
222 D: HostArg,
223 R: HostReturn,
224{
225 move |args: &[Value], _ctx: &ExecutionContext| {
226 let a = A::extract(args, 0)?;
227 let b = B::extract(args, 1)?;
228 let c = C::extract(args, 2)?;
229 let d = D::extract(args, 3)?;
230 f(a, b, c, d).into_result()
231 }
232}
233
234pub fn typed_host_fn_ctx_0<F, R>(f: F) -> impl Fn(&[Value], &ExecutionContext) -> Result<Value>
236where
237 F: Fn(&ExecutionContext) -> R,
238 R: HostReturn,
239{
240 move |_args: &[Value], ctx: &ExecutionContext| f(ctx).into_result()
241}
242
243pub fn typed_host_fn_ctx_1<F, A, R>(f: F) -> impl Fn(&[Value], &ExecutionContext) -> Result<Value>
245where
246 F: Fn(&ExecutionContext, A) -> R,
247 A: HostArg,
248 R: HostReturn,
249{
250 move |args: &[Value], ctx: &ExecutionContext| {
251 let a = A::extract(args, 0)?;
252 f(ctx, a).into_result()
253 }
254}
255
256pub fn typed_host_fn_ctx_2<F, A, B, R>(
258 f: F,
259) -> impl Fn(&[Value], &ExecutionContext) -> Result<Value>
260where
261 F: Fn(&ExecutionContext, A, B) -> R,
262 A: HostArg,
263 B: HostArg,
264 R: HostReturn,
265{
266 move |args: &[Value], ctx: &ExecutionContext| {
267 let a = A::extract(args, 0)?;
268 let b = B::extract(args, 1)?;
269 f(ctx, a, b).into_result()
270 }
271}
272
273#[macro_export]
297macro_rules! host_fn {
298 ($name:ident() -> $ret:ty $body:block) => {
300 $crate::macros::typed_host_fn_0(|| -> $ret $body)
301 };
302
303 ($name:ident($a:ident: $at:ty) -> $ret:ty $body:block) => {
305 $crate::macros::typed_host_fn_1(|$a: $at| -> $ret $body)
306 };
307
308 ($name:ident($a:ident: $at:ty, $b:ident: $bt:ty) -> $ret:ty $body:block) => {
310 $crate::macros::typed_host_fn_2(|$a: $at, $b: $bt| -> $ret $body)
311 };
312
313 ($name:ident($a:ident: $at:ty, $b:ident: $bt:ty, $c:ident: $ct:ty) -> $ret:ty $body:block) => {
315 $crate::macros::typed_host_fn_3(|$a: $at, $b: $bt, $c: $ct| -> $ret $body)
316 };
317
318 ($name:ident($a:ident: $at:ty, $b:ident: $bt:ty, $c:ident: $ct:ty, $d:ident: $dt:ty) -> $ret:ty $body:block) => {
320 $crate::macros::typed_host_fn_4(|$a: $at, $b: $bt, $c: $ct, $d: $dt| -> $ret $body)
321 };
322
323 (ctx, $name:ident() -> $ret:ty $body:block) => {
325 $crate::macros::typed_host_fn_ctx_0(|ctx: &$crate::ExecutionContext| -> $ret $body)
326 };
327
328 (ctx, $name:ident($a:ident: $at:ty) -> $ret:ty $body:block) => {
330 $crate::macros::typed_host_fn_ctx_1(|ctx: &$crate::ExecutionContext, $a: $at| -> $ret $body)
331 };
332
333 (ctx, $name:ident($a:ident: $at:ty, $b:ident: $bt:ty) -> $ret:ty $body:block) => {
335 $crate::macros::typed_host_fn_ctx_2(|ctx: &$crate::ExecutionContext, $a: $at, $b: $bt| -> $ret $body)
336 };
337}
338
339pub struct HostFnBuilder {
341 registry: crate::engine::HostRegistry,
342}
343
344impl HostFnBuilder {
345 pub fn new() -> Self {
347 Self {
348 registry: crate::engine::HostRegistry::new(),
349 }
350 }
351
352 pub fn register<S, F>(mut self, name: S, f: F) -> Self
354 where
355 S: Into<String>,
356 F: Fn(&[Value], &ExecutionContext) -> Result<Value> + Send + Sync + 'static,
357 {
358 self.registry.register(name, f);
359 self
360 }
361
362 pub fn register_module<M, N, F>(mut self, module: M, name: N, f: F) -> Self
364 where
365 M: Into<String>,
366 N: Into<String>,
367 F: Fn(&[Value], &ExecutionContext) -> Result<Value> + Send + Sync + 'static,
368 {
369 self.registry.register_module(module, name, f);
370 self
371 }
372
373 pub fn build(self) -> crate::engine::HostRegistry {
375 self.registry
376 }
377}
378
379impl Default for HostFnBuilder {
380 fn default() -> Self {
381 Self::new()
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388
389 #[test]
390 fn test_arg_extraction() {
391 let args = vec![Value::Int(42), Value::String("hello".into())];
392
393 let a: i64 = i64::extract(&args, 0).unwrap();
394 assert_eq!(a, 42);
395
396 let b: String = String::extract(&args, 1).unwrap();
397 assert_eq!(b, "hello");
398 }
399
400 #[test]
401 fn test_arg_extraction_missing() {
402 let args = vec![Value::Int(42)];
403 let result: std::result::Result<i64, _> = i64::extract(&args, 5);
404 assert!(result.is_err());
405 }
406
407 #[test]
408 fn test_optional_arg() {
409 let args = vec![Value::Int(42)];
410
411 let opt: Optional<i64> = Optional::extract(&args, 0).unwrap();
412 assert_eq!(opt.0, Some(42));
413
414 let opt: Optional<i64> = Optional::extract(&args, 1).unwrap();
415 assert_eq!(opt.0, None);
416
417 let args_with_null = vec![Value::Null];
418 let opt: Optional<i64> = Optional::extract(&args_with_null, 0).unwrap();
419 assert_eq!(opt.0, None);
420 }
421
422 #[test]
423 fn test_rest_args() {
424 let args = vec![
425 Value::Int(1),
426 Value::Int(2),
427 Value::Int(3),
428 Value::Int(4),
429 ];
430
431 let rest: Rest<i64> = Rest::extract(&args, 1).unwrap();
432 assert_eq!(rest.0, vec![2, 3, 4]);
433 }
434
435 #[test]
436 fn test_typed_host_fn() {
437 use crate::sandbox::{Sandbox, SandboxConfig};
438 use crate::Capabilities;
439 use crate::Limits;
440
441 let add = typed_host_fn_2(|a: i64, b: i64| -> i64 { a + b });
442
443 let sandbox = Sandbox::new(SandboxConfig::default()).unwrap();
444 let ctx = ExecutionContext::new(1, Capabilities::none(), Limits::default(), sandbox);
445
446 let result = add(&[Value::Int(3), Value::Int(4)], &ctx).unwrap();
447 assert_eq!(result, Value::Int(7));
448 }
449
450 #[test]
451 fn test_typed_host_fn_with_result() {
452 use crate::sandbox::{Sandbox, SandboxConfig};
453 use crate::Capabilities;
454 use crate::Limits;
455
456 let div = typed_host_fn_2(
457 |a: i64, b: i64| -> std::result::Result<i64, &'static str> {
458 if b == 0 {
459 Err("division by zero")
460 } else {
461 Ok(a / b)
462 }
463 },
464 );
465
466 let sandbox = Sandbox::new(SandboxConfig::default()).unwrap();
467 let ctx = ExecutionContext::new(1, Capabilities::none(), Limits::default(), sandbox);
468
469 let result = div(&[Value::Int(10), Value::Int(2)], &ctx).unwrap();
470 assert_eq!(result, Value::Int(5));
471
472 let result = div(&[Value::Int(10), Value::Int(0)], &ctx);
473 assert!(result.is_err());
474 }
475
476 #[test]
477 fn test_host_fn_builder() {
478 let registry = HostFnBuilder::new()
479 .register("test", |_args, _ctx| Ok(Value::Int(42)))
480 .register_module("math", "pi", |_args, _ctx| Ok(Value::Float(3.14159)))
481 .build();
482
483 assert!(registry.get("test").is_some());
484 assert!(registry.get_module("math", "pi").is_some());
485 }
486}