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