use endbasic_core::{
ArgSep, ArgSepSyntax, CallError, CallResult, Callable, CallableMetadata,
CallableMetadataBuilder, ExprType, RepeatedSyntax, RepeatedTypeSyntax, RequiredValueSyntax,
Scope, SingularArgSyntax,
};
use rand::rngs::SmallRng;
use rand::{RngCore, SeedableRng};
use std::borrow::Cow;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::rc::Rc;
use crate::{Clearable, MachineBuilder};
const CATEGORY: &str = "Numerical functions";
pub fn double_to_integer(d: f64) -> Result<i32, String> {
let d = d.round();
if d.is_finite() && d >= (i32::MIN as f64) && (d <= i32::MAX as f64) {
Ok(d as i32)
} else {
Err(format!("Cannot cast {} to integer due to overflow", d))
}
}
pub enum AngleMode {
Degrees,
Radians,
}
struct ClearableAngleMode {
angle_mode: Rc<RefCell<AngleMode>>,
}
impl Clearable for ClearableAngleMode {
fn reset_state(&self) {
*self.angle_mode.borrow_mut() = AngleMode::Radians;
}
}
fn get_angle(scope: &mut Scope<'_>, angle_mode: &AngleMode) -> CallResult<f64> {
debug_assert_eq!(1, scope.nargs());
let angle = scope.get_double(0);
match angle_mode {
AngleMode::Degrees => Ok(angle.to_radians()),
AngleMode::Radians => Ok(angle),
}
}
fn to_angle(angle: f64, angle_mode: &AngleMode) -> f64 {
match angle_mode {
AngleMode::Degrees => angle.to_degrees(),
AngleMode::Radians => 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 AbsFunction {
metadata: Rc<CallableMetadata>,
}
impl AbsFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("ABS")
.with_return_type(ExprType::Double)
.with_syntax(&[(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("expr"),
vtype: ExprType::Double,
},
ArgSepSyntax::End,
)],
None,
)])
.with_category(CATEGORY)
.with_description("Returns the absolute value of a number.")
.build(),
})
}
}
impl Callable for AbsFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(1, scope.nargs());
let value = scope.get_double(0);
scope.return_double(value.abs())
}
}
pub struct AcosFunction {
metadata: Rc<CallableMetadata>,
angle_mode: Rc<RefCell<AngleMode>>,
}
impl AcosFunction {
pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("ACOS")
.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-cosine 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,
})
}
}
impl Callable for AcosFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(1, scope.nargs());
let n = scope.get_double(0);
if !(-1.0..=1.0).contains(&n) {
return Err(CallError::Syntax(
scope.get_pos(0),
"Cannot take arc-cosine of a number outside of [-1, 1]".to_owned(),
));
}
scope.return_double(to_angle(n.acos(), &self.angle_mode.borrow()))
}
}
pub struct AsinFunction {
metadata: Rc<CallableMetadata>,
angle_mode: Rc<RefCell<AngleMode>>,
}
impl AsinFunction {
pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("ASIN")
.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-sine 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,
})
}
}
impl Callable for AsinFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(1, scope.nargs());
let n = scope.get_double(0);
if !(-1.0..=1.0).contains(&n) {
return Err(CallError::Syntax(
scope.get_pos(0),
"Cannot take arc-sine of a number outside of [-1, 1]".to_owned(),
));
}
scope.return_double(to_angle(n.asin(), &self.angle_mode.borrow()))
}
}
pub struct AtnFunction {
metadata: Rc<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,
})
}
}
impl Callable for AtnFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(1, scope.nargs());
let n = scope.get_double(0);
scope.return_double(to_angle(n.atan(), &self.angle_mode.borrow()))
}
}
pub struct CintFunction {
metadata: Rc<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(),
})
}
}
impl Callable for CintFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(1, scope.nargs());
let value = scope.get_double(0);
let i = double_to_integer(value)
.map_err(|e| CallError::Syntax(scope.get_pos(0), e.to_string()))?;
scope.return_integer(i)
}
}
pub struct CosFunction {
metadata: Rc<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,
})
}
}
impl Callable for CosFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, mut scope: Scope<'_>) -> CallResult<()> {
let angle = get_angle(&mut scope, &self.angle_mode.borrow())?;
scope.return_double(angle.cos())
}
}
pub struct DegCommand {
metadata: Rc<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,
})
}
}
impl Callable for DegCommand {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(0, scope.nargs());
*self.angle_mode.borrow_mut() = AngleMode::Degrees;
Ok(())
}
}
pub struct ExpFunction {
metadata: Rc<CallableMetadata>,
}
impl ExpFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("EXP")
.with_return_type(ExprType::Double)
.with_syntax(&[(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("expr"),
vtype: ExprType::Double,
},
ArgSepSyntax::End,
)],
None,
)])
.with_category(CATEGORY)
.with_description("Raises Euler's number to the power of a number.")
.build(),
})
}
}
impl Callable for ExpFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(1, scope.nargs());
let value = scope.get_double(0);
let exp = value.exp();
if !exp.is_finite() {
return Err(CallError::Syntax(
scope.get_pos(0),
format!("Cannot exponentiate {} due to overflow", value),
));
}
scope.return_double(exp)
}
}
pub struct FixFunction {
metadata: Rc<CallableMetadata>,
}
impl FixFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("FIX")
.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 (towards zero).
When casting a double value to an integer, the double value is first truncated towards zero. For \
example, 4.9 becomes 4 and -4.9 becomes -4.",
)
.build(),
})
}
}
impl Callable for FixFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(1, scope.nargs());
let value = scope.get_double(0);
let i = double_to_integer(value.trunc())
.map_err(|e| CallError::Syntax(scope.get_pos(0), e.to_string()))?;
scope.return_integer(i)
}
}
pub struct IntFunction {
metadata: Rc<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(),
})
}
}
impl Callable for IntFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(1, scope.nargs());
let value = scope.get_double(0);
let i = double_to_integer(value.floor())
.map_err(|e| CallError::Syntax(scope.get_pos(0), e.to_string()))?;
scope.return_integer(i)
}
}
pub struct LogFunction {
metadata: Rc<CallableMetadata>,
}
impl LogFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("LOG")
.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 natural logarithm of the given number.")
.build(),
})
}
}
impl Callable for LogFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(1, scope.nargs());
let num = scope.get_double(0);
if num <= 0.0 {
return Err(CallError::Syntax(
scope.get_pos(0),
"Cannot take logarithm of zero or a negative number".to_owned(),
));
}
scope.return_double(num.ln())
}
}
pub struct MaxFunction {
metadata: Rc<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(),
})
}
}
impl Callable for MaxFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
let mut max = f64::MIN;
for i in 0..(scope.nargs() as u8) {
let n = scope.get_double(i);
if n > max {
max = n;
}
}
scope.return_double(max)
}
}
pub struct MinFunction {
metadata: Rc<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(),
})
}
}
impl Callable for MinFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
let mut min = f64::MAX;
for i in 0..(scope.nargs() as u8) {
let n = scope.get_double(i);
if n < min {
min = n;
}
}
scope.return_double(min)
}
}
pub struct PiFunction {
metadata: Rc<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(),
})
}
}
impl Callable for PiFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(0, scope.nargs());
scope.return_double(std::f64::consts::PI)
}
}
pub struct RadCommand {
metadata: Rc<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,
})
}
}
impl Callable for RadCommand {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(0, scope.nargs());
*self.angle_mode.borrow_mut() = AngleMode::Radians;
Ok(())
}
}
pub struct RandomizeCommand {
metadata: Rc<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,
})
}
}
impl Callable for RandomizeCommand {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
if scope.nargs() == 0 {
*self.prng.borrow_mut() = Prng::new_from_entryopy();
} else {
debug_assert_eq!(1, scope.nargs());
let n = scope.get_integer(0);
*self.prng.borrow_mut() = Prng::new_from_seed(n);
}
Ok(())
}
}
pub struct RndFunction {
metadata: Rc<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 negative, resets the pseudo-random number generator to a sequence derived from n% and \
returns its first value. 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,
})
}
}
impl Callable for RndFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
if scope.nargs() == 0 {
scope.return_double(self.prng.borrow_mut().next())
} else {
debug_assert_eq!(1, scope.nargs());
let n = scope.get_integer(0);
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 => {
let mut prng = self.prng.borrow_mut();
*prng = Prng::new_from_seed(n);
scope.return_double(prng.last())
}
}
}
}
}
pub struct RoundFunction {
metadata: Rc<CallableMetadata>,
}
impl RoundFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("ROUND")
.with_return_type(ExprType::Double)
.with_syntax(&[
(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("expr"),
vtype: ExprType::Double,
},
ArgSepSyntax::End,
)],
None,
),
(
&[
SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("expr"),
vtype: ExprType::Double,
},
ArgSepSyntax::Exactly(ArgSep::Long),
),
SingularArgSyntax::RequiredValue(
RequiredValueSyntax {
name: Cow::Borrowed("decimals"),
vtype: ExprType::Integer,
},
ArgSepSyntax::End,
),
],
None,
),
])
.with_category(CATEGORY)
.with_description(
"Rounds a number to the nearest value.
If decimals% is omitted, rounds to the nearest integer. If decimals% is given, rounds to that \
many digits after the decimal point. Negative values round digits before the decimal point.",
)
.build(),
})
}
}
impl Callable for RoundFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
let value = scope.get_double(0);
if scope.nargs() == 1 {
scope.return_double(value.round())
} else {
debug_assert_eq!(2, scope.nargs());
let decimals = scope.get_integer(1);
let factor = 10f64.powi(decimals);
scope.return_double((value * factor).round() / factor)
}
}
}
pub struct SgnFunction {
metadata: Rc<CallableMetadata>,
}
impl SgnFunction {
pub fn new() -> Rc<Self> {
Rc::from(Self {
metadata: CallableMetadataBuilder::new("SGN")
.with_return_type(ExprType::Integer)
.with_syntax(&[(
&[SingularArgSyntax::RequiredValue(
RequiredValueSyntax { name: Cow::Borrowed("n"), vtype: ExprType::Double },
ArgSepSyntax::End,
)],
None,
)])
.with_category(CATEGORY)
.with_description("Returns -1, 0, or 1 depending on the sign of a number.")
.build(),
})
}
}
impl Callable for SgnFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(1, scope.nargs());
let n = scope.get_double(0);
let sign = if n > 0.0 {
1
} else if n < 0.0 {
-1
} else {
0
};
scope.return_integer(sign)
}
}
pub struct SinFunction {
metadata: Rc<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,
})
}
}
impl Callable for SinFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, mut scope: Scope<'_>) -> CallResult<()> {
let angle = get_angle(&mut scope, &self.angle_mode.borrow())?;
scope.return_double(angle.sin())
}
}
pub struct SqrFunction {
metadata: Rc<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(),
})
}
}
impl Callable for SqrFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
debug_assert_eq!(1, scope.nargs());
let num = scope.get_double(0);
if num < 0.0 {
return Err(CallError::Syntax(
scope.get_pos(0),
"Cannot take square root of a negative number".to_owned(),
));
}
scope.return_double(num.sqrt())
}
}
pub struct TanFunction {
metadata: Rc<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,
})
}
}
impl Callable for TanFunction {
fn metadata(&self) -> Rc<CallableMetadata> {
self.metadata.clone()
}
fn exec(&self, mut scope: Scope<'_>) -> CallResult<()> {
let angle = get_angle(&mut scope, &self.angle_mode.borrow())?;
scope.return_double(angle.tan())
}
}
pub fn add_all(machine: &mut MachineBuilder) {
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(AbsFunction::new());
machine.add_callable(AcosFunction::new(angle_mode.clone()));
machine.add_callable(AsinFunction::new(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(ExpFunction::new());
machine.add_callable(FixFunction::new());
machine.add_callable(IntFunction::new());
machine.add_callable(LogFunction::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(RoundFunction::new());
machine.add_callable(SgnFunction::new());
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_abs() {
check_expr_ok(0.0, "ABS(0)");
check_expr_ok(3.0, "ABS(3)");
check_expr_ok(3.5, "ABS(-3.5)");
check_expr_ok_with_vars(2.5, "ABS(d)", [("d", (-2.5f64).into())]);
check_expr_compilation_error("1:10: ABS expected expr#", "ABS()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "ABS(FALSE)");
check_expr_compilation_error("1:10: ABS expected expr#", "ABS(3, 4)");
}
#[test]
fn test_acos() {
check_expr_ok(1f64.acos(), "ACOS(1)");
check_expr_ok(0.5f64.acos(), "ACOS(0.5)");
check_expr_ok_with_vars(0.5f64.acos(), "ACOS(d)", [("d", 0.5f64.into())]);
let mut t = Tester::default();
t.run("DEG: result = ACOS(0.5)").expect_var("result", 0.5f64.acos().to_degrees()).check();
check_expr_compilation_error("1:10: ACOS expected n#", "ACOS()");
check_expr_compilation_error("1:15: BOOLEAN is not a number", "ACOS(FALSE)");
check_expr_compilation_error("1:10: ACOS expected n#", "ACOS(3, 4)");
check_expr_error(
"1:15: Cannot take arc-cosine of a number outside of [-1, 1]",
"ACOS(1.1)",
);
}
#[test]
fn test_asin() {
check_expr_ok(0f64.asin(), "ASIN(0)");
check_expr_ok(0.5f64.asin(), "ASIN(0.5)");
check_expr_ok_with_vars(0.5f64.asin(), "ASIN(d)", [("d", 0.5f64.into())]);
let mut t = Tester::default();
t.run("DEG: result = ASIN(0.5)").expect_var("result", 0.5f64.asin().to_degrees()).check();
check_expr_compilation_error("1:10: ASIN expected n#", "ASIN()");
check_expr_compilation_error("1:15: BOOLEAN is not a number", "ASIN(FALSE)");
check_expr_compilation_error("1:10: ASIN expected n#", "ASIN(3, 4)");
check_expr_error("1:15: Cannot take arc-sine of a number outside of [-1, 1]", "ASIN(-1.1)");
}
#[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() {
Tester::default()
.run("DEG")
.check()
.clear()
.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_exp() {
check_expr_ok(0f64.exp(), "EXP(0)");
check_expr_ok(1f64.exp(), "EXP(1)");
check_expr_ok((-2.5f64).exp(), "EXP(-2.5)");
check_expr_ok_with_vars(2f64.exp(), "EXP(i)", [("i", 2i32.into())]);
check_expr_compilation_error("1:10: EXP expected expr#", "EXP()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "EXP(FALSE)");
check_expr_compilation_error("1:10: EXP expected expr#", "EXP(3, 4)");
check_expr_error("1:14: Cannot exponentiate 1000 due to overflow", "EXP(1000)");
}
#[test]
fn test_fix() {
check_expr_ok(0, "FIX(0.1)");
check_expr_ok(0, "FIX(-0.1)");
check_expr_ok(4, "FIX(4.9)");
check_expr_ok(-4, "FIX(-4.9)");
check_expr_ok_with_vars(-4, "FIX(d)", [("d", (-4.9f64).into())]);
check_expr_compilation_error("1:10: FIX expected expr#", "FIX()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "FIX(FALSE)");
check_expr_compilation_error("1:10: FIX expected expr#", "FIX(3.0, 4)");
check_expr_error(
"1:14: Cannot cast -1234567890123456 to integer due to overflow",
"FIX(-1234567890123456.0)",
);
}
#[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_log() {
check_expr_ok(1f64.ln(), "LOG(1)");
check_expr_ok(10f64.ln(), "LOG(10)");
check_expr_ok_with_vars(10f64.ln(), "LOG(i)", [("i", 10i32.into())]);
check_expr_compilation_error("1:10: LOG expected num#", "LOG()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "LOG(FALSE)");
check_expr_compilation_error("1:10: LOG expected num#", "LOG(3, 4)");
check_expr_error("1:14: Cannot take logarithm of zero or a negative number", "LOG(0)");
check_expr_error("1:14: Cannot take logarithm of zero or a negative number", "LOG(-0.1)");
}
#[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(-1) = RND(-1)");
check_expr_ok(false, "RND(-1) = RND(-2)");
check_expr_ok(true, "RND(0) = RND(0)");
Tester::default()
.run("RANDOMIZE 10")
.check()
.run("result = RND(1)")
.expect_var("result", 0.7097578208683426)
.check()
.run("result = RND(-1)")
.expect_var("result", 0.6150244305876607)
.check()
.run("result = RND(1)")
.expect_var("result", 0.11707478019340774)
.check()
.run("result = RND(-1)")
.expect_var("result", 0.6150244305876607)
.check()
.run("result = RND(1.1)")
.expect_var("result", 0.11707478019340774)
.check()
.run("result = RND(0)")
.expect_var("result", 0.11707478019340774)
.check()
.run("result = RND(10)")
.expect_var("result", 0.8423819585801992)
.check()
.run("RANDOMIZE 10.2")
.expect_var("result", 0.8423819585801992)
.check()
.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_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_round() {
check_expr_ok(1.0, "ROUND(0.5)");
check_expr_ok(-1.0, "ROUND(-0.5)");
check_expr_ok(12.35, "ROUND(12.345, 2)");
check_expr_ok(12.3, "ROUND(12.345, 1)");
check_expr_ok(120.0, "ROUND(123.45, -1)");
check_expr_ok_with_vars(12.35, "ROUND(d, 2)", [("d", 12.345f64.into())]);
check_expr_compilation_error(
"1:10: ROUND expected <expr#> | <expr#, decimals%>",
"ROUND()",
);
check_expr_compilation_error("1:16: BOOLEAN is not a number", "ROUND(FALSE)");
check_expr_compilation_error(
"1:10: ROUND expected <expr#> | <expr#, decimals%>",
"ROUND(1, 2, 3)",
);
}
#[test]
fn test_sgn() {
check_expr_ok(-1, "SGN(-3)");
check_expr_ok(0, "SGN(0)");
check_expr_ok(1, "SGN(3.5)");
check_expr_ok_with_vars(-1, "SGN(d)", [("d", (-1.5f64).into())]);
check_expr_compilation_error("1:10: SGN expected n#", "SGN()");
check_expr_compilation_error("1:14: BOOLEAN is not a number", "SGN(FALSE)");
check_expr_compilation_error("1:10: SGN expected n#", "SGN(3, 4)");
}
#[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)");
}
}