1use std::collections::HashMap;
4
5use crate::error::{DialogueError, Result};
6use crate::value::Value;
7
8pub type HostFn = Box<dyn Fn(Vec<Value>) -> Result<Value> + Send + Sync + 'static>;
10
11pub struct FunctionLibrary {
16 fns: HashMap<String, HostFn>,
17}
18
19impl FunctionLibrary {
20 #[must_use]
22 pub fn new() -> Self {
23 let mut lib = Self {
24 fns: HashMap::new(),
25 };
26 lib.register_builtins();
27 lib
28 }
29
30 pub fn register<F>(&mut self, name: impl Into<String>, f: F)
34 where
35 F: Fn(Vec<Value>) -> Result<Value> + Send + Sync + 'static,
36 {
37 self.fns.insert(name.into(), Box::new(f));
38 }
39
40 pub fn call(&self, name: &str, args: Vec<Value>) -> Result<Value> {
45 self.fns.get(name).map_or_else(
46 || {
47 Err(DialogueError::Function {
48 name: name.to_owned(),
49 message: "unknown function".into(),
50 })
51 },
52 |f| f(args),
53 )
54 }
55
56 fn register_builtins(&mut self) {
57 #[cfg(feature = "rand")]
58 self.register_rand_builtins();
59 self.register_math_builtins();
60 }
61
62 fn register_math_builtins(&mut self) {
63 self.register("round", |args| {
64 let n = require_one_number("round", &args)?;
65 Ok(Value::Number(n.round()))
66 });
67 self.register("floor", |args| {
68 let n = require_one_number("floor", &args)?;
69 Ok(Value::Number(n.floor()))
70 });
71 self.register("ceil", |args| {
72 let n = require_one_number("ceil", &args)?;
73 Ok(Value::Number(n.ceil()))
74 });
75 self.register("min", |args| {
76 let (a, b) = require_two_numbers("min", &args)?;
77 Ok(Value::Number(a.min(b)))
78 });
79 self.register("max", |args| {
80 let (a, b) = require_two_numbers("max", &args)?;
81 Ok(Value::Number(a.max(b)))
82 });
83 self.register("abs", |args| {
84 let n = require_one_number("abs", &args)?;
85 Ok(Value::Number(n.abs()))
86 });
87 self.register("clamp", |args| match args.as_slice() {
88 [Value::Number(v), Value::Number(lo), Value::Number(hi)] => {
89 Ok(Value::Number(v.clamp(*lo, *hi)))
90 }
91 _ => Err(DialogueError::Function {
92 name: "clamp".into(),
93 message: format!("expected 3 number arguments, got {args:?}"),
94 }),
95 });
96 self.register("string", |args| {
97 args.into_iter().next().map_or_else(
98 || {
99 Err(DialogueError::Function {
100 name: "string".into(),
101 message: "expected 1 argument".into(),
102 })
103 },
104 |v| Ok(Value::Text(v.to_string())),
105 )
106 });
107 self.register("int", |args| {
108 let n = require_one_number("int", &args)?;
109 Ok(Value::Number(n.trunc()))
110 });
111 }
112
113 #[cfg(feature = "rand")]
114 #[allow(
115 clippy::cast_possible_truncation,
116 clippy::cast_sign_loss,
117 clippy::cast_precision_loss
118 )]
119 fn register_rand_builtins(&mut self) {
120 use rand::Rng as _;
121 self.register("random", |_args| {
122 Ok(Value::Number(rand::rng().random::<f64>()))
123 });
124 self.register("random_range", |args| {
125 let (lo, hi) = require_two_numbers("random_range", &args)?;
126 let lo = lo as i64;
127 let hi = hi as i64;
128 if lo > hi {
129 return Err(DialogueError::Function {
130 name: "random_range".into(),
131 message: format!("lo ({lo}) > hi ({hi})"),
132 });
133 }
134 Ok(Value::Number(f64::from(
135 rand::rng().random_range(lo as i32..=hi as i32),
136 )))
137 });
138 self.register("dice", |args| {
139 let (sides, count) = require_two_numbers("dice", &args)?;
140 let sides = sides as u64;
141 let count = count as u64;
142 if sides == 0 {
143 return Err(DialogueError::Function {
144 name: "dice".into(),
145 message: "sides must be > 0".into(),
146 });
147 }
148 let total: u64 = (0..count)
149 .map(|_| rand::rng().random_range(1..=sides))
150 .sum();
151 Ok(Value::Number(total as f64))
152 });
153 }
154}
155
156impl Default for FunctionLibrary {
157 fn default() -> Self {
158 Self::new()
159 }
160}
161
162fn require_one_number(name: &str, args: &[Value]) -> Result<f64> {
165 match args {
166 [Value::Number(n)] => Ok(*n),
167 _ => Err(DialogueError::Function {
168 name: name.to_owned(),
169 message: format!("expected 1 number argument, got {args:?}"),
170 }),
171 }
172}
173
174fn require_two_numbers(name: &str, args: &[Value]) -> Result<(f64, f64)> {
175 match args {
176 [Value::Number(a), Value::Number(b)] => Ok((*a, *b)),
177 _ => Err(DialogueError::Function {
178 name: name.to_owned(),
179 message: format!("expected 2 number arguments, got {args:?}"),
180 }),
181 }
182}
183
184#[cfg(test)]
185mod tests;