1use crate::evaluator::Value;
4
5pub fn eval_builtin(name: &str, args: &[Value]) -> Result<Value, String> {
7 match name {
8 "SUM" => builtin_sum(args),
9 "AVG" | "AVERAGE" => builtin_avg(args),
10 "COUNT" => builtin_count(args),
11 "MIN" => builtin_min(args),
12 "MAX" => builtin_max(args),
13 "EVOLVE" => builtin_evolve(args),
14 "BEST" => builtin_best(args),
15 "SPECIES" => builtin_species(args),
16 "EXHAUSTIVE" => builtin_exhaustive(args),
17 "ENTROPY" => builtin_entropy(args),
18 "PARETO" => builtin_pareto(args),
19 "CORRELATE" => builtin_correlate(args),
20 "ABS" => builtin_abs(args),
21 "SQRT" => builtin_sqrt(args),
22 "POW" | "POWER" => builtin_pow(args),
23 "LOG" => builtin_log(args),
24 "IF" => builtin_if(args),
25 _ => Err(format!("Unknown function: {}", name)),
26 }
27}
28
29fn collect_numbers(args: &[Value]) -> Result<Vec<f64>, String> {
30 let mut nums = Vec::new();
31 for arg in args {
32 match arg {
33 Value::Number(n) => nums.push(*n),
34 Value::Array(arr) => {
35 for v in arr {
36 if let Value::Number(n) = v {
37 nums.push(*n);
38 }
39 }
40 }
41 Value::Error(e) => return Err(e.clone()),
42 Value::Null | Value::Text(_) | Value::Boolean(_) => {}
43 }
44 }
45 Ok(nums)
46}
47
48fn builtin_sum(args: &[Value]) -> Result<Value, String> {
49 let nums = collect_numbers(args)?;
50 Ok(Value::Number(nums.iter().sum()))
51}
52
53fn builtin_avg(args: &[Value]) -> Result<Value, String> {
54 let nums = collect_numbers(args)?;
55 if nums.is_empty() {
56 return Err("AVG: no numeric values".into());
57 }
58 Ok(Value::Number(nums.iter().sum::<f64>() / nums.len() as f64))
59}
60
61fn builtin_count(args: &[Value]) -> Result<Value, String> {
62 let nums = collect_numbers(args)?;
63 Ok(Value::Number(nums.len() as f64))
64}
65
66fn builtin_min(args: &[Value]) -> Result<Value, String> {
67 let nums = collect_numbers(args)?;
68 nums.iter().copied().fold(f64::INFINITY, f64::min)
69 .pipe(|v| if nums.is_empty() { Ok(Value::Number(0.0)) } else { Ok(Value::Number(v)) })
70}
71
72fn builtin_max(args: &[Value]) -> Result<Value, String> {
73 let nums = collect_numbers(args)?;
74 nums.iter().copied().fold(f64::NEG_INFINITY, f64::max)
75 .pipe(|v| if nums.is_empty() { Ok(Value::Number(0.0)) } else { Ok(Value::Number(v)) })
76}
77
78trait Pipe<T> {
79 fn pipe<F: FnOnce(T) -> U, U>(self, f: F) -> U;
80}
81impl<T> Pipe<T> for T {
82 fn pipe<F: FnOnce(T) -> U, U>(self, f: F) -> U { f(self) }
83}
84
85fn builtin_abs(args: &[Value]) -> Result<Value, String> {
86 if args.len() != 1 {
87 return Err("ABS takes exactly 1 argument".into());
88 }
89 match &args[0] {
90 Value::Number(n) => Ok(Value::Number(n.abs())),
91 _ => Err("ABS requires a number".into()),
92 }
93}
94
95fn builtin_sqrt(args: &[Value]) -> Result<Value, String> {
96 if args.len() != 1 {
97 return Err("SQRT takes exactly 1 argument".into());
98 }
99 match &args[0] {
100 Value::Number(n) => {
101 if *n < 0.0 {
102 return Err("SQRT of negative number".into());
103 }
104 Ok(Value::Number(n.sqrt()))
105 }
106 _ => Err("SQRT requires a number".into()),
107 }
108}
109
110fn builtin_pow(args: &[Value]) -> Result<Value, String> {
111 if args.len() != 2 {
112 return Err("POW takes exactly 2 arguments".into());
113 }
114 match (&args[0], &args[1]) {
115 (Value::Number(base), Value::Number(exp)) => Ok(Value::Number(base.powf(*exp))),
116 _ => Err("POW requires two numbers".into()),
117 }
118}
119
120fn builtin_log(args: &[Value]) -> Result<Value, String> {
121 if args.is_empty() || args.len() > 2 {
122 return Err("LOG takes 1 or 2 arguments".into());
123 }
124 match &args[0] {
125 Value::Number(n) => {
126 let base = if args.len() == 2 {
127 if let Value::Number(b) = &args[1] { *b } else { return Err("LOG base must be a number".into()); }
128 } else {
129 std::f64::consts::E
130 };
131 if *n <= 0.0 || base <= 0.0 || base == 1.0 {
132 return Err("LOG: invalid arguments".into());
133 }
134 Ok(Value::Number(n.log(base)))
135 }
136 _ => Err("LOG requires a number".into()),
137 }
138}
139
140fn builtin_if(args: &[Value]) -> Result<Value, String> {
141 if args.len() < 2 || args.len() > 3 {
142 return Err("IF takes 2 or 3 arguments".into());
143 }
144 let cond = match &args[0] {
145 Value::Boolean(b) => *b,
146 Value::Number(n) => *n != 0.0,
147 _ => return Err("IF condition must be boolean or number".into()),
148 };
149 if cond {
150 Ok(args[1].clone())
151 } else if args.len() == 3 {
152 Ok(args[2].clone())
153 } else {
154 Ok(Value::Boolean(false))
155 }
156}
157
158fn builtin_evolve(args: &[Value]) -> Result<Value, String> {
163 if args.len() != 2 {
164 return Err("EVOLVE takes exactly 2 arguments: EVOLVE(range, generations)".into());
165 }
166 let nums = collect_numbers(&[args[0].clone()])?;
167 let generations = match &args[1] {
168 Value::Number(n) => *n as usize,
169 _ => return Err("EVOLVE: generations must be a number".into()),
170 };
171 if nums.is_empty() {
172 return Err("EVOLVE: no data in range".into());
173 }
174
175 let mut best = nums[0];
177 for &n in &nums {
178 if n > best { best = n; }
179 }
180 let mut current = best;
182 for _ in 0..generations {
183 let mutation = (current * 0.01).max(0.001);
184 let candidate = current + mutation;
185 if candidate > current {
186 current = candidate;
187 }
188 }
189 Ok(Value::Number(current))
190}
191
192fn builtin_best(args: &[Value]) -> Result<Value, String> {
194 let nums = collect_numbers(args)?;
195 if nums.is_empty() {
196 return Err("BEST: no data".into());
197 }
198 Ok(Value::Number(nums.iter().cloned().fold(f64::NEG_INFINITY, f64::max)))
199}
200
201fn builtin_species(args: &[Value]) -> Result<Value, String> {
203 let nums = collect_numbers(args)?;
204 if nums.is_empty() {
205 return Ok(Value::Number(0.0));
206 }
207 let mut rounded: Vec<i64> = nums.iter().map(|n| (n * 1000.0).round() as i64).collect();
209 rounded.sort();
210 rounded.dedup();
211 Ok(Value::Number(rounded.len() as f64))
212}
213
214fn builtin_exhaustive(args: &[Value]) -> Result<Value, String> {
217 let nums = collect_numbers(args)?;
218 if nums.is_empty() {
219 return Ok(Value::Number(0.0));
220 }
221 let mut sorted = nums.clone();
223 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
224 sorted.dedup();
225 let combinations = 2usize.pow(sorted.len() as u32);
226 Ok(Value::Number(combinations.min(1_000_000) as f64))
227}
228
229fn builtin_entropy(args: &[Value]) -> Result<Value, String> {
231 let nums = collect_numbers(args)?;
232 if nums.is_empty() {
233 return Ok(Value::Number(0.0));
234 }
235 let total = nums.len() as f64;
237 let mut counts: std::collections::HashMap<i64, usize> = std::collections::HashMap::new();
238 for &n in &nums {
239 let bin = (n * 100.0).round() as i64;
240 *counts.entry(bin).or_insert(0) += 1;
241 }
242 let entropy: f64 = counts.values()
243 .map(|&c| {
244 let p = c as f64 / total;
245 -p * p.log2()
246 })
247 .sum();
248 Ok(Value::Number(entropy))
249}
250
251fn builtin_pareto(args: &[Value]) -> Result<Value, String> {
254 let nums = collect_numbers(args)?;
255 if nums.is_empty() {
256 return Ok(Value::Number(0.0));
257 }
258 let max_val = nums.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
260 let avg: f64 = nums.iter().sum::<f64>() / nums.len() as f64;
261 let threshold = avg + (max_val - avg) * 0.5;
262 let count = nums.iter().filter(|&&n| n >= threshold).count();
263 Ok(Value::Number(count as f64))
264}
265
266fn builtin_correlate(args: &[Value]) -> Result<Value, String> {
268 if args.len() != 2 {
269 return Err("CORRELATE takes exactly 2 arguments".into());
270 }
271 let x = collect_numbers(&[args[0].clone()])?;
272 let y = collect_numbers(&[args[1].clone()])?;
273 if x.len() != y.len() || x.is_empty() {
274 return Err("CORRELATE: ranges must have equal non-zero length".into());
275 }
276 let n = x.len() as f64;
277 let mean_x: f64 = x.iter().sum::<f64>() / n;
278 let mean_y: f64 = y.iter().sum::<f64>() / n;
279
280 let mut cov = 0.0;
281 let mut var_x = 0.0;
282 let mut var_y = 0.0;
283 for i in 0..x.len() {
284 let dx = x[i] - mean_x;
285 let dy = y[i] - mean_y;
286 cov += dx * dy;
287 var_x += dx * dx;
288 var_y += dy * dy;
289 }
290 let denom = (var_x * var_y).sqrt();
291 if denom == 0.0 {
292 return Ok(Value::Number(0.0));
293 }
294 Ok(Value::Number(cov / denom))
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn sum_basic() {
303 let result = builtin_sum(&[Value::Number(1.0), Value::Number(2.0), Value::Number(3.0)]).unwrap();
304 assert_eq!(result, Value::Number(6.0));
305 }
306
307 #[test]
308 fn sum_with_array() {
309 let result = builtin_sum(&[Value::Array(vec![
310 Value::Number(10.0), Value::Number(20.0),
311 ])]).unwrap();
312 assert_eq!(result, Value::Number(30.0));
313 }
314
315 #[test]
316 fn avg_basic() {
317 let result = builtin_avg(&[Value::Number(2.0), Value::Number(4.0)]).unwrap();
318 assert_eq!(result, Value::Number(3.0));
319 }
320
321 #[test]
322 fn count_values() {
323 let result = builtin_count(&[Value::Number(1.0), Value::Text("skip".into()), Value::Number(3.0)]).unwrap();
324 assert_eq!(result, Value::Number(2.0));
325 }
326
327 #[test]
328 fn entropy_uniform() {
329 let vals: Vec<Value> = (0..10).map(|i| Value::Number(i as f64)).collect();
330 let result = builtin_entropy(&vals).unwrap();
331 assert!(matches!(result, Value::Number(n) if n > 2.0));
333 }
334
335 #[test]
336 fn species_count() {
337 let vals = vec![Value::Number(1.0), Value::Number(2.0), Value::Number(1.0), Value::Number(3.0)];
338 let result = builtin_species(&vals).unwrap();
339 assert_eq!(result, Value::Number(3.0));
340 }
341
342 #[test]
343 fn best_value() {
344 let vals = vec![Value::Number(1.0), Value::Number(5.0), Value::Number(3.0)];
345 let result = builtin_best(&vals).unwrap();
346 assert_eq!(result, Value::Number(5.0));
347 }
348
349 #[test]
350 fn correlate_perfect() {
351 let x: Vec<Value> = (1..=5).map(|i| Value::Number(i as f64)).collect();
352 let y: Vec<Value> = (1..=5).map(|i| Value::Number((i * 2) as f64)).collect();
353 let result = builtin_correlate(&[Value::Array(x), Value::Array(y)]).unwrap();
354 if let Value::Number(r) = result {
355 assert!((r - 1.0).abs() < 1e-9, "Expected 1.0, got {}", r);
356 }
357 }
358
359 #[test]
360 fn abs_positive() {
361 assert_eq!(builtin_abs(&[Value::Number(-5.0)]).unwrap(), Value::Number(5.0));
362 }
363
364 #[test]
365 fn if_true() {
366 let result = builtin_if(&[Value::Boolean(true), Value::Number(1.0), Value::Number(2.0)]).unwrap();
367 assert_eq!(result, Value::Number(1.0));
368 }
369
370 #[test]
371 fn evolve_returns_number() {
372 let result = builtin_evolve(&[
373 Value::Array(vec![Value::Number(1.0), Value::Number(5.0), Value::Number(3.0)]),
374 Value::Number(10.0),
375 ]).unwrap();
376 assert!(matches!(result, Value::Number(_)));
377 }
378
379 #[test]
380 fn pareto_count() {
381 let vals: Vec<Value> = (1..=10).map(|i| Value::Number(i as f64)).collect();
382 let result = builtin_pareto(&vals).unwrap();
383 if let Value::Number(n) = result {
384 assert!(n > 0.0);
385 }
386 }
387}