doge_runtime/stdlib/
nerd.rs1use crate::error::{DogeError, DogeResult};
5use crate::value::Value;
6
7fn numeric(fname: &str, v: &Value) -> DogeResult<f64> {
9 match v {
10 Value::Int(n) => Ok(*n as f64),
11 Value::Float(f) => Ok(*f),
12 _ => Err(DogeError::type_error(format!(
13 "nerd.{fname} needs a number, got {}",
14 v.describe()
15 ))),
16 }
17}
18
19fn float_to_int(f: f64) -> DogeResult {
22 if f.is_finite() && f >= i64::MIN as f64 && f < i64::MAX as f64 {
23 Ok(Value::Int(f as i64))
24 } else {
25 Err(DogeError::overflow("the result is outside the Int range"))
26 }
27}
28
29fn round_like(fname: &str, x: &Value, op: impl Fn(f64) -> f64) -> DogeResult {
32 match x {
33 Value::Int(n) => Ok(Value::Int(*n)),
34 Value::Float(f) => float_to_int(op(*f)),
35 _ => Err(DogeError::type_error(format!(
36 "nerd.{fname} needs a number, got {}",
37 x.describe()
38 ))),
39 }
40}
41
42pub fn nerd_abs(x: &Value) -> DogeResult {
45 match x {
46 Value::Int(n) => {
47 if *n < 0 {
48 n.checked_neg()
49 .map(Value::Int)
50 .ok_or_else(|| DogeError::overflow("the result is outside the Int range"))
51 } else {
52 Ok(Value::Int(*n))
53 }
54 }
55 Value::Float(f) => Ok(Value::Float(f.abs())),
56 _ => Err(DogeError::type_error(format!(
57 "nerd.abs needs a number, got {}",
58 x.describe()
59 ))),
60 }
61}
62
63pub fn nerd_sqrt(x: &Value) -> DogeResult {
65 let f = numeric("sqrt", x)?;
66 if f < 0.0 {
67 return Err(DogeError::value_error("cannot sqrt a negative number"));
68 }
69 Ok(Value::Float(f.sqrt()))
70}
71
72pub fn nerd_floor(x: &Value) -> DogeResult {
74 round_like("floor", x, f64::floor)
75}
76
77pub fn nerd_ceil(x: &Value) -> DogeResult {
79 round_like("ceil", x, f64::ceil)
80}
81
82pub fn nerd_round(x: &Value) -> DogeResult {
84 round_like("round", x, f64::round)
85}
86
87pub fn nerd_min(a: &Value, b: &Value) -> DogeResult {
90 let (x, y) = (numeric("min", a)?, numeric("min", b)?);
91 Ok(if y < x { b.clone() } else { a.clone() })
92}
93
94pub fn nerd_max(a: &Value, b: &Value) -> DogeResult {
97 let (x, y) = (numeric("max", a)?, numeric("max", b)?);
98 Ok(if y > x { b.clone() } else { a.clone() })
99}
100
101pub fn nerd_pow(a: &Value, b: &Value) -> DogeResult {
104 match (a, b) {
105 (Value::Int(base), Value::Int(exp)) if *exp >= 0 => {
106 let exp = u32::try_from(*exp)
107 .map_err(|_| DogeError::overflow("the result is outside the Int range"))?;
108 base.checked_pow(exp)
109 .map(Value::Int)
110 .ok_or_else(|| DogeError::overflow("the result is outside the Int range"))
111 }
112 _ => {
113 let (x, y) = (numeric("pow", a)?, numeric("pow", b)?);
114 Ok(Value::Float(x.powf(y)))
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::error::ErrorKind;
123
124 #[test]
125 fn abs_keeps_type_and_overflows_catchably() {
126 assert!(matches!(nerd_abs(&Value::Int(-5)).unwrap(), Value::Int(5)));
127 assert!(matches!(nerd_abs(&Value::Float(-2.5)).unwrap(), Value::Float(f) if f == 2.5));
128 assert_eq!(
129 nerd_abs(&Value::Int(i64::MIN)).unwrap_err().kind,
130 ErrorKind::Overflow
131 );
132 assert_eq!(
133 nerd_abs(&Value::str("x")).unwrap_err().kind,
134 ErrorKind::TypeError
135 );
136 }
137
138 #[test]
139 fn sqrt_is_float_and_rejects_negatives() {
140 assert!(matches!(nerd_sqrt(&Value::Int(16)).unwrap(), Value::Float(f) if f == 4.0));
141 assert_eq!(
142 nerd_sqrt(&Value::Int(-1)).unwrap_err().kind,
143 ErrorKind::ValueError
144 );
145 }
146
147 #[test]
148 fn floor_ceil_round_yield_ints() {
149 assert!(matches!(
150 nerd_floor(&Value::Float(2.9)).unwrap(),
151 Value::Int(2)
152 ));
153 assert!(matches!(
154 nerd_ceil(&Value::Float(2.1)).unwrap(),
155 Value::Int(3)
156 ));
157 assert!(matches!(
158 nerd_round(&Value::Float(2.5)).unwrap(),
159 Value::Int(3)
160 ));
161 assert!(matches!(nerd_floor(&Value::Int(7)).unwrap(), Value::Int(7)));
162 }
163
164 #[test]
165 fn round_out_of_range_is_overflow() {
166 assert_eq!(
167 nerd_floor(&Value::Float(1e300)).unwrap_err().kind,
168 ErrorKind::Overflow
169 );
170 }
171
172 #[test]
173 fn min_max_keep_the_winners_type() {
174 assert!(
175 matches!(nerd_min(&Value::Int(3), &Value::Float(2.5)).unwrap(), Value::Float(f) if f == 2.5)
176 );
177 assert!(matches!(
178 nerd_max(&Value::Int(3), &Value::Float(2.5)).unwrap(),
179 Value::Int(3)
180 ));
181 assert!(matches!(
183 nerd_min(&Value::Int(4), &Value::Float(4.0)).unwrap(),
184 Value::Int(4)
185 ));
186 }
187
188 #[test]
189 fn pow_is_int_for_int_base_and_overflows_catchably() {
190 assert!(matches!(
191 nerd_pow(&Value::Int(2), &Value::Int(10)).unwrap(),
192 Value::Int(1024)
193 ));
194 assert!(matches!(
195 nerd_pow(&Value::Int(2), &Value::Float(0.5)).unwrap(),
196 Value::Float(_)
197 ));
198 assert_eq!(
199 nerd_pow(&Value::Int(10), &Value::Int(100))
200 .unwrap_err()
201 .kind,
202 ErrorKind::Overflow
203 );
204 }
205}