use std::fmt;
use crate::calc::CellRef;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArgType {
Unknown,
Number,
String,
List,
Matrix,
Error,
Empty,
}
#[derive(Debug, Clone)]
pub struct FormulaArg {
pub sheet_name: String,
pub number: f64,
pub string: String,
pub list: Vec<FormulaArg>,
pub matrix: Vec<Vec<FormulaArg>>,
pub boolean: bool,
pub error: String,
pub cell_refs: Vec<CellRef>,
pub cell_ranges: Vec<(CellRef, CellRef)>,
pub typ: ArgType,
}
impl Default for FormulaArg {
fn default() -> Self {
new_empty_formula_arg()
}
}
pub fn new_empty_formula_arg() -> FormulaArg {
FormulaArg {
sheet_name: String::new(),
number: 0.0,
string: String::new(),
list: Vec::new(),
matrix: Vec::new(),
boolean: false,
error: String::new(),
cell_refs: Vec::new(),
cell_ranges: Vec::new(),
typ: ArgType::Empty,
}
}
pub fn new_number_formula_arg(n: f64) -> FormulaArg {
FormulaArg {
sheet_name: String::new(),
number: n,
string: String::new(),
list: Vec::new(),
matrix: Vec::new(),
boolean: false,
error: String::new(),
cell_refs: Vec::new(),
cell_ranges: Vec::new(),
typ: ArgType::Number,
}
}
pub fn new_string_formula_arg(s: impl Into<String>) -> FormulaArg {
FormulaArg {
sheet_name: String::new(),
number: 0.0,
string: s.into(),
list: Vec::new(),
matrix: Vec::new(),
boolean: false,
error: String::new(),
cell_refs: Vec::new(),
cell_ranges: Vec::new(),
typ: ArgType::String,
}
}
pub fn new_bool_formula_arg(b: bool) -> FormulaArg {
FormulaArg {
sheet_name: String::new(),
number: if b { 1.0 } else { 0.0 },
string: String::new(),
list: Vec::new(),
matrix: Vec::new(),
boolean: true,
error: String::new(),
cell_refs: Vec::new(),
cell_ranges: Vec::new(),
typ: ArgType::Number,
}
}
pub fn new_error_formula_arg(err: impl Into<String>) -> FormulaArg {
FormulaArg {
sheet_name: String::new(),
number: 0.0,
string: String::new(),
list: Vec::new(),
matrix: Vec::new(),
boolean: false,
error: err.into(),
cell_refs: Vec::new(),
cell_ranges: Vec::new(),
typ: ArgType::Error,
}
}
pub fn new_list_formula_arg(list: Vec<FormulaArg>) -> FormulaArg {
FormulaArg {
sheet_name: String::new(),
number: 0.0,
string: String::new(),
list,
matrix: Vec::new(),
boolean: false,
error: String::new(),
cell_refs: Vec::new(),
cell_ranges: Vec::new(),
typ: ArgType::List,
}
}
pub fn new_matrix_formula_arg(matrix: Vec<Vec<FormulaArg>>) -> FormulaArg {
FormulaArg {
sheet_name: String::new(),
number: 0.0,
string: String::new(),
list: Vec::new(),
matrix,
boolean: false,
error: String::new(),
cell_refs: Vec::new(),
cell_ranges: Vec::new(),
typ: ArgType::Matrix,
}
}
impl FormulaArg {
pub fn value(&self) -> String {
match self.typ {
ArgType::Number => {
if self.boolean {
if self.number == 0.0 {
"FALSE".to_string()
} else {
"TRUE".to_string()
}
} else {
format!("{}", self.number)
}
}
ArgType::String => self.string.clone(),
ArgType::Matrix => {
if let Some(first) = self.to_list().first() {
return first.value();
}
String::new()
}
ArgType::Error => self.error.clone(),
_ => String::new(),
}
}
pub fn to_number(&self) -> FormulaArg {
match self.typ {
ArgType::String => match self.string.parse::<f64>() {
Ok(n) => new_number_formula_arg(n),
Err(_) => new_error_formula_arg("#VALUE!"),
},
ArgType::Number => new_number_formula_arg(self.number),
ArgType::Matrix => {
if let Some(first) = self.to_list().first() {
return first.to_number();
}
new_number_formula_arg(0.0)
}
ArgType::Empty => new_number_formula_arg(0.0),
_ => new_error_formula_arg("#VALUE!"),
}
}
pub fn to_bool(&self) -> FormulaArg {
match self.typ {
ArgType::String => match self.string.to_lowercase().parse::<bool>() {
Ok(b) => new_bool_formula_arg(b),
Err(_) => new_error_formula_arg("#VALUE!"),
},
ArgType::Number => new_bool_formula_arg(self.number == 1.0),
ArgType::Matrix => {
if let Some(first) = self.to_list().first() {
return first.to_bool();
}
new_bool_formula_arg(false)
}
ArgType::Empty => new_bool_formula_arg(false),
_ => new_error_formula_arg("#VALUE!"),
}
}
pub fn to_list(&self) -> Vec<FormulaArg> {
match self.typ {
ArgType::Matrix => self
.matrix
.iter()
.flat_map(|row| row.iter().cloned())
.collect(),
ArgType::List => self.list.clone(),
ArgType::Empty => Vec::new(),
_ => vec![self.clone()],
}
}
pub fn as_number(&self) -> Option<f64> {
match self.typ {
ArgType::Number => Some(self.number),
ArgType::String => self.string.parse::<f64>().ok(),
ArgType::Empty => Some(0.0),
_ => None,
}
}
pub fn as_bool(&self) -> bool {
match self.typ {
ArgType::Empty => false,
ArgType::Number => self.number != 0.0,
ArgType::String => !self.string.is_empty(),
ArgType::Error => false,
ArgType::List => !self.list.is_empty(),
ArgType::Matrix => !self.matrix.is_empty(),
ArgType::Unknown => false,
}
}
pub fn as_string(&self) -> String {
match self.typ {
ArgType::Number => {
if self.number.is_nan() || self.number.is_infinite() {
return "#NUM!".to_string();
}
if self.boolean {
return self.value();
}
if self.number.fract() == 0.0 {
format!("{}", self.number as i64)
} else {
format!("{}", self.number)
}
}
ArgType::String => self.string.clone(),
ArgType::Empty => String::new(),
ArgType::Error => self.error.clone(),
ArgType::List | ArgType::Matrix => String::new(),
ArgType::Unknown => String::new(),
}
}
pub fn is_error(&self) -> bool {
self.typ == ArgType::Error
}
}
impl fmt::Display for FormulaArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value())
}
}
pub fn collect_numbers(arg: &FormulaArg, out: &mut Vec<f64>) -> Option<FormulaArg> {
match arg.typ {
ArgType::Number => out.push(arg.number),
ArgType::List => {
for x in &arg.list {
if let Some(e) = collect_numbers(x, out) {
return Some(e);
}
}
}
ArgType::Matrix => {
for row in &arg.matrix {
for x in row {
if let Some(e) = collect_numbers(x, out) {
return Some(e);
}
}
}
}
ArgType::Error => return Some(arg.clone()),
_ => {}
}
None
}
pub fn numbers_from_args(args: &[FormulaArg]) -> (Vec<f64>, Option<FormulaArg>) {
let mut out = Vec::new();
for a in args {
if let Some(e) = collect_numbers(a, &mut out) {
return (out, Some(e));
}
}
(out, None)
}
pub fn flatten_string(arg: &FormulaArg) -> String {
match arg.typ {
ArgType::String => arg.string.clone(),
ArgType::List => arg.list.iter().map(flatten_string).collect(),
ArgType::Matrix => arg
.matrix
.iter()
.flat_map(|r| r.iter().map(flatten_string))
.collect(),
_ => arg.as_string(),
}
}
pub fn compare_equal(a: &FormulaArg, b: &FormulaArg) -> bool {
if let (Some(a), Some(b)) = (a.as_number(), b.as_number()) {
return a == b;
}
a.as_string().to_uppercase() == b.as_string().to_uppercase()
}
pub const FORMULA_ERROR_DIV: &str = "#DIV/0!";
pub const FORMULA_ERROR_NAME: &str = "#NAME?";
pub const FORMULA_ERROR_NA: &str = "#N/A";
pub const FORMULA_ERROR_NUM: &str = "#NUM!";
pub const FORMULA_ERROR_VALUE: &str = "#VALUE!";
pub const FORMULA_ERROR_REF: &str = "#REF!";
pub const FORMULA_ERROR_NULL: &str = "#NULL!";
pub const FORMULA_ERROR_SPILL: &str = "#SPILL!";
pub const FORMULA_ERROR_CALC: &str = "#CALC!";
pub const FORMULA_ERROR_GETTING_DATA: &str = "#GETTING_DATA";