1use 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
33const CATEGORY: &str = "Numerical functions";
35
36pub 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
46pub enum AngleMode {
48 Degrees,
50
51 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
65fn 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
77pub struct Prng {
81 prng: SmallRng,
82 last: u32,
83}
84
85impl Prng {
86 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 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 fn last(&self) -> f64 {
102 (self.last as f64) / (u32::MAX as f64)
103 }
104
105 fn next(&mut self) -> f64 {
107 self.last = self.prng.next_u32();
108 self.last()
109 }
110}
111
112pub struct AtnFunction {
114 metadata: Rc<CallableMetadata>,
115 angle_mode: Rc<RefCell<AngleMode>>,
116}
117
118impl AtnFunction {
119 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
159pub struct CintFunction {
161 metadata: Rc<CallableMetadata>,
162}
163
164impl CintFunction {
165 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
206pub struct CosFunction {
208 metadata: Rc<CallableMetadata>,
209 angle_mode: Rc<RefCell<AngleMode>>,
210}
211
212impl CosFunction {
213 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
251pub struct DegCommand {
253 metadata: Rc<CallableMetadata>,
254 angle_mode: Rc<RefCell<AngleMode>>,
255}
256
257impl DegCommand {
258 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
287pub struct IntFunction {
289 metadata: Rc<CallableMetadata>,
290}
291
292impl IntFunction {
293 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
334pub struct MaxFunction {
336 metadata: Rc<CallableMetadata>,
337}
338
339impl MaxFunction {
340 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
379pub struct MinFunction {
381 metadata: Rc<CallableMetadata>,
382}
383
384impl MinFunction {
385 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
424pub struct PiFunction {
426 metadata: Rc<CallableMetadata>,
427}
428
429impl PiFunction {
430 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
454pub struct RadCommand {
456 metadata: Rc<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
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
490pub struct RandomizeCommand {
492 metadata: Rc<CallableMetadata>,
493 prng: Rc<RefCell<Prng>>,
494}
495
496impl RandomizeCommand {
497 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
543pub struct RndFunction {
545 metadata: Rc<CallableMetadata>,
546 prng: Rc<RefCell<Prng>>,
547}
548
549impl RndFunction {
550 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
605pub struct SinFunction {
607 metadata: Rc<CallableMetadata>,
608 angle_mode: Rc<RefCell<AngleMode>>,
609}
610
611impl SinFunction {
612 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
650pub struct SqrFunction {
652 metadata: Rc<CallableMetadata>,
653}
654
655impl SqrFunction {
656 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
694pub struct TanFunction {
696 metadata: Rc<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
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
739pub 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 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}