1use crate::interpreter::builtins::CallArgs;
6use crate::interpreter::environment::Environment;
7use crate::interpreter::value::*;
8use crate::interpreter::BuiltinContext;
9use crate::parser::ast::{Arg, Expr, Param};
10use minir_macros::{builtin, interpreter_builtin};
11
12fn hsv_to_rgb(h: f64, s: f64, v: f64) -> (f64, f64, f64) {
16 if s <= 0.0 {
17 return (v, v, v);
18 }
19 let h = h - h.floor();
21 let h6 = h * 6.0;
22 let sector = h6.floor();
23 let f = h6 - sector;
24 let p = v * (1.0 - s);
25 let q = v * (1.0 - s * f);
26 let t = v * (1.0 - s * (1.0 - f));
27
28 let sector_i = sector as i32;
29 match sector_i {
30 0 => (v, t, p),
31 1 => (q, v, p),
32 2 => (p, v, t),
33 3 => (p, q, v),
34 4 => (t, p, v),
35 _ => (v, p, q), }
37}
38
39fn hcl_to_rgb(h_deg: f64, c: f64, l: f64) -> (f64, f64, f64) {
42 let h_rad = h_deg.to_radians();
44 let lab_a = c * h_rad.cos();
45 let lab_b = c * h_rad.sin();
46
47 const XN: f64 = 0.950_470;
49 const YN: f64 = 1.0;
50 const ZN: f64 = 1.088_830;
51 const KAPPA: f64 = 903.296_3; const EPSILON: f64 = 0.008_856; let fy = (l + 16.0) / 116.0;
55 let fx = fy + lab_a / 500.0;
56 let fz = fy - lab_b / 200.0;
57
58 let x = if fx.powi(3) > EPSILON {
59 XN * fx.powi(3)
60 } else {
61 XN * (116.0 * fx - 16.0) / KAPPA
62 };
63 let y = if l > KAPPA * EPSILON {
64 YN * fy.powi(3)
65 } else {
66 YN * l / KAPPA
67 };
68 let z = if fz.powi(3) > EPSILON {
69 ZN * fz.powi(3)
70 } else {
71 ZN * (116.0 * fz - 16.0) / KAPPA
72 };
73
74 let rl = 3.240_479_f64 * x - 1.537_150 * y - 0.498_535 * z;
76 let gl = -0.969_256_f64 * x + 1.875_992 * y + 0.041_556 * z;
77 let bl = 0.055_648_f64 * x - 0.204_043 * y + 1.057_311 * z;
78
79 fn gamma(u: f64) -> f64 {
81 if u <= 0.003_130_8 {
82 12.92 * u
83 } else {
84 1.055 * u.powf(1.0 / 2.4) - 0.055
85 }
86 }
87
88 (
89 gamma(rl).clamp(0.0, 1.0),
90 gamma(gl).clamp(0.0, 1.0),
91 gamma(bl).clamp(0.0, 1.0),
92 )
93}
94
95fn parse_hex_color(s: &str) -> Result<(f64, f64, f64, f64), RError> {
97 let s = s.trim_start_matches('#');
98 let (r, g, b, a) = match s.len() {
99 6 => {
100 let r = u8::from_str_radix(&s[0..2], 16);
101 let g = u8::from_str_radix(&s[2..4], 16);
102 let b = u8::from_str_radix(&s[4..6], 16);
103 match (r, g, b) {
104 (Ok(r), Ok(g), Ok(b)) => (r, g, b, 255u8),
105 _ => {
106 return Err(RError::new(
107 RErrorKind::Argument,
108 format!("invalid hex color: #{s}"),
109 ))
110 }
111 }
112 }
113 8 => {
114 let r = u8::from_str_radix(&s[0..2], 16);
115 let g = u8::from_str_radix(&s[2..4], 16);
116 let b = u8::from_str_radix(&s[4..6], 16);
117 let a = u8::from_str_radix(&s[6..8], 16);
118 match (r, g, b, a) {
119 (Ok(r), Ok(g), Ok(b), Ok(a)) => (r, g, b, a),
120 _ => {
121 return Err(RError::new(
122 RErrorKind::Argument,
123 format!("invalid hex color: #{s}"),
124 ))
125 }
126 }
127 }
128 _ => {
129 return Err(RError::new(
130 RErrorKind::Argument,
131 format!("invalid hex color: #{s} (expected 6 or 8 hex digits)"),
132 ))
133 }
134 };
135 Ok((
136 f64::from(r) / 255.0,
137 f64::from(g) / 255.0,
138 f64::from(b) / 255.0,
139 f64::from(a) / 255.0,
140 ))
141}
142
143fn rgb_to_hex(r: f64, g: f64, b: f64, a: f64) -> String {
146 let ri = (r.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
147 let gi = (g.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
148 let bi = (b.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
149 if (a - 1.0).abs() < 1e-10 {
150 format!("#{:02X}{:02X}{:02X}", ri, gi, bi)
151 } else {
152 let ai = (a.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
153 format!("#{:02X}{:02X}{:02X}{:02X}", ri, gi, bi, ai)
154 }
155}
156
157fn double_scalar(val: Option<&RValue>, default: f64) -> f64 {
163 match val {
164 Some(RValue::Null) | None => default,
165 Some(v) => v
166 .as_vector()
167 .and_then(|v| v.as_double_scalar())
168 .unwrap_or(default),
169 }
170}
171
172fn extract_n(args: &CallArgs) -> Result<usize, RError> {
174 let n_val = args.value("n", 0).ok_or_else(|| {
175 RError::new(
176 RErrorKind::Argument,
177 "argument 'n' is missing, with no default".to_string(),
178 )
179 })?;
180 let n = n_val
181 .as_vector()
182 .and_then(|v| v.as_integer_scalar())
183 .ok_or_else(|| {
184 RError::new(
185 RErrorKind::Argument,
186 "'n' must be a positive integer".to_string(),
187 )
188 })?;
189 if n < 0 {
190 return Err(RError::new(
191 RErrorKind::Argument,
192 "'n' must be a non-negative integer".to_string(),
193 ));
194 }
195 Ok(n as usize)
196}
197
198#[builtin(namespace = "grDevices")]
213fn builtin_hsv(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
214 let ca = CallArgs::new(args, named);
215
216 let h_vec = extract_doubles(&ca, "h", 0, 0.0);
218 let s_vec = extract_doubles(&ca, "s", 1, 1.0);
219 let v_vec = extract_doubles(&ca, "v", 2, 1.0);
220 let alpha_vec = extract_doubles(&ca, "alpha", 3, 1.0);
221
222 let n = h_vec
223 .len()
224 .max(s_vec.len())
225 .max(v_vec.len())
226 .max(alpha_vec.len());
227
228 let mut result = Vec::with_capacity(n);
229 for i in 0..n {
230 let h = h_vec[i % h_vec.len()];
231 let s = s_vec[i % s_vec.len()];
232 let v = v_vec[i % v_vec.len()];
233 let a = alpha_vec[i % alpha_vec.len()];
234 let (r, g, b) = hsv_to_rgb(h, s, v);
235 result.push(Some(rgb_to_hex(r, g, b, a)));
236 }
237
238 Ok(RValue::vec(Vector::Character(result.into())))
239}
240
241fn extract_doubles(ca: &CallArgs, name: &str, pos: usize, default: f64) -> Vec<f64> {
243 match ca.value(name, pos) {
244 Some(RValue::Vector(rv)) => {
245 let doubles = rv.to_doubles();
246 if doubles.is_empty() {
247 vec![default]
248 } else {
249 doubles.into_iter().map(|d| d.unwrap_or(default)).collect()
250 }
251 }
252 Some(RValue::Null) | None => vec![default],
253 _ => vec![default],
254 }
255}
256
257#[builtin(namespace = "grDevices")]
272fn builtin_hcl(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
273 let ca = CallArgs::new(args, named);
274
275 let h_vec = extract_doubles(&ca, "h", 0, 0.0);
276 let c_vec = extract_doubles(&ca, "c", 1, 35.0);
277 let l_vec = extract_doubles(&ca, "l", 2, 85.0);
278 let alpha_vec = extract_doubles(&ca, "alpha", 3, 1.0);
279
280 let n = h_vec
281 .len()
282 .max(c_vec.len())
283 .max(l_vec.len())
284 .max(alpha_vec.len());
285
286 let mut result = Vec::with_capacity(n);
287 for i in 0..n {
288 let h = h_vec[i % h_vec.len()];
289 let c = c_vec[i % c_vec.len()];
290 let l = l_vec[i % l_vec.len()];
291 let a = alpha_vec[i % alpha_vec.len()];
292 let (r, g, b) = hcl_to_rgb(h, c, l);
293 result.push(Some(rgb_to_hex(r, g, b, a)));
294 }
295
296 Ok(RValue::vec(Vector::Character(result.into())))
297}
298
299#[builtin(namespace = "grDevices", min_args = 1)]
313fn builtin_rainbow(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
314 let ca = CallArgs::new(args, named);
315 let n = extract_n(&ca)?;
316
317 if n == 0 {
318 return Ok(RValue::vec(Vector::Character(
319 Vec::<Option<String>>::new().into(),
320 )));
321 }
322
323 let s = double_scalar(ca.value("s", 1), 1.0);
324 let v = double_scalar(ca.value("v", 2), 1.0);
325 let start = double_scalar(ca.value("start", 3), 0.0);
326 let default_end = if n > 1 {
327 (n as f64 - 1.0) / n as f64
328 } else {
329 1.0
330 };
331 let end = double_scalar(ca.value("end", 4), default_end);
332 let alpha = double_scalar(ca.value("alpha", 5), 1.0);
333
334 let mut result = Vec::with_capacity(n);
335 for i in 0..n {
336 let h = if n == 1 {
337 start
338 } else {
339 start + (end - start) * (i as f64) / (n as f64 - 1.0)
340 };
341 let (r, g, b) = hsv_to_rgb(h, s, v);
342 result.push(Some(rgb_to_hex(r, g, b, alpha)));
343 }
344
345 Ok(RValue::vec(Vector::Character(result.into())))
346}
347
348#[builtin(name = "heat.colors", namespace = "grDevices", min_args = 1)]
358fn builtin_heat_colors(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
359 let ca = CallArgs::new(args, named);
360 let n = extract_n(&ca)?;
361
362 if n == 0 {
363 return Ok(RValue::vec(Vector::Character(
364 Vec::<Option<String>>::new().into(),
365 )));
366 }
367
368 let alpha = double_scalar(ca.value("alpha", 1), 1.0);
369
370 let j = n / 4;
388 let nrainbow = n - j;
389
390 let mut result = Vec::with_capacity(n);
391
392 for i in 0..nrainbow {
394 let h = if nrainbow == 1 {
395 0.0
396 } else {
397 (1.0 / 6.0) * (i as f64) / (nrainbow as f64 - 1.0)
398 };
399 let (r, g, b) = hsv_to_rgb(h, 1.0, 1.0);
400 result.push(Some(rgb_to_hex(r, g, b, alpha)));
401 }
402
403 if j > 0 {
405 for i in 0..j {
406 let s = if j == 1 {
407 0.5
408 } else {
409 let start_s = 1.0 - 1.0 / (2.0 * j as f64);
410 let end_s = 1.0 / (2.0 * j as f64);
411 start_s + (end_s - start_s) * (i as f64) / (j as f64 - 1.0)
412 };
413 let (r, g, b) = hsv_to_rgb(1.0 / 6.0, s, 1.0);
414 result.push(Some(rgb_to_hex(r, g, b, alpha)));
415 }
416 }
417
418 Ok(RValue::vec(Vector::Character(result.into())))
419}
420
421#[builtin(name = "terrain.colors", namespace = "grDevices", min_args = 1)]
431fn builtin_terrain_colors(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
432 let ca = CallArgs::new(args, named);
433 let n = extract_n(&ca)?;
434
435 if n == 0 {
436 return Ok(RValue::vec(Vector::Character(
437 Vec::<Option<String>>::new().into(),
438 )));
439 }
440
441 let alpha = double_scalar(ca.value("alpha", 1), 1.0);
442
443 let j = n / 3;
451 let k = n - 2 * j;
452
453 let mut result = Vec::with_capacity(n);
454
455 for i in 0..j {
457 let t = if j <= 1 {
458 0.0
459 } else {
460 i as f64 / (j as f64 - 1.0)
461 };
462 let h = 2.0 / 6.0 + (1.0 / 6.0 - 2.0 / 6.0) * t;
463 let v = 0.65 + (0.9 - 0.65) * t;
464 let (r, g, b) = hsv_to_rgb(h, 1.0, v);
465 result.push(Some(rgb_to_hex(r, g, b, alpha)));
466 }
467
468 for i in 0..j {
470 let t = if j <= 1 {
471 0.0
472 } else {
473 i as f64 / (j as f64 - 1.0)
474 };
475 let s = 1.0 - t;
476 let v = 0.9 + (0.95 - 0.9) * t;
477 let (r, g, b) = hsv_to_rgb(1.0 / 6.0, s, v);
478 result.push(Some(rgb_to_hex(r, g, b, alpha)));
479 }
480
481 for i in 0..k {
483 let t = if k <= 1 {
484 0.0
485 } else {
486 i as f64 / (k as f64 - 1.0)
487 };
488 let grey = 0.95 + 0.05 * t;
489 result.push(Some(rgb_to_hex(grey, grey, grey, alpha)));
490 }
491
492 Ok(RValue::vec(Vector::Character(result.into())))
493}
494
495#[builtin(name = "topo.colors", namespace = "grDevices", min_args = 1)]
505fn builtin_topo_colors(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
506 let ca = CallArgs::new(args, named);
507 let n = extract_n(&ca)?;
508
509 if n == 0 {
510 return Ok(RValue::vec(Vector::Character(
511 Vec::<Option<String>>::new().into(),
512 )));
513 }
514
515 let alpha = double_scalar(ca.value("alpha", 1), 1.0);
516
517 let anchors: [(f64, f64, f64); 5] = [
524 (0.55, 0.0, 1.0), (0.55, 1.0, 1.0), (0.43, 1.0, 1.0), (0.25, 1.0, 1.0), (0.17, 0.0, 1.0), ];
530
531 let mut result = Vec::with_capacity(n);
532 for i in 0..n {
533 let t = if n == 1 {
534 0.0
535 } else {
536 i as f64 / (n as f64 - 1.0)
537 };
538 let pos = t * (anchors.len() - 1) as f64;
539 let idx = (pos.floor() as usize).min(anchors.len() - 2);
540 let frac = pos - idx as f64;
541
542 let (h1, s1, v1) = anchors[idx];
543 let (h2, s2, v2) = anchors[idx + 1];
544 let h = h1 + (h2 - h1) * frac;
545 let s = s1 + (s2 - s1) * frac;
546 let v = v1 + (v2 - v1) * frac;
547
548 let (r, g, b) = hsv_to_rgb(h, s, v);
549 result.push(Some(rgb_to_hex(r, g, b, alpha)));
550 }
551
552 Ok(RValue::vec(Vector::Character(result.into())))
553}
554
555#[builtin(name = "cm.colors", namespace = "grDevices", min_args = 1)]
565fn builtin_cm_colors(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
566 let ca = CallArgs::new(args, named);
567 let n = extract_n(&ca)?;
568
569 if n == 0 {
570 return Ok(RValue::vec(Vector::Character(
571 Vec::<Option<String>>::new().into(),
572 )));
573 }
574
575 let alpha = double_scalar(ca.value("alpha", 1), 1.0);
576
577 let mut result = Vec::with_capacity(n);
582 for i in 0..n {
583 let t = if n == 1 {
584 0.5
585 } else {
586 i as f64 / (n as f64 - 1.0)
587 };
588 let (r, g, b) = if t < 0.5 {
589 let s = t * 2.0; (s, 1.0, 1.0)
592 } else {
593 let s = (t - 0.5) * 2.0; (1.0, 1.0 - s, 1.0)
596 };
597 result.push(Some(rgb_to_hex(r, g, b, alpha)));
598 }
599
600 Ok(RValue::vec(Vector::Character(result.into())))
601}
602
603#[builtin(name = "gray.colors", namespace = "grDevices", min_args = 1, names = ["grey.colors"])]
616fn builtin_gray_colors(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
617 let ca = CallArgs::new(args, named);
618 let n = extract_n(&ca)?;
619
620 if n == 0 {
621 return Ok(RValue::vec(Vector::Character(
622 Vec::<Option<String>>::new().into(),
623 )));
624 }
625
626 let start = double_scalar(ca.value("start", 1), 0.3);
627 let end = double_scalar(ca.value("end", 2), 0.9);
628 let gamma = double_scalar(ca.value("gamma", 3), 2.2);
629 let alpha = double_scalar(ca.value("alpha", 4), 1.0);
630
631 let mut result = Vec::with_capacity(n);
632 for i in 0..n {
633 let t = if n == 1 {
634 0.0
635 } else {
636 i as f64 / (n as f64 - 1.0)
637 };
638 let grey = (start + (end - start) * t).powf(1.0 / gamma);
640 let grey = grey.clamp(0.0, 1.0);
641 result.push(Some(rgb_to_hex(grey, grey, grey, alpha)));
642 }
643
644 Ok(RValue::vec(Vector::Character(result.into())))
645}
646
647#[builtin(name = "grey", namespace = "grDevices", min_args = 1, names = ["gray"])]
659fn builtin_grey(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
660 let ca = CallArgs::new(args, named);
661 let levels = extract_doubles(&ca, "level", 0, 0.0);
662 let alpha_val = ca.value("alpha", 1);
663 let alpha = match alpha_val {
664 Some(RValue::Null) | None => None,
665 Some(v) => Some(
666 v.as_vector()
667 .and_then(|rv| rv.as_double_scalar())
668 .unwrap_or(1.0),
669 ),
670 };
671
672 let mut result = Vec::with_capacity(levels.len());
673 for level in &levels {
674 let g = level.clamp(0.0, 1.0);
675 let a = alpha.unwrap_or(1.0);
676 result.push(Some(rgb_to_hex(g, g, g, a)));
677 }
678
679 Ok(RValue::vec(Vector::Character(result.into())))
680}
681
682#[interpreter_builtin(name = "colorRampPalette", namespace = "grDevices", min_args = 1)]
694fn interp_color_ramp_palette(
695 args: &[RValue],
696 named: &[(String, RValue)],
697 context: &BuiltinContext,
698) -> Result<RValue, RError> {
699 let ca = CallArgs::new(args, named);
700
701 let colors_val = ca.value("colors", 0).ok_or_else(|| {
703 RError::new(
704 RErrorKind::Argument,
705 "argument 'colors' is missing".to_string(),
706 )
707 })?;
708
709 let colors_vec = match colors_val {
710 RValue::Vector(rv) => match &rv.inner {
711 Vector::Character(cv) => cv.iter().filter_map(|s| s.clone()).collect::<Vec<String>>(),
712 _ => {
713 return Err(RError::new(
714 RErrorKind::Argument,
715 "'colors' must be a character vector of color values".to_string(),
716 ))
717 }
718 },
719 _ => {
720 return Err(RError::new(
721 RErrorKind::Argument,
722 "'colors' must be a character vector of color values".to_string(),
723 ))
724 }
725 };
726
727 if colors_vec.len() < 2 {
728 return Err(RError::new(
729 RErrorKind::Argument,
730 "colorRampPalette requires at least 2 colors".to_string(),
731 ));
732 }
733
734 let env = context.env();
736 let closure_env = Environment::new_child(env);
737 closure_env.set(
738 ".CRP_COLORS".to_string(),
739 RValue::vec(Vector::Character(
740 colors_vec
741 .into_iter()
742 .map(Some)
743 .collect::<Vec<Option<String>>>()
744 .into(),
745 )),
746 );
747
748 let body = Expr::Call {
750 func: Box::new(Expr::Symbol(".colorRampInterp".to_string())),
751 span: None,
752 args: vec![
753 Arg {
754 name: None,
755 value: Some(Expr::Symbol(".CRP_COLORS".to_string())),
756 },
757 Arg {
758 name: None,
759 value: Some(Expr::Symbol("n".to_string())),
760 },
761 ],
762 };
763
764 let params = vec![Param {
765 name: "n".to_string(),
766 default: None,
767 is_dots: false,
768 }];
769
770 Ok(RValue::Function(RFunction::Closure {
771 params,
772 body,
773 env: closure_env,
774 }))
775}
776
777#[builtin(name = ".colorRampInterp", namespace = "grDevices", min_args = 2)]
785fn builtin_color_ramp_interp(
786 args: &[RValue],
787 _named: &[(String, RValue)],
788) -> Result<RValue, RError> {
789 let colors_val = args.first().ok_or_else(|| {
790 RError::new(
791 RErrorKind::Argument,
792 "missing 'colors' argument".to_string(),
793 )
794 })?;
795 let n_val = args
796 .get(1)
797 .ok_or_else(|| RError::new(RErrorKind::Argument, "missing 'n' argument".to_string()))?;
798
799 let colors: Vec<String> = match colors_val {
800 RValue::Vector(rv) => match &rv.inner {
801 Vector::Character(cv) => cv.iter().filter_map(|s| s.clone()).collect(),
802 _ => {
803 return Err(RError::new(
804 RErrorKind::Argument,
805 "colors must be a character vector".to_string(),
806 ))
807 }
808 },
809 _ => {
810 return Err(RError::new(
811 RErrorKind::Argument,
812 "colors must be a character vector".to_string(),
813 ))
814 }
815 };
816
817 let n = n_val
818 .as_vector()
819 .and_then(|v| v.as_integer_scalar())
820 .ok_or_else(|| {
821 RError::new(
822 RErrorKind::Argument,
823 "'n' must be a positive integer".to_string(),
824 )
825 })?;
826
827 if n < 0 {
828 return Err(RError::new(
829 RErrorKind::Argument,
830 "'n' must be a non-negative integer".to_string(),
831 ));
832 }
833 let n = n as usize;
834
835 if n == 0 {
836 return Ok(RValue::vec(Vector::Character(
837 Vec::<Option<String>>::new().into(),
838 )));
839 }
840
841 let mut anchors = Vec::with_capacity(colors.len());
843 for c in &colors {
844 anchors.push(parse_hex_color(c)?);
845 }
846
847 let mut result = Vec::with_capacity(n);
848 for i in 0..n {
849 let t = if n == 1 {
850 0.0
851 } else {
852 i as f64 / (n as f64 - 1.0)
853 };
854 let pos = t * (anchors.len() - 1) as f64;
855 let idx = (pos.floor() as usize).min(anchors.len() - 2);
856 let frac = pos - idx as f64;
857
858 let (r1, g1, b1, a1) = anchors[idx];
859 let (r2, g2, b2, a2) = anchors[idx + 1];
860 let r = r1 + (r2 - r1) * frac;
861 let g = g1 + (g2 - g1) * frac;
862 let b = b1 + (b2 - b1) * frac;
863 let a = a1 + (a2 - a1) * frac;
864
865 result.push(Some(rgb_to_hex(r, g, b, a)));
866 }
867
868 Ok(RValue::vec(Vector::Character(result.into())))
869}
870
871