1use 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
35const CATEGORY: &str = "Numerical functions";
37
38pub enum AngleMode {
40 Degrees,
42
43 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
57async 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
69pub struct Prng {
73 prng: SmallRng,
74 last: u32,
75}
76
77impl Prng {
78 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 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 fn last(&self) -> f64 {
94 (self.last as f64) / (u32::MAX as f64)
95 }
96
97 fn next(&mut self) -> f64 {
99 self.last = self.prng.next_u32();
100 self.last()
101 }
102}
103
104pub struct AtnFunction {
106 metadata: CallableMetadata,
107 angle_mode: Rc<RefCell<AngleMode>>,
108}
109
110impl AtnFunction {
111 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
152pub struct CintFunction {
154 metadata: CallableMetadata,
155}
156
157impl CintFunction {
158 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
200pub struct CosFunction {
202 metadata: CallableMetadata,
203 angle_mode: Rc<RefCell<AngleMode>>,
204}
205
206impl CosFunction {
207 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
246pub struct DegCommand {
248 metadata: CallableMetadata,
249 angle_mode: Rc<RefCell<AngleMode>>,
250}
251
252impl DegCommand {
253 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
283pub struct IntFunction {
285 metadata: CallableMetadata,
286}
287
288impl IntFunction {
289 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
331pub struct MaxFunction {
333 metadata: CallableMetadata,
334}
335
336impl MaxFunction {
337 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
377pub struct MinFunction {
379 metadata: CallableMetadata,
380}
381
382impl MinFunction {
383 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
423pub struct PiFunction {
425 metadata: CallableMetadata,
426}
427
428impl PiFunction {
429 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
454pub struct RadCommand {
456 metadata: CallableMetadata,
457 angle_mode: Rc<RefCell<AngleMode>>,
458}
459
460impl RadCommand {
461 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
491pub struct RandomizeCommand {
493 metadata: CallableMetadata,
494 prng: Rc<RefCell<Prng>>,
495}
496
497impl RandomizeCommand {
498 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
545pub struct RndFunction {
547 metadata: CallableMetadata,
548 prng: Rc<RefCell<Prng>>,
549}
550
551impl RndFunction {
552 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
608pub struct SinFunction {
610 metadata: CallableMetadata,
611 angle_mode: Rc<RefCell<AngleMode>>,
612}
613
614impl SinFunction {
615 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
654pub struct SqrFunction {
656 metadata: CallableMetadata,
657}
658
659impl SqrFunction {
660 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
699pub struct TanFunction {
701 metadata: CallableMetadata,
702 angle_mode: Rc<RefCell<AngleMode>>,
703}
704
705impl TanFunction {
706 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
745pub 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 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}