Skip to main content

endbasic_std/
numerics.rs

1// EndBASIC
2// Copyright 2020 Julio Merino
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License.  You may obtain a copy
6// of the License at:
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16//! Numerical functions for EndBASIC.
17
18use async_trait::async_trait;
19use endbasic_core::ast::{ArgSep, ExprType};
20use endbasic_core::compiler::{
21    ArgSepSyntax, RepeatedSyntax, RepeatedTypeSyntax, RequiredValueSyntax, SingularArgSyntax,
22};
23use endbasic_core::exec::{Clearable, Error, Machine, Result, Scope};
24use endbasic_core::syms::{Callable, CallableMetadata, CallableMetadataBuilder, Symbols};
25use endbasic_core::value::double_to_integer;
26use rand::rngs::SmallRng;
27use rand::{RngCore, SeedableRng};
28use std::borrow::Cow;
29use std::cell::RefCell;
30use std::cmp::Ordering;
31use std::rc::Rc;
32
33/// Category description for all symbols provided by this module.
34const CATEGORY: &str = "Numerical functions";
35
36/// Indicates the calculation mode for trigonometric functions.
37pub enum AngleMode {
38    /// Specifies degrees mode of calculation.
39    Degrees,
40
41    /// Specifies radians mode of calculation.
42    Radians,
43}
44
45struct ClearableAngleMode {
46    angle_mode: Rc<RefCell<AngleMode>>,
47}
48
49impl Clearable for ClearableAngleMode {
50    fn reset_state(&self, _syms: &mut Symbols) {
51        *self.angle_mode.borrow_mut() = AngleMode::Radians;
52    }
53}
54
55/// Gets the single argument to a trigonometric function, which is its angle.  Applies units
56/// conversion based on `angle_mode`.
57async fn get_angle(scope: &mut Scope<'_>, angle_mode: &AngleMode) -> Result<f64> {
58    debug_assert_eq!(1, scope.nargs());
59    let angle = scope.pop_double();
60
61    match angle_mode {
62        AngleMode::Degrees => Ok(angle.to_radians()),
63        AngleMode::Radians => Ok(angle),
64    }
65}
66
67/// Tracks the state of the PRNG used by the random number manipulation functions and commands.
68///
69/// The PRNG implemented here is intentionally simplistic and has no cryptographical guarantees.
70pub struct Prng {
71    prng: SmallRng,
72    last: u32,
73}
74
75impl Prng {
76    /// Generates a new PRNG based on system entropy.
77    pub fn new_from_entryopy() -> Self {
78        let mut prng = SmallRng::from_entropy();
79        let last = prng.next_u32();
80        Self { prng, last }
81    }
82
83    /// Generates a new PRNG based on the given seed.
84    pub fn new_from_seed(seed: i32) -> Self {
85        let mut prng = SmallRng::seed_from_u64(seed as u64);
86        let last = prng.next_u32();
87        Self { prng, last }
88    }
89
90    /// Returns the previously returned random number.
91    fn last(&self) -> f64 {
92        (self.last as f64) / (u32::MAX as f64)
93    }
94
95    /// Computes the next random number and returns it.
96    fn next(&mut self) -> f64 {
97        self.last = self.prng.next_u32();
98        self.last()
99    }
100}
101
102/// The `ATN` function.
103pub struct AtnFunction {
104    metadata: CallableMetadata,
105    angle_mode: Rc<RefCell<AngleMode>>,
106}
107
108impl AtnFunction {
109    /// Creates a new instance of the function.
110    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
111        Rc::from(Self {
112            metadata: CallableMetadataBuilder::new("ATN")
113                .with_return_type(ExprType::Double)
114                .with_syntax(&[(
115                    &[SingularArgSyntax::RequiredValue(
116                        RequiredValueSyntax { name: Cow::Borrowed("n"), vtype: ExprType::Double },
117                        ArgSepSyntax::End,
118                    )],
119                    None,
120                )])
121                .with_category(CATEGORY)
122                .with_description(
123                    "Computes the arc-tangent of a number.
124The resulting angle is measured in degrees or radians depending on the angle mode as selected by \
125the DEG and RAD commands.",
126                )
127                .build(),
128            angle_mode,
129        })
130    }
131}
132
133#[async_trait(?Send)]
134impl Callable for AtnFunction {
135    fn metadata(&self) -> &CallableMetadata {
136        &self.metadata
137    }
138
139    async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
140        debug_assert_eq!(1, scope.nargs());
141        let n = scope.pop_double();
142
143        match *self.angle_mode.borrow() {
144            AngleMode::Degrees => scope.return_double(n.atan().to_degrees()),
145            AngleMode::Radians => scope.return_double(n.atan()),
146        }
147    }
148}
149
150/// The `CINT` function.
151pub struct CintFunction {
152    metadata: CallableMetadata,
153}
154
155impl CintFunction {
156    /// Creates a new instance of the function.
157    pub fn new() -> Rc<Self> {
158        Rc::from(Self {
159            metadata: CallableMetadataBuilder::new("CINT")
160                .with_return_type(ExprType::Integer)
161                .with_syntax(&[(
162                    &[SingularArgSyntax::RequiredValue(
163                        RequiredValueSyntax {
164                            name: Cow::Borrowed("expr"),
165                            vtype: ExprType::Double,
166                        },
167                        ArgSepSyntax::End,
168                    )],
169                    None,
170                )])
171                .with_category(CATEGORY)
172                .with_description(
173                    "Casts the given numeric expression to an integer (with rounding).
174When casting a double value to an integer, the double value is first rounded to the closest \
175integer.  For example, 4.4 becomes 4, but both 4.5 and 4.6 become 5.",
176                )
177                .build(),
178        })
179    }
180}
181
182#[async_trait(?Send)]
183impl Callable for CintFunction {
184    fn metadata(&self) -> &CallableMetadata {
185        &self.metadata
186    }
187
188    async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
189        debug_assert_eq!(1, scope.nargs());
190        let (value, pos) = scope.pop_double_with_pos();
191
192        let i = double_to_integer(value).map_err(|e| Error::SyntaxError(pos, e.to_string()))?;
193        scope.return_integer(i)
194    }
195}
196
197/// The `COS` function.
198pub struct CosFunction {
199    metadata: CallableMetadata,
200    angle_mode: Rc<RefCell<AngleMode>>,
201}
202
203impl CosFunction {
204    /// Creates a new instance of the function.
205    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
206        Rc::from(Self {
207            metadata: CallableMetadataBuilder::new("COS")
208                .with_return_type(ExprType::Double)
209                .with_syntax(&[(
210                    &[SingularArgSyntax::RequiredValue(
211                        RequiredValueSyntax {
212                            name: Cow::Borrowed("angle"),
213                            vtype: ExprType::Double,
214                        },
215                        ArgSepSyntax::End,
216                    )],
217                    None,
218                )])
219                .with_category(CATEGORY)
220                .with_description(
221                    "Computes the cosine of an angle.
222The input angle% or angle# is measured in degrees or radians depending on the angle mode as \
223selected by the DEG and RAD commands.",
224                )
225                .build(),
226            angle_mode,
227        })
228    }
229}
230
231#[async_trait(?Send)]
232impl Callable for CosFunction {
233    fn metadata(&self) -> &CallableMetadata {
234        &self.metadata
235    }
236
237    async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
238        let angle = get_angle(&mut scope, &self.angle_mode.borrow()).await?;
239        scope.return_double(angle.cos())
240    }
241}
242
243/// The `DEG` command.
244pub struct DegCommand {
245    metadata: CallableMetadata,
246    angle_mode: Rc<RefCell<AngleMode>>,
247}
248
249impl DegCommand {
250    /// Creates a new instance of the command.
251    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
252        Rc::from(Self {
253            metadata: CallableMetadataBuilder::new("DEG")
254                .with_syntax(&[(&[], None)])
255                .with_category(CATEGORY)
256                .with_description(
257                    "Sets degrees mode of calculation.
258The default condition for the trigonometric functions is to use radians.  DEG configures the \
259environment to use degrees until instructed otherwise.",
260                )
261                .build(),
262            angle_mode,
263        })
264    }
265}
266
267#[async_trait(?Send)]
268impl Callable for DegCommand {
269    fn metadata(&self) -> &CallableMetadata {
270        &self.metadata
271    }
272
273    async fn exec(&self, scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
274        debug_assert_eq!(0, scope.nargs());
275        *self.angle_mode.borrow_mut() = AngleMode::Degrees;
276        Ok(())
277    }
278}
279
280/// The `INT` function.
281pub struct IntFunction {
282    metadata: CallableMetadata,
283}
284
285impl IntFunction {
286    /// Creates a new instance of the function.
287    pub fn new() -> Rc<Self> {
288        Rc::from(Self {
289            metadata: CallableMetadataBuilder::new("INT")
290                .with_return_type(ExprType::Integer)
291                .with_syntax(&[(
292                    &[SingularArgSyntax::RequiredValue(
293                        RequiredValueSyntax {
294                            name: Cow::Borrowed("expr"),
295                            vtype: ExprType::Double,
296                        },
297                        ArgSepSyntax::End,
298                    )],
299                    None,
300                )])
301                .with_category(CATEGORY)
302                .with_description(
303                    "Casts the given numeric expression to an integer (with truncation).
304When casting a double value to an integer, the double value is first truncated to the smallest
305integer that is not larger than the double value.  For example, all of 4.4, 4.5 and 4.6 become 4.",
306                )
307                .build(),
308        })
309    }
310}
311
312#[async_trait(?Send)]
313impl Callable for IntFunction {
314    fn metadata(&self) -> &CallableMetadata {
315        &self.metadata
316    }
317
318    async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
319        debug_assert_eq!(1, scope.nargs());
320        let (value, pos) = scope.pop_double_with_pos();
321
322        let i =
323            double_to_integer(value.floor()).map_err(|e| Error::SyntaxError(pos, e.to_string()))?;
324        scope.return_integer(i)
325    }
326}
327
328/// The `MAX` function.
329pub struct MaxFunction {
330    metadata: CallableMetadata,
331}
332
333impl MaxFunction {
334    /// Creates a new instance of the function.
335    pub fn new() -> Rc<Self> {
336        Rc::from(Self {
337            metadata: CallableMetadataBuilder::new("MAX")
338                .with_return_type(ExprType::Double)
339                .with_syntax(&[(
340                    &[],
341                    Some(&RepeatedSyntax {
342                        name: Cow::Borrowed("expr"),
343                        type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Double),
344                        sep: ArgSepSyntax::Exactly(ArgSep::Long),
345                        require_one: true,
346                        allow_missing: false,
347                    }),
348                )])
349                .with_category(CATEGORY)
350                .with_description("Returns the maximum number out of a set of numbers.")
351                .build(),
352        })
353    }
354}
355
356#[async_trait(?Send)]
357impl Callable for MaxFunction {
358    fn metadata(&self) -> &CallableMetadata {
359        &self.metadata
360    }
361
362    async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
363        let mut max = f64::MIN;
364        while scope.nargs() > 0 {
365            let n = scope.pop_double();
366            if n > max {
367                max = n;
368            }
369        }
370        scope.return_double(max)
371    }
372}
373
374/// The `MIN` function.
375pub struct MinFunction {
376    metadata: CallableMetadata,
377}
378
379impl MinFunction {
380    /// Creates a new instance of the function.
381    pub fn new() -> Rc<Self> {
382        Rc::from(Self {
383            metadata: CallableMetadataBuilder::new("MIN")
384                .with_return_type(ExprType::Double)
385                .with_syntax(&[(
386                    &[],
387                    Some(&RepeatedSyntax {
388                        name: Cow::Borrowed("expr"),
389                        type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Double),
390                        sep: ArgSepSyntax::Exactly(ArgSep::Long),
391                        require_one: true,
392                        allow_missing: false,
393                    }),
394                )])
395                .with_category(CATEGORY)
396                .with_description("Returns the minimum number out of a set of numbers.")
397                .build(),
398        })
399    }
400}
401
402#[async_trait(?Send)]
403impl Callable for MinFunction {
404    fn metadata(&self) -> &CallableMetadata {
405        &self.metadata
406    }
407
408    async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
409        let mut min = f64::MAX;
410        while scope.nargs() > 0 {
411            let n = scope.pop_double();
412            if n < min {
413                min = n;
414            }
415        }
416        scope.return_double(min)
417    }
418}
419
420/// The `PI` function.
421pub struct PiFunction {
422    metadata: CallableMetadata,
423}
424
425impl PiFunction {
426    /// Creates a new instance of the function.
427    pub fn new() -> Rc<Self> {
428        Rc::from(Self {
429            metadata: CallableMetadataBuilder::new("PI")
430                .with_return_type(ExprType::Double)
431                .with_syntax(&[(&[], None)])
432                .with_category(CATEGORY)
433                .with_description("Returns the Archimedes' constant.")
434                .build(),
435        })
436    }
437}
438
439#[async_trait(?Send)]
440impl Callable for PiFunction {
441    fn metadata(&self) -> &CallableMetadata {
442        &self.metadata
443    }
444
445    async fn exec(&self, scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
446        debug_assert_eq!(0, scope.nargs());
447        scope.return_double(std::f64::consts::PI)
448    }
449}
450
451/// The `RAD` command.
452pub struct RadCommand {
453    metadata: CallableMetadata,
454    angle_mode: Rc<RefCell<AngleMode>>,
455}
456
457impl RadCommand {
458    /// Creates a new instance of the command.
459    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
460        Rc::from(Self {
461            metadata: CallableMetadataBuilder::new("RAD")
462                .with_syntax(&[(&[], None)])
463                .with_category(CATEGORY)
464                .with_description(
465                    "Sets radians mode of calculation.
466The default condition for the trigonometric functions is to use radians but it can be set to \
467degrees with the DEG command.  RAD restores the environment to use radians mode.",
468                )
469                .build(),
470            angle_mode,
471        })
472    }
473}
474
475#[async_trait(?Send)]
476impl Callable for RadCommand {
477    fn metadata(&self) -> &CallableMetadata {
478        &self.metadata
479    }
480
481    async fn exec(&self, scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
482        debug_assert_eq!(0, scope.nargs());
483        *self.angle_mode.borrow_mut() = AngleMode::Radians;
484        Ok(())
485    }
486}
487
488/// The `RANDOMIZE` command.
489pub struct RandomizeCommand {
490    metadata: CallableMetadata,
491    prng: Rc<RefCell<Prng>>,
492}
493
494impl RandomizeCommand {
495    /// Creates a new command that updates `code` with the exit code once called.
496    pub fn new(prng: Rc<RefCell<Prng>>) -> Rc<Self> {
497        Rc::from(Self {
498            metadata: CallableMetadataBuilder::new("RANDOMIZE")
499                .with_syntax(&[
500                    (&[], None),
501                    (
502                        &[SingularArgSyntax::RequiredValue(
503                            RequiredValueSyntax {
504                                name: Cow::Borrowed("seed"),
505                                vtype: ExprType::Integer,
506                            },
507                            ArgSepSyntax::End,
508                        )],
509                        None,
510                    ),
511                ])
512                .with_category(CATEGORY)
513                .with_description(
514                    "Reinitializes the pseudo-random number generator.
515If no seed is given, uses system entropy to create a new sequence of random numbers.
516WARNING: These random numbers offer no cryptographic guarantees.",
517                )
518                .build(),
519            prng,
520        })
521    }
522}
523
524#[async_trait(?Send)]
525impl Callable for RandomizeCommand {
526    fn metadata(&self) -> &CallableMetadata {
527        &self.metadata
528    }
529
530    async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
531        if scope.nargs() == 0 {
532            *self.prng.borrow_mut() = Prng::new_from_entryopy();
533        } else {
534            debug_assert_eq!(1, scope.nargs());
535            let n = scope.pop_integer();
536            *self.prng.borrow_mut() = Prng::new_from_seed(n);
537        }
538        Ok(())
539    }
540}
541
542/// The `RND` function.
543pub struct RndFunction {
544    metadata: CallableMetadata,
545    prng: Rc<RefCell<Prng>>,
546}
547
548impl RndFunction {
549    /// Creates a new instance of the function.
550    pub fn new(prng: Rc<RefCell<Prng>>) -> Rc<Self> {
551        Rc::from(Self {
552            metadata: CallableMetadataBuilder::new("RND")
553                .with_return_type(ExprType::Double)
554                .with_syntax(&[
555                    (&[], None),
556                    (
557                        &[SingularArgSyntax::RequiredValue(
558                            RequiredValueSyntax {
559                                name: Cow::Borrowed("n"),
560                                vtype: ExprType::Integer,
561                            },
562                            ArgSepSyntax::End,
563                        )],
564                        None,
565                    ),
566                ])
567                .with_category(CATEGORY)
568                .with_description(
569                    "Returns a random number in the [0..1] range.
570If n% is zero, returns the previously generated random number.  If n% is positive or is not \
571specified, returns a new random number.
572If you need to generate an integer random number within a specific range, say [0..100], compute it \
573with an expression like CINT%(RND#(1) * 100.0).
574WARNING: These random numbers offer no cryptographic guarantees.",
575                )
576                .build(),
577            prng,
578        })
579    }
580}
581
582#[async_trait(?Send)]
583impl Callable for RndFunction {
584    fn metadata(&self) -> &CallableMetadata {
585        &self.metadata
586    }
587
588    async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
589        if scope.nargs() == 0 {
590            scope.return_double(self.prng.borrow_mut().next())
591        } else {
592            debug_assert_eq!(1, scope.nargs());
593            let (n, npos) = scope.pop_integer_with_pos();
594            match n.cmp(&0) {
595                Ordering::Equal => scope.return_double(self.prng.borrow_mut().last()),
596                Ordering::Greater => scope.return_double(self.prng.borrow_mut().next()),
597                Ordering::Less => Err(Error::SyntaxError(npos, "n% cannot be negative".to_owned())),
598            }
599        }
600    }
601}
602
603/// The `SIN` function.
604pub struct SinFunction {
605    metadata: CallableMetadata,
606    angle_mode: Rc<RefCell<AngleMode>>,
607}
608
609impl SinFunction {
610    /// Creates a new instance of the function.
611    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
612        Rc::from(Self {
613            metadata: CallableMetadataBuilder::new("SIN")
614                .with_return_type(ExprType::Double)
615                .with_syntax(&[(
616                    &[SingularArgSyntax::RequiredValue(
617                        RequiredValueSyntax {
618                            name: Cow::Borrowed("angle"),
619                            vtype: ExprType::Double,
620                        },
621                        ArgSepSyntax::End,
622                    )],
623                    None,
624                )])
625                .with_category(CATEGORY)
626                .with_description(
627                    "Computes the sine of an angle.
628The input angle% or angle# is measured in degrees or radians depending on the angle mode as \
629selected by the DEG and RAD commands.",
630                )
631                .build(),
632            angle_mode,
633        })
634    }
635}
636
637#[async_trait(?Send)]
638impl Callable for SinFunction {
639    fn metadata(&self) -> &CallableMetadata {
640        &self.metadata
641    }
642
643    async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
644        let angle = get_angle(&mut scope, &self.angle_mode.borrow()).await?;
645        scope.return_double(angle.sin())
646    }
647}
648
649/// The `SQR` function.
650pub struct SqrFunction {
651    metadata: CallableMetadata,
652}
653
654impl SqrFunction {
655    /// Creates a new instance of the function.
656    pub fn new() -> Rc<Self> {
657        Rc::from(Self {
658            metadata: CallableMetadataBuilder::new("SQR")
659                .with_return_type(ExprType::Double)
660                .with_syntax(&[(
661                    &[SingularArgSyntax::RequiredValue(
662                        RequiredValueSyntax { name: Cow::Borrowed("num"), vtype: ExprType::Double },
663                        ArgSepSyntax::End,
664                    )],
665                    None,
666                )])
667                .with_category(CATEGORY)
668                .with_description("Computes the square root of the given number.")
669                .build(),
670        })
671    }
672}
673
674#[async_trait(?Send)]
675impl Callable for SqrFunction {
676    fn metadata(&self) -> &CallableMetadata {
677        &self.metadata
678    }
679
680    async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
681        debug_assert_eq!(1, scope.nargs());
682        let (num, numpos) = scope.pop_double_with_pos();
683
684        if num < 0.0 {
685            return Err(Error::SyntaxError(
686                numpos,
687                "Cannot take square root of a negative number".to_owned(),
688            ));
689        }
690        scope.return_double(num.sqrt())
691    }
692}
693
694/// The `TAN` function.
695pub struct TanFunction {
696    metadata: CallableMetadata,
697    angle_mode: Rc<RefCell<AngleMode>>,
698}
699
700impl TanFunction {
701    /// Creates a new instance of the function.
702    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
703        Rc::from(Self {
704            metadata: CallableMetadataBuilder::new("TAN")
705                .with_return_type(ExprType::Double)
706                .with_syntax(&[(
707                    &[SingularArgSyntax::RequiredValue(
708                        RequiredValueSyntax {
709                            name: Cow::Borrowed("angle"),
710                            vtype: ExprType::Double,
711                        },
712                        ArgSepSyntax::End,
713                    )],
714                    None,
715                )])
716                .with_category(CATEGORY)
717                .with_description(
718                    "Computes the tangent of an angle.
719The input angle% or angle# is measured in degrees or radians depending on the angle mode as \
720selected by the DEG and RAD commands.",
721                )
722                .build(),
723            angle_mode,
724        })
725    }
726}
727
728#[async_trait(?Send)]
729impl Callable for TanFunction {
730    fn metadata(&self) -> &CallableMetadata {
731        &self.metadata
732    }
733
734    async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
735        let angle = get_angle(&mut scope, &self.angle_mode.borrow()).await?;
736        scope.return_double(angle.tan())
737    }
738}
739
740/// Adds all symbols provided by this module to the given `machine`.
741pub fn add_all(machine: &mut Machine) {
742    let angle_mode = Rc::from(RefCell::from(AngleMode::Radians));
743    let prng = Rc::from(RefCell::from(Prng::new_from_entryopy()));
744    machine.add_clearable(Box::from(ClearableAngleMode { angle_mode: angle_mode.clone() }));
745    machine.add_callable(AtnFunction::new(angle_mode.clone()));
746    machine.add_callable(CintFunction::new());
747    machine.add_callable(CosFunction::new(angle_mode.clone()));
748    machine.add_callable(DegCommand::new(angle_mode.clone()));
749    machine.add_callable(IntFunction::new());
750    machine.add_callable(MaxFunction::new());
751    machine.add_callable(MinFunction::new());
752    machine.add_callable(PiFunction::new());
753    machine.add_callable(RadCommand::new(angle_mode.clone()));
754    machine.add_callable(RandomizeCommand::new(prng.clone()));
755    machine.add_callable(RndFunction::new(prng));
756    machine.add_callable(SinFunction::new(angle_mode.clone()));
757    machine.add_callable(SqrFunction::new());
758    machine.add_callable(TanFunction::new(angle_mode));
759}
760
761#[cfg(test)]
762mod tests {
763    use crate::testutils::*;
764
765    #[test]
766    fn test_atn() {
767        check_expr_ok(123f64.atan(), "ATN(123)");
768        check_expr_ok(45.5f64.atan(), "ATN(45.5)");
769
770        check_expr_ok_with_vars(123f64.atan(), "ATN(a)", [("a", 123i32.into())]);
771
772        check_expr_compilation_error("1:10: ATN expected n#", "ATN()");
773        check_expr_compilation_error("1:14: BOOLEAN is not a number", "ATN(FALSE)");
774        check_expr_compilation_error("1:10: ATN expected n#", "ATN(3, 4)");
775    }
776
777    #[test]
778    fn test_cint() {
779        check_expr_ok(0, "CINT(0.1)");
780        check_expr_ok(0, "CINT(-0.1)");
781        check_expr_ok(1, "CINT(0.9)");
782        check_expr_ok(-1, "CINT(-0.9)");
783
784        check_expr_ok_with_vars(1, "CINT(d)", [("d", 0.9f64.into())]);
785
786        check_expr_compilation_error("1:10: CINT expected expr#", "CINT()");
787        check_expr_compilation_error("1:15: BOOLEAN is not a number", "CINT(FALSE)");
788        check_expr_compilation_error("1:10: CINT expected expr#", "CINT(3.0, 4)");
789
790        check_expr_error(
791            "1:15: Cannot cast -1234567890123456 to integer due to overflow",
792            "CINT(-1234567890123456.0)",
793        );
794    }
795
796    #[test]
797    fn test_cos() {
798        check_expr_ok(123f64.cos(), "COS(123)");
799        check_expr_ok(45.5f64.cos(), "COS(45.5)");
800
801        check_expr_ok_with_vars(123f64.cos(), "COS(i)", [("i", 123i32.into())]);
802
803        check_expr_compilation_error("1:10: COS expected angle#", "COS()");
804        check_expr_compilation_error("1:14: BOOLEAN is not a number", "COS(FALSE)");
805        check_expr_compilation_error("1:10: COS expected angle#", "COS(3, 4)");
806    }
807
808    #[test]
809    fn test_deg_rad_commands() {
810        let mut t = Tester::default();
811        t.run("result = SIN(90)").expect_var("result", 90f64.sin()).check();
812        t.run("DEG: result = SIN(90)").expect_var("result", 1.0).check();
813        t.run("RAD: result = SIN(90)").expect_var("result", 90f64.sin()).check();
814    }
815
816    #[test]
817    fn test_deg_rad_reset_on_clear() {
818        let mut t = Tester::default();
819        t.run("DEG").check();
820        t.get_machine().clear();
821        t.run("result = SIN(90)").expect_clear().expect_var("result", 90f64.sin()).check();
822    }
823
824    #[test]
825    fn test_deg_rad_errors() {
826        check_stmt_compilation_err("1:1: DEG expected no arguments", "DEG 1");
827        check_stmt_compilation_err("1:1: RAD expected no arguments", "RAD 1");
828    }
829
830    #[test]
831    fn test_int() {
832        check_expr_ok(0, "INT(0.1)");
833        check_expr_ok(-1, "INT(-0.1)");
834        check_expr_ok(0, "INT(0.9)");
835        check_expr_ok(-1, "INT(-0.9)");
836
837        check_expr_ok_with_vars(0, "INT(d)", [("d", 0.9f64.into())]);
838
839        check_expr_compilation_error("1:10: INT expected expr#", "INT()");
840        check_expr_compilation_error("1:14: BOOLEAN is not a number", "INT(FALSE)");
841        check_expr_compilation_error("1:10: INT expected expr#", "INT(3.0, 4)");
842
843        check_expr_error(
844            "1:14: Cannot cast -1234567890123456 to integer due to overflow",
845            "INT(-1234567890123456.0)",
846        );
847    }
848
849    #[test]
850    fn test_max() {
851        check_expr_ok(0.0, "MAX(0)");
852        check_expr_ok(0.0, "MAX(0, 0)");
853
854        check_expr_ok(0.0, "MAX(0.0)");
855        check_expr_ok(0.0, "MAX(0.0, 0.0)");
856
857        check_expr_ok(1.0, "MAX(1)");
858        check_expr_ok(5.0, "MAX(5, 3, 4)");
859        check_expr_ok(-3.0, "MAX(-5, -3, -4)");
860
861        check_expr_ok(1.0, "MAX(1.0)");
862        check_expr_ok(5.3, "MAX(5.3, 3.5, 4.2)");
863        check_expr_ok(-3.5, "MAX(-5.3, -3.5, -4.2)");
864
865        check_expr_ok(2.5, "MAX(1, 0.5, 2.5, 2)");
866
867        check_expr_ok_with_vars(
868            5.0,
869            "MAX(i, j, k)",
870            [("i", 5i32.into()), ("j", 3i32.into()), ("k", 4i32.into())],
871        );
872
873        check_expr_compilation_error("1:10: MAX expected expr1#[, .., exprN#]", "MAX()");
874        check_expr_compilation_error("1:14: BOOLEAN is not a number", "MAX(FALSE)");
875    }
876
877    #[test]
878    fn test_min() {
879        check_expr_ok(0.0, "MIN(0)");
880        check_expr_ok(0.0, "MIN(0, 0)");
881
882        check_expr_ok(0.0, "MIN(0.0)");
883        check_expr_ok(0.0, "MIN(0.0, 0.0)");
884
885        check_expr_ok(1.0, "MIN(1)");
886        check_expr_ok(3.0, "MIN(5, 3, 4)");
887        check_expr_ok(-5.0, "MIN(-5, -3, -4)");
888
889        check_expr_ok(1.0, "MIN(1.0)");
890        check_expr_ok(3.5, "MIN(5.3, 3.5, 4.2)");
891        check_expr_ok(-5.3, "MIN(-5.3, -3.5, -4.2)");
892
893        check_expr_ok(0.5, "MIN(1, 0.5, 2.5, 2)");
894
895        check_expr_ok_with_vars(
896            3.0,
897            "MIN(i, j, k)",
898            [("i", 5i32.into()), ("j", 3i32.into()), ("k", 4i32.into())],
899        );
900
901        check_expr_compilation_error("1:10: MIN expected expr1#[, .., exprN#]", "MIN()");
902        check_expr_compilation_error("1:14: BOOLEAN is not a number", "MIN(FALSE)");
903    }
904
905    #[test]
906    fn test_pi() {
907        check_expr_ok(std::f64::consts::PI, "PI");
908
909        check_expr_compilation_error("1:10: PI expected no arguments", "PI()");
910        check_expr_compilation_error("1:10: PI expected no arguments", "PI(3)");
911    }
912
913    #[test]
914    fn test_randomize_and_rnd() {
915        // These tests could lead to flakiness if the PRNG happens to yield the same number twice
916        // in a row because we did not previously configure the seed.  It is very unlikely though,
917        // and we need a way to test that the PRNG was initialized before we call RANDOMIZE.
918        check_expr_ok(false, "RND(1) = RND(1)");
919        check_expr_ok(false, "RND(1) = RND(10)");
920        check_expr_ok(true, "RND(0) = RND(0)");
921
922        let mut t = Tester::default();
923        t.run("RANDOMIZE 10").check();
924
925        t.run("result = RND(1)").expect_var("result", 0.7097578208683426).check();
926        t.run("result = RND(1.1)").expect_var("result", 0.2205558922655312).check();
927        t.run("result = RND(0)").expect_var("result", 0.2205558922655312).check();
928        t.run("result = RND(10)").expect_var("result", 0.8273883964464507).check();
929
930        t.run("RANDOMIZE 10.2").expect_var("result", 0.8273883964464507).check();
931
932        t.run("result = RND(1)").expect_var("result", 0.7097578208683426).check();
933
934        check_expr_compilation_error("1:10: RND expected <> | <n%>", "RND(1, 7)");
935        check_expr_compilation_error("1:14: BOOLEAN is not a number", "RND(FALSE)");
936        check_expr_error("1:14: n% cannot be negative", "RND(-1)");
937
938        check_stmt_compilation_err("1:1: RANDOMIZE expected <> | <seed%>", "RANDOMIZE ,");
939        check_stmt_compilation_err("1:11: BOOLEAN is not a number", "RANDOMIZE TRUE");
940    }
941
942    #[test]
943    fn test_sin() {
944        check_expr_ok(123f64.sin(), "SIN(123)");
945        check_expr_ok(45.5f64.sin(), "SIN(45.5)");
946
947        check_expr_ok_with_vars(123f64.sin(), "SIN(i)", [("i", 123i32.into())]);
948
949        check_expr_compilation_error("1:10: SIN expected angle#", "SIN()");
950        check_expr_compilation_error("1:14: BOOLEAN is not a number", "SIN(FALSE)");
951        check_expr_compilation_error("1:10: SIN expected angle#", "SIN(3, 4)");
952    }
953
954    #[test]
955    fn test_sqr() {
956        check_expr_ok(0f64.sqrt(), "SQR(0)");
957        check_expr_ok(0f64.sqrt(), "SQR(-0.0)");
958        check_expr_ok(9f64.sqrt(), "SQR(9)");
959        check_expr_ok(100.50f64.sqrt(), "SQR(100.50)");
960
961        check_expr_ok_with_vars(9f64.sqrt(), "SQR(i)", [("i", 9i32.into())]);
962
963        check_expr_compilation_error("1:10: SQR expected num#", "SQR()");
964        check_expr_compilation_error("1:14: BOOLEAN is not a number", "SQR(FALSE)");
965        check_expr_compilation_error("1:10: SQR expected num#", "SQR(3, 4)");
966        check_expr_error("1:14: Cannot take square root of a negative number", "SQR(-3)");
967        check_expr_error("1:14: Cannot take square root of a negative number", "SQR(-0.1)");
968    }
969
970    #[test]
971    fn test_tan() {
972        check_expr_ok(123f64.tan(), "TAN(123)");
973        check_expr_ok(45.5f64.tan(), "TAN(45.5)");
974
975        check_expr_ok_with_vars(123f64.tan(), "TAN(i)", [("i", 123i32.into())]);
976
977        check_expr_compilation_error("1:10: TAN expected angle#", "TAN()");
978        check_expr_compilation_error("1:14: BOOLEAN is not a number", "TAN(FALSE)");
979        check_expr_compilation_error("1:10: TAN expected angle#", "TAN(3, 4)");
980    }
981}