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