use async_trait::async_trait;
use endbasic_core::ast::{ArgSep, ExprType};
use endbasic_core::compiler::{
ArgSepSyntax, RepeatedSyntax, RepeatedTypeSyntax, RequiredValueSyntax, SingularArgSyntax,
};
use endbasic_core::exec::{Clearable, Error, Machine, Result, Scope};
use endbasic_core::syms::{Callable, CallableMetadata, CallableMetadataBuilder, Symbols};
use endbasic_core::value::double_to_integer;
use rand::rngs::SmallRng;
use rand::{RngCore, SeedableRng};
use std::borrow::Cow;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::rc::Rc;
const CATEGORY: &str = "Numerical functions";
pub enum AngleMode {
Degrees,
Radians,
}
struct ClearableAngleMode {
angle_mode: Rc<RefCell<AngleMode>>,
}
impl Clearable for ClearableAngleMode {
fn reset_state(&self, _syms: &mut Symbols) {
*self.angle_mode.borrow_mut() = AngleMode::Radians;
}
}
async fn get_angle(scope: &mut Scope<'_>, angle_mode: &AngleMode) -> Result<f64> {
debug_assert_eq!(1, scope.nargs());
let angle = scope.pop_double();
match angle_mode {
AngleMode::Degrees => Ok(angle.to_radians()),
AngleMode::Radians => Ok(angle),
}
}
pub struct Prng {
prng: SmallRng,
last: u32,
}
impl Prng {
pub fn new_from_entryopy() -> Self {
let mut prng = SmallRng::from_entropy();
let last = prng.next_u32();
Self { prng, last }
}
pub fn new_from_seed(seed: i32) -> Self {
let mut prng = SmallRng::seed_from_u64(seed as u64);
let last = prng.next_u32();
Self { prng, last }
}
fn last(&self) -> f64 {
(self.last as f64) / (u32::MAX as f64)
}
fn next(&mut self) -> f64 {
self.last = self.prng.next_u32();
self.last()
}
}
pub struct AtnFunction {
metadata: CallableMetadata,
angle_mode: Rc<RefCell<AngleMode>>,
}
impl AtnFunction {
pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("ATN")
.with_return_type(ExprType::Double)
.with_syntax(&[(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax { name: Cow::Borrowed("n"), vtype: ExprType::Double },
ArgSepSyntax::End,
)],
None,
)])
.with_category(CATEGORY)
.with_description(
"Computes the arc-tangent of a number.
The resulting angle is measured in degrees or radians depending on the angle mode as selected by \
the DEG and RAD commands.",
)
.build(),
angle_mode,
})
}
}
#[async_trait(?Send)]
impl Callable for AtnFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
debug_assert_eq!(1, scope.nargs());
let n = scope.pop_double();
match *self.angle_mode.borrow() {
AngleMode::Degrees => scope.return_double(n.atan().to_degrees()),
AngleMode::Radians => scope.return_double(n.atan()),
}
}
}
pub struct CintFunction {
metadata: CallableMetadata,
}
impl CintFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("CINT")
.with_return_type(ExprType::Integer)
.with_syntax(&[(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("expr"),
vtype: ExprType::Double,
},
ArgSepSyntax::End,
)],
None,
)])
.with_category(CATEGORY)
.with_description(
"Casts the given numeric expression to an integer (with rounding).
When casting a double value to an integer, the double value is first rounded to the closest \
integer. For example, 4.4 becomes 4, but both 4.5 and 4.6 become 5.",
)
.build(),
})
}
}
#[async_trait(?Send)]
impl Callable for CintFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
debug_assert_eq!(1, scope.nargs());
let (value, pos) = scope.pop_double_with_pos();
let i = double_to_integer(value).map_err(|e| Error::SyntaxError(pos, e.to_string()))?;
scope.return_integer(i)
}
}
pub struct CosFunction {
metadata: CallableMetadata,
angle_mode: Rc<RefCell<AngleMode>>,
}
impl CosFunction {
pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("COS")
.with_return_type(ExprType::Double)
.with_syntax(&[(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("angle"),
vtype: ExprType::Double,
},
ArgSepSyntax::End,
)],
None,
)])
.with_category(CATEGORY)
.with_description(
"Computes the cosine of an angle.
The input angle% or angle# is measured in degrees or radians depending on the angle mode as \
selected by the DEG and RAD commands.",
)
.build(),
angle_mode,
})
}
}
#[async_trait(?Send)]
impl Callable for CosFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
let angle = get_angle(&mut scope, &self.angle_mode.borrow()).await?;
scope.return_double(angle.cos())
}
}
pub struct DegCommand {
metadata: CallableMetadata,
angle_mode: Rc<RefCell<AngleMode>>,
}
impl DegCommand {
pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("DEG")
.with_syntax(&[(&[], None)])
.with_category(CATEGORY)
.with_description(
"Sets degrees mode of calculation.
The default condition for the trigonometric functions is to use radians. DEG configures the \
environment to use degrees until instructed otherwise.",
)
.build(),
angle_mode,
})
}
}
#[async_trait(?Send)]
impl Callable for DegCommand {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
debug_assert_eq!(0, scope.nargs());
*self.angle_mode.borrow_mut() = AngleMode::Degrees;
Ok(())
}
}
pub struct IntFunction {
metadata: CallableMetadata,
}
impl IntFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("INT")
.with_return_type(ExprType::Integer)
.with_syntax(&[(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("expr"),
vtype: ExprType::Double,
},
ArgSepSyntax::End,
)],
None,
)])
.with_category(CATEGORY)
.with_description(
"Casts the given numeric expression to an integer (with truncation).
When casting a double value to an integer, the double value is first truncated to the smallest
integer that is not larger than the double value. For example, all of 4.4, 4.5 and 4.6 become 4.",
)
.build(),
})
}
}
#[async_trait(?Send)]
impl Callable for IntFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
debug_assert_eq!(1, scope.nargs());
let (value, pos) = scope.pop_double_with_pos();
let i =
double_to_integer(value.floor()).map_err(|e| Error::SyntaxError(pos, e.to_string()))?;
scope.return_integer(i)
}
}
pub struct MaxFunction {
metadata: CallableMetadata,
}
impl MaxFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("MAX")
.with_return_type(ExprType::Double)
.with_syntax(&[(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("expr"),
type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Double),
sep: ArgSepSyntax::Exactly(ArgSep::Long),
require_one: true,
allow_missing: false,
}),
)])
.with_category(CATEGORY)
.with_description("Returns the maximum number out of a set of numbers.")
.build(),
})
}
}
#[async_trait(?Send)]
impl Callable for MaxFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
let mut max = f64::MIN;
while scope.nargs() > 0 {
let n = scope.pop_double();
if n > max {
max = n;
}
}
scope.return_double(max)
}
}
pub struct MinFunction {
metadata: CallableMetadata,
}
impl MinFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("MIN")
.with_return_type(ExprType::Double)
.with_syntax(&[(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("expr"),
type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Double),
sep: ArgSepSyntax::Exactly(ArgSep::Long),
require_one: true,
allow_missing: false,
}),
)])
.with_category(CATEGORY)
.with_description("Returns the minimum number out of a set of numbers.")
.build(),
})
}
}
#[async_trait(?Send)]
impl Callable for MinFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
let mut min = f64::MAX;
while scope.nargs() > 0 {
let n = scope.pop_double();
if n < min {
min = n;
}
}
scope.return_double(min)
}
}
pub struct PiFunction {
metadata: CallableMetadata,
}
impl PiFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("PI")
.with_return_type(ExprType::Double)
.with_syntax(&[(&[], None)])
.with_category(CATEGORY)
.with_description("Returns the Archimedes' constant.")
.build(),
})
}
}
#[async_trait(?Send)]
impl Callable for PiFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
debug_assert_eq!(0, scope.nargs());
scope.return_double(std::f64::consts::PI)
}
}
pub struct RadCommand {
metadata: CallableMetadata,
angle_mode: Rc<RefCell<AngleMode>>,
}
impl RadCommand {
pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("RAD")
.with_syntax(&[(&[], None)])
.with_category(CATEGORY)
.with_description(
"Sets radians mode of calculation.
The default condition for the trigonometric functions is to use radians but it can be set to \
degrees with the DEG command. RAD restores the environment to use radians mode.",
)
.build(),
angle_mode,
})
}
}
#[async_trait(?Send)]
impl Callable for RadCommand {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
debug_assert_eq!(0, scope.nargs());
*self.angle_mode.borrow_mut() = AngleMode::Radians;
Ok(())
}
}
pub struct RandomizeCommand {
metadata: CallableMetadata,
prng: Rc<RefCell<Prng>>,
}
impl RandomizeCommand {
pub fn new(prng: Rc<RefCell<Prng>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("RANDOMIZE")
.with_syntax(&[
(&[], None),
(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("seed"),
vtype: ExprType::Integer,
},
ArgSepSyntax::End,
)],
None,
),
])
.with_category(CATEGORY)
.with_description(
"Reinitializes the pseudo-random number generator.
If no seed is given, uses system entropy to create a new sequence of random numbers.
WARNING: These random numbers offer no cryptographic guarantees.",
)
.build(),
prng,
})
}
}
#[async_trait(?Send)]
impl Callable for RandomizeCommand {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
if scope.nargs() == 0 {
*self.prng.borrow_mut() = Prng::new_from_entryopy();
} else {
debug_assert_eq!(1, scope.nargs());
let n = scope.pop_integer();
*self.prng.borrow_mut() = Prng::new_from_seed(n);
}
Ok(())
}
}
pub struct RndFunction {
metadata: CallableMetadata,
prng: Rc<RefCell<Prng>>,
}
impl RndFunction {
pub fn new(prng: Rc<RefCell<Prng>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("RND")
.with_return_type(ExprType::Double)
.with_syntax(&[
(&[], None),
(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("n"),
vtype: ExprType::Integer,
},
ArgSepSyntax::End,
)],
None,
),
])
.with_category(CATEGORY)
.with_description(
"Returns a random number in the [0..1] range.
If n% is zero, returns the previously generated random number. If n% is positive or is not \
specified, returns a new random number.
If you need to generate an integer random number within a specific range, say [0..100], compute it \
with an expression like CINT%(RND#(1) * 100.0).
WARNING: These random numbers offer no cryptographic guarantees.",
)
.build(),
prng,
})
}
}
#[async_trait(?Send)]
impl Callable for RndFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
if scope.nargs() == 0 {
scope.return_double(self.prng.borrow_mut().next())
} else {
debug_assert_eq!(1, scope.nargs());
let (n, npos) = scope.pop_integer_with_pos();
match n.cmp(&0) {
Ordering::Equal => scope.return_double(self.prng.borrow_mut().last()),
Ordering::Greater => scope.return_double(self.prng.borrow_mut().next()),
Ordering::Less => Err(Error::SyntaxError(npos, "n% cannot be negative".to_owned())),
}
}
}
}
pub struct SinFunction {
metadata: CallableMetadata,
angle_mode: Rc<RefCell<AngleMode>>,
}
impl SinFunction {
pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("SIN")
.with_return_type(ExprType::Double)
.with_syntax(&[(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("angle"),
vtype: ExprType::Double,
},
ArgSepSyntax::End,
)],
None,
)])
.with_category(CATEGORY)
.with_description(
"Computes the sine of an angle.
The input angle% or angle# is measured in degrees or radians depending on the angle mode as \
selected by the DEG and RAD commands.",
)
.build(),
angle_mode,
})
}
}
#[async_trait(?Send)]
impl Callable for SinFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
let angle = get_angle(&mut scope, &self.angle_mode.borrow()).await?;
scope.return_double(angle.sin())
}
}
pub struct SqrFunction {
metadata: CallableMetadata,
}
impl SqrFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("SQR")
.with_return_type(ExprType::Double)
.with_syntax(&[(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax { name: Cow::Borrowed("num"), vtype: ExprType::Double },
ArgSepSyntax::End,
)],
None,
)])
.with_category(CATEGORY)
.with_description("Computes the square root of the given number.")
.build(),
})
}
}
#[async_trait(?Send)]
impl Callable for SqrFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
debug_assert_eq!(1, scope.nargs());
let (num, numpos) = scope.pop_double_with_pos();
if num < 0.0 {
return Err(Error::SyntaxError(
numpos,
"Cannot take square root of a negative number".to_owned(),
));
}
scope.return_double(num.sqrt())
}
}
pub struct TanFunction {
metadata: CallableMetadata,
angle_mode: Rc<RefCell<AngleMode>>,
}
impl TanFunction {
pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("TAN")
.with_return_type(ExprType::Double)
.with_syntax(&[(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("angle"),
vtype: ExprType::Double,
},
ArgSepSyntax::End,
)],
None,
)])
.with_category(CATEGORY)
.with_description(
"Computes the tangent of an angle.
The input angle% or angle# is measured in degrees or radians depending on the angle mode as \
selected by the DEG and RAD commands.",
)
.build(),
angle_mode,
})
}
}
#[async_trait(?Send)]
impl Callable for TanFunction {
fn metadata(&self) -> &CallableMetadata {
&self.metadata
}
async fn exec(&self, mut scope: Scope<'_>, _machine: &mut Machine) -> Result<()> {
let angle = get_angle(&mut scope, &self.angle_mode.borrow()).await?;
scope.return_double(angle.tan())
}
}
pub fn add_all(machine: &mut Machine) {
let angle_mode = Rc::from(RefCell::from(AngleMode::Radians));
let prng = Rc::from(RefCell::from(Prng::new_from_entryopy()));
machine.add_clearable(Box::from(ClearableAngleMode { angle_mode: angle_mode.clone() }));
machine.add_callable(AtnFunction::new(angle_mode.clone()));
machine.add_callable(CintFunction::new());
machine.add_callable(CosFunction::new(angle_mode.clone()));
machine.add_callable(DegCommand::new(angle_mode.clone()));
machine.add_callable(IntFunction::new());
machine.add_callable(MaxFunction::new());
machine.add_callable(MinFunction::new());
machine.add_callable(PiFunction::new());
machine.add_callable(RadCommand::new(angle_mode.clone()));
machine.add_callable(RandomizeCommand::new(prng.clone()));
machine.add_callable(RndFunction::new(prng));
machine.add_callable(SinFunction::new(angle_mode.clone()));
machine.add_callable(SqrFunction::new());
machine.add_callable(TanFunction::new(angle_mode));
}
#[cfg(test)]
mod tests {
use crate::testutils::*;
#[test]
fn test_atn() {
check_expr_ok(123f64.atan(), "ATN(123)");
check_expr_ok(45.5f64.atan(), "ATN(45.5)");
check_expr_ok_with_vars(123f64.atan(), "ATN(a)", [("a", 123i32.into())]);
check_expr_compilation_error("1:10: ATN expected n#", "ATN()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "ATN(FALSE)");
check_expr_compilation_error("1:10: ATN expected n#", "ATN(3, 4)");
}
#[test]
fn test_cint() {
check_expr_ok(0, "CINT(0.1)");
check_expr_ok(0, "CINT(-0.1)");
check_expr_ok(1, "CINT(0.9)");
check_expr_ok(-1, "CINT(-0.9)");
check_expr_ok_with_vars(1, "CINT(d)", [("d", 0.9f64.into())]);
check_expr_compilation_error("1:10: CINT expected expr#", "CINT()");
check_expr_compilation_error("1:15: BOOLEAN is not a number", "CINT(FALSE)");
check_expr_compilation_error("1:10: CINT expected expr#", "CINT(3.0, 4)");
check_expr_error(
"1:15: Cannot cast -1234567890123456 to integer due to overflow",
"CINT(-1234567890123456.0)",
);
}
#[test]
fn test_cos() {
check_expr_ok(123f64.cos(), "COS(123)");
check_expr_ok(45.5f64.cos(), "COS(45.5)");
check_expr_ok_with_vars(123f64.cos(), "COS(i)", [("i", 123i32.into())]);
check_expr_compilation_error("1:10: COS expected angle#", "COS()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "COS(FALSE)");
check_expr_compilation_error("1:10: COS expected angle#", "COS(3, 4)");
}
#[test]
fn test_deg_rad_commands() {
let mut t = Tester::default();
t.run("result = SIN(90)").expect_var("result", 90f64.sin()).check();
t.run("DEG: result = SIN(90)").expect_var("result", 1.0).check();
t.run("RAD: result = SIN(90)").expect_var("result", 90f64.sin()).check();
}
#[test]
fn test_deg_rad_reset_on_clear() {
let mut t = Tester::default();
t.run("DEG").check();
t.get_machine().clear();
t.run("result = SIN(90)").expect_clear().expect_var("result", 90f64.sin()).check();
}
#[test]
fn test_deg_rad_errors() {
check_stmt_compilation_err("1:1: DEG expected no arguments", "DEG 1");
check_stmt_compilation_err("1:1: RAD expected no arguments", "RAD 1");
}
#[test]
fn test_int() {
check_expr_ok(0, "INT(0.1)");
check_expr_ok(-1, "INT(-0.1)");
check_expr_ok(0, "INT(0.9)");
check_expr_ok(-1, "INT(-0.9)");
check_expr_ok_with_vars(0, "INT(d)", [("d", 0.9f64.into())]);
check_expr_compilation_error("1:10: INT expected expr#", "INT()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "INT(FALSE)");
check_expr_compilation_error("1:10: INT expected expr#", "INT(3.0, 4)");
check_expr_error(
"1:14: Cannot cast -1234567890123456 to integer due to overflow",
"INT(-1234567890123456.0)",
);
}
#[test]
fn test_max() {
check_expr_ok(0.0, "MAX(0)");
check_expr_ok(0.0, "MAX(0, 0)");
check_expr_ok(0.0, "MAX(0.0)");
check_expr_ok(0.0, "MAX(0.0, 0.0)");
check_expr_ok(1.0, "MAX(1)");
check_expr_ok(5.0, "MAX(5, 3, 4)");
check_expr_ok(-3.0, "MAX(-5, -3, -4)");
check_expr_ok(1.0, "MAX(1.0)");
check_expr_ok(5.3, "MAX(5.3, 3.5, 4.2)");
check_expr_ok(-3.5, "MAX(-5.3, -3.5, -4.2)");
check_expr_ok(2.5, "MAX(1, 0.5, 2.5, 2)");
check_expr_ok_with_vars(
5.0,
"MAX(i, j, k)",
[("i", 5i32.into()), ("j", 3i32.into()), ("k", 4i32.into())],
);
check_expr_compilation_error("1:10: MAX expected expr1#[, .., exprN#]", "MAX()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "MAX(FALSE)");
}
#[test]
fn test_min() {
check_expr_ok(0.0, "MIN(0)");
check_expr_ok(0.0, "MIN(0, 0)");
check_expr_ok(0.0, "MIN(0.0)");
check_expr_ok(0.0, "MIN(0.0, 0.0)");
check_expr_ok(1.0, "MIN(1)");
check_expr_ok(3.0, "MIN(5, 3, 4)");
check_expr_ok(-5.0, "MIN(-5, -3, -4)");
check_expr_ok(1.0, "MIN(1.0)");
check_expr_ok(3.5, "MIN(5.3, 3.5, 4.2)");
check_expr_ok(-5.3, "MIN(-5.3, -3.5, -4.2)");
check_expr_ok(0.5, "MIN(1, 0.5, 2.5, 2)");
check_expr_ok_with_vars(
3.0,
"MIN(i, j, k)",
[("i", 5i32.into()), ("j", 3i32.into()), ("k", 4i32.into())],
);
check_expr_compilation_error("1:10: MIN expected expr1#[, .., exprN#]", "MIN()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "MIN(FALSE)");
}
#[test]
fn test_pi() {
check_expr_ok(std::f64::consts::PI, "PI");
check_expr_compilation_error("1:10: PI expected no arguments", "PI()");
check_expr_compilation_error("1:10: PI expected no arguments", "PI(3)");
}
#[test]
fn test_randomize_and_rnd() {
check_expr_ok(false, "RND(1) = RND(1)");
check_expr_ok(false, "RND(1) = RND(10)");
check_expr_ok(true, "RND(0) = RND(0)");
let mut t = Tester::default();
t.run("RANDOMIZE 10").check();
t.run("result = RND(1)").expect_var("result", 0.7097578208683426).check();
t.run("result = RND(1.1)").expect_var("result", 0.2205558922655312).check();
t.run("result = RND(0)").expect_var("result", 0.2205558922655312).check();
t.run("result = RND(10)").expect_var("result", 0.8273883964464507).check();
t.run("RANDOMIZE 10.2").expect_var("result", 0.8273883964464507).check();
t.run("result = RND(1)").expect_var("result", 0.7097578208683426).check();
check_expr_compilation_error("1:10: RND expected <> | <n%>", "RND(1, 7)");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "RND(FALSE)");
check_expr_error("1:14: n% cannot be negative", "RND(-1)");
check_stmt_compilation_err("1:1: RANDOMIZE expected <> | <seed%>", "RANDOMIZE ,");
check_stmt_compilation_err("1:11: BOOLEAN is not a number", "RANDOMIZE TRUE");
}
#[test]
fn test_sin() {
check_expr_ok(123f64.sin(), "SIN(123)");
check_expr_ok(45.5f64.sin(), "SIN(45.5)");
check_expr_ok_with_vars(123f64.sin(), "SIN(i)", [("i", 123i32.into())]);
check_expr_compilation_error("1:10: SIN expected angle#", "SIN()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "SIN(FALSE)");
check_expr_compilation_error("1:10: SIN expected angle#", "SIN(3, 4)");
}
#[test]
fn test_sqr() {
check_expr_ok(0f64.sqrt(), "SQR(0)");
check_expr_ok(0f64.sqrt(), "SQR(-0.0)");
check_expr_ok(9f64.sqrt(), "SQR(9)");
check_expr_ok(100.50f64.sqrt(), "SQR(100.50)");
check_expr_ok_with_vars(9f64.sqrt(), "SQR(i)", [("i", 9i32.into())]);
check_expr_compilation_error("1:10: SQR expected num#", "SQR()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "SQR(FALSE)");
check_expr_compilation_error("1:10: SQR expected num#", "SQR(3, 4)");
check_expr_error("1:14: Cannot take square root of a negative number", "SQR(-3)");
check_expr_error("1:14: Cannot take square root of a negative number", "SQR(-0.1)");
}
#[test]
fn test_tan() {
check_expr_ok(123f64.tan(), "TAN(123)");
check_expr_ok(45.5f64.tan(), "TAN(45.5)");
check_expr_ok_with_vars(123f64.tan(), "TAN(i)", [("i", 123i32.into())]);
check_expr_compilation_error("1:10: TAN expected angle#", "TAN()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "TAN(FALSE)");
check_expr_compilation_error("1:10: TAN expected angle#", "TAN(3, 4)");
}
}