use super::super::ast::Expr;
use super::super::eval::{Engine, EvalContext};
use super::super::value::{ErrorKind, Value};
use super::convert;
use super::engineering_complex;
use super::kernel::EngineeringFunction;
use super::special_functions::bessel::{BesselFamily, worksheet_bessel};
use super::util::{required_number, required_text};
const MAX_BIT_VALUE: u64 = (1_u64 << 48) - 1;
pub(super) fn call(
engine: &Engine<'_>,
context: EvalContext<'_>,
function: EngineeringFunction,
args: &[Expr],
) -> Value {
match function {
EngineeringFunction::BesselI => worksheet_bessel(engine, context, args, BesselFamily::I),
EngineeringFunction::BesselJ => worksheet_bessel(engine, context, args, BesselFamily::J),
EngineeringFunction::BesselK => worksheet_bessel(engine, context, args, BesselFamily::K),
EngineeringFunction::BesselY => worksheet_bessel(engine, context, args, BesselFamily::Y),
EngineeringFunction::Convert => convert::call(engine, context, args),
EngineeringFunction::Complex => engineering_complex::construct(engine, context, args),
EngineeringFunction::BitAnd => {
bit_binary(engine, context, args, |left, right| left & right)
}
EngineeringFunction::BitOr => bit_binary(engine, context, args, |left, right| left | right),
EngineeringFunction::BitXor => {
bit_binary(engine, context, args, |left, right| left ^ right)
}
EngineeringFunction::BitLShift => bit_shift(engine, context, args, true),
EngineeringFunction::BitRShift => bit_shift(engine, context, args, false),
EngineeringFunction::Bin2Dec => {
convert_source(engine, context, args, SourceRadix::Binary, None)
}
EngineeringFunction::Bin2Hex => convert_source(
engine,
context,
args,
SourceRadix::Binary,
Some(TargetRadix::Hex),
),
EngineeringFunction::Bin2Oct => convert_source(
engine,
context,
args,
SourceRadix::Binary,
Some(TargetRadix::Octal),
),
EngineeringFunction::Hex2Bin => convert_source(
engine,
context,
args,
SourceRadix::Hex,
Some(TargetRadix::Binary),
),
EngineeringFunction::Hex2Dec => {
convert_source(engine, context, args, SourceRadix::Hex, None)
}
EngineeringFunction::Hex2Oct => convert_source(
engine,
context,
args,
SourceRadix::Hex,
Some(TargetRadix::Octal),
),
EngineeringFunction::Oct2Bin => convert_source(
engine,
context,
args,
SourceRadix::Octal,
Some(TargetRadix::Binary),
),
EngineeringFunction::Oct2Dec => {
convert_source(engine, context, args, SourceRadix::Octal, None)
}
EngineeringFunction::Oct2Hex => convert_source(
engine,
context,
args,
SourceRadix::Octal,
Some(TargetRadix::Hex),
),
EngineeringFunction::Dec2Bin => convert_decimal(engine, context, args, TargetRadix::Binary),
EngineeringFunction::Dec2Hex => convert_decimal(engine, context, args, TargetRadix::Hex),
EngineeringFunction::Dec2Oct => convert_decimal(engine, context, args, TargetRadix::Octal),
EngineeringFunction::Delta => comparison(engine, context, args, true),
EngineeringFunction::GeStep => comparison(engine, context, args, false),
EngineeringFunction::Erf => erf(engine, context, args, false),
EngineeringFunction::ErfPrecise => erf(engine, context, args, true),
EngineeringFunction::Erfc | EngineeringFunction::ErfcPrecise => erfc(engine, context, args),
EngineeringFunction::ImAbs => engineering_complex::magnitude(engine, context, args),
EngineeringFunction::ImArgument => engineering_complex::argument(engine, context, args),
EngineeringFunction::ImConjugate => engineering_complex::conjugate(engine, context, args),
EngineeringFunction::ImDiv => engineering_complex::divide(engine, context, args),
EngineeringFunction::ImExp => engineering_complex::exponential(engine, context, args),
EngineeringFunction::ImImaginary => engineering_complex::imaginary(engine, context, args),
EngineeringFunction::ImLn => engineering_complex::logarithm(engine, context, args),
EngineeringFunction::ImPower => engineering_complex::power(engine, context, args),
EngineeringFunction::ImProduct => engineering_complex::product(engine, context, args),
EngineeringFunction::ImReal => engineering_complex::real(engine, context, args),
EngineeringFunction::ImSqrt => engineering_complex::square_root(engine, context, args),
EngineeringFunction::ImSub => engineering_complex::subtract(engine, context, args),
EngineeringFunction::ImSum => engineering_complex::sum(engine, context, args),
}
}
fn bit_binary(
engine: &Engine<'_>,
context: EvalContext<'_>,
args: &[Expr],
operation: impl FnOnce(u64, u64) -> u64,
) -> Value {
if args.len() != 2 {
return Value::Error(ErrorKind::Value);
}
match (
required_bit_value(engine, context, &args[0]),
required_bit_value(engine, context, &args[1]),
) {
(Ok(left), Ok(right)) => Value::Number(operation(left, right) as f64),
(Err(kind), _) | (_, Err(kind)) => Value::Error(kind),
}
}
fn bit_shift(engine: &Engine<'_>, context: EvalContext<'_>, args: &[Expr], left: bool) -> Value {
if args.len() != 2 {
return Value::Error(ErrorKind::Value);
}
let number = match required_bit_value(engine, context, &args[0]) {
Ok(number) => number,
Err(kind) => return Value::Error(kind),
};
let shift = match required_number(engine, context, &args[1]) {
Ok(shift) if shift.abs().trunc() <= 53.0 => shift.trunc() as i32,
Ok(_) => return Value::Error(ErrorKind::Num),
Err(kind) => return Value::Error(kind),
};
let shift_left = left == (shift >= 0);
let amount = shift.unsigned_abs();
let result = if shift_left {
number
.checked_shl(amount)
.filter(|result| *result <= MAX_BIT_VALUE)
} else {
number.checked_shr(amount)
};
result.map_or(Value::Error(ErrorKind::Num), |result| {
Value::Number(result as f64)
})
}
fn required_bit_value(
engine: &Engine<'_>,
context: EvalContext<'_>,
expr: &Expr,
) -> Result<u64, ErrorKind> {
match required_number(engine, context, expr)? {
number if number >= 0.0 && number <= MAX_BIT_VALUE as f64 && number.fract() == 0.0 => {
Ok(number as u64)
}
_ => Err(ErrorKind::Num),
}
}
#[derive(Debug, Clone, Copy)]
enum SourceRadix {
Binary,
Octal,
Hex,
}
impl SourceRadix {
const fn radix(self) -> u32 {
match self {
Self::Binary => 2,
Self::Octal => 8,
Self::Hex => 16,
}
}
const fn bits(self) -> u32 {
match self {
Self::Binary => 10,
Self::Octal => 30,
Self::Hex => 40,
}
}
}
#[derive(Debug, Clone, Copy)]
enum TargetRadix {
Binary,
Octal,
Hex,
}
impl TargetRadix {
const fn radix(self) -> u32 {
match self {
Self::Binary => 2,
Self::Octal => 8,
Self::Hex => 16,
}
}
const fn bits(self) -> u32 {
match self {
Self::Binary => 10,
Self::Octal => 30,
Self::Hex => 40,
}
}
}
fn convert_source(
engine: &Engine<'_>,
context: EvalContext<'_>,
args: &[Expr],
source: SourceRadix,
target: Option<TargetRadix>,
) -> Value {
let valid_len = if target.is_some() {
(1..=2).contains(&args.len())
} else {
args.len() == 1
};
if !valid_len {
return Value::Error(ErrorKind::Value);
}
let text = match required_text(engine, context, &args[0]) {
Ok(text) => text,
Err(kind) => return Value::Error(kind),
};
let value = match parse_source(&text, source) {
Ok(value) => value,
Err(kind) => return Value::Error(kind),
};
match target {
Some(target) => format_target(engine, context, args.get(1), value, target),
None => Value::Number(value as f64),
}
}
fn parse_source(text: &str, source: SourceRadix) -> Result<i64, ErrorKind> {
if text.is_empty() || text.len() > 10 {
return Err(ErrorKind::Num);
}
let raw = u64::from_str_radix(text, source.radix()).map_err(|_| ErrorKind::Num)?;
let bits = source.bits();
if raw >= (1_u64 << bits) {
return Err(ErrorKind::Num);
}
if text.len() == 10 && raw & (1_u64 << (bits - 1)) != 0 {
Ok(raw as i64 - (1_i64 << bits))
} else {
Ok(raw as i64)
}
}
fn convert_decimal(
engine: &Engine<'_>,
context: EvalContext<'_>,
args: &[Expr],
target: TargetRadix,
) -> Value {
if args.is_empty() || args.len() > 2 {
return Value::Error(ErrorKind::Value);
}
let value = match required_number(engine, context, &args[0]) {
Ok(value) => value.trunc() as i64,
Err(kind) => return Value::Error(kind),
};
format_target(engine, context, args.get(1), value, target)
}
fn format_target(
engine: &Engine<'_>,
context: EvalContext<'_>,
places: Option<&Expr>,
value: i64,
target: TargetRadix,
) -> Value {
let bits = target.bits();
let minimum = -(1_i64 << (bits - 1));
let maximum = (1_i64 << (bits - 1)) - 1;
if value < minimum || value > maximum {
return Value::Error(ErrorKind::Num);
}
let places = match places {
Some(expr) => match required_number(engine, context, expr) {
Ok(places) if (1.0..=10.0).contains(&places.trunc()) => places.trunc() as usize,
Ok(_) => return Value::Error(ErrorKind::Num),
Err(kind) => return Value::Error(kind),
},
None => 0,
};
let raw = if value < 0 {
(1_i64 << bits) + value
} else {
value
};
let encoded = encode_unsigned(raw as u64, target.radix());
let width = if value < 0 { 10 } else { places };
if width != 0 && encoded.len() > width {
return Value::Error(ErrorKind::Num);
}
let padded = if encoded.len() < width {
format!("{}{}", "0".repeat(width - encoded.len()), encoded)
} else {
encoded
};
engine.bounded_text(padded)
}
fn encode_unsigned(mut number: u64, radix: u32) -> String {
if number == 0 {
return "0".to_owned();
}
let mut digits = Vec::new();
while number > 0 {
let digit = (number % u64::from(radix)) as u8;
digits.push(if digit < 10 {
char::from(b'0' + digit)
} else {
char::from(b'A' + digit - 10)
});
number /= u64::from(radix);
}
digits.into_iter().rev().collect()
}
fn comparison(engine: &Engine<'_>, context: EvalContext<'_>, args: &[Expr], equal: bool) -> Value {
if args.is_empty() || args.len() > 2 {
return Value::Error(ErrorKind::Value);
}
let number = match required_number(engine, context, &args[0]) {
Ok(number) => number,
Err(kind) => return Value::Error(kind),
};
let comparison = match args.get(1) {
Some(expr) => match required_number(engine, context, expr) {
Ok(number) => number,
Err(kind) => return Value::Error(kind),
},
None => 0.0,
};
Value::Number(f64::from(if equal {
number == comparison
} else {
number >= comparison
}))
}
fn erf(engine: &Engine<'_>, context: EvalContext<'_>, args: &[Expr], precise: bool) -> Value {
let valid_len = if precise {
args.len() == 1
} else {
(1..=2).contains(&args.len())
};
if !valid_len {
return Value::Error(ErrorKind::Value);
}
let lower = match required_number(engine, context, &args[0]) {
Ok(number) => number,
Err(kind) => return Value::Error(kind),
};
let result = match args.get(1) {
Some(expr) => match required_number(engine, context, expr) {
Ok(upper) => libm::erf(upper) - libm::erf(lower),
Err(kind) => return Value::Error(kind),
},
None => libm::erf(lower),
};
Value::Number(result)
}
fn erfc(engine: &Engine<'_>, context: EvalContext<'_>, args: &[Expr]) -> Value {
if args.len() != 1 {
return Value::Error(ErrorKind::Value);
}
match required_number(engine, context, &args[0]) {
Ok(number) => Value::Number(libm::erfc(number)),
Err(kind) => Value::Error(kind),
}
}
#[cfg(test)]
mod tests {
use super::{SourceRadix, parse_source};
#[test]
fn ten_digit_sources_use_the_excel_twos_complement_width() {
assert_eq!(parse_source("1111111111", SourceRadix::Binary), Ok(-1));
assert_eq!(parse_source("7777777777", SourceRadix::Octal), Ok(-1));
assert_eq!(parse_source("FFFFFFFFFF", SourceRadix::Hex), Ok(-1));
}
}