Skip to main content

endbasic_std/
numerics.rs

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