Skip to main content

tancore/
arithmetic.rs

1use rust_decimal_macros::dec;
2
3use tan::{
4    context::Context,
5    error::Error,
6    expr::{annotate_type, Expr},
7    util::{
8        args::{unpack_float_arg, unpack_int_arg},
9        module_util::require_module,
10    },
11};
12
13// #todo Split into multiple files, int, float, time, date, etc...
14
15// #todo rename rust implementations to {type}_{func}.
16
17// #insight
18// Named `arithmetic` as those operators can apply to non-numbers, e.g. Time, Date
19
20// #todo use AsRef, to avoid Annotated!
21// #todo use macros to generate specializations for generic versions.
22// #todo deduct from type if the function can affect the env or have any other side-effects.
23
24// #todo ranges for arguments is too detailed, most probably we do not have the ranges!
25// #todo support invalid_arguments without range.
26
27// #fixme (+ 1.2 1.4 1.5) does not work, falls back to Int (more than 2 arguments)
28
29// #todo autogen with a macro!
30pub fn add_int(args: &[Expr]) -> Result<Expr, Error> {
31    let mut xs = Vec::new();
32
33    for arg in args {
34        let Some(n) = arg.as_int() else {
35            // #todo we could return the argument position here and enrich the error upstream.
36            // #todo hmm, the error is too precise here, do we really need the annotations?
37            return Err(Error::invalid_arguments(
38                &format!("{arg} is not an Int"),
39                arg.range(),
40            ));
41        };
42        xs.push(n);
43    }
44
45    let sum = add_int_impl(xs);
46
47    Ok(Expr::Int(sum))
48}
49
50// #insight example of splitting wrapper from impl.
51fn add_int_impl(xs: Vec<i64>) -> i64 {
52    xs.iter().sum()
53}
54
55pub fn add_float(args: &[Expr]) -> Result<Expr, Error> {
56    let mut sum = 0.0;
57
58    for arg in args {
59        let Some(n) = arg.as_float() else {
60            return Err(Error::invalid_arguments(
61                &format!("{arg} is not a Float"),
62                arg.range(),
63            ));
64        };
65        sum += n;
66    }
67
68    Ok(Expr::Float(sum))
69}
70
71// #todo Move to dec.rs
72pub fn add_dec(args: &[Expr]) -> Result<Expr, Error> {
73    let mut sum = dec!(0.0);
74
75    for arg in args {
76        let Some(n) = arg.as_decimal() else {
77            return Err(Error::invalid_arguments(
78                &format!("{arg} is not a Dec"),
79                arg.range(),
80            ));
81        };
82        sum += n;
83    }
84
85    Ok(Expr::Dec(sum))
86}
87
88// #todo keep separate, optimized version with just 2 arguments!
89// #todo should support variable args.
90// #todo should return the error without range and range should be added by caller.
91// pub fn sub_int(args: &[Expr]) -> Result<Expr, Error> {
92//     // #todo support multiple arguments.
93//     let [a, b] = args else {
94//         return Err(Error::invalid_arguments(
95//             "- requires at least two arguments",
96//             None,
97//         ));
98//     };
99
100//     let Some(a) = a.as_int() else {
101//         return Err(Error::invalid_arguments(
102//             &format!("{a} is not an Int"),
103//             a.range(),
104//         ));
105//     };
106//
107//     let Some(b) = b.as_int() else {
108//         return Err(Error::invalid_arguments(
109//             &format!("{b} is not an Int"),
110//             b.range(),
111//         ));
112//     };
113//
114//     Ok(Expr::Int(a - b))
115// }
116
117pub fn sub_int(args: &[Expr]) -> Result<Expr, Error> {
118    // #todo what if there is a single argument?
119
120    let mut diff = unpack_int_arg(args, 0, "n")?;
121
122    for arg in args.iter().skip(1) {
123        let Some(n) = arg.as_int() else {
124            return Err(Error::invalid_arguments(
125                &format!("{arg} is not an Int"),
126                arg.range(),
127            ));
128        };
129        diff -= n;
130    }
131
132    Ok(Expr::Int(diff))
133}
134
135pub fn neg_int(args: &[Expr]) -> Result<Expr, Error> {
136    // #todo What about 0?
137    let n = unpack_int_arg(args, 0, "n")?;
138    Ok(Expr::Int(-n))
139}
140
141pub fn sub_float(args: &[Expr]) -> Result<Expr, Error> {
142    // #todo support multiple arguments.
143    let [a, b] = args else {
144        return Err(Error::invalid_arguments(
145            "- requires at least two arguments",
146            None,
147        ));
148    };
149
150    let Some(a) = a.as_float() else {
151        return Err(Error::invalid_arguments(
152            &format!("{a} is not a Float"),
153            a.range(),
154        ));
155    };
156
157    let Some(b) = b.as_float() else {
158        return Err(Error::invalid_arguments(
159            &format!("{b} is not a Float"),
160            b.range(),
161        ));
162    };
163
164    Ok(Expr::Float(a - b))
165}
166
167pub fn neg_float(args: &[Expr]) -> Result<Expr, Error> {
168    let n = unpack_float_arg(args, 0, "n")?;
169    Ok(Expr::Float(-n))
170}
171
172pub fn mul_int(args: &[Expr]) -> Result<Expr, Error> {
173    // #todo optimize!
174    let mut product = 1;
175
176    for arg in args {
177        let Some(n) = arg.as_int() else {
178            return Err(Error::invalid_arguments(
179                &format!("{arg} is not an Int"),
180                arg.range(),
181            ));
182        };
183        product *= n;
184    }
185
186    Ok(Expr::Int(product))
187}
188
189pub fn mul_float(args: &[Expr]) -> Result<Expr, Error> {
190    // #todo optimize!
191    let mut product = 1.0;
192
193    for arg in args {
194        let Some(n) = arg.as_float() else {
195            return Err(Error::invalid_arguments(
196                &format!("{arg} is not a Float"),
197                arg.range(),
198            ));
199        };
200        product *= n;
201    }
202
203    Ok(Expr::Float(product))
204}
205
206pub fn div_int(args: &[Expr]) -> Result<Expr, Error> {
207    // #todo optimize!
208    let mut quotient = unpack_int_arg(args, 0, "n")?;
209
210    for arg in args.iter().skip(1) {
211        let Some(n) = arg.as_int() else {
212            return Err(Error::invalid_arguments(
213                &format!("{arg} is not an Int"),
214                arg.range(),
215            ));
216        };
217
218        quotient /= n;
219    }
220
221    Ok(Expr::Int(quotient))
222}
223
224// #todo support int/float.
225pub fn div_float(args: &[Expr]) -> Result<Expr, Error> {
226    // #todo optimize!
227    let mut quotient = f64::NAN;
228
229    // #todo check for divide by zero! even statically check!
230    // #todo actually, divide by zero should return Infinity, not panic!!
231
232    for arg in args {
233        let Some(n) = arg.as_float() else {
234            return Err(Error::invalid_arguments(
235                &format!("{arg} is not a Float"),
236                arg.range(),
237            ));
238        };
239
240        if quotient.is_nan() {
241            quotient = n;
242        } else {
243            quotient /= n;
244        }
245    }
246
247    Ok(Expr::Float(quotient))
248}
249
250pub fn mod_int(args: &[Expr]) -> Result<Expr, Error> {
251    // #todo what are good variable names.
252    let x = unpack_int_arg(args, 0, "x")?;
253    let y = unpack_int_arg(args, 1, "y")?;
254
255    Ok(Expr::Int(x % y))
256}
257
258pub fn mod_float(args: &[Expr]) -> Result<Expr, Error> {
259    // #todo what are good variable names.
260    let x = unpack_float_arg(args, 0, "x")?;
261    let y = unpack_float_arg(args, 1, "y")?;
262
263    Ok(Expr::Float(x % y))
264}
265
266// #todo should be associated with `Ordering` and `Comparable`.
267pub fn int_compare(args: &[Expr]) -> Result<Expr, Error> {
268    // #todo support multiple arguments.
269    let [a, b] = args else {
270        return Err(Error::invalid_arguments(
271            "requires at least two arguments",
272            None,
273        ));
274    };
275
276    let Some(a) = a.as_int() else {
277        return Err(Error::invalid_arguments(
278            &format!("{a} is not an Int"),
279            a.range(),
280        ));
281    };
282
283    let Some(b) = b.as_int() else {
284        return Err(Error::invalid_arguments(
285            &format!("{b} is not an Int"),
286            b.range(),
287        ));
288    };
289
290    // #todo temp hack until Tan has enums?
291    let ordering = match a.cmp(&b) {
292        std::cmp::Ordering::Less => -1,
293        std::cmp::Ordering::Equal => 0,
294        std::cmp::Ordering::Greater => 1,
295    };
296
297    Ok(Expr::Int(ordering))
298}
299
300pub fn setup_lib_arithmetic(context: &mut Context) {
301    let module = require_module("prelude", context);
302
303    // #todo notice the use of annotate_type.
304
305    // #todo forget the mangling, implement with a dispatcher function, multi-function.
306    module.insert_invocable("+", annotate_type(Expr::foreign_func(&add_int), "Int"));
307    module.insert_invocable(
308        "+$$Int$$Int",
309        annotate_type(Expr::foreign_func(&add_int), "Int"),
310    );
311    // #todo #temp hack to support multiple args.
312    module.insert_invocable(
313        "+$$Int$$Int$$Int",
314        annotate_type(Expr::foreign_func(&add_int), "Int"),
315    );
316    module.insert_invocable(
317        "+$$Int$$Int$$Int$$Int",
318        annotate_type(Expr::foreign_func(&add_int), "Int"),
319    );
320    module.insert_invocable(
321        "+$$Float$$Float",
322        // #todo add the proper type: (Func Float Float Float)
323        // #todo even better: (Func (Many Float) Float)
324        annotate_type(Expr::foreign_func(&add_float), "Float"),
325    );
326    // #todo #temp hack to support multiple args.
327    module.insert_invocable(
328        "+$$Float$$Float$$Float",
329        // #todo add the proper type: (Func Float Float Float)
330        // #todo even better: (Func (Many Float) Float)
331        annotate_type(Expr::foreign_func(&add_float), "Float"),
332    );
333    module.insert_invocable(
334        "+$$Float$$Float$$Float$$Float",
335        // #todo add the proper type: (Func Float Float Float)
336        // #todo even better: (Func (Many Float) Float)
337        annotate_type(Expr::foreign_func(&add_float), "Float"),
338    );
339    module.insert_invocable(
340        "+$$Dec$$Dec",
341        // #todo add the proper type: (Func Dec Dec Dec)
342        // #todo even better: (Func (Many Dec) Dec)
343        annotate_type(Expr::foreign_func(&add_dec), "Dec"),
344    );
345    module.insert_invocable("-", Expr::foreign_func(&sub_int));
346    module.insert_invocable("-$$Int", annotate_type(Expr::foreign_func(&neg_int), "Int"));
347    module.insert_invocable(
348        "-$$Int$$Int",
349        annotate_type(Expr::foreign_func(&sub_int), "Int"),
350    );
351    // #todo #hack implement a version of module.insert that automatically adds a few methods/overloads.
352    module.insert_invocable(
353        "-$$Int$$Int$$Int",
354        annotate_type(Expr::foreign_func(&sub_int), "Int"),
355    );
356    module.insert_invocable(
357        "-$$Int$$Int$$Int$$Int",
358        annotate_type(Expr::foreign_func(&sub_int), "Int"),
359    );
360    module.insert_invocable(
361        "-$$Float",
362        annotate_type(Expr::foreign_func(&neg_float), "Float"),
363    );
364    module.insert_invocable(
365        "-$$Float$$Float",
366        annotate_type(Expr::foreign_func(&sub_float), "Float"),
367    );
368    module.insert_invocable("*", Expr::foreign_func(&mul_int));
369    module.insert_invocable(
370        "*$$Int$$Int",
371        annotate_type(Expr::foreign_func(&mul_int), "Int"),
372    );
373    // #todo #temp hack to support multiple args.
374    module.insert_invocable(
375        "*$$Int$$Int$$Int",
376        annotate_type(Expr::foreign_func(&mul_int), "Int"),
377    );
378    module.insert_invocable(
379        "*$$Float$$Float",
380        // #todo add the proper type: (Func Float Float Float)
381        // #todo even better: (Func (Many Float) Float)
382        annotate_type(Expr::foreign_func(&mul_float), "Float"),
383    );
384    module.insert_invocable(
385        "*$$Float$$Float$$Float",
386        // #todo add the proper type: (Func Float Float Float)
387        // #todo even better: (Func (Many Float) Float)
388        annotate_type(Expr::foreign_func(&mul_float), "Float"),
389    );
390    module.insert_invocable("/", annotate_type(Expr::foreign_func(&div_float), "Float"));
391    module.insert_invocable(
392        "/$$Int$$Int",
393        annotate_type(Expr::foreign_func(&div_int), "Int"),
394    );
395    // #todo ultra-hack
396    module.insert_invocable(
397        "/$$Float$$Float",
398        annotate_type(Expr::foreign_func(&div_float), "Float"),
399    );
400    // #todo ultra-hack
401    module.insert_invocable(
402        "/$$Float$$Float$$Float",
403        annotate_type(Expr::foreign_func(&div_float), "Float"),
404    );
405    // #todo Add support for float exponentiation.
406    // #todo shouldn't be required.
407    module.insert_invocable("%", annotate_type(Expr::foreign_func(&mod_int), "Int"));
408    module.insert_invocable(
409        "%$$Int$$Int",
410        annotate_type(Expr::foreign_func(&mod_int), "Int"),
411    );
412    module.insert_invocable(
413        "%$$Float$$Float",
414        annotate_type(Expr::foreign_func(&mod_float), "Float"),
415    );
416}