Skip to main content

tancore/
float.rs

1use core::f64;
2
3use tan::{
4    context::Context,
5    error::Error,
6    expr::Expr,
7    util::{
8        args::{unpack_bool_arg, unpack_float_arg, unpack_stringable_arg},
9        module_util::require_module,
10    },
11};
12
13// #todo Remove unneeded pubs.
14
15// #todo Don't include functions like floor/ceil/round in prelude.
16// #todo Consider the names floor-of, ceil-of, round-of.
17
18// #todo Implement with Tan.
19pub fn float_from_int(args: &[Expr]) -> Result<Expr, Error> {
20    let [value] = args else {
21        return Err(Error::invalid_arguments("requires `value` argument", None));
22    };
23
24    // #todo create a helper.
25    let Some(value) = value.as_int() else {
26        return Err(Error::invalid_arguments(
27            &format!("value=`{value}` is not Int"),
28            value.range(),
29        ));
30    };
31
32    Ok(Expr::Float(value as f64))
33}
34
35// #todo Implement with Tan.
36pub fn float_from_bool(args: &[Expr]) -> Result<Expr, Error> {
37    let value = unpack_bool_arg(args, 0, "value")?;
38
39    Ok(Expr::Float(if value { 1.0 } else { 0.0 }))
40}
41
42// #todo Consider (Float/from-string ...)
43pub fn float_from_string(args: &[Expr]) -> Result<Expr, Error> {
44    let string = unpack_stringable_arg(args, 0, "string")?;
45    let Ok(value) = string.parse::<f64>() else {
46        return Err(Error::invalid_arguments(
47            &format!("string=`{string}` is not a valid Float number"),
48            args[0].range(),
49        ));
50    };
51    Ok(Expr::Float(value))
52}
53
54// #todo Introduce Float/+Infinity, Float/-Infinity.
55
56// #todo Consider skipping the prelude for min?
57// #todo What could be another name instead of min? `min-of`? `minimum`?
58pub fn float_min(args: &[Expr]) -> Result<Expr, Error> {
59    let mut min = f64::MAX;
60
61    for arg in args {
62        let Some(n) = arg.as_float() else {
63            return Err(Error::invalid_arguments(
64                &format!("{arg} is not a Float"),
65                arg.range(),
66            ));
67        };
68        if n < min {
69            min = n;
70        }
71    }
72
73    Ok(Expr::Float(min))
74}
75
76pub fn float_max(args: &[Expr]) -> Result<Expr, Error> {
77    let mut max = f64::MIN;
78
79    for arg in args {
80        let Some(n) = arg.as_float() else {
81            return Err(Error::invalid_arguments(
82                &format!("{arg} is not a Float"),
83                arg.range(),
84            ));
85        };
86        if n > max {
87            max = n;
88        }
89    }
90
91    Ok(Expr::Float(max))
92}
93
94// #todo Implement in Tan.
95pub fn float_abs(args: &[Expr]) -> Result<Expr, Error> {
96    let n = unpack_float_arg(args, 0, "n")?;
97    Ok(Expr::Float(n.abs()))
98}
99
100// #todo Introduce multiple rounding functions.
101// #todo Should the rounding functions also handle floor/ceil?
102
103pub fn float_floor(args: &[Expr]) -> Result<Expr, Error> {
104    let n = unpack_float_arg(args, 0, "n")?;
105    Ok(Expr::Float(n.floor()))
106}
107
108pub fn float_ceil(args: &[Expr]) -> Result<Expr, Error> {
109    let n = unpack_float_arg(args, 0, "n")?;
110    Ok(Expr::Float(n.ceil()))
111}
112
113// #todo Support multiple rounding modes.
114// #todo What would be a non-ambiguous number?
115pub fn float_round(args: &[Expr]) -> Result<Expr, Error> {
116    let n = unpack_float_arg(args, 0, "n")?;
117    // #todo Consider round_ties_even.
118    Ok(Expr::Float(n.round()))
119}
120
121pub fn float_sqrt(args: &[Expr]) -> Result<Expr, Error> {
122    let n = unpack_float_arg(args, 0, "n")?;
123    Ok(Expr::Float(n.sqrt()))
124}
125
126pub fn float_sin(args: &[Expr]) -> Result<Expr, Error> {
127    let Some(n) = args.first() else {
128        return Err(Error::invalid_arguments("missing argument", None));
129    };
130
131    let Some(n) = n.as_float() else {
132        return Err(Error::invalid_arguments(
133            "expected Float argument",
134            n.range(),
135        ));
136    };
137
138    Ok(Expr::Float(n.sin()))
139}
140
141pub fn float_cos(args: &[Expr]) -> Result<Expr, Error> {
142    let Some(n) = args.first() else {
143        return Err(Error::invalid_arguments("missing argument", None));
144    };
145
146    let Some(n) = n.as_float() else {
147        return Err(Error::invalid_arguments(
148            "expected Float argument",
149            n.range(),
150        ));
151    };
152
153    Ok(Expr::Float(n.cos()))
154}
155
156// #todo Avoid confusion with ...Tan.
157pub fn float_tan(args: &[Expr]) -> Result<Expr, Error> {
158    let n = unpack_float_arg(args, 0, "n")?;
159    Ok(Expr::Float(n.tan()))
160}
161
162// #todo support variable args?
163pub fn float_powi(args: &[Expr]) -> Result<Expr, Error> {
164    let [n, e] = args else {
165        return Err(Error::invalid_arguments(
166            "- requires at least two arguments",
167            None,
168        ));
169    };
170
171    // #todo version of as_float that automatically throws an Error?
172    let Some(n) = n.as_float() else {
173        return Err(Error::invalid_arguments(
174            &format!("{n} is not a Float"),
175            n.range(),
176        ));
177    };
178
179    let Some(e) = e.as_int() else {
180        return Err(Error::invalid_arguments(
181            &format!("{e} is not an Int"),
182            e.range(),
183        ));
184    };
185
186    Ok(Expr::Float(n.powi(e as i32)))
187}
188
189// #todo Introduce clamp
190// #todo Also introduce clamp in Range and/or Interval.
191
192pub fn setup_lib_float(context: &mut Context) {
193    // #todo put in 'float' path, and import selected functionality to prelude.
194    let module = require_module("prelude", context);
195
196    // #todo consider to-float instead?
197
198    // #todo #think having so many overloads can cover issues, e.g. use the wrong implicit overload.
199
200    // #todo make `float_new` the default.
201    module.insert_invocable("Float", Expr::foreign_func(&float_from_int));
202    module.insert_invocable("Float$$Int", Expr::foreign_func(&float_from_int));
203    module.insert_invocable("Float$$Bool", Expr::foreign_func(&float_from_bool));
204    module.insert_invocable("Float$$String", Expr::foreign_func(&float_from_string));
205    module.insert_invocable("min", Expr::foreign_func(&float_min));
206    module.insert_invocable(
207        "min$$Float$$Float",
208        // annotate_type(Expr::foreign_func(&add_float)), "Float"),
209        Expr::foreign_func(&float_min),
210    );
211    module.insert_invocable("max", Expr::foreign_func(&float_max));
212    module.insert_invocable(
213        "max$$Float$$Float",
214        // annotate_type(Expr::foreign_func(&add_float)), "Float"),
215        Expr::foreign_func(&float_max),
216    );
217
218    module.insert_invocable("abs", Expr::foreign_func(&float_abs));
219    module.insert_invocable("abs$$Float", Expr::foreign_func(&float_abs));
220
221    // #todo Kind of annoying that these are non-verbs.
222
223    module.insert_invocable("floor$$Float", Expr::foreign_func(&float_floor));
224    module.insert_invocable("ceil$$Float", Expr::foreign_func(&float_ceil));
225    module.insert_invocable("round$$Float", Expr::foreign_func(&float_round));
226
227    // #todo Consider sqrt-of or Num/sqrt or math/sqrt.
228    // #todo Note that `sqrt` does not follow Tan naming conventions but it's a standard term.
229    module.insert_invocable("sqrt", Expr::foreign_func(&float_sqrt));
230    module.insert_invocable("sqrt$$Float", Expr::foreign_func(&float_sqrt));
231
232    module.insert_invocable("sin", Expr::foreign_func(&float_sin));
233    module.insert_invocable("cos", Expr::foreign_func(&float_cos));
234    module.insert_invocable("tan", Expr::foreign_func(&float_tan));
235    module.insert_invocable("**", Expr::foreign_func(&float_powi));
236    module.insert_invocable("**$$Float$$Int", Expr::foreign_func(&float_powi));
237
238    // Constants.
239
240    // #warning Don't use those yet!
241    // #todo Fix Float/max, it self-evaluates, duh!
242    // #todo Mark as constant / make immutable?
243    // #todo Should we skip `Float/` prefix?
244    // #todo Rename to max-value?
245    module.insert_invocable("float/max", Expr::Float(f64::MAX));
246    module.insert_invocable("float/infinity", Expr::Float(f64::INFINITY));
247}