UPDATE: Dump of initial files

This commit is contained in:
Nicolás Hatcher
2023-11-18 21:26:18 +01:00
commit c5b8efd83d
279 changed files with 42654 additions and 0 deletions

1
base/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
target/*

521
base/Cargo.lock generated Normal file
View File

@@ -0,0 +1,521 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets",
]
[[package]]
name = "chrono-tz"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbc529705a6e0028189c83f0a5dd9fb214105116f7e3c0eeab7ff0369766b0d1"
dependencies = [
"chrono",
"chrono-tz-build",
"phf",
]
[[package]]
name = "chrono-tz-build"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9998fb9f7e9b2111641485bf8beb32f92945f97f92a3d061f744cfef335f751"
dependencies = [
"parse-zoneinfo",
"phf",
"phf_codegen",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "getrandom"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "iana-time-zone"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "ironcalc_base"
version = "0.1.0"
dependencies = [
"chrono",
"chrono-tz",
"js-sys",
"once_cell",
"rand",
"regex",
"ryu",
"serde",
"serde_json",
"serde_repr",
]
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "js-sys"
version = "0.3.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "num-traits"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "parse-zoneinfo"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
dependencies = [
"regex",
]
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "serde"
version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "syn"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
[[package]]
name = "windows-core"
version = "0.51.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

25
base/Cargo.toml Normal file
View File

@@ -0,0 +1,25 @@
[package]
name = "ironcalc_base"
version = "0.1.0"
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_repr = "0.1"
ryu = "1.0"
chrono = "0.4"
chrono-tz = "0.7.0"
regex = "1.0"
once_cell = "1.16.0"
[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = { version = "0.3.60" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rand = "0.8.4"

27
base/README.md Normal file
View File

@@ -0,0 +1,27 @@
# IronCalc Base
## About
IronCalc Base is the engine of the IronCalc ecosystem
## Build
To build the library
```bash
$ cargo build --release
```
## Tests
To run the tests:
```bash
$ cargo tests
```

291
base/src/actions.rs Normal file
View File

@@ -0,0 +1,291 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::expressions::parser::stringify::DisplaceData;
use crate::model::Model;
// NOTE: There is a difference with Excel behaviour when deleting cells/rows/columns
// In Excel if the whole range is deleted then it will substitute for #REF!
// In IronCalc, if one of the edges of the range is deleted will replace the edge with #REF!
// I feel this is unimportant for now.
impl Model {
fn displace_cells(&mut self, displace_data: &DisplaceData) {
let cells = self.get_all_cells();
for cell in cells {
self.shift_cell_formula(cell.index, cell.row, cell.column, displace_data);
}
}
/// Returns the list of columns in row
fn get_columns_for_row(
&self,
sheet: u32,
row: i32,
descending: bool,
) -> Result<Vec<i32>, String> {
let worksheet = self.workbook.worksheet(sheet)?;
if let Some(row_data) = worksheet.sheet_data.get(&row) {
let mut columns: Vec<i32> = row_data.keys().copied().collect();
columns.sort_unstable();
if descending {
columns.reverse();
}
Ok(columns)
} else {
Ok(vec![])
}
}
/// Moves the contents of cell (source_row, source_column) tp (target_row, target_column)
fn move_cell(
&mut self,
sheet: u32,
source_row: i32,
source_column: i32,
target_row: i32,
target_column: i32,
) -> Result<(), String> {
let source_cell = self
.workbook
.worksheet(sheet)?
.cell(source_row, source_column)
.ok_or("Expected Cell to exist")?;
let style = source_cell.get_style();
// FIXME: we need some user_input getter instead of get_text
let formula_or_value = self
.cell_formula(sheet, source_row, source_column)?
.unwrap_or_else(|| source_cell.get_text(&self.workbook.shared_strings, &self.language));
self.set_user_input(sheet, target_row, target_column, formula_or_value);
self.workbook
.worksheet_mut(sheet)?
.set_cell_style(target_row, target_column, style);
self.delete_cell(sheet, source_row, source_column)?;
Ok(())
}
pub fn insert_columns(
&mut self,
sheet: u32,
column: i32,
column_count: i32,
) -> Result<(), String> {
if column_count <= 0 {
return Err("Cannot add a negative number of cells :)".to_string());
}
// check if it is possible:
let dimensions = self.workbook.worksheet(sheet)?.dimension();
let last_column = dimensions.max_column + column_count;
if last_column > LAST_COLUMN {
return Err(
"Cannot shift cells because that would delete cells at the end of a row"
.to_string(),
);
}
let worksheet = self.workbook.worksheet(sheet)?;
let all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
for row in all_rows {
let sorted_columns = self.get_columns_for_row(sheet, row, true)?;
for col in sorted_columns {
if col >= column {
self.move_cell(sheet, row, col, row, col + column_count)?;
} else {
// Break because columns are in descending order.
break;
}
}
}
// Update all formulas in the workbook
self.displace_cells(&DisplaceData::Column {
sheet,
column,
delta: column_count,
});
Ok(())
}
pub fn delete_columns(
&mut self,
sheet: u32,
column: i32,
column_count: i32,
) -> Result<(), String> {
if column_count <= 0 {
return Err("Please use insert columns instead".to_string());
}
// Move cells
let worksheet = &self.workbook.worksheet(sheet)?;
let mut all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
// We do not need to do that, but it is safer to eliminate sources of randomness in the algorithm
all_rows.sort_unstable();
for r in all_rows {
let columns: Vec<i32> = self.get_columns_for_row(sheet, r, false)?;
for col in columns {
if col >= column {
if col >= column + column_count {
self.move_cell(sheet, r, col, r, col - column_count)?;
} else {
self.delete_cell(sheet, r, col)?;
}
}
}
}
// Update all formulas in the workbook
self.displace_cells(&DisplaceData::Column {
sheet,
column,
delta: -column_count,
});
Ok(())
}
pub fn insert_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> {
if row_count <= 0 {
return Err("Cannot add a negative number of cells :)".to_string());
}
// Check if it is possible:
let dimensions = self.workbook.worksheet(sheet)?.dimension();
let last_row = dimensions.max_row + row_count;
if last_row > LAST_ROW {
return Err(
"Cannot shift cells because that would delete cells at the end of a column"
.to_string(),
);
}
// Move cells
let worksheet = &self.workbook.worksheet(sheet)?;
let mut all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
all_rows.sort_unstable();
all_rows.reverse();
for r in all_rows {
if r >= row {
// We do not really need the columns in any order
let columns: Vec<i32> = self.get_columns_for_row(sheet, r, false)?;
for column in columns {
self.move_cell(sheet, r, column, r + row_count, column)?;
}
} else {
// Rows are in descending order
break;
}
}
// In the list of rows styles:
// * Add all rows above the rows we are inserting unchanged
// * Shift the ones below
let rows = &self.workbook.worksheets[sheet as usize].rows;
let mut new_rows = vec![];
for r in rows {
if r.r < row {
new_rows.push(r.clone());
} else if r.r >= row {
let mut new_row = r.clone();
new_row.r = r.r + row_count;
new_rows.push(new_row);
}
}
self.workbook.worksheets[sheet as usize].rows = new_rows;
// Update all formulas in the workbook
self.displace_cells(&DisplaceData::Row {
sheet,
row,
delta: row_count,
});
Ok(())
}
pub fn delete_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> {
if row_count <= 0 {
return Err("Please use insert rows instead".to_string());
}
// Move cells
let worksheet = &self.workbook.worksheet(sheet)?;
let mut all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
all_rows.sort_unstable();
for r in all_rows {
if r >= row {
// We do not need ordered, but it is safer to eliminate sources of randomness in the algorithm
let columns: Vec<i32> = self.get_columns_for_row(sheet, r, false)?;
if r >= row + row_count {
// displace all cells in column
for column in columns {
self.move_cell(sheet, r, column, r - row_count, column)?;
}
} else {
// remove all cells in row
// FIXME: We could just remove the entire row in one go
for column in columns {
self.delete_cell(sheet, r, column)?;
}
}
}
}
// In the list of rows styles:
// * Add all rows above the rows we are deleting unchanged
// * Skip all those we are deleting
// * Shift the ones below
let rows = &self.workbook.worksheets[sheet as usize].rows;
let mut new_rows = vec![];
for r in rows {
if r.r < row {
new_rows.push(r.clone());
} else if r.r >= row + row_count {
let mut new_row = r.clone();
new_row.r = r.r - row_count;
new_rows.push(new_row);
}
}
self.workbook.worksheets[sheet as usize].rows = new_rows;
self.displace_cells(&DisplaceData::Row {
sheet,
row,
delta: -row_count,
});
Ok(())
}
/// Displaces cells due to a move column action
/// from initial_column to target_column = initial_column + column_delta
/// References will be updated following:
/// Cell references:
/// * All cell references to initial_column will go to target_column
/// * All cell references to columns in between (initial_column, target_column] will be displaced one to the left
/// * All other cell references are left unchanged
/// Ranges. This is the tricky bit:
/// * Column is one of the extremes of the range. The new extreme would be target_column.
/// Range is then normalized
/// * Any other case, range is left unchanged.
/// NOTE: This does NOT move the data in the columns or move the colum styles
pub fn move_column_action(
&mut self,
sheet: u32,
column: i32,
delta: i32,
) -> Result<(), &'static str> {
// Check boundaries
let target_column = column + delta;
if !(1..=LAST_COLUMN).contains(&target_column) {
return Err("Target column out of boundaries");
}
if !(1..=LAST_COLUMN).contains(&column) {
return Err("Initial column out of boundaries");
}
// TODO: Add the actual displacement of data and styles
// Update all formulas in the workbook
self.displace_cells(&DisplaceData::ColumnMove {
sheet,
column,
delta,
});
Ok(())
}
}

114
base/src/calc_result.rs Normal file
View File

@@ -0,0 +1,114 @@
use std::cmp::Ordering;
use crate::expressions::token::Error;
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub struct CellReference {
pub sheet: u32,
pub column: i32,
pub row: i32,
}
#[derive(Debug, Clone)]
pub struct Range {
pub left: CellReference,
pub right: CellReference,
}
#[derive(Clone)]
pub(crate) enum CalcResult {
String(String),
Number(f64),
Boolean(bool),
Error {
error: Error,
origin: CellReference,
message: String,
},
Range {
left: CellReference,
right: CellReference,
},
EmptyCell,
EmptyArg,
}
impl CalcResult {
pub fn new_error(error: Error, origin: CellReference, message: String) -> CalcResult {
CalcResult::Error {
error,
origin,
message,
}
}
pub fn new_args_number_error(origin: CellReference) -> CalcResult {
CalcResult::Error {
error: Error::ERROR,
origin,
message: "Wrong number of arguments".to_string(),
}
}
pub fn is_error(&self) -> bool {
matches!(self, CalcResult::Error { .. })
}
}
impl Ord for CalcResult {
// ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE, empty;
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(CalcResult::Number(value1), CalcResult::Number(value2)) => {
if (value2 - value1).abs() < f64::EPSILON {
return Ordering::Equal;
}
if value1 < value2 {
return Ordering::Less;
}
Ordering::Greater
}
(CalcResult::Number(_value1), CalcResult::String(_value2)) => Ordering::Less,
(CalcResult::Number(_value1), CalcResult::Boolean(_value2)) => Ordering::Less,
(CalcResult::String(value1), CalcResult::String(value2)) => {
let value1 = value1.to_uppercase();
let value2 = value2.to_uppercase();
value1.cmp(&value2)
}
(CalcResult::String(_value1), CalcResult::Boolean(_value2)) => Ordering::Less,
(CalcResult::Boolean(value1), CalcResult::Boolean(value2)) => {
if value1 == value2 {
return Ordering::Equal;
}
if *value1 {
return Ordering::Greater;
}
Ordering::Less
}
(CalcResult::EmptyCell, CalcResult::String(_value2)) => Ordering::Greater,
(CalcResult::EmptyArg, CalcResult::String(_value2)) => Ordering::Greater,
(CalcResult::String(_value1), CalcResult::EmptyCell) => Ordering::Less,
(CalcResult::EmptyCell, CalcResult::Number(_value2)) => Ordering::Greater,
(CalcResult::EmptyArg, CalcResult::Number(_value2)) => Ordering::Greater,
(CalcResult::Number(_value1), CalcResult::EmptyCell) => Ordering::Less,
(CalcResult::EmptyCell, CalcResult::EmptyCell) => Ordering::Equal,
(CalcResult::EmptyCell, CalcResult::EmptyArg) => Ordering::Equal,
(CalcResult::EmptyArg, CalcResult::EmptyCell) => Ordering::Equal,
(CalcResult::EmptyArg, CalcResult::EmptyArg) => Ordering::Equal,
// NOTE: Errors and Ranges are not covered
(_, _) => Ordering::Greater,
}
}
}
impl PartialOrd for CalcResult {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for CalcResult {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for CalcResult {}

214
base/src/cast.rs Normal file
View File

@@ -0,0 +1,214 @@
use crate::{
calc_result::{CalcResult, CellReference, Range},
expressions::{parser::Node, token::Error},
implicit_intersection::implicit_intersection,
model::Model,
};
impl Model {
pub(crate) fn get_number(
&mut self,
node: &Node,
cell: CellReference,
) -> Result<f64, CalcResult> {
let result = self.evaluate_node_in_context(node, cell);
self.cast_to_number(result, cell)
}
fn cast_to_number(
&mut self,
result: CalcResult,
cell: CellReference,
) -> Result<f64, CalcResult> {
match result {
CalcResult::Number(f) => Ok(f),
CalcResult::String(s) => match s.parse::<f64>() {
Ok(f) => Ok(f),
_ => Err(CalcResult::new_error(
Error::VALUE,
cell,
"Expecting number".to_string(),
)),
},
CalcResult::Boolean(f) => {
if f {
Ok(1.0)
} else {
Ok(0.0)
}
}
CalcResult::EmptyCell | CalcResult::EmptyArg => Ok(0.0),
error @ CalcResult::Error { .. } => Err(error),
CalcResult::Range { left, right } => {
match implicit_intersection(&cell, &Range { left, right }) {
Some(cell_reference) => {
let result = self.evaluate_cell(cell_reference);
self.cast_to_number(result, cell_reference)
}
None => Err(CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Invalid reference (number)".to_string(),
}),
}
}
}
}
pub(crate) fn get_number_no_bools(
&mut self,
node: &Node,
cell: CellReference,
) -> Result<f64, CalcResult> {
let result = self.evaluate_node_in_context(node, cell);
if matches!(result, CalcResult::Boolean(_)) {
return Err(CalcResult::new_error(
Error::VALUE,
cell,
"Expecting number".to_string(),
));
}
self.cast_to_number(result, cell)
}
pub(crate) fn get_string(
&mut self,
node: &Node,
cell: CellReference,
) -> Result<String, CalcResult> {
let result = self.evaluate_node_in_context(node, cell);
self.cast_to_string(result, cell)
}
pub(crate) fn cast_to_string(
&mut self,
result: CalcResult,
cell: CellReference,
) -> Result<String, CalcResult> {
// FIXME: I think when casting a number we should convert it to_precision(x, 15)
// See function Exact
match result {
CalcResult::Number(f) => Ok(format!("{}", f)),
CalcResult::String(s) => Ok(s),
CalcResult::Boolean(f) => {
if f {
Ok("TRUE".to_string())
} else {
Ok("FALSE".to_string())
}
}
CalcResult::EmptyCell | CalcResult::EmptyArg => Ok("".to_string()),
error @ CalcResult::Error { .. } => Err(error),
CalcResult::Range { left, right } => {
match implicit_intersection(&cell, &Range { left, right }) {
Some(cell_reference) => {
let result = self.evaluate_cell(cell_reference);
self.cast_to_string(result, cell_reference)
}
None => Err(CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Invalid reference (string)".to_string(),
}),
}
}
}
}
pub(crate) fn get_boolean(
&mut self,
node: &Node,
cell: CellReference,
) -> Result<bool, CalcResult> {
let result = self.evaluate_node_in_context(node, cell);
self.cast_to_bool(result, cell)
}
fn cast_to_bool(
&mut self,
result: CalcResult,
cell: CellReference,
) -> Result<bool, CalcResult> {
match result {
CalcResult::Number(f) => {
if f == 0.0 {
return Ok(false);
}
Ok(true)
}
CalcResult::String(s) => {
if s.to_lowercase() == *"true" {
return Ok(true);
} else if s.to_lowercase() == *"false" {
return Ok(false);
}
Err(CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Expected boolean".to_string(),
})
}
CalcResult::Boolean(b) => Ok(b),
CalcResult::EmptyCell | CalcResult::EmptyArg => Ok(false),
error @ CalcResult::Error { .. } => Err(error),
CalcResult::Range { left, right } => {
match implicit_intersection(&cell, &Range { left, right }) {
Some(cell_reference) => {
let result = self.evaluate_cell(cell_reference);
self.cast_to_bool(result, cell_reference)
}
None => Err(CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Invalid reference (bool)".to_string(),
}),
}
}
}
}
// tries to return a reference. That is either a reference or a formula that evaluates to a range/reference
pub(crate) fn get_reference(
&mut self,
node: &Node,
cell: CellReference,
) -> Result<Range, CalcResult> {
match node {
Node::ReferenceKind {
column,
absolute_column,
row,
absolute_row,
sheet_index,
sheet_name: _,
} => {
let left = CellReference {
sheet: *sheet_index,
row: if *absolute_row { *row } else { *row + cell.row },
column: if *absolute_column {
*column
} else {
*column + cell.column
},
};
Ok(Range { left, right: left })
}
_ => {
let value = self.evaluate_node_in_context(node, cell);
if value.is_error() {
return Err(value);
}
if let CalcResult::Range { left, right } = value {
Ok(Range { left, right })
} else {
Err(CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Expected reference".to_string(),
})
}
}
}
}
}

193
base/src/cell.rs Normal file
View File

@@ -0,0 +1,193 @@
use crate::{
expressions::token::Error, language::Language, number_format::to_excel_precision_str, types::*,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
/// A CellValue is the representation of the cell content.
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(untagged)]
pub enum CellValue {
None,
String(String),
Number(f64),
Boolean(bool),
}
impl CellValue {
pub fn to_json_str(&self) -> String {
match &self {
CellValue::None => "null".to_string(),
CellValue::String(s) => json!(s).to_string(),
CellValue::Number(f) => json!(f).to_string(),
CellValue::Boolean(b) => json!(b).to_string(),
}
}
}
impl From<f64> for CellValue {
fn from(value: f64) -> Self {
Self::Number(value)
}
}
impl From<String> for CellValue {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<&str> for CellValue {
fn from(value: &str) -> Self {
Self::String(value.to_string())
}
}
impl From<bool> for CellValue {
fn from(value: bool) -> Self {
Self::Boolean(value)
}
}
impl Cell {
/// Creates a new Cell with a shared string (`si` is the string index)
pub fn new_string(si: i32, s: i32) -> Cell {
Cell::SharedString { si, s }
}
/// Creates a new Cell with a number
pub fn new_number(v: f64, s: i32) -> Cell {
Cell::NumberCell { v, s }
}
/// Creates a new Cell with a boolean
pub fn new_boolean(v: bool, s: i32) -> Cell {
Cell::BooleanCell { v, s }
}
/// Creates a new Cell with an error value
pub fn new_error(ei: Error, s: i32) -> Cell {
Cell::ErrorCell { ei, s }
}
/// Creates a new Cell with an unevaluated formula
pub fn new_formula(f: i32, s: i32) -> Cell {
Cell::CellFormula { f, s }
}
/// Returns the formula of a cell if any.
pub fn get_formula(&self) -> Option<i32> {
match self {
Cell::CellFormula { f, .. } => Some(*f),
Cell::CellFormulaBoolean { f, .. } => Some(*f),
Cell::CellFormulaNumber { f, .. } => Some(*f),
Cell::CellFormulaString { f, .. } => Some(*f),
Cell::CellFormulaError { f, .. } => Some(*f),
_ => None,
}
}
pub fn has_formula(&self) -> bool {
self.get_formula().is_some()
}
pub fn set_style(&mut self, style: i32) {
match self {
Cell::EmptyCell { s, .. } => *s = style,
Cell::BooleanCell { s, .. } => *s = style,
Cell::NumberCell { s, .. } => *s = style,
Cell::ErrorCell { s, .. } => *s = style,
Cell::SharedString { s, .. } => *s = style,
Cell::CellFormula { s, .. } => *s = style,
Cell::CellFormulaBoolean { s, .. } => *s = style,
Cell::CellFormulaNumber { s, .. } => *s = style,
Cell::CellFormulaString { s, .. } => *s = style,
Cell::CellFormulaError { s, .. } => *s = style,
};
}
pub fn get_style(&self) -> i32 {
match self {
Cell::EmptyCell { s, .. } => *s,
Cell::BooleanCell { s, .. } => *s,
Cell::NumberCell { s, .. } => *s,
Cell::ErrorCell { s, .. } => *s,
Cell::SharedString { s, .. } => *s,
Cell::CellFormula { s, .. } => *s,
Cell::CellFormulaBoolean { s, .. } => *s,
Cell::CellFormulaNumber { s, .. } => *s,
Cell::CellFormulaString { s, .. } => *s,
Cell::CellFormulaError { s, .. } => *s,
}
}
pub fn get_type(&self) -> CellType {
match self {
Cell::EmptyCell { .. } => CellType::Number,
Cell::BooleanCell { .. } => CellType::LogicalValue,
Cell::NumberCell { .. } => CellType::Number,
Cell::ErrorCell { .. } => CellType::ErrorValue,
// TODO: An empty string should likely be considered a Number (like an empty cell).
Cell::SharedString { .. } => CellType::Text,
Cell::CellFormula { .. } => CellType::Number,
Cell::CellFormulaBoolean { .. } => CellType::LogicalValue,
Cell::CellFormulaNumber { .. } => CellType::Number,
Cell::CellFormulaString { .. } => CellType::Text,
Cell::CellFormulaError { .. } => CellType::ErrorValue,
}
}
pub fn get_text(&self, shared_strings: &[String], language: &Language) -> String {
match self.value(shared_strings, language) {
CellValue::None => "".to_string(),
CellValue::String(v) => v,
CellValue::Boolean(v) => v.to_string().to_uppercase(),
CellValue::Number(v) => to_excel_precision_str(v),
}
}
pub fn value(&self, shared_strings: &[String], language: &Language) -> CellValue {
match self {
Cell::EmptyCell { .. } => CellValue::None,
Cell::BooleanCell { v, s: _ } => CellValue::Boolean(*v),
Cell::NumberCell { v, s: _ } => CellValue::Number(*v),
Cell::ErrorCell { ei, .. } => {
let v = ei.to_localized_error_string(language);
CellValue::String(v)
}
Cell::SharedString { si, .. } => {
let s = shared_strings.get(*si as usize);
let v = match s {
Some(str) => str.clone(),
None => "".to_string(),
};
CellValue::String(v)
}
Cell::CellFormula { .. } => CellValue::String("#ERROR!".to_string()),
Cell::CellFormulaBoolean { v, .. } => CellValue::Boolean(*v),
Cell::CellFormulaNumber { v, .. } => CellValue::Number(*v),
Cell::CellFormulaString { v, .. } => CellValue::String(v.clone()),
Cell::CellFormulaError { ei, .. } => {
let v = ei.to_localized_error_string(language);
CellValue::String(v)
}
}
}
pub fn formatted_value<F>(
&self,
shared_strings: &[String],
language: &Language,
format_number: F,
) -> String
where
F: Fn(f64) -> String,
{
match self.value(shared_strings, language) {
CellValue::None => "".to_string(),
CellValue::String(value) => value,
CellValue::Boolean(value) => value.to_string().to_uppercase(),
CellValue::Number(value) => format_number(value),
}
}
}

16
base/src/constants.rs Normal file
View File

@@ -0,0 +1,16 @@
/// Excel compatibility values
/// COLUMN_WIDTH and ROW_HEIGHT are pixel values
/// A column width of Excel value `w` will result in `w * COLUMN_WIDTH_FACTOR` pixels
/// Note that these constants are inlined
pub(crate) const DEFAULT_COLUMN_WIDTH: f64 = 100.0;
pub(crate) const DEFAULT_ROW_HEIGHT: f64 = 21.0;
pub(crate) const COLUMN_WIDTH_FACTOR: f64 = 12.0;
pub(crate) const ROW_HEIGHT_FACTOR: f64 = 2.0;
pub(crate) const LAST_COLUMN: i32 = 16_384;
pub(crate) const LAST_ROW: i32 = 1_048_576;
// 693_594 is computed as:
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
// The 2 days offset is because of Excel 1900 bug
pub(crate) const EXCEL_DATE_BASE: i32 = 693_594;

136
base/src/diffs.rs Normal file
View File

@@ -0,0 +1,136 @@
use crate::{
expressions::{
parser::{
move_formula::ref_is_in_area,
stringify::{to_string, to_string_displaced, DisplaceData},
walk::forward_references,
},
types::{Area, CellReferenceIndex, CellReferenceRC},
},
model::Model,
};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged, deny_unknown_fields)]
pub enum CellValue {
Value(String),
None,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct SetCellValue {
cell: CellReferenceIndex,
new_value: CellValue,
old_value: CellValue,
}
impl Model {
pub(crate) fn shift_cell_formula(
&mut self,
sheet: u32,
row: i32,
column: i32,
displace_data: &DisplaceData,
) {
if let Some(f) = self
.workbook
.worksheet(sheet)
.expect("Worksheet must exist")
.cell(row, column)
.expect("Cell must exist")
.get_formula()
{
let node = &self.parsed_formulas[sheet as usize][f as usize].clone();
let cell_reference = CellReferenceRC {
sheet: self.workbook.worksheets[sheet as usize].get_name(),
row,
column,
};
// FIXME: This is not a very performant way if the formula has changed :S.
let formula = to_string(node, &cell_reference);
let formula_displaced = to_string_displaced(node, &cell_reference, displace_data);
if formula != formula_displaced {
self.update_cell_with_formula(sheet, row, column, format!("={formula_displaced}"))
.expect("Failed to shift cell formula");
}
}
}
pub fn forward_references(
&mut self,
source_area: &Area,
target: &CellReferenceIndex,
) -> Result<Vec<SetCellValue>, String> {
let mut diff_list: Vec<SetCellValue> = Vec::new();
let target_area = &Area {
sheet: target.sheet,
row: target.row,
column: target.column,
width: source_area.width,
height: source_area.height,
};
// Walk over every formula
let cells = self.get_all_cells();
for cell in cells {
if let Some(f) = self
.workbook
.worksheet(cell.index)
.expect("Worksheet must exist")
.cell(cell.row, cell.column)
.expect("Cell must exist")
.get_formula()
{
let sheet = cell.index;
let row = cell.row;
let column = cell.column;
// If cell is in the source or target area, skip
if ref_is_in_area(sheet, row, column, source_area)
|| ref_is_in_area(sheet, row, column, target_area)
{
continue;
}
// Get the formula
// Get a copy of the AST
let node = &mut self.parsed_formulas[sheet as usize][f as usize].clone();
let cell_reference = CellReferenceRC {
sheet: self.workbook.worksheets[sheet as usize].get_name(),
column: cell.column,
row: cell.row,
};
let context = CellReferenceIndex { sheet, column, row };
let formula = to_string(node, &cell_reference);
let target_sheet_name = &self.workbook.worksheets[target.sheet as usize].name;
forward_references(
node,
&context,
source_area,
target.sheet,
target_sheet_name,
target.row,
target.column,
);
// If the string representation of the formula has changed update the cell
let updated_formula = to_string(node, &cell_reference);
if formula != updated_formula {
self.update_cell_with_formula(
sheet,
row,
column,
format!("={updated_formula}"),
)?;
// Update the diff list
diff_list.push(SetCellValue {
cell: CellReferenceIndex { sheet, column, row },
new_value: CellValue::Value(format!("={}", updated_formula)),
old_value: CellValue::Value(format!("={}", formula)),
});
}
}
}
Ok(diff_list)
}
}

View File

@@ -0,0 +1,762 @@
//! A tokenizer for spreadsheet formulas.
//!
//! This is meant to feed a formula parser.
//!
//! You will need to instantiate it with a language and a locale.
//!
//! It supports two working modes:
//!
//! 1. A1 or display mode
//! This is for user formulas. References are like `D4`, `D$4` or `F5:T10`
//! 2. R1C1, internal or runtime mode
//! A reference like R1C1 refers to $A$1 and R3C4 to $D$4
//! R[2]C[5] refers to a cell two rows below and five columns to the right
//! It uses the 'en' locale and language.
//! This is used internally at runtime.
//!
//! Formulas look different in different locales:
//!
//! =IF(A1, B1, NA()) versus =IF(A1; B1; NA())
//!
//! Also numbers are different:
//!
//! 1,123.45 versus 1.123,45
//!
//! The names of the errors and functions are different in different languages,
//! but they stay the same in different locales.
//!
//! Note that in IronCalc if you are using a locale different from 'en' or a language different from 'en'
//! you will still need the 'en' locale and language because formulas are stored in that language and locale
//!
//! # Examples:
//! ```
//! use ironcalc_base::expressions::lexer::{Lexer, LexerMode};
//! use ironcalc_base::expressions::token::{TokenType, OpCompare};
//! use ironcalc_base::locale::get_locale;
//! use ironcalc_base::language::get_language;
//!
//! let locale = get_locale("en").unwrap();
//! let language = get_language("en").unwrap();
//! let mut lexer = Lexer::new("=A1*SUM(Sheet2!C3:D5)", LexerMode::A1, &locale, &language);
//! assert_eq!(lexer.next_token(), TokenType::Compare(OpCompare::Equal));
//! assert!(matches!(lexer.next_token(), TokenType::Reference { .. }));
//! ```
use crate::expressions::token::{OpCompare, OpProduct, OpSum};
use crate::language::Language;
use crate::locale::Locale;
use super::token::{index, Error, TokenType};
use super::types::*;
use super::utils;
pub mod util;
#[cfg(test)]
mod test;
mod ranges;
mod structured_references;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LexerError {
pub position: usize,
pub message: String,
}
pub(super) type Result<T> = std::result::Result<T, LexerError>;
#[derive(Clone, PartialEq, Eq)]
pub enum LexerMode {
A1,
R1C1,
}
/// Tokenize an input
#[derive(Clone)]
pub struct Lexer {
position: usize,
next_token_position: Option<usize>,
len: usize,
chars: Vec<char>,
mode: LexerMode,
locale: Locale,
language: Language,
}
impl Lexer {
/// Creates a new `Lexer` that returns the tokens of a formula.
pub fn new(formula: &str, mode: LexerMode, locale: &Locale, language: &Language) -> Lexer {
let chars: Vec<char> = formula.chars().collect();
let len = chars.len();
Lexer {
chars,
position: 0,
next_token_position: None,
len,
mode,
locale: locale.clone(),
language: language.clone(),
}
}
/// Changes the lexer mode
pub fn set_lexer_mode(&mut self, mode: LexerMode) {
self.mode = mode;
}
// FIXME: I don't think we should have `is_a1_mode` and `get_formula`.
// The caller already knows those two
/// Returns true if mode is A1
pub fn is_a1_mode(&self) -> bool {
self.mode == LexerMode::A1
}
/// Returns the formula
pub fn get_formula(&self) -> String {
self.chars.iter().collect()
}
// FIXME: This is used to get the "marked tokens"
// I think a better API would be to return the marked tokens
/// Returns the position of the lexer
pub fn get_position(&self) -> i32 {
self.position as i32
}
/// Resets the formula
pub fn set_formula(&mut self, content: &str) {
self.chars = content.chars().collect();
self.len = self.chars.len();
self.position = 0;
self.next_token_position = None;
}
/// Returns an error if the token is not the expected one.
pub fn expect(&mut self, tk: TokenType) -> Result<()> {
let nt = self.next_token();
if index(&nt) != index(&tk) {
return Err(self.set_error(&format!("Error, expected {}", tk), self.position));
}
Ok(())
}
/// Checks the next token without advancing position
/// See also [advance_token](Self::advance_token)
pub fn peek_token(&mut self) -> TokenType {
let position = self.position;
let tk = self.next_token();
self.next_token_position = Some(self.position);
self.position = position;
tk
}
/// Advances position. This is used in conjunction with [`peek_token`](Self::peek_token)
/// It is a noop if the has not been a previous peek_token
pub fn advance_token(&mut self) {
if let Some(position) = self.next_token_position {
self.position = position;
self.next_token_position = None;
}
}
/// Returns the next token
pub fn next_token(&mut self) -> TokenType {
self.next_token_position = None;
self.consume_whitespace();
match self.read_next_char() {
Some(char) => {
match char {
'+' => TokenType::Addition(OpSum::Add),
'-' => TokenType::Addition(OpSum::Minus),
'*' => TokenType::Product(OpProduct::Times),
'/' => TokenType::Product(OpProduct::Divide),
'(' => TokenType::LeftParenthesis,
')' => TokenType::RightParenthesis,
'=' => TokenType::Compare(OpCompare::Equal),
'{' => TokenType::LeftBrace,
'}' => TokenType::RightBrace,
'[' => TokenType::LeftBracket,
']' => TokenType::RightBracket,
':' => TokenType::Colon,
';' => TokenType::Semicolon,
',' => {
if self.locale.numbers.symbols.decimal == "," {
match self.consume_number(',') {
Ok(number) => TokenType::Number(number),
Err(error) => TokenType::Illegal(error),
}
} else {
TokenType::Comma
}
}
'.' => {
if self.locale.numbers.symbols.decimal == "." {
match self.consume_number('.') {
Ok(number) => TokenType::Number(number),
Err(error) => TokenType::Illegal(error),
}
} else {
// There is no TokenType::PERIOD
TokenType::Illegal(self.set_error("Expecting a number", self.position))
}
}
'!' => TokenType::Bang,
'^' => TokenType::Power,
'%' => TokenType::Percent,
'&' => TokenType::And,
'$' => self.consume_absolute_reference(),
'<' => {
let next_token = self.peek_char();
if next_token == Some('=') {
self.position += 1;
TokenType::Compare(OpCompare::LessOrEqualThan)
} else if next_token == Some('>') {
self.position += 1;
TokenType::Compare(OpCompare::NonEqual)
} else {
TokenType::Compare(OpCompare::LessThan)
}
}
'>' => {
if self.peek_char() == Some('=') {
self.position += 1;
TokenType::Compare(OpCompare::GreaterOrEqualThan)
} else {
TokenType::Compare(OpCompare::GreaterThan)
}
}
'#' => self.consume_error(),
'"' => TokenType::String(self.consume_string()),
'\'' => self.consume_quoted_sheet_reference(),
'0'..='9' => {
let position = self.position - 1;
match self.consume_number(char) {
Ok(number) => {
if self.peek_token() == TokenType::Colon
&& self.mode == LexerMode::A1
{
// Its a row range 3:5
// FIXME: There are faster ways of parsing this
// Like checking that 'number' is integer and that the next token is integer
self.position = position;
match self.consume_range_a1() {
Ok(ParsedRange { left, right }) => {
if let Some(right) = right {
TokenType::Range {
sheet: None,
left,
right,
}
} else {
TokenType::Illegal(
self.set_error("Expecting row range", position),
)
}
}
Err(error) => {
// Examples:
// * 'Sheet 1'!3.4:5
// * 'Sheet 1'!3:A2
// * 'Sheet 1'!3:
TokenType::Illegal(error)
}
}
} else {
TokenType::Number(number)
}
}
Err(error) => {
// tried to read a number but failed
self.position = self.len;
TokenType::Illegal(error)
}
}
}
_ => {
if char.is_alphabetic() || char == '_' {
// At this point is one of the following:
// 1. A range with sheet: Sheet3!A3:D7
// 2. A boolean: TRUE or FALSE (dependent on the language)
// 3. A reference like WS34 or R3C5
// 4. A range without sheet ER4:ER7
// 5. A column range E:E
// 6. An identifier like a function name or a defined name
// 7. A range operator A1:OFFSET(...)
// 8. An Invalid token
let position = self.position;
self.position -= 1;
let name = self.consume_identifier();
let position_indent = self.position;
let peek_char = self.peek_char();
let next_char_is_colon = self.peek_char() == Some(':');
if peek_char == Some('!') {
// reference
self.position += 1;
return self.consume_range(Some(name));
} else if peek_char == Some('$') {
self.position = position - 1;
return self.consume_range(None);
}
let name_upper = name.to_ascii_uppercase();
if name_upper == self.language.booleans.true_value {
return TokenType::Boolean(true);
} else if name_upper == self.language.booleans.false_value {
return TokenType::Boolean(false);
}
if self.mode == LexerMode::A1 {
let parsed_reference = utils::parse_reference_a1(&name_upper);
if parsed_reference.is_some()
|| (utils::is_valid_column(name_upper.trim_start_matches('$'))
&& next_char_is_colon)
{
self.position = position - 1;
match self.consume_range_a1() {
Ok(ParsedRange { left, right }) => {
if let Some(right) = right {
return TokenType::Range {
sheet: None,
left,
right,
};
} else {
return TokenType::Reference {
sheet: None,
column: left.column,
row: left.row,
absolute_row: left.absolute_row,
absolute_column: left.absolute_column,
};
}
}
Err(error) => {
// This could be the range operator: ":"
if let Some(r) = parsed_reference {
if next_char_is_colon {
self.position = position_indent;
return TokenType::Reference {
sheet: None,
row: r.row,
column: r.column,
absolute_column: r.absolute_column,
absolute_row: r.absolute_row,
};
}
}
self.position = self.len;
return TokenType::Illegal(error);
}
}
} else if utils::is_valid_identifier(&name) {
if peek_char == Some('[') {
if let Ok(r) = self.consume_structured_reference(&name) {
return r;
}
return TokenType::Illegal(self.set_error(
"Invalid structured reference",
self.position,
));
}
return TokenType::Ident(name);
} else {
return TokenType::Illegal(
self.set_error("Invalid identifier (A1)", self.position),
);
}
} else {
let pos = self.position;
self.position = position - 1;
match self.consume_range_r1c1() {
// it's a valid R1C1 range
// We need to check it's not something like R1C1P
Ok(ParsedRange { left, right }) => {
if pos > self.position {
self.position = pos;
if utils::is_valid_identifier(&name) {
return TokenType::Ident(name);
} else {
self.position = self.len;
return TokenType::Illegal(
self.set_error(
"Invalid identifier (R1C1)",
pos,
),
);
}
}
if let Some(right) = right {
return TokenType::Range {
sheet: None,
left,
right,
};
} else {
return TokenType::Reference {
sheet: None,
column: left.column,
row: left.row,
absolute_row: left.absolute_row,
absolute_column: left.absolute_column,
};
}
}
Err(error) => {
self.position = position - 1;
if let Ok(r) = self.consume_reference_r1c1() {
if self.peek_char() == Some(':') {
return TokenType::Reference {
sheet: None,
row: r.row,
column: r.column,
absolute_column: r.absolute_column,
absolute_row: r.absolute_row,
};
}
}
self.position = pos;
if utils::is_valid_identifier(&name) {
return TokenType::Ident(name);
} else {
return TokenType::Illegal(self.set_error(
&format!("Invalid identifier (R1C1): {name}"),
error.position,
));
}
}
}
}
}
TokenType::Illegal(self.set_error("Unknown error", self.position))
}
}
}
None => TokenType::EOF,
}
}
// Private methods
fn set_error(&mut self, message: &str, position: usize) -> LexerError {
self.position = self.len;
LexerError {
position,
message: message.to_string(),
}
}
fn peek_char(&mut self) -> Option<char> {
let position = self.position;
if position < self.len {
Some(self.chars[position])
} else {
None
}
}
fn expect_char(&mut self, ch_expected: char) -> Result<()> {
let position = self.position;
if position >= self.len {
return Err(self.set_error(
&format!("Error, expected {} found EOF", &ch_expected),
self.position,
));
} else {
let ch = self.chars[position];
if ch_expected != ch {
return Err(self.set_error(
&format!("Error, expected {} found {}", &ch_expected, &ch),
self.position,
));
}
self.position += 1;
}
Ok(())
}
fn read_next_char(&mut self) -> Option<char> {
let position = self.position;
if position < self.len {
self.position = position + 1;
Some(self.chars[position])
} else {
None
}
}
// Consumes an integer from the input stream
fn consume_integer(&mut self, first: char) -> Result<i32> {
let mut position = self.position;
let len = self.len;
let mut chars = first.to_string();
while position < len {
let next_char = self.chars[position];
if next_char.is_ascii_digit() {
chars.push(next_char);
} else {
break;
}
position += 1;
}
self.position = position;
chars.parse::<i32>().map_err(|_| LexerError {
position,
message: format!("Failed to parse to int: {}", chars),
})
}
// Consumes a number in the current locale.
// It only takes into account the decimal separator
// Note that we do not parse the thousands separator
// Let's say ',' is the thousands separator. Then 1,234 would be an error.
// This is ok for most cases:
// =IF(A1=1,234, TRUE, FALSE) will not work
// If a user introduces a single number in the cell 1,234 we should be able to parse
// and format the cell appropriately
fn consume_number(&mut self, first: char) -> Result<f64> {
let mut position = self.position;
let len = self.len;
let mut chars = first.to_string();
// numbers before the decimal point
while position < len {
let x = self.chars[position];
if x.is_ascii_digit() {
chars.push(x);
} else {
break;
}
position += 1;
}
if position < len && self.chars[position].to_string() == self.locale.numbers.symbols.decimal
{
// numbers after the decimal point
chars.push('.');
position += 1;
while position < len {
let x = self.chars[position];
if x.is_ascii_digit() {
chars.push(x);
} else {
break;
}
position += 1;
}
}
if position + 1 < len && (self.chars[position] == 'e' || self.chars[position] == 'E') {
// exponential side
let x = self.chars[position + 1];
if x == '-' || x == '+' || x.is_ascii_digit() {
chars.push('e');
chars.push(x);
position += 2;
while position < len {
let x = self.chars[position];
if x.is_ascii_digit() {
chars.push(x);
} else {
break;
}
position += 1;
}
}
}
self.position = position;
match chars.parse::<f64>() {
Err(_) => {
Err(self.set_error(&format!("Failed to parse to double: {}", chars), position))
}
Ok(v) => Ok(v),
}
}
// Consumes an identifier from the input stream
fn consume_identifier(&mut self) -> String {
let mut position = self.position;
while position < self.len {
let next_char = self.chars[position];
if next_char.is_alphanumeric() || next_char == '_' || next_char == '.' {
position += 1;
} else {
break;
}
}
let chars = self.chars[self.position..position].iter().collect();
self.position = position;
chars
}
fn consume_string(&mut self) -> String {
let mut position = self.position;
let len = self.len;
let mut chars = "".to_string();
while position < len {
let x = self.chars[position];
position += 1;
if x != '"' {
chars.push(x);
} else if position < len && self.chars[position] == '"' {
chars.push(x);
chars.push(self.chars[position]);
position += 1;
} else {
break;
}
}
self.position = position;
chars
}
// Consumes a quoted string from input
// 'This is a quoted string'
// ' Also is a ''quoted'' string'
// Returns an error if it does not find a closing quote
fn consume_single_quote_string(&mut self) -> Result<String> {
let mut position = self.position;
let len = self.len;
let mut success = false;
let mut needs_escape = false;
while position < len {
let next_char = self.chars[position];
position += 1;
if next_char == '\'' {
if position == len {
success = true;
break;
}
if self.chars[position] != '\'' {
success = true;
break;
} else {
// In Excel we escape "'" with "''"
needs_escape = true;
position += 1;
}
}
}
if !success {
// We reached the end without the closing quote
return Err(self.set_error("Expected closing \"'\" but found end of input", position));
}
let chars: String = self.chars[self.position..position - 1].iter().collect();
self.position = position;
if needs_escape {
// In most cases we will not needs escaping so this would be an overkill
return Ok(chars.replace("''", "'"));
}
Ok(chars)
}
// Reads an error from the input stream
fn consume_error(&mut self) -> TokenType {
let errors = &self.language.errors;
let rest_of_formula: String = self.chars[self.position - 1..self.len].iter().collect();
if rest_of_formula.starts_with(&errors.ref_value) {
self.position += errors.ref_value.chars().count() - 1;
return TokenType::Error(Error::REF);
} else if rest_of_formula.starts_with(&errors.name) {
self.position += errors.name.chars().count() - 1;
return TokenType::Error(Error::NAME);
} else if rest_of_formula.starts_with(&errors.value) {
self.position += errors.value.chars().count() - 1;
return TokenType::Error(Error::VALUE);
} else if rest_of_formula.starts_with(&errors.div) {
self.position += errors.div.chars().count() - 1;
return TokenType::Error(Error::DIV);
} else if rest_of_formula.starts_with(&errors.na) {
self.position += errors.na.chars().count() - 1;
return TokenType::Error(Error::NA);
} else if rest_of_formula.starts_with(&errors.num) {
self.position += errors.num.chars().count() - 1;
return TokenType::Error(Error::NUM);
} else if rest_of_formula.starts_with(&errors.error) {
self.position += errors.error.chars().count() - 1;
return TokenType::Error(Error::ERROR);
} else if rest_of_formula.starts_with(&errors.nimpl) {
self.position += errors.nimpl.chars().count() - 1;
return TokenType::Error(Error::NIMPL);
} else if rest_of_formula.starts_with(&errors.spill) {
self.position += errors.spill.chars().count() - 1;
return TokenType::Error(Error::SPILL);
} else if rest_of_formula.starts_with(&errors.calc) {
self.position += errors.calc.chars().count() - 1;
return TokenType::Error(Error::CALC);
} else if rest_of_formula.starts_with(&errors.null) {
self.position += errors.null.chars().count() - 1;
return TokenType::Error(Error::NULL);
} else if rest_of_formula.starts_with(&errors.circ) {
self.position += errors.circ.chars().count() - 1;
return TokenType::Error(Error::CIRC);
}
TokenType::Illegal(self.set_error("Invalid error.", self.position))
}
fn consume_whitespace(&mut self) {
let mut position = self.position;
let len = self.len;
while position < len {
let x = self.chars[position];
if !x.is_whitespace() {
break;
}
position += 1;
}
self.position = position;
}
fn consume_absolute_reference(&mut self) -> TokenType {
// This is an absolute reference.
// $A$4
if self.mode == LexerMode::R1C1 {
return TokenType::Illegal(
self.set_error("Cannot parse A1 reference in R1C1 mode", self.position),
);
}
self.position -= 1;
self.consume_range(None)
}
fn consume_quoted_sheet_reference(&mut self) -> TokenType {
// This is a reference:
// 'First Sheet'!A34
let sheet_name = match self.consume_single_quote_string() {
Ok(v) => v,
Err(error) => {
return TokenType::Illegal(error);
}
};
if self.next_token() != TokenType::Bang {
return TokenType::Illegal(self.set_error("Expected '!'", self.position));
}
self.consume_range(Some(sheet_name))
}
fn consume_range(&mut self, sheet: Option<String>) -> TokenType {
let m = if self.mode == LexerMode::A1 {
self.consume_range_a1()
} else {
self.consume_range_r1c1()
};
match m {
Ok(ParsedRange { left, right }) => {
if let Some(right) = right {
TokenType::Range { sheet, left, right }
} else {
TokenType::Reference {
sheet,
column: left.column,
row: left.row,
absolute_row: left.absolute_row,
absolute_column: left.absolute_column,
}
}
}
Err(error) => TokenType::Illegal(error),
}
}
}

View File

@@ -0,0 +1,319 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::expressions::{token::TokenType, utils::column_to_number};
use super::Lexer;
use super::{ParsedRange, ParsedReference, Result};
impl Lexer {
// Consumes a reference in A1 style like:
// AS23, $AS23, AS$23, $AS$23, R12
// Or returns an error
fn consume_reference_a1(&mut self) -> Result<ParsedReference> {
let mut absolute_column = false;
let mut absolute_row = false;
let mut position = self.position;
let len = self.len;
if position < len && self.chars[position] == '$' {
absolute_column = true;
position += 1;
}
let mut column = "".to_string();
while position < len {
let x = self.chars[position].to_ascii_uppercase();
match x {
'A'..='Z' => column.push(x),
_ => break,
}
position += 1;
}
if column.is_empty() {
return Err(self.set_error("Failed to parse reference", position));
}
if position < len && self.chars[position] == '$' {
absolute_row = true;
position += 1;
}
let mut row = "".to_string();
while position < len {
let x = self.chars[position];
match x {
'0'..='9' => row.push(x),
_ => break,
}
position += 1;
}
// Note that row numbers could start with 0
self.position = position;
let column = column_to_number(&column).map_err(|error| self.set_error(&error, position))?;
match row.parse::<i32>() {
Ok(row) => {
if row > LAST_ROW {
return Err(self.set_error("Row too large in reference", position));
}
Ok(ParsedReference {
column,
row,
absolute_column,
absolute_row,
})
}
Err(..) => Err(self.set_error("Failed to parse integer", position)),
}
}
// Parsing a range is a parser on it's own right. Here is the grammar:
//
// range -> cell | cell ':' cell | row ':' row | column ':' column
// cell -> column row
// column -> '$' column_name | column_name
// row -> '$' row_name | row_name
// column_name -> 'A'..'XFD'
// row_name -> 1..1_048_576
pub(super) fn consume_range_a1(&mut self) -> Result<ParsedRange> {
// first let's try to parse a cell
let mut position = self.position;
match self.consume_reference_a1() {
Ok(cell) => {
if self.peek_char() == Some(':') {
// It's a range
self.position += 1;
if let Ok(cell2) = self.consume_reference_a1() {
Ok(ParsedRange {
left: cell,
right: Some(cell2),
})
} else {
Err(self.set_error("Expecting reference in range", self.position))
}
} else {
// just a reference
Ok(ParsedRange {
left: cell,
right: None,
})
}
}
Err(_) => {
self.position = position;
// It's either a row range or a column range (or not a range at all)
let len = self.len;
let mut absolute_left = false;
if position < len && self.chars[position] == '$' {
absolute_left = true;
position += 1;
}
let mut column_left = "".to_string();
let mut row_left = "".to_string();
while position < len {
let x = self.chars[position].to_ascii_uppercase();
match x {
'A'..='Z' => column_left.push(x),
'0'..='9' => row_left.push(x),
_ => break,
}
position += 1;
}
if position >= len || self.chars[position] != ':' {
return Err(self.set_error("Expecting reference in range", self.position));
}
position += 1;
let mut absolute_right = false;
if position < len && self.chars[position] == '$' {
absolute_right = true;
position += 1;
}
let mut column_right = "".to_string();
let mut row_right = "".to_string();
while position < len {
let x = self.chars[position].to_ascii_uppercase();
match x {
'A'..='Z' => column_right.push(x),
'0'..='9' => row_right.push(x),
_ => break,
}
position += 1;
}
self.position = position;
// At this point either the columns are the empty string or the rows are the empty string
if !row_left.is_empty() {
// It is a row range 23:56
if row_right.is_empty() || !column_left.is_empty() || !column_right.is_empty() {
return Err(self.set_error("Error parsing Range", position));
}
// Note that row numbers can start with 0
let row_left = match row_left.parse::<i32>() {
Ok(n) => n,
Err(_) => {
return Err(self
.set_error(&format!("Failed parsing row {}", row_left), position))
}
};
let row_right = match row_right.parse::<i32>() {
Ok(n) => n,
Err(_) => {
return Err(self
.set_error(&format!("Failed parsing row {}", row_right), position))
}
};
if row_left > LAST_ROW {
return Err(self.set_error("Row too large in reference", position));
}
if row_right > LAST_ROW {
return Err(self.set_error("Row too large in reference", position));
}
return Ok(ParsedRange {
left: ParsedReference {
row: row_left,
absolute_row: absolute_left,
column: 1,
absolute_column: true,
},
right: Some(ParsedReference {
row: row_right,
absolute_row: absolute_right,
column: LAST_COLUMN,
absolute_column: true,
}),
});
}
// It is a column range
if column_right.is_empty() || !row_right.is_empty() {
return Err(self.set_error("Error parsing Range", position));
}
let column_left = column_to_number(&column_left)
.map_err(|error| self.set_error(&error, position))?;
let column_right = column_to_number(&column_right)
.map_err(|error| self.set_error(&error, position))?;
Ok(ParsedRange {
left: ParsedReference {
row: 1,
absolute_row: true,
column: column_left,
absolute_column: absolute_left,
},
right: Some(ParsedReference {
row: LAST_ROW,
absolute_row: true,
column: column_right,
absolute_column: absolute_right,
}),
})
}
}
}
pub(super) fn consume_range_r1c1(&mut self) -> Result<ParsedRange> {
// first let's try to parse a cell
match self.consume_reference_r1c1() {
Ok(cell) => {
if self.peek_char() == Some(':') {
// It's a range
self.position += 1;
if let Ok(cell2) = self.consume_reference_r1c1() {
Ok(ParsedRange {
left: cell,
right: Some(cell2),
})
} else {
Err(self.set_error("Expecting reference in range", self.position))
}
} else {
// just a reference
Ok(ParsedRange {
left: cell,
right: None,
})
}
}
Err(s) => Err(s),
}
}
pub(super) fn consume_reference_r1c1(&mut self) -> Result<ParsedReference> {
// R12C3, R[2]C[-2], R3C[6], R[-3]C4, RC1, R[-2]C
let absolute_column;
let absolute_row;
let position = self.position;
let row;
let column;
self.expect_char('R')?;
match self.peek_char() {
Some('[') => {
absolute_row = false;
self.expect_char('[')?;
let c = match self.read_next_char() {
Some(s) => s,
None => {
return Err(self.set_error("Expected column number", position));
}
};
match self.consume_integer(c) {
Ok(v) => row = v,
Err(_) => {
return Err(self.set_error("Expected row number", position));
}
}
self.expect(TokenType::RightBracket)?;
}
Some(c) => {
absolute_row = true;
self.expect_char(c)?;
match self.consume_integer(c) {
Ok(v) => row = v,
Err(_) => {
return Err(self.set_error("Expected row number", position));
}
}
}
None => {
return Err(self.set_error("Expected row number or '['", position));
}
}
self.expect_char('C')?;
match self.peek_char() {
Some('[') => {
self.expect_char('[')?;
absolute_column = false;
let c = match self.read_next_char() {
Some(s) => s,
None => {
return Err(self.set_error("Expected column number", position));
}
};
match self.consume_integer(c) {
Ok(v) => column = v,
Err(_) => {
return Err(self.set_error("Expected column number", position));
}
}
self.expect(TokenType::RightBracket)?;
}
Some(c) => {
absolute_column = true;
self.expect_char(c)?;
match self.consume_integer(c) {
Ok(v) => column = v,
Err(_) => {
return Err(self.set_error("Expected column number", position));
}
}
}
None => {
return Err(self.set_error("Expected column number or '['", position));
}
}
if let Some(c) = self.peek_char() {
if c.is_alphanumeric() {
return Err(self.set_error("Expected end of reference", position));
}
}
Ok(ParsedReference {
column,
row,
absolute_column,
absolute_row,
})
}
}

View File

@@ -0,0 +1,188 @@
// Grammar:
// structured references -> table_name "[" arguments "]"
// arguments -> table_reference | "["specifier"]" "," table_reference
// specifier > "#All" |
// "#This Row" |
// "#Data" |
// "#Headers" |
// "#Totals"
// table_reference -> column_reference | range_reference
// column reference -> column_name | "["column_name"]"
// range_reference -> column_reference":"column_reference
use crate::expressions::token::TokenType;
use crate::expressions::token::{TableReference, TableSpecifier};
use super::Result;
use super::{Lexer, LexerError};
impl Lexer {
fn consume_table_specifier(&mut self) -> Result<Option<TableSpecifier>> {
if self.peek_char() == Some('#') {
// It's a specifier
// TODO(TD): There are better ways of doing this :)
let rest_of_formula: String = self.chars[self.position..self.len].iter().collect();
let specifier = if rest_of_formula.starts_with("#This Row]") {
self.position += "#This Row]".bytes().len();
TableSpecifier::ThisRow
} else if rest_of_formula.starts_with("#All]") {
self.position += "#All]".bytes().len();
TableSpecifier::All
} else if rest_of_formula.starts_with("#Data]") {
self.position += "#Data]".bytes().len();
TableSpecifier::Data
} else if rest_of_formula.starts_with("#Headers]") {
self.position += "#Headers]".bytes().len();
TableSpecifier::Headers
} else if rest_of_formula.starts_with("#Totals]") {
self.position += "#Totals]".bytes().len();
TableSpecifier::Totals
} else {
return Err(LexerError {
position: self.position,
message: "Invalid structured reference".to_string(),
});
};
Ok(Some(specifier))
} else {
Ok(None)
}
}
fn consume_column_reference(&mut self) -> Result<String> {
self.consume_whitespace();
let end_char = if self.peek_char() == Some('[') {
self.position += 1;
']'
} else {
')'
};
let mut position = self.position;
while position < self.len {
let next_char = self.chars[position];
if next_char != end_char {
position += 1;
if next_char == '\'' {
if position == self.len {
return Err(LexerError {
position: self.position,
message: "Invalid column name".to_string(),
});
}
// skip next char
position += 1
}
} else {
break;
}
}
let chars: String = self.chars[self.position..position].iter().collect();
if end_char == ']' {
position += 1;
}
self.position = position;
Ok(chars
.replace("'[", "[")
.replace("']", "]")
.replace("'#", "#")
.replace("'@", "@")
.replace("''", "'"))
}
// Possibilities:
// 1. MyTable[#Totals] or MyTable[#This Row]
// 2. MyTable[MyColumn]
// 3. MyTable[[My Column]]
// 4. MyTable[[#This Row], [My Column]]
// 5. MyTable[[#Totals], [MyColumn]]
// 6. MyTable[[#This Row], [Jan]:[Dec]]
// 7. MyTable[]
//
// Multiple specifiers are not supported yet:
// 1. MyTable[[#Data], [#Totals], [MyColumn]]
//
// In particular note that names of columns are escaped only when they are in the first argument
// We use '[' and ']'
// When there is only a specifier but not a reference the specifier is not in brackets
//
// Invalid:
// * MyTable[#Totals, [Jan]:[March]] => MyTable[[#Totals], [Jan]:[March]]
//
// NOTES:
// * MyTable[[#Totals]] is translated into MyTable[#Totals]
// * Excel shows '@' instead of '#This Row':
// MyTable[[#This Row], [Jan]:[Dec]] => MyTable[@[Jan]:[Dec]]
// But this is only a UI thing that we will ignore for now.
pub(crate) fn consume_structured_reference(&mut self, table_name: &str) -> Result<TokenType> {
self.expect(TokenType::LeftBracket)?;
let peek_char = self.peek_char();
if peek_char == Some(']') {
// This is just a reference to the full table
self.expect(TokenType::RightBracket)?;
return Ok(TokenType::Ident(table_name.to_string()));
}
if peek_char == Some('#') {
// Expecting MyTable[#Totals]
if let Some(specifier) = self.consume_table_specifier()? {
return Ok(TokenType::StructuredReference {
table_name: table_name.to_string(),
specifier: Some(specifier),
table_reference: None,
});
} else {
return Err(LexerError {
position: self.position,
message: "Invalid structured reference".to_string(),
});
}
} else if peek_char != Some('[') {
// Expecting MyTable[MyColumn]
self.position -= 1;
let column_name = self.consume_column_reference()?;
return Ok(TokenType::StructuredReference {
table_name: table_name.to_string(),
specifier: None,
table_reference: Some(TableReference::ColumnReference(column_name)),
});
}
self.expect(TokenType::LeftBracket)?;
let specifier = self.consume_table_specifier()?;
if specifier.is_some() {
let peek_token = self.peek_token();
if peek_token == TokenType::Comma {
self.advance_token();
self.expect(TokenType::LeftBracket)?;
} else if peek_token == TokenType::RightBracket {
return Ok(TokenType::StructuredReference {
table_name: table_name.to_string(),
specifier,
table_reference: None,
});
}
}
// Now it's either:
// [Column Name]
// [Column Name]:[Column Name]
self.position -= 1;
let column_reference = self.consume_column_reference()?;
let table_reference = if self.peek_char() == Some(':') {
self.position += 1;
let column_reference_right = self.consume_column_reference()?;
self.expect(TokenType::RightBracket)?;
Some(TableReference::RangeReference((
column_reference,
column_reference_right,
)))
} else {
self.expect(TokenType::RightBracket)?;
Some(TableReference::ColumnReference(column_reference))
};
Ok(TokenType::StructuredReference {
table_name: table_name.to_string(),
specifier,
table_reference,
})
}
}

View File

@@ -0,0 +1,6 @@
mod test_common;
mod test_language;
mod test_locale;
mod test_ranges;
mod test_tables;
mod test_util;

View File

@@ -0,0 +1,508 @@
#![allow(clippy::unwrap_used)]
use crate::language::get_language;
use crate::locale::get_locale;
use crate::expressions::{
lexer::{Lexer, LexerMode},
token::TokenType::*,
token::{Error, OpSum},
};
fn new_lexer(formula: &str, a1_mode: bool) -> Lexer {
let locale = get_locale("en").unwrap();
let language = get_language("en").unwrap();
let mode = if a1_mode {
LexerMode::A1
} else {
LexerMode::R1C1
};
Lexer::new(formula, mode, locale, language)
}
#[test]
fn test_number_zero() {
let mut lx = new_lexer("0", true);
assert_eq!(lx.next_token(), Number(0.0));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_number_integer() {
let mut lx = new_lexer("42", true);
assert_eq!(lx.next_token(), Number(42.0));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_number_pi() {
let mut lx = new_lexer("3.415", true);
assert_eq!(lx.next_token(), Number(3.415));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_number_less_than_one() {
let mut lx = new_lexer(".1415", true);
assert_eq!(lx.next_token(), Number(0.1415));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_number_less_than_one_bis() {
let mut lx = new_lexer("0.1415", true);
assert_eq!(lx.next_token(), Number(0.1415));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_number_scientific() {
let mut lx = new_lexer("1.1415e12", true);
assert_eq!(lx.next_token(), Number(1.1415e12));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_number_scientific_1() {
let mut lx = new_lexer("2.4e-12", true);
assert_eq!(lx.next_token(), Number(2.4e-12));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_number_scientific_1b() {
let mut lx = new_lexer("2.4E-12", true);
assert_eq!(lx.next_token(), Number(2.4e-12));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_not_a_number() {
let mut lx = new_lexer("..", true);
assert!(matches!(lx.next_token(), Illegal(_)));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_string() {
let mut lx = new_lexer("\"Hello World!\"", true);
assert_eq!(lx.next_token(), String("Hello World!".to_string()));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_string_unicode() {
let mut lx = new_lexer("\"你好,世界!\"", true);
assert_eq!(lx.next_token(), String("你好,世界!".to_string()));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_boolean() {
let mut lx = new_lexer("FALSE", true);
assert_eq!(lx.next_token(), Boolean(false));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_boolean_true() {
let mut lx = new_lexer("True", true);
assert_eq!(lx.next_token(), Boolean(true));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference() {
let mut lx = new_lexer("A1", true);
assert_eq!(
lx.next_token(),
Reference {
sheet: None,
column: 1,
row: 1,
absolute_column: false,
absolute_row: false,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_absolute() {
let mut lx = new_lexer("$A$1", true);
assert_eq!(
lx.next_token(),
Reference {
sheet: None,
column: 1,
row: 1,
absolute_column: true,
absolute_row: true,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_absolute_1() {
let mut lx = new_lexer("AB$12", true);
assert_eq!(
lx.next_token(),
Reference {
sheet: None,
column: 28,
row: 12,
absolute_column: false,
absolute_row: true,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_absolute_2() {
let mut lx = new_lexer("$CC234", true);
assert_eq!(
lx.next_token(),
Reference {
sheet: None,
column: 81,
row: 234,
absolute_column: true,
absolute_row: false,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_sheet() {
let mut lx = new_lexer("Sheet1!C34", true);
assert_eq!(
lx.next_token(),
Reference {
sheet: Some("Sheet1".to_string()),
column: 3,
row: 34,
absolute_column: false,
absolute_row: false,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_sheet_unicode() {
// Not that also tests the '!'
let mut lx = new_lexer("'A € world!'!C34", true);
assert_eq!(
lx.next_token(),
Reference {
sheet: Some("A € world!".to_string()),
column: 3,
row: 34,
absolute_column: false,
absolute_row: false,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_sheet_unicode_absolute() {
let mut lx = new_lexer("'A €'!$C$34", true);
assert_eq!(
lx.next_token(),
Reference {
sheet: Some("A €".to_string()),
column: 3,
row: 34,
absolute_column: true,
absolute_row: true,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_unmatched_quote() {
let mut lx = new_lexer("'A €!$C$34", true);
assert!(matches!(lx.next_token(), Illegal(_)));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_sum() {
let mut lx = new_lexer("2.4+3.415", true);
assert_eq!(lx.next_token(), Number(2.4));
assert_eq!(lx.next_token(), Addition(OpSum::Add));
assert_eq!(lx.next_token(), Number(3.415));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_sum_1() {
let mut lx = new_lexer("A2 + 'First Sheet'!$B$3", true);
assert_eq!(
lx.next_token(),
Reference {
sheet: None,
column: 1,
row: 2,
absolute_column: false,
absolute_row: false,
}
);
assert_eq!(lx.next_token(), Addition(OpSum::Add));
assert_eq!(
lx.next_token(),
Reference {
sheet: Some("First Sheet".to_string()),
column: 2,
row: 3,
absolute_column: true,
absolute_row: true,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_error_value() {
let mut lx = new_lexer("#VALUE!", true);
assert_eq!(lx.next_token(), Error(Error::VALUE));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_error_error() {
let mut lx = new_lexer("#ERROR!", true);
assert_eq!(lx.next_token(), Error(Error::ERROR));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_error_div() {
let mut lx = new_lexer("#DIV/0!", true);
assert_eq!(lx.next_token(), Error(Error::DIV));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_error_na() {
let mut lx = new_lexer("#N/A", true);
assert_eq!(lx.next_token(), Error(Error::NA));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_error_name() {
let mut lx = new_lexer("#NAME?", true);
assert_eq!(lx.next_token(), Error(Error::NAME));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_error_num() {
let mut lx = new_lexer("#NUM!", true);
assert_eq!(lx.next_token(), Error(Error::NUM));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_error_calc() {
let mut lx = new_lexer("#CALC!", true);
assert_eq!(lx.next_token(), Error(Error::CALC));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_error_null() {
let mut lx = new_lexer("#NULL!", true);
assert_eq!(lx.next_token(), Error(Error::NULL));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_error_spill() {
let mut lx = new_lexer("#SPILL!", true);
assert_eq!(lx.next_token(), Error(Error::SPILL));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_error_circ() {
let mut lx = new_lexer("#CIRC!", true);
assert_eq!(lx.next_token(), Error(Error::CIRC));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_error_invalid() {
let mut lx = new_lexer("#VALU!", true);
assert!(matches!(lx.next_token(), Illegal(_)));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_add_errors() {
let mut lx = new_lexer("#DIV/0!+#NUM!", true);
assert_eq!(lx.next_token(), Error(Error::DIV));
assert_eq!(lx.next_token(), Addition(OpSum::Add));
assert_eq!(lx.next_token(), Error(Error::NUM));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_variable_name() {
let mut lx = new_lexer("MyVar", true);
assert_eq!(lx.next_token(), Ident("MyVar".to_string()));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_last_reference() {
let mut lx = new_lexer("XFD1048576", true);
assert_eq!(
lx.next_token(),
Reference {
sheet: None,
column: 16384,
row: 1048576,
absolute_column: false,
absolute_row: false,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_not_a_reference() {
let mut lx = new_lexer("XFE10", true);
assert_eq!(lx.next_token(), Ident("XFE10".to_string()));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_r1c1() {
let mut lx = new_lexer("R1C1", false);
assert_eq!(
lx.next_token(),
Reference {
sheet: None,
column: 1,
row: 1,
absolute_column: true,
absolute_row: true,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_r1c1_true() {
let mut lx = new_lexer("R1C1", true);
// NOTE: This is what google docs does.
// Excel will not let you enter this formula.
// Online Excel will let you and will mark the cell as in Error
assert!(matches!(lx.next_token(), Illegal(_)));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_name_r1c1p() {
let mut lx = new_lexer("R1C1P", false);
assert_eq!(lx.next_token(), Ident("R1C1P".to_string()));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_name_wrong_ref() {
let mut lx = new_lexer("Sheet1!2", false);
assert!(matches!(lx.next_token(), Illegal(_)));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_1() {
let mut lx = new_lexer("Sheet1!R[1]C[2]", false);
assert_eq!(
lx.next_token(),
Reference {
sheet: Some("Sheet1".to_string()),
column: 2,
row: 1,
absolute_column: false,
absolute_row: false,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_quotes() {
let mut lx = new_lexer("'Sheet 1'!R[1]C[2]", false);
assert_eq!(
lx.next_token(),
Reference {
sheet: Some("Sheet 1".to_string()),
column: 2,
row: 1,
absolute_column: false,
absolute_row: false,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_escape_quotes() {
let mut lx = new_lexer("'Sheet ''one'' 1'!R[1]C[2]", false);
assert_eq!(
lx.next_token(),
Reference {
sheet: Some("Sheet 'one' 1".to_string()),
column: 2,
row: 1,
absolute_column: false,
absolute_row: false,
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_reference_unfinished_quotes() {
let mut lx = new_lexer("'Sheet 1!R[1]C[2]", false);
assert!(matches!(lx.next_token(), Illegal(_)));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_round_function() {
let mut lx = new_lexer("ROUND", false);
assert_eq!(lx.next_token(), Ident("ROUND".to_string()));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_ident_with_underscore() {
let mut lx = new_lexer("_IDENT", false);
assert_eq!(lx.next_token(), Ident("_IDENT".to_string()));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_ident_with_period() {
let mut lx = new_lexer("IDENT.IFIER", false);
assert_eq!(lx.next_token(), Ident("IDENT.IFIER".to_string()));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_ident_cannot_start_with_period() {
let mut lx = new_lexer(".IFIER", false);
assert!(matches!(lx.next_token(), Illegal(_)));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_xlfn() {
let mut lx = new_lexer("_xlfn.MyVar", true);
assert_eq!(lx.next_token(), Ident("_xlfn.MyVar".to_string()));
assert_eq!(lx.next_token(), EOF);
}

View File

@@ -0,0 +1,101 @@
#![allow(clippy::unwrap_used)]
use crate::{
expressions::{
lexer::{Lexer, LexerMode},
token::{Error, TokenType},
},
language::get_language,
locale::get_locale,
};
fn new_language_lexer(formula: &str, language: &str) -> Lexer {
let locale = get_locale("en").unwrap();
let language = get_language(language).unwrap();
Lexer::new(formula, LexerMode::A1, locale, language)
}
// Spanish
#[test]
fn test_verdadero_falso() {
let mut lx = new_language_lexer("IF(A1, VERDADERO, FALSO)", "es");
assert_eq!(lx.next_token(), TokenType::Ident("IF".to_string()));
assert_eq!(lx.next_token(), TokenType::LeftParenthesis);
assert!(matches!(lx.next_token(), TokenType::Reference { .. }));
assert_eq!(lx.next_token(), TokenType::Comma);
assert_eq!(lx.next_token(), TokenType::Boolean(true));
assert_eq!(lx.next_token(), TokenType::Comma);
assert_eq!(lx.next_token(), TokenType::Boolean(false));
assert_eq!(lx.next_token(), TokenType::RightParenthesis);
assert_eq!(lx.next_token(), TokenType::EOF);
}
#[test]
fn test_spanish_errors_ref() {
let mut lx = new_language_lexer("#¡REF!", "es");
assert_eq!(lx.next_token(), TokenType::Error(Error::REF));
assert_eq!(lx.next_token(), TokenType::EOF);
}
// German
#[test]
fn test_wahr_falsch() {
let mut lx = new_language_lexer("IF(A1, WAHR, FALSCH)", "de");
assert_eq!(lx.next_token(), TokenType::Ident("IF".to_string()));
assert_eq!(lx.next_token(), TokenType::LeftParenthesis);
assert!(matches!(lx.next_token(), TokenType::Reference { .. }));
assert_eq!(lx.next_token(), TokenType::Comma);
assert_eq!(lx.next_token(), TokenType::Boolean(true));
assert_eq!(lx.next_token(), TokenType::Comma);
assert_eq!(lx.next_token(), TokenType::Boolean(false));
assert_eq!(lx.next_token(), TokenType::RightParenthesis);
assert_eq!(lx.next_token(), TokenType::EOF);
}
#[test]
fn test_german_errors_ref() {
let mut lx = new_language_lexer("#BEZUG!", "de");
assert_eq!(lx.next_token(), TokenType::Error(Error::REF));
assert_eq!(lx.next_token(), TokenType::EOF);
}
// French
#[test]
fn test_vrai_faux() {
let mut lx = new_language_lexer("IF(A1, VRAI, FAUX)", "fr");
assert_eq!(lx.next_token(), TokenType::Ident("IF".to_string()));
assert_eq!(lx.next_token(), TokenType::LeftParenthesis);
assert!(matches!(lx.next_token(), TokenType::Reference { .. }));
assert_eq!(lx.next_token(), TokenType::Comma);
assert_eq!(lx.next_token(), TokenType::Boolean(true));
assert_eq!(lx.next_token(), TokenType::Comma);
assert_eq!(lx.next_token(), TokenType::Boolean(false));
assert_eq!(lx.next_token(), TokenType::RightParenthesis);
assert_eq!(lx.next_token(), TokenType::EOF);
}
#[test]
fn test_french_errors_ref() {
let mut lx = new_language_lexer("#REF!", "fr");
assert_eq!(lx.next_token(), TokenType::Error(Error::REF));
assert_eq!(lx.next_token(), TokenType::EOF);
}
// English with errors
#[test]
fn test_english_with_spanish_words() {
let mut lx = new_language_lexer("IF(A1, VERDADERO, FALSO)", "en");
assert_eq!(lx.next_token(), TokenType::Ident("IF".to_string()));
assert_eq!(lx.next_token(), TokenType::LeftParenthesis);
assert!(matches!(lx.next_token(), TokenType::Reference { .. }));
assert_eq!(lx.next_token(), TokenType::Comma);
assert_eq!(lx.next_token(), TokenType::Ident("VERDADERO".to_string()));
assert_eq!(lx.next_token(), TokenType::Comma);
assert_eq!(lx.next_token(), TokenType::Ident("FALSO".to_string()));
assert_eq!(lx.next_token(), TokenType::RightParenthesis);
assert_eq!(lx.next_token(), TokenType::EOF);
}

View File

@@ -0,0 +1,48 @@
#![allow(clippy::unwrap_used)]
use crate::{
expressions::{
lexer::{Lexer, LexerMode},
token::TokenType,
},
language::get_language,
locale::get_locale_fix,
};
fn new_language_lexer(formula: &str, locale: &str, language: &str) -> Lexer {
let locale = get_locale_fix(locale).unwrap();
let language = get_language(language).unwrap();
Lexer::new(formula, LexerMode::A1, locale, language)
}
#[test]
fn test_german_locale() {
let mut lx = new_language_lexer("2,34e-3", "de", "en");
assert_eq!(lx.next_token(), TokenType::Number(2.34e-3));
assert_eq!(lx.next_token(), TokenType::EOF);
}
#[test]
fn test_german_locale_does_not_parse() {
let mut lx = new_language_lexer("2.34e-3", "de", "en");
assert_eq!(lx.next_token(), TokenType::Number(2.0));
assert!(matches!(lx.next_token(), TokenType::Illegal { .. }));
assert_eq!(lx.next_token(), TokenType::EOF);
}
#[test]
fn test_english_locale() {
let mut lx = new_language_lexer("2.34e-3", "en", "en");
assert_eq!(lx.next_token(), TokenType::Number(2.34e-3));
assert_eq!(lx.next_token(), TokenType::EOF);
}
#[test]
fn test_english_locale_does_not_parse() {
// a comma is a separator
let mut lx = new_language_lexer("2,34e-3", "en", "en");
assert_eq!(lx.next_token(), TokenType::Number(2.0));
assert_eq!(lx.next_token(), TokenType::Comma);
assert_eq!(lx.next_token(), TokenType::Number(34e-3));
assert_eq!(lx.next_token(), TokenType::EOF);
}

View File

@@ -0,0 +1,487 @@
#![allow(clippy::unwrap_used)]
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::expressions::lexer::LexerError;
use crate::expressions::{
lexer::{Lexer, LexerMode},
token::TokenType::*,
types::ParsedReference,
};
use crate::language::get_language;
use crate::locale::get_locale;
fn new_lexer(formula: &str) -> Lexer {
let locale = get_locale("en").unwrap();
let language = get_language("en").unwrap();
Lexer::new(formula, LexerMode::A1, locale, language)
}
#[test]
fn test_range() {
let mut lx = new_lexer("C4:D4");
assert_eq!(
lx.next_token(),
Range {
sheet: None,
left: ParsedReference {
column: 3,
row: 4,
absolute_column: false,
absolute_row: false,
},
right: ParsedReference {
column: 4,
row: 4,
absolute_column: false,
absolute_row: false,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_absolute_column() {
let mut lx = new_lexer("$A1:B$4");
assert_eq!(
lx.next_token(),
Range {
sheet: None,
left: ParsedReference {
column: 1,
row: 1,
absolute_column: true,
absolute_row: false,
},
right: ParsedReference {
column: 2,
row: 4,
absolute_column: false,
absolute_row: true,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_with_sheet() {
let mut lx = new_lexer("Sheet1!A1:B4");
assert_eq!(
lx.next_token(),
Range {
sheet: Some("Sheet1".to_string()),
left: ParsedReference {
column: 1,
row: 1,
absolute_column: false,
absolute_row: false,
},
right: ParsedReference {
column: 2,
row: 4,
absolute_column: false,
absolute_row: false,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_with_sheet_with_space() {
let mut lx = new_lexer("'New sheet'!$A$1:B44");
assert_eq!(
lx.next_token(),
Range {
sheet: Some("New sheet".to_string()),
left: ParsedReference {
column: 1,
row: 1,
absolute_column: true,
absolute_row: true,
},
right: ParsedReference {
column: 2,
row: 44,
absolute_column: false,
absolute_row: false,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_column() {
let mut lx = new_lexer("C:D");
assert_eq!(
lx.next_token(),
Range {
sheet: None,
left: ParsedReference {
column: 3,
row: 1,
absolute_column: false,
absolute_row: true,
},
right: ParsedReference {
column: 4,
row: LAST_ROW,
absolute_column: false,
absolute_row: true,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_column_out_of_range() {
let mut lx = new_lexer("C:XFE");
assert_eq!(
lx.next_token(),
Illegal(LexerError {
position: 5,
message: "Column is not valid.".to_string(),
})
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_column_absolute1() {
let mut lx = new_lexer("$C:D");
assert_eq!(
lx.next_token(),
Range {
sheet: None,
left: ParsedReference {
column: 3,
row: 1,
absolute_column: true,
absolute_row: true,
},
right: ParsedReference {
column: 4,
row: LAST_ROW,
absolute_column: false,
absolute_row: true,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_column_absolute2() {
let mut lx = new_lexer("$C:$AA");
assert_eq!(
lx.next_token(),
Range {
sheet: None,
left: ParsedReference {
column: 3,
row: 1,
absolute_column: true,
absolute_row: true,
},
right: ParsedReference {
column: 27,
row: LAST_ROW,
absolute_column: true,
absolute_row: true,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_rows() {
let mut lx = new_lexer("3:5");
assert_eq!(
lx.next_token(),
Range {
sheet: None,
left: ParsedReference {
column: 1,
row: 3,
absolute_column: true,
absolute_row: false,
},
right: ParsedReference {
column: LAST_COLUMN,
row: 5,
absolute_column: true,
absolute_row: false,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_rows_absolute1() {
let mut lx = new_lexer("$3:5");
assert_eq!(
lx.next_token(),
Range {
sheet: None,
left: ParsedReference {
column: 1,
row: 3,
absolute_column: true,
absolute_row: true,
},
right: ParsedReference {
column: LAST_COLUMN,
row: 5,
absolute_column: true,
absolute_row: false,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_rows_absolute2() {
let mut lx = new_lexer("$3:$55");
assert_eq!(
lx.next_token(),
Range {
sheet: None,
left: ParsedReference {
column: 1,
row: 3,
absolute_column: true,
absolute_row: true,
},
right: ParsedReference {
column: LAST_COLUMN,
row: 55,
absolute_column: true,
absolute_row: true,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_column_sheet() {
let mut lx = new_lexer("Sheet1!C:D");
assert_eq!(
lx.next_token(),
Range {
sheet: Some("Sheet1".to_string()),
left: ParsedReference {
column: 3,
row: 1,
absolute_column: false,
absolute_row: true,
},
right: ParsedReference {
column: 4,
row: LAST_ROW,
absolute_column: false,
absolute_row: true,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_column_sheet_absolute() {
let mut lx = new_lexer("Sheet1!$C:$D");
assert_eq!(
lx.next_token(),
Range {
sheet: Some("Sheet1".to_string()),
left: ParsedReference {
column: 3,
row: 1,
absolute_column: true,
absolute_row: true,
},
right: ParsedReference {
column: 4,
row: LAST_ROW,
absolute_column: true,
absolute_row: true,
}
}
);
assert_eq!(lx.next_token(), EOF);
let mut lx = new_lexer("'Woops ans'!$C:$D");
assert_eq!(
lx.next_token(),
Range {
sheet: Some("Woops ans".to_string()),
left: ParsedReference {
column: 3,
row: 1,
absolute_column: true,
absolute_row: true,
},
right: ParsedReference {
column: 4,
row: LAST_ROW,
absolute_column: true,
absolute_row: true,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_rows_sheet() {
let mut lx = new_lexer("'A new sheet'!3:5");
assert_eq!(
lx.next_token(),
Range {
sheet: Some("A new sheet".to_string()),
left: ParsedReference {
column: 1,
row: 3,
absolute_column: true,
absolute_row: false,
},
right: ParsedReference {
column: LAST_COLUMN,
row: 5,
absolute_column: true,
absolute_row: false,
}
}
);
assert_eq!(lx.next_token(), EOF);
let mut lx = new_lexer("Sheet12!3:5");
assert_eq!(
lx.next_token(),
Range {
sheet: Some("Sheet12".to_string()),
left: ParsedReference {
column: 1,
row: 3,
absolute_column: true,
absolute_row: false,
},
right: ParsedReference {
column: LAST_COLUMN,
row: 5,
absolute_column: true,
absolute_row: false,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
// Non ranges
#[test]
fn test_non_range_variable_name() {
let mut lx = new_lexer("AB");
assert_eq!(lx.next_token(), Ident("AB".to_string()));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_non_range_invalid_variable_name() {
let mut lx = new_lexer("$AB");
assert!(matches!(lx.next_token(), Illegal(_)));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_non_range_invalid_variable_name_a03() {
let mut lx = new_lexer("A03");
assert_eq!(
lx.next_token(),
Reference {
sheet: None,
row: 3,
column: 1,
absolute_column: false,
absolute_row: false
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_non_range_invalid_variable_name_sheet1_a03() {
let mut lx = new_lexer("Sheet1!A03");
assert_eq!(
lx.next_token(),
Reference {
sheet: Some("Sheet1".to_string()),
row: 3,
column: 1,
absolute_column: false,
absolute_row: false
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_rows_with_0() {
let mut lx = new_lexer("03:05");
assert_eq!(
lx.next_token(),
Range {
sheet: None,
left: ParsedReference {
column: 1,
row: 3,
absolute_column: true,
absolute_row: false,
},
right: ParsedReference {
column: LAST_COLUMN,
row: 5,
absolute_column: true,
absolute_row: false,
}
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_incomplete_row() {
let mut lx = new_lexer("R[");
lx.set_lexer_mode(LexerMode::R1C1);
assert!(matches!(lx.next_token(), Illegal(_)));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn test_range_incomplete_column() {
let mut lx = new_lexer("R[3][");
lx.set_lexer_mode(LexerMode::R1C1);
assert!(matches!(lx.next_token(), Illegal(_)));
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn range_operator() {
let mut lx = new_lexer("A1:OFFSET(B1,1,2)");
lx.set_lexer_mode(LexerMode::A1);
assert!(matches!(lx.next_token(), Reference { .. }));
assert!(matches!(lx.next_token(), Colon));
assert!(matches!(lx.next_token(), Ident(_)));
assert!(matches!(lx.next_token(), LeftParenthesis));
assert!(matches!(lx.next_token(), Reference { .. }));
assert_eq!(lx.next_token(), Comma);
assert!(matches!(lx.next_token(), Number(_)));
assert_eq!(lx.next_token(), Comma);
assert!(matches!(lx.next_token(), Number(_)));
assert!(matches!(lx.next_token(), RightParenthesis));
assert_eq!(lx.next_token(), EOF);
}

View File

@@ -0,0 +1,73 @@
#![allow(clippy::unwrap_used)]
use crate::expressions::{
lexer::{Lexer, LexerMode},
token::{TableReference, TableSpecifier, TokenType::*},
};
use crate::language::get_language;
use crate::locale::get_locale;
fn new_lexer(formula: &str) -> Lexer {
let locale = get_locale("en").unwrap();
let language = get_language("en").unwrap();
Lexer::new(formula, LexerMode::A1, locale, language)
}
#[test]
fn table_this_row() {
let mut lx = new_lexer("tbInfo[[#This Row], [Jan]:[Dec]]");
assert_eq!(
lx.next_token(),
StructuredReference {
table_name: "tbInfo".to_string(),
specifier: Some(TableSpecifier::ThisRow),
table_reference: Some(TableReference::RangeReference((
"Jan".to_string(),
"Dec".to_string()
)))
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn table_no_specifier() {
let mut lx = new_lexer("tbInfo[December]");
assert_eq!(
lx.next_token(),
StructuredReference {
table_name: "tbInfo".to_string(),
specifier: None,
table_reference: Some(TableReference::ColumnReference("December".to_string()))
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn table_no_specifier_white_spaces() {
let mut lx = new_lexer("tbInfo[[First Month]]");
assert_eq!(
lx.next_token(),
StructuredReference {
table_name: "tbInfo".to_string(),
specifier: None,
table_reference: Some(TableReference::ColumnReference("First Month".to_string()))
}
);
assert_eq!(lx.next_token(), EOF);
}
#[test]
fn table_totals_no_reference() {
let mut lx = new_lexer("tbInfo[#Totals]");
assert_eq!(
lx.next_token(),
StructuredReference {
table_name: "tbInfo".to_string(),
specifier: Some(TableSpecifier::Totals),
table_reference: None
}
);
assert_eq!(lx.next_token(), EOF);
}

View File

@@ -0,0 +1,146 @@
use crate::expressions::{
lexer::util::get_tokens,
token::{OpCompare, OpSum, TokenType},
};
fn get_tokens_types(formula: &str) -> Vec<TokenType> {
let marked_tokens = get_tokens(formula);
marked_tokens.iter().map(|s| s.token.clone()).collect()
}
#[test]
fn test_get_tokens() {
let formula = "1+1";
let t = get_tokens(formula);
assert_eq!(t.len(), 3);
let formula = "1 + AA23 +";
let t = get_tokens(formula);
assert_eq!(t.len(), 4);
let l = t.get(2).expect("expected token");
assert_eq!(l.start, 3);
assert_eq!(l.end, 10);
}
#[test]
fn test_simple_tokens() {
assert_eq!(
get_tokens_types("()"),
vec![TokenType::LeftParenthesis, TokenType::RightParenthesis]
);
assert_eq!(
get_tokens_types("{}"),
vec![TokenType::LeftBrace, TokenType::RightBrace]
);
assert_eq!(
get_tokens_types("[]"),
vec![TokenType::LeftBracket, TokenType::RightBracket]
);
assert_eq!(get_tokens_types("&"), vec![TokenType::And]);
assert_eq!(
get_tokens_types("<"),
vec![TokenType::Compare(OpCompare::LessThan)]
);
assert_eq!(
get_tokens_types(">"),
vec![TokenType::Compare(OpCompare::GreaterThan)]
);
assert_eq!(
get_tokens_types("<="),
vec![TokenType::Compare(OpCompare::LessOrEqualThan)]
);
assert_eq!(
get_tokens_types(">="),
vec![TokenType::Compare(OpCompare::GreaterOrEqualThan)]
);
assert_eq!(
get_tokens_types("IF"),
vec![TokenType::Ident("IF".to_owned())]
);
assert_eq!(get_tokens_types("45"), vec![TokenType::Number(45.0)]);
// The lexer parses this as two tokens
assert_eq!(
get_tokens_types("-45"),
vec![TokenType::Addition(OpSum::Minus), TokenType::Number(45.0)]
);
assert_eq!(
get_tokens_types("23.45e-2"),
vec![TokenType::Number(23.45e-2)]
);
assert_eq!(
get_tokens_types("4-3"),
vec![
TokenType::Number(4.0),
TokenType::Addition(OpSum::Minus),
TokenType::Number(3.0)
]
);
assert_eq!(get_tokens_types("True"), vec![TokenType::Boolean(true)]);
assert_eq!(get_tokens_types("FALSE"), vec![TokenType::Boolean(false)]);
assert_eq!(
get_tokens_types("2,3.5"),
vec![
TokenType::Number(2.0),
TokenType::Comma,
TokenType::Number(3.5)
]
);
assert_eq!(
get_tokens_types("2.4;3.5"),
vec![
TokenType::Number(2.4),
TokenType::Semicolon,
TokenType::Number(3.5)
]
);
assert_eq!(
get_tokens_types("AB34"),
vec![TokenType::Reference {
sheet: None,
row: 34,
column: 28,
absolute_column: false,
absolute_row: false
}]
);
assert_eq!(
get_tokens_types("$A3"),
vec![TokenType::Reference {
sheet: None,
row: 3,
column: 1,
absolute_column: true,
absolute_row: false
}]
);
assert_eq!(
get_tokens_types("AB$34"),
vec![TokenType::Reference {
sheet: None,
row: 34,
column: 28,
absolute_column: false,
absolute_row: true
}]
);
assert_eq!(
get_tokens_types("$AB$34"),
vec![TokenType::Reference {
sheet: None,
row: 34,
column: 28,
absolute_column: true,
absolute_row: true
}]
);
assert_eq!(
get_tokens_types("'My House'!AB34"),
vec![TokenType::Reference {
sheet: Some("My House".to_string()),
row: 34,
column: 28,
absolute_column: false,
absolute_row: false
}]
);
}

View File

@@ -0,0 +1,85 @@
use std::fmt;
use crate::expressions::token;
use crate::language::get_language;
use crate::locale::get_locale;
use super::{Lexer, LexerMode};
/// A MarkedToken is a token together with its position on a formula
#[derive(Debug, PartialEq)]
pub struct MarkedToken {
pub token: token::TokenType,
pub start: i32,
pub end: i32,
}
/// Returns a list of marked tokens for a formula
///
/// # Examples
/// ```
/// use ironcalc_base::expressions::{
/// lexer::util::{get_tokens, MarkedToken},
/// token::{OpSum, TokenType},
/// };
///
/// let marked_tokens = get_tokens("A1+1");
/// let first_t = MarkedToken {
/// token: TokenType::Reference {
/// sheet: None,
/// row: 1,
/// column: 1,
/// absolute_column: false,
/// absolute_row: false,
/// },
/// start: 0,
/// end: 2,
/// };
/// let second_t = MarkedToken {
/// token: TokenType::Addition(OpSum::Add),
/// start:2,
/// end: 3
/// };
/// let third_t = MarkedToken {
/// token: TokenType::Number(1.0),
/// start:3,
/// end: 4
/// };
/// assert_eq!(marked_tokens, vec![first_t, second_t, third_t]);
/// ```
pub fn get_tokens(formula: &str) -> Vec<MarkedToken> {
let mut tokens = Vec::new();
let mut lexer = Lexer::new(
formula,
LexerMode::A1,
get_locale("en").expect(""),
get_language("en").expect(""),
);
let mut start = lexer.get_position();
let mut next_token = lexer.next_token();
let mut end = lexer.get_position();
loop {
match next_token {
token::TokenType::EOF => {
break;
}
_ => {
tokens.push(MarkedToken {
start,
end,
token: next_token,
});
start = lexer.get_position();
next_token = lexer.next_token();
end = lexer.get_position();
}
}
}
tokens
}
impl fmt::Display for MarkedToken {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.token)
}
}

View File

@@ -0,0 +1,6 @@
// public modules
pub mod lexer;
pub mod parser;
pub mod token;
pub mod types;
pub mod utils;

View File

@@ -0,0 +1,877 @@
/*!
# GRAMAR
<pre class="rust">
opComp => '=' | '<' | '>' | '<=' } '>=' | '<>'
opFactor => '*' | '/'
unaryOp => '-' | '+'
expr => concat (opComp concat)*
concat => term ('&' term)*
term => factor (opFactor factor)*
factor => prod (opProd prod)*
prod => power ('^' power)*
power => (unaryOp)* range '%'*
range => primary (':' primary)?
primary => '(' expr ')'
=> number
=> function '(' f_args ')'
=> name
=> string
=> '{' a_args '}'
=> bool
=> bool()
=> error
f_args => e (',' e)*
</pre>
*/
use std::collections::HashMap;
use crate::functions::Function;
use crate::language::get_language;
use crate::locale::get_locale;
use crate::types::Table;
use super::lexer;
use super::token;
use super::token::OpUnary;
use super::token::TableReference;
use super::token::TokenType;
use super::types::*;
use super::utils::number_to_column;
use token::OpCompare;
pub mod move_formula;
pub mod stringify;
pub mod walk;
#[cfg(test)]
mod test;
#[cfg(test)]
mod test_ranges;
#[cfg(test)]
mod test_move_formula;
#[cfg(test)]
mod test_tables;
pub(crate) fn parse_range(formula: &str) -> Result<(i32, i32, i32, i32), String> {
let mut lexer = lexer::Lexer::new(
formula,
lexer::LexerMode::A1,
get_locale("en").expect(""),
get_language("en").expect(""),
);
if let TokenType::Range {
left,
right,
sheet: _,
} = lexer.next_token()
{
Ok((left.column, left.row, right.column, right.row))
} else {
Err("Not a range".to_string())
}
}
fn get_table_column_by_name(table_column_name: &str, table: &Table) -> Option<i32> {
for (index, table_column) in table.columns.iter().enumerate() {
if table_column.name == table_column_name {
return Some(index as i32);
}
}
None
}
pub(crate) struct Reference<'a> {
sheet_name: &'a Option<String>,
sheet_index: u32,
absolute_row: bool,
absolute_column: bool,
row: i32,
column: i32,
}
#[derive(PartialEq, Clone, Debug)]
pub enum Node {
BooleanKind(bool),
NumberKind(f64),
StringKind(String),
ReferenceKind {
sheet_name: Option<String>,
sheet_index: u32,
absolute_row: bool,
absolute_column: bool,
row: i32,
column: i32,
},
RangeKind {
sheet_name: Option<String>,
sheet_index: u32,
absolute_row1: bool,
absolute_column1: bool,
row1: i32,
column1: i32,
absolute_row2: bool,
absolute_column2: bool,
row2: i32,
column2: i32,
},
WrongReferenceKind {
sheet_name: Option<String>,
absolute_row: bool,
absolute_column: bool,
row: i32,
column: i32,
},
WrongRangeKind {
sheet_name: Option<String>,
absolute_row1: bool,
absolute_column1: bool,
row1: i32,
column1: i32,
absolute_row2: bool,
absolute_column2: bool,
row2: i32,
column2: i32,
},
OpRangeKind {
left: Box<Node>,
right: Box<Node>,
},
OpConcatenateKind {
left: Box<Node>,
right: Box<Node>,
},
OpSumKind {
kind: token::OpSum,
left: Box<Node>,
right: Box<Node>,
},
OpProductKind {
kind: token::OpProduct,
left: Box<Node>,
right: Box<Node>,
},
OpPowerKind {
left: Box<Node>,
right: Box<Node>,
},
FunctionKind {
kind: Function,
args: Vec<Node>,
},
InvalidFunctionKind {
name: String,
args: Vec<Node>,
},
ArrayKind(Vec<Node>),
VariableKind(String),
CompareKind {
kind: OpCompare,
left: Box<Node>,
right: Box<Node>,
},
UnaryKind {
kind: OpUnary,
right: Box<Node>,
},
ErrorKind(token::Error),
ParseErrorKind {
formula: String,
message: String,
position: usize,
},
EmptyArgKind,
}
#[derive(Clone)]
pub struct Parser {
lexer: lexer::Lexer,
worksheets: Vec<String>,
context: Option<CellReferenceRC>,
tables: HashMap<String, Table>,
}
impl Parser {
pub fn new(worksheets: Vec<String>, tables: HashMap<String, Table>) -> Parser {
let lexer = lexer::Lexer::new(
"",
lexer::LexerMode::A1,
get_locale("en").expect(""),
get_language("en").expect(""),
);
Parser {
lexer,
worksheets,
context: None,
tables,
}
}
pub fn set_lexer_mode(&mut self, mode: lexer::LexerMode) {
self.lexer.set_lexer_mode(mode)
}
pub fn set_worksheets(&mut self, worksheets: Vec<String>) {
self.worksheets = worksheets;
}
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node {
self.lexer.set_formula(formula);
self.context = context.clone();
self.parse_expr()
}
fn get_sheet_index_by_name(&self, name: &str) -> Option<u32> {
let worksheets = &self.worksheets;
for (i, sheet) in worksheets.iter().enumerate() {
if sheet == name {
return Some(i as u32);
}
}
None
}
fn parse_expr(&mut self) -> Node {
let mut t = self.parse_concat();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
while let TokenType::Compare(op) = next_token {
self.lexer.advance_token();
let p = self.parse_concat();
if let Node::ParseErrorKind { .. } = p {
return p;
}
t = Node::CompareKind {
kind: op,
left: Box::new(t),
right: Box::new(p),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_concat(&mut self) -> Node {
let mut t = self.parse_term();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
while next_token == TokenType::And {
self.lexer.advance_token();
let p = self.parse_term();
if let Node::ParseErrorKind { .. } = p {
return p;
}
t = Node::OpConcatenateKind {
left: Box::new(t),
right: Box::new(p),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_term(&mut self) -> Node {
let mut t = self.parse_factor();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
while let TokenType::Addition(op) = next_token {
self.lexer.advance_token();
let p = self.parse_factor();
if let Node::ParseErrorKind { .. } = p {
return p;
}
t = Node::OpSumKind {
kind: op,
left: Box::new(t),
right: Box::new(p),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_factor(&mut self) -> Node {
let mut t = self.parse_prod();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
while let TokenType::Product(op) = next_token {
self.lexer.advance_token();
let p = self.parse_prod();
if let Node::ParseErrorKind { .. } = p {
return p;
}
t = Node::OpProductKind {
kind: op,
left: Box::new(t),
right: Box::new(p),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_prod(&mut self) -> Node {
let mut t = self.parse_power();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
while next_token == TokenType::Power {
self.lexer.advance_token();
let p = self.parse_power();
if let Node::ParseErrorKind { .. } = p {
return p;
}
t = Node::OpPowerKind {
left: Box::new(t),
right: Box::new(p),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_power(&mut self) -> Node {
let mut next_token = self.lexer.peek_token();
let mut sign = 1;
while let TokenType::Addition(op) = next_token {
self.lexer.advance_token();
if op == token::OpSum::Minus {
sign = -sign;
}
next_token = self.lexer.peek_token();
}
let mut t = self.parse_range();
if let Node::ParseErrorKind { .. } = t {
return t;
}
if sign == -1 {
t = Node::UnaryKind {
kind: token::OpUnary::Minus,
right: Box::new(t),
}
}
next_token = self.lexer.peek_token();
while next_token == TokenType::Percent {
self.lexer.advance_token();
t = Node::UnaryKind {
kind: token::OpUnary::Percentage,
right: Box::new(t),
};
next_token = self.lexer.peek_token();
}
t
}
fn parse_range(&mut self) -> Node {
let t = self.parse_primary();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let next_token = self.lexer.peek_token();
if next_token == TokenType::Colon {
self.lexer.advance_token();
let p = self.parse_primary();
if let Node::ParseErrorKind { .. } = p {
return p;
}
return Node::OpRangeKind {
left: Box::new(t),
right: Box::new(p),
};
}
t
}
fn parse_primary(&mut self) -> Node {
let next_token = self.lexer.next_token();
match next_token {
TokenType::LeftParenthesis => {
let t = self.parse_expr();
if let Node::ParseErrorKind { .. } = t {
return t;
}
if let Err(err) = self.lexer.expect(TokenType::RightParenthesis) {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: err.position,
message: err.message,
};
}
t
}
TokenType::Number(s) => Node::NumberKind(s),
TokenType::String(s) => Node::StringKind(s),
TokenType::LeftBrace => {
let t = self.parse_expr();
if let Node::ParseErrorKind { .. } = t {
return t;
}
let mut next_token = self.lexer.peek_token();
let mut args: Vec<Node> = vec![t];
while next_token == TokenType::Semicolon {
self.lexer.advance_token();
let p = self.parse_expr();
if let Node::ParseErrorKind { .. } = p {
return p;
}
next_token = self.lexer.peek_token();
args.push(p);
}
if let Err(err) = self.lexer.expect(TokenType::RightBrace) {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: err.position,
message: err.message,
};
}
Node::ArrayKind(args)
}
TokenType::Reference {
sheet,
row,
column,
absolute_column,
absolute_row,
} => {
let context = match &self.context {
Some(c) => c,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: self.lexer.get_position() as usize,
message: "Expected context for the reference".to_string(),
}
}
};
let sheet_index = match &sheet {
Some(name) => self.get_sheet_index_by_name(name),
None => self.get_sheet_index_by_name(&context.sheet),
};
let a1_mode = self.lexer.is_a1_mode();
let row = if absolute_row || !a1_mode {
row
} else {
row - context.row
};
let column = if absolute_column || !a1_mode {
column
} else {
column - context.column
};
match sheet_index {
Some(index) => Node::ReferenceKind {
sheet_name: sheet,
sheet_index: index,
row,
column,
absolute_row,
absolute_column,
},
None => Node::WrongReferenceKind {
sheet_name: sheet,
row,
column,
absolute_row,
absolute_column,
},
}
}
TokenType::Range { sheet, left, right } => {
let context = match &self.context {
Some(c) => c,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: self.lexer.get_position() as usize,
message: "Expected context for the reference".to_string(),
}
}
};
let sheet_index = match &sheet {
Some(name) => self.get_sheet_index_by_name(name),
None => self.get_sheet_index_by_name(&context.sheet),
};
let mut row1 = left.row;
let mut column1 = left.column;
let mut row2 = right.row;
let mut column2 = right.column;
let mut absolute_column1 = left.absolute_column;
let mut absolute_column2 = right.absolute_column;
let mut absolute_row1 = left.absolute_row;
let mut absolute_row2 = right.absolute_row;
if self.lexer.is_a1_mode() {
if !left.absolute_row {
row1 -= context.row
};
if !left.absolute_column {
column1 -= context.column
};
if !right.absolute_row {
row2 -= context.row
};
if !right.absolute_column {
column2 -= context.column
};
}
if row1 > row2 {
(row2, row1) = (row1, row2);
(absolute_row2, absolute_row1) = (absolute_row1, absolute_row2);
}
if column1 > column2 {
(column2, column1) = (column1, column2);
(absolute_column2, absolute_column1) = (absolute_column1, absolute_column2);
}
match sheet_index {
Some(index) => Node::RangeKind {
sheet_name: sheet,
sheet_index: index,
row1,
column1,
row2,
column2,
absolute_column1,
absolute_column2,
absolute_row1,
absolute_row2,
},
None => Node::WrongRangeKind {
sheet_name: sheet,
row1,
column1,
row2,
column2,
absolute_column1,
absolute_column2,
absolute_row1,
absolute_row2,
},
}
}
TokenType::Ident(name) => {
let next_token = self.lexer.peek_token();
if next_token == TokenType::LeftParenthesis {
// It's a function call "SUM(.."
self.lexer.advance_token();
let args = match self.parse_function_args() {
Ok(s) => s,
Err(e) => return e,
};
if let Err(err) = self.lexer.expect(TokenType::RightParenthesis) {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: err.position,
message: err.message,
};
}
if let Some(function_kind) = Function::get_function(&name) {
return Node::FunctionKind {
kind: function_kind,
args,
};
} else {
return Node::InvalidFunctionKind { name, args };
}
}
Node::VariableKind(name)
}
TokenType::Error(kind) => Node::ErrorKind(kind),
TokenType::Illegal(error) => Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: error.position,
message: error.message,
},
TokenType::EOF => Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected end of input.".to_string(),
},
TokenType::Boolean(value) => Node::BooleanKind(value),
TokenType::Compare(_) => {
// A primary Node cannot start with an operator
Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected token: 'COMPARE'".to_string(),
}
}
TokenType::Addition(_) => {
// A primary Node cannot start with an operator
Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected token: 'SUM'".to_string(),
}
}
TokenType::Product(_) => {
// A primary Node cannot start with an operator
Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected token: 'PRODUCT'".to_string(),
}
}
TokenType::Power => {
// A primary Node cannot start with an operator
Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected token: 'POWER'".to_string(),
}
}
TokenType::RightParenthesis
| TokenType::RightBracket
| TokenType::Colon
| TokenType::Semicolon
| TokenType::RightBrace
| TokenType::Comma
| TokenType::Bang
| TokenType::And
| TokenType::Percent => Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: format!("Unexpected token: '{}'", next_token),
},
TokenType::LeftBracket => Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Unexpected token: '['".to_string(),
},
TokenType::StructuredReference {
table_name,
specifier,
table_reference,
} => {
// We will try to convert to a normal reference
// table_name[column_name] => cell1:cell2
// table_name[[#This Row], [column_name]:[column_name]] => cell1:cell2
if let Some(context) = &self.context {
let context_sheet_index = match self.get_sheet_index_by_name(&context.sheet) {
Some(i) => i,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "sheet not found".to_string(),
};
}
};
// table-name => table
let table = self.tables.get(&table_name).unwrap_or_else(|| {
panic!(
"Table not found: '{table_name}' at '{}!{}{}'",
context.sheet,
number_to_column(context.column).expect(""),
context.row
)
});
let table_sheet_index = match self.get_sheet_index_by_name(&table.sheet_name) {
Some(i) => i,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "sheet not found".to_string(),
};
}
};
let sheet_name = if table_sheet_index == context_sheet_index {
None
} else {
Some(table.sheet_name.clone())
};
// context must be with tables.reference
let (column_start, mut row_start, column_end, mut row_end) =
parse_range(&table.reference).expect("Failed parsing range");
let totals_row_count = table.totals_row_count as i32;
let header_row_count = table.header_row_count as i32;
row_end -= totals_row_count;
match specifier {
Some(token::TableSpecifier::ThisRow) => {
row_start = context.row;
row_end = context.row;
}
Some(token::TableSpecifier::Totals) => {
if totals_row_count != 0 {
row_start = row_end + 1;
row_end = row_start;
} else {
// Table1[#Totals] is #REF! if Table1 does not have totals
return Node::ErrorKind(token::Error::REF);
}
}
Some(token::TableSpecifier::Headers) => {
row_end = row_start;
}
Some(token::TableSpecifier::Data) => {
row_start += header_row_count;
}
Some(token::TableSpecifier::All) => {
if totals_row_count != 0 {
row_end += 1;
}
}
None => {
// skip the headers
row_start += header_row_count;
}
}
match table_reference {
None => {
return Node::RangeKind {
sheet_name,
sheet_index: table_sheet_index,
absolute_row1: true,
absolute_column1: true,
row1: row_start,
column1: column_start,
absolute_row2: true,
absolute_column2: true,
row2: row_end,
column2: column_end,
};
}
Some(TableReference::ColumnReference(s)) => {
let column_index = match get_table_column_by_name(&s, table) {
Some(s) => s + column_start,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: self.lexer.get_position() as usize,
message: format!(
"Expecting column: {s} in table {table_name}"
),
};
}
};
if row_start == row_end {
return Node::ReferenceKind {
sheet_name,
sheet_index: table_sheet_index,
absolute_row: true,
absolute_column: true,
row: row_start,
column: column_index,
};
}
return Node::RangeKind {
sheet_name,
sheet_index: table_sheet_index,
absolute_row1: true,
absolute_column1: true,
row1: row_start,
column1: column_index,
absolute_row2: true,
absolute_column2: true,
row2: row_end,
column2: column_index,
};
}
Some(TableReference::RangeReference((left, right))) => {
let left_column_index = match get_table_column_by_name(&left, table) {
Some(f) => f + column_start,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: self.lexer.get_position() as usize,
message: format!(
"Expecting column: {left} in table {table_name}"
),
};
}
};
let right_column_index = match get_table_column_by_name(&right, table) {
Some(f) => f + column_start,
None => {
return Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: self.lexer.get_position() as usize,
message: format!(
"Expecting column: {right} in table {table_name}"
),
};
}
};
return Node::RangeKind {
sheet_name,
sheet_index: table_sheet_index,
absolute_row1: true,
absolute_column1: true,
row1: row_start,
column1: left_column_index,
absolute_row2: true,
absolute_column2: true,
row2: row_end,
column2: right_column_index,
};
}
}
}
Node::ParseErrorKind {
formula: self.lexer.get_formula(),
position: 0,
message: "Structured references not supported in R1C1 mode".to_string(),
}
}
}
}
fn parse_function_args(&mut self) -> Result<Vec<Node>, Node> {
let mut args: Vec<Node> = Vec::new();
let mut next_token = self.lexer.peek_token();
if next_token == TokenType::RightParenthesis {
return Ok(args);
}
if self.lexer.peek_token() == TokenType::Comma {
args.push(Node::EmptyArgKind);
} else {
let t = self.parse_expr();
if let Node::ParseErrorKind { .. } = t {
return Err(t);
}
args.push(t);
}
next_token = self.lexer.peek_token();
while next_token == TokenType::Comma {
self.lexer.advance_token();
if self.lexer.peek_token() == TokenType::Comma {
args.push(Node::EmptyArgKind);
next_token = TokenType::Comma;
} else if self.lexer.peek_token() == TokenType::RightParenthesis {
args.push(Node::EmptyArgKind);
return Ok(args);
} else {
let p = self.parse_expr();
if let Node::ParseErrorKind { .. } = p {
return Err(p);
}
next_token = self.lexer.peek_token();
args.push(p);
}
}
Ok(args)
}
}

View File

@@ -0,0 +1,397 @@
use super::{
stringify::{stringify_reference, DisplaceData},
Node, Reference,
};
use crate::{
constants::{LAST_COLUMN, LAST_ROW},
expressions::token::OpUnary,
};
use crate::{
expressions::types::{Area, CellReferenceRC},
number_format::to_excel_precision_str,
};
pub(crate) fn ref_is_in_area(sheet: u32, row: i32, column: i32, area: &Area) -> bool {
if area.sheet != sheet {
return false;
}
if row < area.row || row > area.row + area.height - 1 {
return false;
}
if column < area.column || column > area.column + area.width - 1 {
return false;
}
true
}
pub(crate) struct MoveContext<'a> {
pub source_sheet_name: &'a str,
pub row: i32,
pub column: i32,
pub area: &'a Area,
pub target_sheet_name: &'a str,
pub row_delta: i32,
pub column_delta: i32,
}
/// This implements Excel's cut && paste
/// We are moving a formula in (row, column) to (row+row_delta, column + column_delta).
/// All references that do not point to a cell in area will be left untouched.
/// All references that point to a cell in area will be displaced
pub(crate) fn move_formula(node: &Node, move_context: &MoveContext) -> String {
to_string_moved(node, move_context)
}
fn move_function(name: &str, args: &Vec<Node>, move_context: &MoveContext) -> String {
let mut first = true;
let mut arguments = "".to_string();
for el in args {
if !first {
arguments = format!("{},{}", arguments, to_string_moved(el, move_context));
} else {
first = false;
arguments = to_string_moved(el, move_context);
}
}
format!("{}({})", name, arguments)
}
fn to_string_moved(node: &Node, move_context: &MoveContext) -> String {
use self::Node::*;
match node {
BooleanKind(value) => format!("{}", value).to_ascii_uppercase(),
NumberKind(number) => to_excel_precision_str(*number),
StringKind(value) => format!("\"{}\"", value),
ReferenceKind {
sheet_name,
sheet_index,
absolute_row,
absolute_column,
row,
column,
} => {
let reference_row = if *absolute_row {
*row
} else {
row + move_context.row
};
let reference_column = if *absolute_column {
*column
} else {
column + move_context.column
};
let new_row;
let new_column;
let mut ref_sheet_name = sheet_name;
let source_sheet_name = &Some(move_context.source_sheet_name.to_string());
if ref_is_in_area(
*sheet_index,
reference_row,
reference_column,
move_context.area,
) {
// if the reference is in the area we are moving we want to displace the reference
new_row = row + move_context.row_delta;
new_column = column + move_context.column_delta;
} else {
// If the reference is not in the area we are moving the reference remains unchanged
new_row = *row;
new_column = *column;
if move_context.target_sheet_name != move_context.source_sheet_name
&& sheet_name.is_none()
{
ref_sheet_name = source_sheet_name;
}
};
let context = CellReferenceRC {
sheet: move_context.source_sheet_name.to_string(),
column: move_context.column,
row: move_context.row,
};
stringify_reference(
Some(&context),
&DisplaceData::None,
&Reference {
sheet_name: ref_sheet_name,
sheet_index: *sheet_index,
absolute_row: *absolute_row,
absolute_column: *absolute_column,
row: new_row,
column: new_column,
},
false,
false,
)
}
RangeKind {
sheet_name,
sheet_index,
absolute_row1,
absolute_column1,
row1,
column1,
absolute_row2,
absolute_column2,
row2,
column2,
} => {
let full_row = *absolute_row1 && *absolute_row2 && (*row1 == 1) && (*row2 == LAST_ROW);
let full_column = *absolute_column1
&& *absolute_column2
&& (*column1 == 1)
&& (*column2 == LAST_COLUMN);
let reference_row1 = if *absolute_row1 {
*row1
} else {
row1 + move_context.row
};
let reference_column1 = if *absolute_column1 {
*column1
} else {
column1 + move_context.column
};
let reference_row2 = if *absolute_row2 {
*row2
} else {
row2 + move_context.row
};
let reference_column2 = if *absolute_column2 {
*column2
} else {
column2 + move_context.column
};
let new_row1;
let new_column1;
let new_row2;
let new_column2;
let mut ref_sheet_name = sheet_name;
let source_sheet_name = &Some(move_context.source_sheet_name.to_string());
if ref_is_in_area(
*sheet_index,
reference_row1,
reference_column1,
move_context.area,
) && ref_is_in_area(
*sheet_index,
reference_row2,
reference_column2,
move_context.area,
) {
// if the whole range is inside the area we are moving we want to displace the context
new_row1 = row1 + move_context.row_delta;
new_column1 = column1 + move_context.column_delta;
new_row2 = row2 + move_context.row_delta;
new_column2 = column2 + move_context.column_delta;
} else {
// If the reference is not in the area we are moving the context remains unchanged
new_row1 = *row1;
new_column1 = *column1;
new_row2 = *row2;
new_column2 = *column2;
if move_context.target_sheet_name != move_context.source_sheet_name
&& sheet_name.is_none()
{
ref_sheet_name = source_sheet_name;
}
};
let context = CellReferenceRC {
sheet: move_context.source_sheet_name.to_string(),
column: move_context.column,
row: move_context.row,
};
let s1 = stringify_reference(
Some(&context),
&DisplaceData::None,
&Reference {
sheet_name: ref_sheet_name,
sheet_index: *sheet_index,
absolute_row: *absolute_row1,
absolute_column: *absolute_column1,
row: new_row1,
column: new_column1,
},
full_row,
full_column,
);
let s2 = stringify_reference(
Some(&context),
&DisplaceData::None,
&Reference {
sheet_name: &None,
sheet_index: *sheet_index,
absolute_row: *absolute_row2,
absolute_column: *absolute_column2,
row: new_row2,
column: new_column2,
},
full_row,
full_column,
);
format!("{}:{}", s1, s2)
}
WrongReferenceKind {
sheet_name,
absolute_row,
absolute_column,
row,
column,
} => {
// NB: Excel does not displace wrong references but Google Docs does. We follow Excel
let context = CellReferenceRC {
sheet: move_context.source_sheet_name.to_string(),
column: move_context.column,
row: move_context.row,
};
// It's a wrong reference, so there is no valid `sheet_index`.
// We don't need it, since the `sheet_index` is only used if `displace_data` is not `None`.
// I should fix it, maybe putting the `sheet_index` inside the `displace_data`
stringify_reference(
Some(&context),
&DisplaceData::None,
&Reference {
sheet_name,
sheet_index: 0, // HACK
row: *row,
column: *column,
absolute_row: *absolute_row,
absolute_column: *absolute_column,
},
false,
false,
)
}
WrongRangeKind {
sheet_name,
absolute_row1,
absolute_column1,
row1,
column1,
absolute_row2,
absolute_column2,
row2,
column2,
} => {
let full_row = *absolute_row1 && *absolute_row2 && (*row1 == 1) && (*row2 == LAST_ROW);
let full_column = *absolute_column1
&& *absolute_column2
&& (*column1 == 1)
&& (*column2 == LAST_COLUMN);
// NB: Excel does not displace wrong references but Google Docs does. We follow Excel
let context = CellReferenceRC {
sheet: move_context.source_sheet_name.to_string(),
column: move_context.column,
row: move_context.row,
};
let s1 = stringify_reference(
Some(&context),
&DisplaceData::None,
&Reference {
sheet_name,
sheet_index: 0, // HACK
row: *row1,
column: *column1,
absolute_row: *absolute_row1,
absolute_column: *absolute_column1,
},
full_row,
full_column,
);
let s2 = stringify_reference(
Some(&context),
&DisplaceData::None,
&Reference {
sheet_name: &None,
sheet_index: 0, // HACK
row: *row2,
column: *column2,
absolute_row: *absolute_row2,
absolute_column: *absolute_column2,
},
full_row,
full_column,
);
format!("{}:{}", s1, s2)
}
OpRangeKind { left, right } => format!(
"{}:{}",
to_string_moved(left, move_context),
to_string_moved(right, move_context),
),
OpConcatenateKind { left, right } => format!(
"{}&{}",
to_string_moved(left, move_context),
to_string_moved(right, move_context),
),
OpSumKind { kind, left, right } => format!(
"{}{}{}",
to_string_moved(left, move_context),
kind,
to_string_moved(right, move_context),
),
OpProductKind { kind, left, right } => {
let x = match **left {
OpSumKind { .. } => format!("({})", to_string_moved(left, move_context)),
CompareKind { .. } => format!("({})", to_string_moved(left, move_context)),
_ => to_string_moved(left, move_context),
};
let y = match **right {
OpSumKind { .. } => format!("({})", to_string_moved(right, move_context)),
CompareKind { .. } => format!("({})", to_string_moved(right, move_context)),
OpProductKind { .. } => format!("({})", to_string_moved(right, move_context)),
UnaryKind { .. } => {
format!("({})", to_string_moved(right, move_context))
}
_ => to_string_moved(right, move_context),
};
format!("{}{}{}", x, kind, y)
}
OpPowerKind { left, right } => format!(
"{}^{}",
to_string_moved(left, move_context),
to_string_moved(right, move_context),
),
InvalidFunctionKind { name, args } => move_function(name, args, move_context),
FunctionKind { kind, args } => {
let name = &kind.to_string();
move_function(name, args, move_context)
}
ArrayKind(args) => {
// This code is a placeholder. Arrays are not yet implemented
let mut first = true;
let mut arguments = "".to_string();
for el in args {
if !first {
arguments = format!("{},{}", arguments, to_string_moved(el, move_context));
} else {
first = false;
arguments = to_string_moved(el, move_context);
}
}
format!("{{{}}}", arguments)
}
VariableKind(value) => value.to_string(),
CompareKind { kind, left, right } => format!(
"{}{}{}",
to_string_moved(left, move_context),
kind,
to_string_moved(right, move_context),
),
UnaryKind { kind, right } => match kind {
OpUnary::Minus => format!("-{}", to_string_moved(right, move_context)),
OpUnary::Percentage => format!("{}%", to_string_moved(right, move_context)),
},
ErrorKind(kind) => format!("{}", kind),
ParseErrorKind {
formula,
message: _,
position: _,
} => formula.to_string(),
EmptyArgKind => "".to_string(),
}
}

View File

@@ -0,0 +1,612 @@
use super::{super::utils::quote_name, Node, Reference};
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::expressions::token::OpUnary;
use crate::{expressions::types::CellReferenceRC, number_format::to_excel_precision_str};
pub enum DisplaceData {
Column {
sheet: u32,
column: i32,
delta: i32,
},
Row {
sheet: u32,
row: i32,
delta: i32,
},
CellHorizontal {
sheet: u32,
row: i32,
column: i32,
delta: i32,
},
CellVertical {
sheet: u32,
row: i32,
column: i32,
delta: i32,
},
ColumnMove {
sheet: u32,
column: i32,
delta: i32,
},
None,
}
pub fn to_rc_format(node: &Node) -> String {
stringify(node, None, &DisplaceData::None, false)
}
pub fn to_string_displaced(
node: &Node,
context: &CellReferenceRC,
displace_data: &DisplaceData,
) -> String {
stringify(node, Some(context), displace_data, false)
}
pub fn to_string(node: &Node, context: &CellReferenceRC) -> String {
stringify(node, Some(context), &DisplaceData::None, false)
}
pub fn to_excel_string(node: &Node, context: &CellReferenceRC) -> String {
stringify(node, Some(context), &DisplaceData::None, true)
}
/// Converts a local reference to a string applying some displacement if needed.
/// It uses A1 style if context is not None. If context is None it uses R1C1 style
/// If full_row is true then the row details will be omitted in the A1 case
/// If full_colum is true then column details will be omitted.
pub(crate) fn stringify_reference(
context: Option<&CellReferenceRC>,
displace_data: &DisplaceData,
reference: &Reference,
full_row: bool,
full_column: bool,
) -> String {
let sheet_name = reference.sheet_name;
let sheet_index = reference.sheet_index;
let absolute_row = reference.absolute_row;
let absolute_column = reference.absolute_column;
let row = reference.row;
let column = reference.column;
match context {
Some(context) => {
let mut row = if absolute_row { row } else { row + context.row };
let mut column = if absolute_column {
column
} else {
column + context.column
};
match displace_data {
DisplaceData::Row {
sheet,
row: displace_row,
delta,
} => {
if sheet_index == *sheet && !full_row {
if *delta < 0 {
if &row >= displace_row {
if row < displace_row - *delta {
return "#REF!".to_string();
}
row += *delta;
}
} else if &row >= displace_row {
row += *delta;
}
}
}
DisplaceData::Column {
sheet,
column: displace_column,
delta,
} => {
if sheet_index == *sheet && !full_column {
if *delta < 0 {
if &column >= displace_column {
if column < displace_column - *delta {
return "#REF!".to_string();
}
column += *delta;
}
} else if &column >= displace_column {
column += *delta;
}
}
}
DisplaceData::CellHorizontal {
sheet,
row: displace_row,
column: displace_column,
delta,
} => {
if sheet_index == *sheet && displace_row == &row {
if *delta < 0 {
if &column >= displace_column {
if column < displace_column - *delta {
return "#REF!".to_string();
}
column += *delta;
}
} else if &column >= displace_column {
column += *delta;
}
}
}
DisplaceData::CellVertical {
sheet,
row: displace_row,
column: displace_column,
delta,
} => {
if sheet_index == *sheet && displace_column == &column {
if *delta < 0 {
if &row >= displace_row {
if row < displace_row - *delta {
return "#REF!".to_string();
}
row += *delta;
}
} else if &row >= displace_row {
row += *delta;
}
}
}
DisplaceData::ColumnMove {
sheet,
column: move_column,
delta,
} => {
if sheet_index == *sheet {
if column == *move_column {
column += *delta;
} else if (*delta > 0
&& column > *move_column
&& column <= *move_column + *delta)
|| (*delta < 0
&& column < *move_column
&& column >= *move_column + *delta)
{
column -= *delta;
}
}
}
DisplaceData::None => {}
}
if row < 1 {
return "#REF!".to_string();
}
let mut row_abs = if absolute_row {
format!("${}", row)
} else {
format!("{}", row)
};
let column = match crate::expressions::utils::number_to_column(column) {
Some(s) => s,
None => return "#REF!".to_string(),
};
let mut col_abs = if absolute_column {
format!("${}", column)
} else {
column
};
if full_row {
row_abs = "".to_string()
}
if full_column {
col_abs = "".to_string()
}
match &sheet_name {
Some(name) => {
format!("{}!{}{}", quote_name(name), col_abs, row_abs)
}
None => {
format!("{}{}", col_abs, row_abs)
}
}
}
None => {
let row_abs = if absolute_row {
format!("R{}", row)
} else {
format!("R[{}]", row)
};
let col_abs = if absolute_column {
format!("C{}", column)
} else {
format!("C[{}]", column)
};
match &sheet_name {
Some(name) => {
format!("{}!{}{}", quote_name(name), row_abs, col_abs)
}
None => {
format!("{}{}", row_abs, col_abs)
}
}
}
}
}
fn format_function(
name: &str,
args: &Vec<Node>,
context: Option<&CellReferenceRC>,
displace_data: &DisplaceData,
use_original_name: bool,
) -> String {
let mut first = true;
let mut arguments = "".to_string();
for el in args {
if !first {
arguments = format!(
"{},{}",
arguments,
stringify(el, context, displace_data, use_original_name)
);
} else {
first = false;
arguments = stringify(el, context, displace_data, use_original_name);
}
}
format!("{}({})", name, arguments)
}
fn stringify(
node: &Node,
context: Option<&CellReferenceRC>,
displace_data: &DisplaceData,
use_original_name: bool,
) -> String {
use self::Node::*;
match node {
BooleanKind(value) => format!("{}", value).to_ascii_uppercase(),
NumberKind(number) => to_excel_precision_str(*number),
StringKind(value) => format!("\"{}\"", value),
WrongReferenceKind {
sheet_name,
column,
row,
absolute_row,
absolute_column,
} => stringify_reference(
context,
&DisplaceData::None,
&Reference {
sheet_name,
sheet_index: 0,
row: *row,
column: *column,
absolute_row: *absolute_row,
absolute_column: *absolute_column,
},
false,
false,
),
ReferenceKind {
sheet_name,
sheet_index,
column,
row,
absolute_row,
absolute_column,
} => stringify_reference(
context,
displace_data,
&Reference {
sheet_name,
sheet_index: *sheet_index,
row: *row,
column: *column,
absolute_row: *absolute_row,
absolute_column: *absolute_column,
},
false,
false,
),
RangeKind {
sheet_name,
sheet_index,
absolute_row1,
absolute_column1,
row1,
column1,
absolute_row2,
absolute_column2,
row2,
column2,
} => {
// Note that open ranges SUM(A:A) or SUM(1:1) will be treated as normal ranges in the R1C1 (internal) representation
// A:A will be R1C[0]:R1048576C[0]
// So when we are forming the A1 range we need to strip the irrelevant information
let full_row = *absolute_row1 && *absolute_row2 && (*row1 == 1) && (*row2 == LAST_ROW);
let full_column = *absolute_column1
&& *absolute_column2
&& (*column1 == 1)
&& (*column2 == LAST_COLUMN);
let s1 = stringify_reference(
context,
displace_data,
&Reference {
sheet_name,
sheet_index: *sheet_index,
row: *row1,
column: *column1,
absolute_row: *absolute_row1,
absolute_column: *absolute_column1,
},
full_row,
full_column,
);
let s2 = stringify_reference(
context,
displace_data,
&Reference {
sheet_name: &None,
sheet_index: *sheet_index,
row: *row2,
column: *column2,
absolute_row: *absolute_row2,
absolute_column: *absolute_column2,
},
full_row,
full_column,
);
format!("{}:{}", s1, s2)
}
WrongRangeKind {
sheet_name,
absolute_row1,
absolute_column1,
row1,
column1,
absolute_row2,
absolute_column2,
row2,
column2,
} => {
// Note that open ranges SUM(A:A) or SUM(1:1) will be treated as normal ranges in the R1C1 (internal) representation
// A:A will be R1C[0]:R1048576C[0]
// So when we are forming the A1 range we need to strip the irrelevant information
let full_row = *absolute_row1 && *absolute_row2 && (*row1 == 1) && (*row2 == LAST_ROW);
let full_column = *absolute_column1
&& *absolute_column2
&& (*column1 == 1)
&& (*column2 == LAST_COLUMN);
let s1 = stringify_reference(
context,
&DisplaceData::None,
&Reference {
sheet_name,
sheet_index: 0, // HACK
row: *row1,
column: *column1,
absolute_row: *absolute_row1,
absolute_column: *absolute_column1,
},
full_row,
full_column,
);
let s2 = stringify_reference(
context,
&DisplaceData::None,
&Reference {
sheet_name: &None,
sheet_index: 0, // HACK
row: *row2,
column: *column2,
absolute_row: *absolute_row2,
absolute_column: *absolute_column2,
},
full_row,
full_column,
);
format!("{}:{}", s1, s2)
}
OpRangeKind { left, right } => format!(
"{}:{}",
stringify(left, context, displace_data, use_original_name),
stringify(right, context, displace_data, use_original_name)
),
OpConcatenateKind { left, right } => format!(
"{}&{}",
stringify(left, context, displace_data, use_original_name),
stringify(right, context, displace_data, use_original_name)
),
CompareKind { kind, left, right } => format!(
"{}{}{}",
stringify(left, context, displace_data, use_original_name),
kind,
stringify(right, context, displace_data, use_original_name)
),
OpSumKind { kind, left, right } => format!(
"{}{}{}",
stringify(left, context, displace_data, use_original_name),
kind,
stringify(right, context, displace_data, use_original_name)
),
OpProductKind { kind, left, right } => {
let x = match **left {
OpSumKind { .. } => format!(
"({})",
stringify(left, context, displace_data, use_original_name)
),
CompareKind { .. } => format!(
"({})",
stringify(left, context, displace_data, use_original_name)
),
_ => stringify(left, context, displace_data, use_original_name),
};
let y = match **right {
OpSumKind { .. } => format!(
"({})",
stringify(right, context, displace_data, use_original_name)
),
CompareKind { .. } => format!(
"({})",
stringify(right, context, displace_data, use_original_name)
),
OpProductKind { .. } => format!(
"({})",
stringify(right, context, displace_data, use_original_name)
),
_ => stringify(right, context, displace_data, use_original_name),
};
format!("{}{}{}", x, kind, y)
}
OpPowerKind { left, right } => format!(
"{}^{}",
stringify(left, context, displace_data, use_original_name),
stringify(right, context, displace_data, use_original_name)
),
InvalidFunctionKind { name, args } => {
format_function(name, args, context, displace_data, use_original_name)
}
FunctionKind { kind, args } => {
let name = if use_original_name {
kind.to_xlsx_string()
} else {
kind.to_string()
};
format_function(&name, args, context, displace_data, use_original_name)
}
ArrayKind(args) => {
let mut first = true;
let mut arguments = "".to_string();
for el in args {
if !first {
arguments = format!(
"{},{}",
arguments,
stringify(el, context, displace_data, use_original_name)
);
} else {
first = false;
arguments = stringify(el, context, displace_data, use_original_name);
}
}
format!("{{{}}}", arguments)
}
VariableKind(value) => value.to_string(),
UnaryKind { kind, right } => match kind {
OpUnary::Minus => {
format!(
"-{}",
stringify(right, context, displace_data, use_original_name)
)
}
OpUnary::Percentage => {
format!(
"{}%",
stringify(right, context, displace_data, use_original_name)
)
}
},
ErrorKind(kind) => format!("{}", kind),
ParseErrorKind {
formula,
position: _,
message: _,
} => formula.to_string(),
EmptyArgKind => "".to_string(),
}
}
pub(crate) fn rename_sheet_in_node(node: &mut Node, sheet_index: u32, new_name: &str) {
match node {
// Rename
Node::ReferenceKind {
sheet_name,
sheet_index: index,
..
} => {
if *index == sheet_index && sheet_name.is_some() {
*sheet_name = Some(new_name.to_owned());
}
}
Node::RangeKind {
sheet_name,
sheet_index: index,
..
} => {
if *index == sheet_index && sheet_name.is_some() {
*sheet_name = Some(new_name.to_owned());
}
}
Node::WrongReferenceKind { sheet_name, .. } => {
if let Some(name) = sheet_name {
if name.to_uppercase() == new_name.to_uppercase() {
*sheet_name = Some(name.to_owned())
}
}
}
Node::WrongRangeKind { sheet_name, .. } => {
if sheet_name.is_some() {
*sheet_name = Some(new_name.to_owned());
}
}
// Go next level
Node::OpRangeKind { left, right } => {
rename_sheet_in_node(left, sheet_index, new_name);
rename_sheet_in_node(right, sheet_index, new_name);
}
Node::OpConcatenateKind { left, right } => {
rename_sheet_in_node(left, sheet_index, new_name);
rename_sheet_in_node(right, sheet_index, new_name);
}
Node::OpSumKind {
kind: _,
left,
right,
} => {
rename_sheet_in_node(left, sheet_index, new_name);
rename_sheet_in_node(right, sheet_index, new_name);
}
Node::OpProductKind {
kind: _,
left,
right,
} => {
rename_sheet_in_node(left, sheet_index, new_name);
rename_sheet_in_node(right, sheet_index, new_name);
}
Node::OpPowerKind { left, right } => {
rename_sheet_in_node(left, sheet_index, new_name);
rename_sheet_in_node(right, sheet_index, new_name);
}
Node::FunctionKind { kind: _, args } => {
for arg in args {
rename_sheet_in_node(arg, sheet_index, new_name);
}
}
Node::InvalidFunctionKind { name: _, args } => {
for arg in args {
rename_sheet_in_node(arg, sheet_index, new_name);
}
}
Node::CompareKind {
kind: _,
left,
right,
} => {
rename_sheet_in_node(left, sheet_index, new_name);
rename_sheet_in_node(right, sheet_index, new_name);
}
Node::UnaryKind { kind: _, right } => {
rename_sheet_in_node(right, sheet_index, new_name);
}
// Do nothing
Node::BooleanKind(_) => {}
Node::NumberKind(_) => {}
Node::StringKind(_) => {}
Node::ErrorKind(_) => {}
Node::ParseErrorKind { .. } => {}
Node::ArrayKind(_) => {}
Node::VariableKind(_) => {}
Node::EmptyArgKind => {}
}
}

View File

@@ -0,0 +1,497 @@
use std::collections::HashMap;
use crate::expressions::lexer::LexerMode;
use crate::expressions::parser::stringify::DisplaceData;
use super::super::types::CellReferenceRC;
use super::Parser;
use super::{
super::parser::{
stringify::{to_rc_format, to_string},
Node,
},
stringify::to_string_displaced,
};
struct Formula<'a> {
initial: &'a str,
expected: &'a str,
}
#[test]
fn test_parser_reference() {
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("A2", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R[1]C[0]");
}
#[test]
fn test_parser_absolute_column() {
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("$A1", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R[0]C1");
}
#[test]
fn test_parser_absolute_row_col() {
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("$C$5", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R5C3");
}
#[test]
fn test_parser_absolute_row_col_1() {
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("$A$1", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R1C1");
}
#[test]
fn test_parser_simple_formula() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("C3+Sheet2!D4", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R[2]C[2]+Sheet2!R[3]C[3]");
}
#[test]
fn test_parser_boolean() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("true", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "TRUE");
}
#[test]
fn test_parser_bad_formula() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("#Value", &Some(cell_reference));
match &t {
Node::ParseErrorKind {
formula,
message,
position,
} => {
assert_eq!(formula, "#Value");
assert_eq!(message, "Invalid error.");
assert_eq!(*position, 1);
}
_ => {
panic!("Expected error in formula");
}
}
assert_eq!(to_rc_format(&t), "#Value");
}
#[test]
fn test_parser_bad_formula_1() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("<5", &Some(cell_reference));
match &t {
Node::ParseErrorKind {
formula,
message,
position,
} => {
assert_eq!(formula, "<5");
assert_eq!(message, "Unexpected token: 'COMPARE'");
assert_eq!(*position, 0);
}
_ => {
panic!("Expected error in formula");
}
}
assert_eq!(to_rc_format(&t), "<5");
}
#[test]
fn test_parser_bad_formula_2() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("*5", &Some(cell_reference));
match &t {
Node::ParseErrorKind {
formula,
message,
position,
} => {
assert_eq!(formula, "*5");
assert_eq!(message, "Unexpected token: 'PRODUCT'");
assert_eq!(*position, 0);
}
_ => {
panic!("Expected error in formula");
}
}
assert_eq!(to_rc_format(&t), "*5");
}
#[test]
fn test_parser_bad_formula_3() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("SUM(#VALVE!)", &Some(cell_reference));
match &t {
Node::ParseErrorKind {
formula,
message,
position,
} => {
assert_eq!(formula, "SUM(#VALVE!)");
assert_eq!(message, "Invalid error.");
assert_eq!(*position, 5);
}
_ => {
panic!("Expected error in formula");
}
}
assert_eq!(to_rc_format(&t), "SUM(#VALVE!)");
}
#[test]
fn test_parser_formulas() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let formulas = vec![
Formula {
initial: "IF(C3:D4>2,B5,SUM(D1:D7))",
expected: "IF(R[2]C[2]:R[3]C[3]>2,R[4]C[1],SUM(R[0]C[3]:R[6]C[3]))",
},
Formula {
initial: "-A1",
expected: "-R[0]C[0]",
},
Formula {
initial: "#VALUE!",
expected: "#VALUE!",
},
Formula {
initial: "SUM(C3:D4)",
expected: "SUM(R[2]C[2]:R[3]C[3])",
},
Formula {
initial: "A1/(B1-C1)",
expected: "R[0]C[0]/(R[0]C[1]-R[0]C[2])",
},
];
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
for formula in formulas {
let t = parser.parse(
formula.initial,
&Some(CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
}),
);
assert_eq!(to_rc_format(&t), formula.expected);
assert_eq!(to_string(&t, &cell_reference), formula.initial);
}
}
#[test]
fn test_parser_r1c1_formulas() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
parser.set_lexer_mode(LexerMode::R1C1);
let formulas = vec![
Formula {
initial: "IF(R[2]C[2]:R[3]C[3]>2,R[4]C[1],SUM(R[0]C[3]:R[6]C[3]))",
expected: "IF(E5:F6>2,D7,SUM(F3:F9))",
},
Formula {
initial: "-R[0]C[0]",
expected: "-C3",
},
Formula {
initial: "R[1]C[-1]+1",
expected: "B4+1",
},
Formula {
initial: "#VALUE!",
expected: "#VALUE!",
},
Formula {
initial: "SUM(R[2]C[2]:R[3]C[3])",
expected: "SUM(E5:F6)",
},
Formula {
initial: "R[-3]C[0]",
expected: "#REF!",
},
Formula {
initial: "R[0]C[-3]",
expected: "#REF!",
},
Formula {
initial: "R[-2]C[-2]",
expected: "A1",
},
Formula {
initial: "SIN(R[-3]C[-3])",
expected: "SIN(#REF!)",
},
];
// Reference cell is Sheet1!C3
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 3,
column: 3,
};
for formula in formulas {
let t = parser.parse(
formula.initial,
&Some(CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
}),
);
assert_eq!(to_string(&t, &cell_reference), formula.expected);
assert_eq!(to_rc_format(&t), formula.initial);
}
}
#[test]
fn test_parser_quotes() {
let worksheets = vec!["Sheet1".to_string(), "Second Sheet".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("C3+'Second Sheet'!D4", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R[2]C[2]+'Second Sheet'!R[3]C[3]");
}
#[test]
fn test_parser_escape_quotes() {
let worksheets = vec!["Sheet1".to_string(), "Second '2' Sheet".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("C3+'Second ''2'' Sheet'!D4", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "R[2]C[2]+'Second ''2'' Sheet'!R[3]C[3]");
}
#[test]
fn test_parser_parenthesis() {
let worksheets = vec!["Sheet1".to_string(), "Second2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("(C3=\"Yes\")*5", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "(R[2]C[2]=\"Yes\")*5");
}
#[test]
fn test_parser_excel_xlfn() {
let worksheets = vec!["Sheet1".to_string(), "Second2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let t = parser.parse("_xlfn.CONCAT(C3)", &Some(cell_reference));
assert_eq!(to_rc_format(&t), "CONCAT(R[2]C[2])");
}
#[test]
fn test_to_string_displaced() {
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let node = parser.parse("C3", &Some(context.clone()));
let displace_data = DisplaceData::Column {
sheet: 0,
column: 1,
delta: 4,
};
let t = to_string_displaced(&node, context, &displace_data);
assert_eq!(t, "G3".to_string());
}
#[test]
fn test_to_string_displaced_full_ranges() {
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let node = parser.parse("SUM(3:3)", &Some(context.clone()));
let displace_data = DisplaceData::Column {
sheet: 0,
column: 1,
delta: 4,
};
assert_eq!(
to_string_displaced(&node, context, &displace_data),
"SUM(3:3)".to_string()
);
let node = parser.parse("SUM(D:D)", &Some(context.clone()));
let displace_data = DisplaceData::Row {
sheet: 0,
row: 3,
delta: 4,
};
assert_eq!(
to_string_displaced(&node, context, &displace_data),
"SUM(D:D)".to_string()
);
}
#[test]
fn test_to_string_displaced_too_low() {
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let node = parser.parse("C3", &Some(context.clone()));
let displace_data = DisplaceData::Column {
sheet: 0,
column: 1,
delta: -40,
};
let t = to_string_displaced(&node, context, &displace_data);
assert_eq!(t, "#REF!".to_string());
}
#[test]
fn test_to_string_displaced_too_high() {
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let node = parser.parse("C3", &Some(context.clone()));
let displace_data = DisplaceData::Column {
sheet: 0,
column: 1,
delta: 4000000,
};
let t = to_string_displaced(&node, context, &displace_data);
assert_eq!(t, "#REF!".to_string());
}

View File

@@ -0,0 +1,482 @@
use std::collections::HashMap;
use crate::expressions::parser::move_formula::{move_formula, MoveContext};
use crate::expressions::types::Area;
use super::super::types::CellReferenceRC;
use super::Parser;
#[test]
fn test_move_formula() {
// top left corner C2
let row = 2;
let column = 3;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Area is C2:F6
let area = &Area {
sheet: 0,
row,
column,
width: 4,
height: 5,
};
// formula AB31 will not change
let node = parser.parse("AB31", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "AB31");
// formula $AB$31 will not change
let node = parser.parse("AB31", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "AB31");
// but formula D5 will change to N15 (N = D + 10)
let node = parser.parse("D5", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "N15");
// Also formula $D$5 will change to N15 (N = D + 10)
let node = parser.parse("$D$5", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "$N$15");
}
#[test]
fn test_move_formula_context_offset() {
// context is E4
let row = 4;
let column = 5;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Area is C2:F6
let area = &Area {
sheet: 0,
row: 2,
column: 3,
width: 4,
height: 5,
};
let node = parser.parse("-X9+C2%", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "-X9+M12%");
}
#[test]
fn test_move_formula_area_limits() {
// context is E4
let row = 4;
let column = 5;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Area is C2:F6
let area = &Area {
sheet: 0,
row: 2,
column: 3,
width: 4,
height: 5,
};
// Outside of the area. Not moved
let node = parser.parse("B2+B3+C1+G6+H5", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "B2+B3+C1+G6+H5");
// In the area. Moved
let node = parser.parse("C2+F4+F5+F6", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "M12+P14+P15+P16");
}
#[test]
fn test_move_formula_ranges() {
// top left corner C2
let row = 2;
let column = 3;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let area = &Area {
sheet: 0,
row,
column,
width: 4,
height: 5,
};
// Ranges inside the area are fully displaced (absolute or not)
let node = parser.parse("SUM(C2:F5)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM(M12:P15)");
let node = parser.parse("SUM($C$2:$F$5)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM($M$12:$P$15)");
// Ranges completely outside of the area are not touched
let node = parser.parse("SUM(A1:B3)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM(A1:B3)");
let node = parser.parse("SUM($A$1:$B$3)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM($A$1:$B$3)");
// Ranges that overlap with the area are also NOT displaced
let node = parser.parse("SUM(A1:F5)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM(A1:F5)");
// Ranges that contain the area are also NOT displaced
let node = parser.parse("SUM(A1:X50)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM(A1:X50)");
}
#[test]
fn test_move_formula_wrong_reference() {
// context is E4
let row = 4;
let column = 5;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
// Area is C2:G5
let area = &Area {
sheet: 0,
row: 2,
column: 3,
width: 4,
height: 5,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Wrong formulas will NOT be displaced
let node = parser.parse("Sheet3!AB31", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "Sheet3!AB31");
let node = parser.parse("Sheet3!$X$9", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "Sheet3!$X$9");
let node = parser.parse("SUM(Sheet3!D2:D3)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "SUM(Sheet3!D2:D3)");
}
#[test]
fn test_move_formula_misc() {
// context is E4
let row = 4;
let column = 5;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
let worksheets = vec!["Sheet1".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Area is C2:F6
let area = &Area {
sheet: 0,
row: 2,
column: 3,
width: 4,
height: 5,
};
let node = parser.parse("X9^C2-F4*H2", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "X9^M12-P14*H2");
let node = parser.parse("F5*(-D5)*SUM(A1, X9, $D$5)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "P15*(-N15)*SUM(A1,X9,$N$15)");
let node = parser.parse("IF(F5 < -D5, X9 & F5, FALSE)", &Some(context.clone()));
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet1",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(t, "IF(P15<-N15,X9&P15,FALSE)");
}
#[test]
fn test_move_formula_another_sheet() {
// top left corner C2
let row = 2;
let column = 3;
let context = &CellReferenceRC {
sheet: "Sheet1".to_string(),
row,
column,
};
// we add two sheets and we cut/paste from Sheet1 to Sheet2
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Area is C2:F6
let area = &Area {
sheet: 0,
row,
column,
width: 4,
height: 5,
};
// Formula AB31 and JJ3:JJ4 refers to original Sheet1!AB31 and Sheet1!JJ3:JJ4
let node = parser.parse(
"AB31*SUM(JJ3:JJ4)+SUM(Sheet2!C2:F6)*SUM(C2:F6)",
&Some(context.clone()),
);
let t = move_formula(
&node,
&MoveContext {
source_sheet_name: "Sheet1",
row,
column,
area,
target_sheet_name: "Sheet2",
row_delta: 10,
column_delta: 10,
},
);
assert_eq!(
t,
"Sheet1!AB31*SUM(Sheet1!JJ3:JJ4)+SUM(Sheet2!C2:F6)*SUM(M12:P16)"
);
}

View File

@@ -0,0 +1,102 @@
use std::collections::HashMap;
use crate::expressions::lexer::LexerMode;
use super::super::parser::stringify::{to_rc_format, to_string};
use super::super::types::CellReferenceRC;
use super::Parser;
struct Formula<'a> {
formula_a1: &'a str,
formula_r1c1: &'a str,
}
#[test]
fn test_parser_formulas_with_full_ranges() {
let worksheets = vec!["Sheet1".to_string(), "Second Sheet".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
let formulas = vec![
Formula {
formula_a1: "IF(C:D>2,B5,SUM(D:D))",
formula_r1c1: "IF(R1C[2]:R1048576C[3]>2,R[4]C[1],SUM(R1C[3]:R1048576C[3]))",
},
Formula {
formula_a1: "A:A",
formula_r1c1: "R1C[0]:R1048576C[0]",
},
Formula {
formula_a1: "SUM(3:3)",
formula_r1c1: "SUM(R[2]C1:R[2]C16384)",
},
Formula {
formula_a1: "SUM($3:$3)",
formula_r1c1: "SUM(R3C1:R3C16384)",
},
Formula {
formula_a1: "SUM(Sheet1!3:$3)",
formula_r1c1: "SUM(Sheet1!R[2]C1:R3C16384)",
},
Formula {
formula_a1: "SUM('Second Sheet'!C:D)",
formula_r1c1: "SUM('Second Sheet'!R1C[2]:R1048576C[3])",
},
];
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
for formula in &formulas {
let t = parser.parse(
formula.formula_a1,
&Some(CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
}),
);
assert_eq!(to_rc_format(&t), formula.formula_r1c1);
assert_eq!(to_string(&t, &cell_reference), formula.formula_a1);
}
// Now the inverse
parser.set_lexer_mode(LexerMode::R1C1);
for formula in &formulas {
let t = parser.parse(
formula.formula_r1c1,
&Some(CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
}),
);
assert_eq!(to_rc_format(&t), formula.formula_r1c1);
assert_eq!(to_string(&t, &cell_reference), formula.formula_a1);
}
}
#[test]
fn test_range_inverse_order() {
let worksheets = vec!["Sheet1".to_string(), "Sheet2".to_string()];
let mut parser = Parser::new(worksheets, HashMap::new());
// Reference cell is Sheet1!A1
let cell_reference = CellReferenceRC {
sheet: "Sheet1".to_string(),
row: 1,
column: 1,
};
// D4:C2 => C2:D4
let t = parser.parse(
"SUM(D4:C2)*SUM(Sheet2!D4:C20)*SUM($C$20:D4)",
&Some(cell_reference.clone()),
);
assert_eq!(
to_string(&t, &cell_reference),
"SUM(C2:D4)*SUM(Sheet2!C4:D20)*SUM($C4:D$20)".to_string()
);
}

View File

@@ -0,0 +1,100 @@
#![allow(clippy::unwrap_used)]
use std::collections::HashMap;
use crate::expressions::parser::stringify::to_string;
use crate::expressions::utils::{number_to_column, parse_reference_a1};
use crate::types::{Table, TableColumn, TableStyleInfo};
use super::super::types::CellReferenceRC;
use super::Parser;
fn create_test_table(
table_name: &str,
column_names: &[&str],
cell_ref: &str,
row_count: i32,
) -> HashMap<String, Table> {
let mut table = HashMap::new();
let mut columns = Vec::new();
for (id, name) in column_names.iter().enumerate() {
columns.push(TableColumn {
id: id as u32,
name: name.to_string(),
..Default::default()
})
}
let init_cell = parse_reference_a1(cell_ref).unwrap();
let start_row = init_cell.row;
let start_column = number_to_column(init_cell.column).unwrap();
let end_column = number_to_column(init_cell.column + column_names.len() as i32).unwrap();
let end_row = start_row + row_count - 1;
let area_ref = format!("{start_column}{start_row}:{end_column}{end_row}");
table.insert(
table_name.to_string(),
Table {
name: table_name.to_string(),
display_name: table_name.to_string(),
sheet_name: "Sheet One".to_string(),
reference: area_ref,
totals_row_count: 0,
header_row_count: 1,
header_row_dxf_id: None,
data_dxf_id: None,
columns,
style_info: TableStyleInfo {
..Default::default()
},
totals_row_dxf_id: None,
has_filters: false,
},
);
table
}
#[test]
fn simple_table() {
let worksheets = vec!["Sheet One".to_string(), "Second Sheet".to_string()];
// This is a table A1:F3, the column F has a formula
let column_names = ["Jan", "Feb", "Mar", "Apr", "Dec", "Year"];
let row_count = 3;
let tables = create_test_table("tblIncome", &column_names, "A1", row_count);
let mut parser = Parser::new(worksheets, tables);
// Reference cell is 'Sheet One'!F2
let cell_reference = CellReferenceRC {
sheet: "Sheet One".to_string(),
row: 2,
column: 6,
};
let formula = "SUM(tblIncome[[#This Row],[Jan]:[Dec]])";
let t = parser.parse(formula, &Some(cell_reference.clone()));
assert_eq!(to_string(&t, &cell_reference), "SUM($A$2:$E$2)");
// Cell A3
let cell_reference = CellReferenceRC {
sheet: "Sheet One".to_string(),
row: 4,
column: 1,
};
let formula = "SUBTOTAL(109, tblIncome[Jan])";
let t = parser.parse(formula, &Some(cell_reference.clone()));
assert_eq!(to_string(&t, &cell_reference), "SUBTOTAL(109,$A$2:$A$3)");
// Cell A3 in 'Second Sheet'
let cell_reference = CellReferenceRC {
sheet: "Second Sheet".to_string(),
row: 4,
column: 1,
};
let formula = "SUBTOTAL(109, tblIncome[Jan])";
let t = parser.parse(formula, &Some(cell_reference.clone()));
assert_eq!(
to_string(&t, &cell_reference),
"SUBTOTAL(109,'Sheet One'!$A$2:$A$3)"
);
}

View File

@@ -0,0 +1,276 @@
use super::{move_formula::ref_is_in_area, Node};
use crate::expressions::types::{Area, CellReferenceIndex};
pub(crate) fn forward_references(
node: &mut Node,
context: &CellReferenceIndex,
source_area: &Area,
target_sheet: u32,
target_sheet_name: &str,
target_row: i32,
target_column: i32,
) {
match node {
Node::ReferenceKind {
sheet_name,
sheet_index: reference_sheet,
absolute_row,
absolute_column,
row: reference_row,
column: reference_column,
} => {
let reference_row_absolute = if *absolute_row {
*reference_row
} else {
*reference_row + context.row
};
let reference_column_absolute = if *absolute_column {
*reference_column
} else {
*reference_column + context.column
};
if ref_is_in_area(
*reference_sheet,
reference_row_absolute,
reference_column_absolute,
source_area,
) {
if *reference_sheet != target_sheet {
*sheet_name = Some(target_sheet_name.to_string());
*reference_sheet = target_sheet;
}
*reference_row = target_row + *reference_row - source_area.row;
*reference_column = target_column + *reference_column - source_area.column;
}
}
Node::RangeKind {
sheet_name,
sheet_index,
absolute_row1,
absolute_column1,
row1,
column1,
absolute_row2,
absolute_column2,
row2,
column2,
} => {
let reference_row1 = if *absolute_row1 {
*row1
} else {
*row1 + context.row
};
let reference_column1 = if *absolute_column1 {
*column1
} else {
*column1 + context.column
};
let reference_row2 = if *absolute_row2 {
*row2
} else {
*row2 + context.row
};
let reference_column2 = if *absolute_column2 {
*column2
} else {
*column2 + context.column
};
if ref_is_in_area(*sheet_index, reference_row1, reference_column1, source_area)
&& ref_is_in_area(*sheet_index, reference_row2, reference_column2, source_area)
{
if *sheet_index != target_sheet {
*sheet_index = target_sheet;
*sheet_name = Some(target_sheet_name.to_string());
}
*row1 = target_row + *row1 - source_area.row;
*column1 = target_column + *column1 - source_area.column;
*row2 = target_row + *row2 - source_area.row;
*column2 = target_column + *column2 - source_area.column;
}
}
// Recurse
Node::OpRangeKind { left, right } => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::OpConcatenateKind { left, right } => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::OpSumKind {
kind: _,
left,
right,
} => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::OpProductKind {
kind: _,
left,
right,
} => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::OpPowerKind { left, right } => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::FunctionKind { kind: _, args } => {
for arg in args {
forward_references(
arg,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
}
Node::InvalidFunctionKind { name: _, args } => {
for arg in args {
forward_references(
arg,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
}
Node::CompareKind {
kind: _,
left,
right,
} => {
forward_references(
left,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
Node::UnaryKind { kind: _, right } => {
forward_references(
right,
context,
source_area,
target_sheet,
target_sheet_name,
target_row,
target_column,
);
}
// TODO: Not implemented
Node::ArrayKind(_) => {}
// Do nothing. Note: we could do a blanket _ => {}
Node::VariableKind(_) => {}
Node::ErrorKind(_) => {}
Node::ParseErrorKind { .. } => {}
Node::EmptyArgKind => {}
Node::BooleanKind(_) => {}
Node::NumberKind(_) => {}
Node::StringKind(_) => {}
Node::WrongReferenceKind { .. } => {}
Node::WrongRangeKind { .. } => {}
}
}

View File

@@ -0,0 +1,21 @@
use super::*;
#[test]
fn test_error_codes() {
let errors = vec![
Error::REF,
Error::NAME,
Error::VALUE,
Error::DIV,
Error::NA,
Error::NUM,
Error::ERROR,
];
for (i, error) in errors.iter().enumerate() {
let s = format!("{}", error);
let index = error_index(s.clone()).unwrap();
assert_eq!(i as i32, index);
let s2 = error_string(i as usize).unwrap();
assert_eq!(s, s2);
}
}

View File

@@ -0,0 +1,388 @@
use std::fmt;
use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::language::Language;
use super::{lexer::LexerError, types::ParsedReference};
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum OpCompare {
LessThan,
GreaterThan,
Equal,
LessOrEqualThan,
GreaterOrEqualThan,
NonEqual,
}
impl fmt::Display for OpCompare {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
OpCompare::LessThan => write!(fmt, "<"),
OpCompare::GreaterThan => write!(fmt, ">"),
OpCompare::Equal => write!(fmt, "="),
OpCompare::LessOrEqualThan => write!(fmt, "<="),
OpCompare::GreaterOrEqualThan => write!(fmt, ">="),
OpCompare::NonEqual => write!(fmt, "<>"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum OpUnary {
Minus,
Percentage,
}
impl fmt::Display for OpUnary {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
OpUnary::Minus => write!(fmt, "-"),
OpUnary::Percentage => write!(fmt, "%"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum OpSum {
Add,
Minus,
}
impl fmt::Display for OpSum {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
OpSum::Add => write!(fmt, "+"),
OpSum::Minus => write!(fmt, "-"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum OpProduct {
Times,
Divide,
}
impl fmt::Display for OpProduct {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
OpProduct::Times => write!(fmt, "*"),
OpProduct::Divide => write!(fmt, "/"),
}
}
}
/// List of `errors`
/// Note that "#ERROR!" and "#N/IMPL!" are not part of the xlsx standard
/// * "#ERROR!" means there was an error processing the formula (for instance "=A1+")
/// * "#N/IMPL!" means the formula or feature in Excel but has not been implemented in IronCalc
/// Note that they are serialized/deserialized by index
#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Eq, Clone)]
#[repr(u8)]
pub enum Error {
REF,
NAME,
VALUE,
DIV,
NA,
NUM,
ERROR,
NIMPL,
SPILL,
CALC,
CIRC,
NULL,
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::NULL => write!(fmt, "#NULL!"),
Error::REF => write!(fmt, "#REF!"),
Error::NAME => write!(fmt, "#NAME?"),
Error::VALUE => write!(fmt, "#VALUE!"),
Error::DIV => write!(fmt, "#DIV/0!"),
Error::NA => write!(fmt, "#N/A"),
Error::NUM => write!(fmt, "#NUM!"),
Error::ERROR => write!(fmt, "#ERROR!"),
Error::NIMPL => write!(fmt, "#N/IMPL"),
Error::SPILL => write!(fmt, "#SPILL!"),
Error::CALC => write!(fmt, "#CALC!"),
Error::CIRC => write!(fmt, "#CIRC!"),
}
}
}
impl Error {
pub fn to_localized_error_string(&self, language: &Language) -> String {
match self {
Error::NULL => language.errors.null.to_string(),
Error::REF => language.errors.ref_value.to_string(),
Error::NAME => language.errors.name.to_string(),
Error::VALUE => language.errors.value.to_string(),
Error::DIV => language.errors.div.to_string(),
Error::NA => language.errors.na.to_string(),
Error::NUM => language.errors.num.to_string(),
Error::ERROR => language.errors.error.to_string(),
Error::NIMPL => language.errors.nimpl.to_string(),
Error::SPILL => language.errors.spill.to_string(),
Error::CALC => language.errors.calc.to_string(),
Error::CIRC => language.errors.circ.to_string(),
}
}
}
pub fn get_error_by_name(name: &str, language: &Language) -> Option<Error> {
let errors = &language.errors;
if name == errors.ref_value {
return Some(Error::REF);
} else if name == errors.name {
return Some(Error::NAME);
} else if name == errors.value {
return Some(Error::VALUE);
} else if name == errors.div {
return Some(Error::DIV);
} else if name == errors.na {
return Some(Error::NA);
} else if name == errors.num {
return Some(Error::NUM);
} else if name == errors.error {
return Some(Error::ERROR);
} else if name == errors.nimpl {
return Some(Error::NIMPL);
} else if name == errors.spill {
return Some(Error::SPILL);
} else if name == errors.calc {
return Some(Error::CALC);
} else if name == errors.circ {
return Some(Error::CIRC);
} else if name == errors.null {
return Some(Error::NULL);
}
None
}
pub fn get_error_by_english_name(name: &str) -> Option<Error> {
if name == "#REF!" {
return Some(Error::REF);
} else if name == "#NAME?" {
return Some(Error::NAME);
} else if name == "#VALUE!" {
return Some(Error::VALUE);
} else if name == "#DIV/0!" {
return Some(Error::DIV);
} else if name == "#N/A" {
return Some(Error::NA);
} else if name == "#NUM!" {
return Some(Error::NUM);
} else if name == "#ERROR!" {
return Some(Error::ERROR);
} else if name == "#N/IMPL!" {
return Some(Error::NIMPL);
} else if name == "#SPILL!" {
return Some(Error::SPILL);
} else if name == "#CALC!" {
return Some(Error::CALC);
} else if name == "#CIRC!" {
return Some(Error::CIRC);
} else if name == "#NULL!" {
return Some(Error::NULL);
}
None
}
pub fn is_english_error_string(name: &str) -> bool {
let names = [
"#REF!", "#NAME?", "#VALUE!", "#DIV/0!", "#N/A", "#NUM!", "#ERROR!", "#N/IMPL!", "#SPILL!",
"#CALC!", "#CIRC!", "#NULL!",
];
names.iter().any(|e| *e == name)
}
#[derive(Debug, PartialEq, Clone)]
pub enum TableSpecifier {
All,
Data,
Headers,
ThisRow,
Totals,
}
#[derive(Debug, PartialEq, Clone)]
pub enum TableReference {
ColumnReference(String),
RangeReference((String, String)),
}
#[derive(Debug, PartialEq, Clone)]
pub enum TokenType {
Illegal(LexerError),
EOF,
Ident(String), // abc123
String(String), // "A season"
Number(f64), // 123.4
Boolean(bool), // TRUE | FALSE
Error(Error), // #VALUE!
Compare(OpCompare), // <,>, ...
Addition(OpSum), // +,-
Product(OpProduct), // *,/
Power, // ^
LeftParenthesis, // (
RightParenthesis, // )
Colon, // :
Semicolon, // ;
LeftBracket, // [
RightBracket, // ]
LeftBrace, // {
RightBrace, // }
Comma, // ,
Bang, // !
Percent, // %
And, // &
Reference {
sheet: Option<String>,
row: i32,
column: i32,
absolute_column: bool,
absolute_row: bool,
},
Range {
sheet: Option<String>,
left: ParsedReference,
right: ParsedReference,
},
StructuredReference {
table_name: String,
specifier: Option<TableSpecifier>,
table_reference: Option<TableReference>,
},
}
impl fmt::Display for TokenType {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use self::TokenType::*;
match self {
Illegal(_) => write!(fmt, "Illegal"),
EOF => write!(fmt, ""),
Ident(value) => write!(fmt, "{}", value),
String(value) => write!(fmt, "\"{}\"", value),
Number(value) => write!(fmt, "{}", value),
Boolean(value) => write!(fmt, "{}", value),
Error(value) => write!(fmt, "{}", value),
Compare(value) => write!(fmt, "{}", value),
Addition(value) => write!(fmt, "{}", value),
Product(value) => write!(fmt, "{}", value),
Power => write!(fmt, "^"),
LeftParenthesis => write!(fmt, "("),
RightParenthesis => write!(fmt, ")"),
Colon => write!(fmt, ":"),
Semicolon => write!(fmt, ";"),
LeftBracket => write!(fmt, "["),
RightBracket => write!(fmt, "]"),
LeftBrace => write!(fmt, "{{"),
RightBrace => write!(fmt, "}}"),
Comma => write!(fmt, ","),
Bang => write!(fmt, "!"),
Percent => write!(fmt, "%"),
And => write!(fmt, "&"),
Reference {
sheet,
row,
column,
absolute_column,
absolute_row,
} => {
let row_data = if *absolute_row {
format!("{}", row)
} else {
format!("${}", row)
};
let column_data = if *absolute_column {
format!("{}", column)
} else {
format!("${}", column)
};
match sheet {
Some(name) => write!(fmt, "{}!{}{}", name, column_data, row_data),
None => write!(fmt, "{}{}", column, row),
}
}
Range { sheet, left, right } => {
let row_left_data = if left.absolute_row {
format!("{}", left.row)
} else {
format!("${}", left.row)
};
let column_left_data = if left.absolute_column {
format!("{}", left.column)
} else {
format!("${}", left.column)
};
let row_right_data = if right.absolute_row {
format!("{}", right.row)
} else {
format!("${}", right.row)
};
let column_right_data = if right.absolute_column {
format!("{}", right.column)
} else {
format!("${}", right.column)
};
match sheet {
Some(name) => write!(
fmt,
"{}!{}{}:{}{}",
name, column_left_data, row_left_data, column_right_data, row_right_data
),
None => write!(
fmt,
"{}{}:{}{}",
left.column, left.row, right.column, right.row
),
}
}
StructuredReference {
table_name: _,
specifier: _,
table_reference: _,
} => {
// This should never happen
write!(fmt, "-----ERROR-----")
}
}
}
}
pub fn index(token: &TokenType) -> u32 {
use self::TokenType::*;
match token {
Illegal(..) => 1,
EOF => 2,
Ident(..) => 3,
String(..) => 4,
Number(..) => 6,
Boolean(..) => 7,
Error(..) => 8,
Addition(..) => 9,
Product(..) => 10,
Power => 14,
LeftParenthesis => 15,
RightParenthesis => 16,
Colon => 17,
Semicolon => 18,
LeftBracket => 19,
RightBracket => 20,
LeftBrace => 21,
RightBrace => 22,
Comma => 23,
Bang => 24,
Percent => 30,
And => 31,
Reference { .. } => 34,
Range { .. } => 35,
Compare(..) => 37,
StructuredReference { .. } => 40,
}
}

View File

@@ -0,0 +1,51 @@
use serde::{Deserialize, Serialize};
// $A$34
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct ParsedReference {
pub column: i32,
pub row: i32,
pub absolute_column: bool,
pub absolute_row: bool,
}
/// If right is None it is just a reference
/// Column ranges like D:D will have `absolute_row=true` and `left.row=1` and `right.row=LAST_ROW`
/// Row ranges like 5:5 will have `absolute_column=true` and `left.column=1` and `right.column=LAST_COLUMN`
pub struct ParsedRange {
pub left: ParsedReference,
pub right: Option<ParsedReference>,
}
// FIXME: It does not make sense to have two different structures.
// We should have a single one CellReferenceNamed or something like that.
// Sheet1!C3
pub struct CellReference {
pub sheet: String,
pub column: String,
pub row: String,
}
// Sheet1!C3 -> CellReferenceRC{Sheet1, 3, 3}
#[derive(Clone)]
pub struct CellReferenceRC {
pub sheet: String,
pub column: i32,
pub row: i32,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct CellReferenceIndex {
pub sheet: u32,
pub column: i32,
pub row: i32,
}
#[derive(Serialize, Deserialize)]
pub struct Area {
pub sheet: u32,
pub row: i32,
pub column: i32,
pub width: i32,
pub height: i32,
}

View File

@@ -0,0 +1,281 @@
use super::types::*;
use crate::constants::{LAST_COLUMN, LAST_ROW};
#[cfg(test)]
mod test;
/// Converts column letter identifier to number.
pub fn column_to_number(column: &str) -> Result<i32, String> {
if column.is_empty() {
return Err("Column identifier cannot be empty.".to_string());
}
if !column.is_ascii() {
return Err("Column identifier must be ASCII.".to_string());
}
let mut column_number = 0;
for character in column.chars() {
if !character.is_ascii_uppercase() {
return Err("Column identifier can use only A-Z characters".to_string());
}
column_number = column_number * 26 + ((character as i32) - 64);
}
match is_valid_column_number(column_number) {
true => Ok(column_number),
false => Err("Column is not valid.".to_string()),
}
}
/// If input number is outside valid range `None` is returned.
pub fn number_to_column(mut i: i32) -> Option<String> {
if !is_valid_column_number(i) {
return None;
}
let mut column = "".to_string();
while i > 0 {
let r = ((i - 1) % 26) as u8;
column.insert(0, (65 + r) as char);
i = (i - 1) / 26;
}
Some(column)
}
/// Checks if column number is in valid range.
pub fn is_valid_column_number(column: i32) -> bool {
(1..=LAST_COLUMN).contains(&column)
}
pub fn is_valid_column(column: &str) -> bool {
// last column XFD
if column.len() > 3 {
return false;
}
let column_number = column_to_number(column);
match column_number {
Ok(column_number) => is_valid_column_number(column_number),
Err(_) => false,
}
}
pub fn is_valid_row(row: i32) -> bool {
(1..=LAST_ROW).contains(&row)
}
fn is_valid_row_str(row: &str) -> bool {
match row.parse::<i32>() {
Ok(r) => is_valid_row(r),
Err(_r) => false,
}
}
pub fn parse_reference_r1c1(r: &str) -> Option<ParsedReference> {
let chars = r.as_bytes();
let len = chars.len();
let absolute_column;
let absolute_row;
let mut row = "".to_string();
let mut column = "".to_string();
if len < 4 {
return None;
}
if chars[0] != b'R' {
return None;
}
let mut i = 1;
if chars[i] == b'[' {
i += 1;
absolute_row = false;
if chars[i] == b'-' {
i += 1;
row.push('-');
}
} else {
absolute_row = true;
}
while i < len {
let ch = chars[i];
if ch.is_ascii_digit() {
row.push(ch as char);
} else {
break;
}
i += 1;
}
if !absolute_row {
if i >= len || chars[i] != b']' {
return None;
};
i += 1;
}
if i >= len || chars[i] != b'C' {
return None;
};
i += 1;
if i < len && chars[i] == b'[' {
absolute_column = false;
i += 1;
if i < len && chars[i] == b'-' {
i += 1;
column.push('-');
}
} else {
absolute_column = true;
}
while i < len {
let ch = chars[i];
if ch.is_ascii_digit() {
column.push(ch as char);
} else {
break;
}
i += 1;
}
if !absolute_column {
if i >= len || chars[i] != b']' {
return None;
};
i += 1;
}
if i != len {
return None;
}
Some(ParsedReference {
row: row.parse::<i32>().unwrap_or(0),
column: column.parse::<i32>().unwrap_or(0),
absolute_column,
absolute_row,
})
}
pub fn parse_reference_a1(r: &str) -> Option<ParsedReference> {
let chars = r.chars();
let mut absolute_column = false;
let mut absolute_row = false;
let mut row = "".to_string();
let mut column = "".to_string();
let mut state = 1; // 1(colum), 2(row)
for ch in chars {
match ch {
'A'..='Z' => {
if state == 1 {
column.push(ch);
} else {
return None;
}
}
'0'..='9' => {
if state == 1 {
state = 2
}
row.push(ch);
}
'$' => {
if column == *"" {
absolute_column = true;
} else if state == 1 {
absolute_row = true;
state = 2;
} else {
return None;
}
}
_ => {
return None;
}
}
}
if !is_valid_column(&column) {
return None;
}
if !is_valid_row_str(&row) {
return None;
}
let row = match row.parse::<i32>() {
Ok(r) => r,
Err(_) => return None,
};
Some(ParsedReference {
row,
column: column_to_number(&column).ok()?,
absolute_column,
absolute_row,
})
}
pub fn is_valid_identifier(name: &str) -> bool {
// https://support.microsoft.com/en-us/office/names-in-formulas-fc2935f9-115d-4bef-a370-3aa8bb4c91f1
// https://github.com/MartinTrummer/excel-names/
// NOTE: We are being much more restrictive than Excel.
// In particular we do not support non ascii characters.
let upper = name.to_ascii_uppercase();
let bytes = upper.as_bytes();
let len = bytes.len();
if len > 255 || len == 0 {
return false;
}
let first = bytes[0] as char;
// The first character of a name must be a letter, an underscore character (_), or a backslash (\).
if !(first.is_ascii_alphabetic() || first == '_' || first == '\\') {
return false;
}
// You cannot use the uppercase and lowercase characters "C", "c", "R", or "r" as a defined name
if len == 1 && (first == 'R' || first == 'C') {
return false;
}
if upper == *"TRUE" || upper == *"FALSE" {
return false;
}
if parse_reference_a1(name).is_some() {
return false;
}
if parse_reference_r1c1(name).is_some() {
return false;
}
let mut i = 1;
while i < len {
let ch = bytes[i] as char;
match ch {
'a'..='z' => {}
'A'..='Z' => {}
'0'..='9' => {}
'_' => {}
'.' => {}
_ => {
return false;
}
}
i += 1;
}
true
}
fn name_needs_quoting(name: &str) -> bool {
let chars = name.chars();
// it contains any of these characters: ()'$,;-+{} or space
for char in chars {
if [' ', '(', ')', '\'', '$', ',', ';', '-', '+', '{', '}'].contains(&char) {
return true;
}
}
// TODO:
// cell reference in A1 notation, e.g. B1048576 is quoted, B1048577 is not
// cell reference in R1C1 notation, e.g. RC, RC2, R5C, R-4C, RC-8, R, C
// integers
false
}
/// Quotes a string sheet name if it needs to
/// NOTE: Invalid characters in a sheet name \, /, *, \[, \], :, ?
pub fn quote_name(name: &str) -> String {
if name_needs_quoting(name) {
return format!("'{}'", name.replace('\'', "''"));
};
name.to_string()
}

View File

@@ -0,0 +1,214 @@
use super::*;
#[test]
fn test_column_to_number() {
assert_eq!(column_to_number("A"), Ok(1));
assert_eq!(column_to_number("Z"), Ok(26));
assert_eq!(column_to_number("AA"), Ok(27));
assert_eq!(column_to_number("AB"), Ok(28));
assert_eq!(column_to_number("XFD"), Ok(16_384));
assert_eq!(column_to_number("XFD"), Ok(LAST_COLUMN));
assert_eq!(
column_to_number("XFE"),
Err("Column is not valid.".to_string())
);
assert_eq!(
column_to_number(""),
Err("Column identifier cannot be empty.".to_string())
);
assert_eq!(
column_to_number("💥"),
Err("Column identifier must be ASCII.".to_string())
);
assert_eq!(
column_to_number("A1"),
Err("Column identifier can use only A-Z characters".to_string())
);
assert_eq!(
column_to_number("ab"),
Err("Column identifier can use only A-Z characters".to_string())
);
}
#[test]
fn test_is_valid_column() {
assert!(is_valid_column("A"));
assert!(is_valid_column("AA"));
assert!(is_valid_column("XFD"));
assert!(!is_valid_column("a"));
assert!(!is_valid_column("aa"));
assert!(!is_valid_column("xfd"));
assert!(!is_valid_column("1"));
assert!(!is_valid_column("-1"));
assert!(!is_valid_column("XFE"));
assert!(!is_valid_column(""));
}
#[test]
fn test_number_to_column() {
assert_eq!(number_to_column(1), Some("A".to_string()));
assert_eq!(number_to_column(26), Some("Z".to_string()));
assert_eq!(number_to_column(27), Some("AA".to_string()));
assert_eq!(number_to_column(28), Some("AB".to_string()));
assert_eq!(number_to_column(16_384), Some("XFD".to_string()));
assert_eq!(number_to_column(0), None);
assert_eq!(number_to_column(16_385), None);
}
#[test]
fn test_references() {
assert_eq!(
parse_reference_a1("A1"),
Some(ParsedReference {
row: 1,
column: 1,
absolute_column: false,
absolute_row: false
})
);
}
#[test]
fn test_references_1() {
assert_eq!(
parse_reference_a1("AB$23"),
Some(ParsedReference {
row: 23,
column: 28,
absolute_column: false,
absolute_row: true
})
);
}
#[test]
fn test_references_2() {
assert_eq!(
parse_reference_a1("$AB123"),
Some(ParsedReference {
row: 123,
column: 28,
absolute_column: true,
absolute_row: false
})
);
}
#[test]
fn test_references_3() {
assert_eq!(
parse_reference_a1("$AB$123"),
Some(ParsedReference {
row: 123,
column: 28,
absolute_column: true,
absolute_row: true
})
);
}
#[test]
fn test_r1c1_references() {
assert_eq!(
parse_reference_r1c1("R1C1"),
Some(ParsedReference {
row: 1,
column: 1,
absolute_column: true,
absolute_row: true
})
);
}
#[test]
fn test_r1c1_references_1() {
assert_eq!(
parse_reference_r1c1("R32C[-3]"),
Some(ParsedReference {
row: 32,
column: -3,
absolute_column: false,
absolute_row: true
})
);
}
#[test]
fn test_r1c1_references_2() {
assert_eq!(
parse_reference_r1c1("R32C"),
Some(ParsedReference {
row: 32,
column: 0,
absolute_column: true,
absolute_row: true
})
);
}
#[test]
fn test_r1c1_references_3() {
assert_eq!(
parse_reference_r1c1("R[-2]C[-3]"),
Some(ParsedReference {
row: -2,
column: -3,
absolute_column: false,
absolute_row: false
})
);
}
#[test]
fn test_r1c1_references_4() {
assert_eq!(
parse_reference_r1c1("RC[-3]"),
Some(ParsedReference {
row: 0,
column: -3,
absolute_column: false,
absolute_row: true
})
);
}
#[test]
fn test_names() {
assert!(is_valid_identifier("hola1"));
assert!(is_valid_identifier("hola_1"));
assert!(is_valid_identifier("hola.1"));
assert!(is_valid_identifier("sum_total_"));
assert!(is_valid_identifier("sum.total"));
assert!(is_valid_identifier("_hola"));
assert!(is_valid_identifier("t"));
assert!(is_valid_identifier("q"));
assert!(is_valid_identifier("true_that"));
assert!(is_valid_identifier("true1"));
// weird names apparently valid in Excel
assert!(is_valid_identifier("_"));
assert!(is_valid_identifier("\\hola1"));
assert!(is_valid_identifier("__"));
assert!(is_valid_identifier("_."));
assert!(is_valid_identifier("_1"));
assert!(is_valid_identifier("\\."));
// invalid
assert!(!is_valid_identifier("true"));
assert!(!is_valid_identifier("false"));
assert!(!is_valid_identifier("SUM THAT"));
assert!(!is_valid_identifier("A23"));
assert!(!is_valid_identifier("R1C1"));
assert!(!is_valid_identifier("R23C"));
assert!(!is_valid_identifier("R"));
assert!(!is_valid_identifier("c"));
assert!(!is_valid_identifier("1true"));
assert!(!is_valid_identifier("test€"));
assert!(!is_valid_identifier("truñe"));
assert!(!is_valid_identifier("tr&ue"));
}

View File

@@ -0,0 +1,17 @@
use chrono::Datelike;
use chrono::Duration;
use chrono::NaiveDate;
use crate::constants::EXCEL_DATE_BASE;
pub fn from_excel_date(days: i64) -> NaiveDate {
let dt = NaiveDate::from_ymd_opt(1900, 1, 1).expect("problem with chrono::NaiveDate");
dt + Duration::days(days - 2)
}
pub fn date_to_serial_number(day: u32, month: u32, year: i32) -> Result<i32, String> {
match NaiveDate::from_ymd_opt(year, month, day) {
Some(native_date) => Ok(native_date.num_days_from_ce() - EXCEL_DATE_BASE),
None => Err("Out of range parameters for date".to_string()),
}
}

View File

@@ -0,0 +1,763 @@
use chrono::Datelike;
use crate::{locale::Locale, number_format::to_precision};
use super::{
dates::{date_to_serial_number, from_excel_date},
parser::{ParsePart, Parser, TextToken},
};
pub struct Formatted {
pub color: Option<i32>,
pub text: String,
pub error: Option<String>,
}
/// Returns the vector of chars of the fractional part of a *positive* number:
/// 3.1415926 ==> ['1', '4', '1', '5', '9', '2', '6']
fn get_fract_part(value: f64, precision: i32) -> Vec<char> {
let b = format!("{:.1$}", value.fract(), precision as usize)
.chars()
.collect::<Vec<char>>();
let l = b.len() - 1;
let mut last_non_zero = b.len() - 1;
for i in 0..l {
if b[l - i] != '0' {
last_non_zero = l - i + 1;
break;
}
}
if last_non_zero < 2 {
return vec![];
}
b[2..last_non_zero].to_vec()
}
/// Return true if we need to add a separator in position digit_index
/// It normally happens if if digit_index -1 is 3, 6, 9,... digit_index ≡ 1 mod 3
fn use_group_separator(use_thousands: bool, digit_index: i32, group_sizes: &str) -> bool {
if use_thousands {
if group_sizes == "#,##0.###" {
if digit_index > 1 && (digit_index - 1) % 3 == 0 {
return true;
}
} else if group_sizes == "#,##,##0.###"
&& (digit_index == 3 || (digit_index > 3 && digit_index % 2 == 0))
{
return true;
}
}
false
}
pub fn format_number(value_original: f64, format: &str, locale: &Locale) -> Formatted {
let mut parser = Parser::new(format);
parser.parse();
let parts = parser.parts;
// There are four parts:
// 1) Positive numbers
// 2) Negative numbers
// 3) Zero
// 4) Text
// If you specify only one section of format code, the code in that section is used for all numbers.
// If you specify two sections of format code, the first section of code is used
// for positive numbers and zeros, and the second section of code is used for negative numbers.
// When you skip code sections in your number format,
// you must include a semicolon for each of the missing sections of code.
// You can use the ampersand (&) text operator to join, or concatenate, two values.
let mut value = value_original;
let part;
match parts.len() {
1 => {
part = &parts[0];
}
2 => {
if value >= 0.0 {
part = &parts[0]
} else {
value = -value;
part = &parts[1];
}
}
3 => {
if value > 0.0 {
part = &parts[0]
} else if value < 0.0 {
value = -value;
part = &parts[1];
} else {
value = 0.0;
part = &parts[2];
}
}
4 => {
if value > 0.0 {
part = &parts[0]
} else if value < 0.0 {
value = -value;
part = &parts[1];
} else {
value = 0.0;
part = &parts[2];
}
}
_ => {
return Formatted {
text: "#VALUE!".to_owned(),
color: None,
error: Some("Too many parts".to_owned()),
};
}
}
match part {
ParsePart::Error(..) => Formatted {
text: "#VALUE!".to_owned(),
color: None,
error: Some("Problem parsing format string".to_owned()),
},
ParsePart::General(..) => {
// FIXME: This is "General formatting"
// We should have different codepaths for general formatting and errors
let value_abs = value.abs();
if (1.0e-8..1.0e+11).contains(&value_abs) {
let mut text = format!("{:.9}", value);
text = text.trim_end_matches('0').trim_end_matches('.').to_string();
Formatted {
text,
color: None,
error: None,
}
} else {
if value_abs == 0.0 {
return Formatted {
text: "0".to_string(),
color: None,
error: None,
};
}
let exponent = value_abs.log10().floor();
value /= 10.0_f64.powf(exponent);
let sign = if exponent < 0.0 { '-' } else { '+' };
let s = format!("{:.5}", value);
Formatted {
text: format!(
"{}E{}{:02}",
s.trim_end_matches('0').trim_end_matches('.'),
sign,
exponent.abs()
),
color: None,
error: None,
}
}
}
ParsePart::Date(p) => {
let tokens = &p.tokens;
let mut text = "".to_string();
if !(1.0..=2_958_465.0).contains(&value) {
// 2_958_465 is 31 December 9999
return Formatted {
text: "#VALUE!".to_owned(),
color: None,
error: Some("Date negative or too long".to_owned()),
};
}
let date = from_excel_date(value as i64);
for token in tokens {
match token {
TextToken::Literal(c) => {
text = format!("{}{}", text, c);
}
TextToken::Text(t) => {
text = format!("{}{}", text, t);
}
TextToken::Ghost(_) => {
// we just leave a whitespace
// This is what the TEXT function does
text = format!("{} ", text);
}
TextToken::Spacer(_) => {
// we just leave a whitespace
// This is what the TEXT function does
text = format!("{} ", text);
}
TextToken::Raw => {
text = format!("{}{}", text, value);
}
TextToken::Digit(_) => {}
TextToken::Period => {}
TextToken::Day => {
let day = date.day() as usize;
text = format!("{}{}", text, day);
}
TextToken::DayPadded => {
let day = date.day() as usize;
text = format!("{}{:02}", text, day);
}
TextToken::DayNameShort => {
let mut day = date.weekday().number_from_monday() as usize;
if day == 7 {
day = 0;
}
text = format!("{}{}", text, &locale.dates.day_names_short[day]);
}
TextToken::DayName => {
let mut day = date.weekday().number_from_monday() as usize;
if day == 7 {
day = 0;
}
text = format!("{}{}", text, &locale.dates.day_names[day]);
}
TextToken::Month => {
let month = date.month() as usize;
text = format!("{}{}", text, month);
}
TextToken::MonthPadded => {
let month = date.month() as usize;
text = format!("{}{:02}", text, month);
}
TextToken::MonthNameShort => {
let month = date.month() as usize;
text = format!("{}{}", text, &locale.dates.months_short[month - 1]);
}
TextToken::MonthName => {
let month = date.month() as usize;
text = format!("{}{}", text, &locale.dates.months[month - 1]);
}
TextToken::MonthLetter => {
let month = date.month() as usize;
let months_letter = &locale.dates.months_letter[month - 1];
text = format!("{}{}", text, months_letter);
}
TextToken::YearShort => {
text = format!("{}{}", text, date.format("%y"));
}
TextToken::Year => {
text = format!("{}{}", text, date.year());
}
}
}
Formatted {
text,
color: p.color,
error: None,
}
}
ParsePart::Number(p) => {
let mut text = "".to_string();
let tokens = &p.tokens;
value = value * 100.0_f64.powi(p.percent) / (1000.0_f64.powi(p.comma));
// p.precision is the number of significant digits _after_ the decimal point
value = to_precision(
value,
(p.precision as usize) + format!("{}", value.abs().floor()).len(),
);
let mut value_abs = value.abs();
let mut exponent_part: Vec<char> = vec![];
let mut exponent_is_negative = value_abs < 10.0;
if p.is_scientific {
if value_abs == 0.0 {
exponent_part = vec!['0'];
exponent_is_negative = false;
} else {
// TODO: Implement engineering formatting.
let exponent = value_abs.log10().floor();
exponent_part = format!("{}", exponent.abs()).chars().collect();
value /= 10.0_f64.powf(exponent);
value = to_precision(value, 15);
value_abs = value.abs();
}
}
let l_exp = exponent_part.len() as i32;
let mut int_part: Vec<char> = format!("{}", value_abs.floor()).chars().collect();
if value_abs as i64 == 0 {
int_part = vec![];
}
let fract_part = get_fract_part(value_abs, p.precision);
// ln is the number of digits of the integer part of the value
let ln = int_part.len() as i32;
// digit count is the number of digit tokens ('0', '?' and '#') to the left of the decimal point
let digit_count = p.digit_count;
// digit_index points to the digit index in value that we have already formatted
let mut digit_index = 0;
let symbols = &locale.numbers.symbols;
let group_sizes = locale.numbers.decimal_formats.standard.to_owned();
let group_separator = symbols.group.to_owned();
let decimal_separator = symbols.decimal.to_owned();
// There probably are better ways to check if a number at a given precision is negative :/
let is_negative = value < -(10.0_f64.powf(-(p.precision as f64)));
for token in tokens {
match token {
TextToken::Literal(c) => {
text = format!("{}{}", text, c);
}
TextToken::Text(t) => {
text = format!("{}{}", text, t);
}
TextToken::Ghost(_) => {
// we just leave a whitespace
// This is what the TEXT function does
text = format!("{} ", text);
}
TextToken::Spacer(_) => {
// we just leave a whitespace
// This is what the TEXT function does
text = format!("{} ", text);
}
TextToken::Raw => {
text = format!("{}{}", text, value);
}
TextToken::Period => {
text = format!("{}{}", text, decimal_separator);
}
TextToken::Digit(digit) => {
if digit.number == 'i' {
// 1. Integer part
let index = digit.index;
let number_index = ln - digit_count + index;
if index == 0 && is_negative {
text = format!("-{}", text);
}
if ln <= digit_count {
// The number of digits is less or equal than the number of digit tokens
// i.e. the value is 123 and the format_code is ##### (ln = 3 and digit_count = 5)
if !(number_index < 0 && digit.kind == '#') {
let c = if number_index < 0 {
if digit.kind == '0' {
'0'
} else {
// digit.kind = '?'
' '
}
} else {
int_part[number_index as usize]
};
let sep = if use_group_separator(
p.use_thousands,
ln - digit_index,
&group_sizes,
) {
&group_separator
} else {
""
};
text = format!("{}{}{}", text, c, sep);
}
digit_index += 1;
} else {
// The number is larger than the formatting code 12345 and 0##
// We just hit the first formatting digit (0 in the example above) so we write as many digits as we can (123 in the example)
for i in digit_index..number_index + 1 {
let sep = if use_group_separator(
p.use_thousands,
ln - i,
&group_sizes,
) {
&group_separator
} else {
""
};
text = format!("{}{}{}", text, int_part[i as usize], sep);
}
digit_index = number_index + 1;
}
} else if digit.number == 'd' {
// 2. After the decimal point
let index = digit.index as usize;
if index < fract_part.len() {
text = format!("{}{}", text, fract_part[index]);
} else if digit.kind == '0' {
text = format!("{}0", text);
} else if digit.kind == '?' {
text = format!("{} ", text);
}
} else if digit.number == 'e' {
// 3. Exponent part
let index = digit.index;
if index == 0 {
if exponent_is_negative {
text = format!("{}E-", text);
} else {
text = format!("{}E+", text);
}
}
let number_index = l_exp - (p.exponent_digit_count - index);
if l_exp <= p.exponent_digit_count {
if !(number_index < 0 && digit.kind == '#') {
let c = if number_index < 0 {
if digit.kind == '?' {
' '
} else {
'0'
}
} else {
exponent_part[number_index as usize]
};
text = format!("{}{}", text, c);
}
} else {
for i in 0..number_index + 1 {
text = format!("{}{}", text, exponent_part[i as usize]);
}
digit_index += number_index + 1;
}
}
}
// Date tokens should not be present
TextToken::Day => {}
TextToken::DayPadded => {}
TextToken::DayNameShort => {}
TextToken::DayName => {}
TextToken::Month => {}
TextToken::MonthPadded => {}
TextToken::MonthNameShort => {}
TextToken::MonthName => {}
TextToken::MonthLetter => {}
TextToken::YearShort => {}
TextToken::Year => {}
}
}
Formatted {
text,
color: p.color,
error: None,
}
}
}
}
fn parse_day(day_str: &str) -> Result<(u32, String), String> {
let bytes = day_str.bytes();
let bytes_len = bytes.len();
if bytes_len <= 2 {
match day_str.parse::<u32>() {
Ok(y) => {
if bytes_len == 2 {
return Ok((y, "dd".to_string()));
} else {
return Ok((y, "d".to_string()));
}
}
Err(_) => return Err("Not a valid year".to_string()),
}
}
Err("Not a valid day".to_string())
}
fn parse_month(month_str: &str) -> Result<(u32, String), String> {
let bytes = month_str.bytes();
let bytes_len = bytes.len();
if bytes_len <= 2 {
match month_str.parse::<u32>() {
Ok(y) => {
if bytes_len == 2 {
return Ok((y, "mm".to_string()));
} else {
return Ok((y, "m".to_string()));
}
}
Err(_) => return Err("Not a valid year".to_string()),
}
}
let month_names_short = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec",
];
let month_names_long = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
if let Some(m) = month_names_short.iter().position(|&r| r == month_str) {
return Ok((m as u32 + 1, "mmm".to_string()));
}
if let Some(m) = month_names_long.iter().position(|&r| r == month_str) {
return Ok((m as u32 + 1, "mmmm".to_string()));
}
Err("Not a valid day".to_string())
}
fn parse_year(year_str: &str) -> Result<(i32, String), String> {
// year is either 2 digits or 4 digits
// 23 -> 2023
// 75 -> 1975
// 30 is the split number (yeah, that's not going to be a problem any time soon)
// 30 => 1930
// 29 => 2029
let bytes = year_str.bytes();
let bytes_len = bytes.len();
if bytes_len != 2 && bytes_len != 4 {
return Err("Not a valid year".to_string());
}
match year_str.parse::<i32>() {
Ok(y) => {
if y < 30 {
Ok((2000 + y, "yy".to_string()))
} else if y < 100 {
Ok((1900 + y, "yy".to_string()))
} else {
Ok((y, "yyyy".to_string()))
}
}
Err(_) => Err("Not a valid year".to_string()),
}
}
// Check if it is a date. Other spreadsheet engines support a wide variety of dates formats
// Here we support a small subset of them.
//
// The grammar is:
//
// date -> long_date | short_date | iso-date
// short_date -> month separator year
// long_date -> day separator month separator year
// iso_date -> long_year separator number_month separator number_day
// separator -> "/" | "-"
// day -> number | padded number
// month -> number_month | name_month
// number_month -> number | padded number |
// name_month -> short name | full name
// year -> short_year | long year
//
// NOTE 1: The separator has to be the same
// NOTE 2: In some engines "2/3" is implemented ad "2/March of the present year"
// NOTE 3: I did not implement the "short date"
fn parse_date(value: &str) -> Result<(i32, String), String> {
let separator = if value.contains('/') {
'/'
} else if value.contains('-') {
'-'
} else {
return Err("Not a valid date".to_string());
};
let parts: Vec<&str> = value.split(separator).collect();
let mut is_iso_date = false;
let (day_str, month_str, year_str) = if parts.len() == 3 {
if parts[0].len() == 4 {
// ISO date yyyy-mm-dd
if !parts[1].chars().all(char::is_numeric) {
return Err("Not a valid date".to_string());
}
if !parts[2].chars().all(char::is_numeric) {
return Err("Not a valid date".to_string());
}
is_iso_date = true;
(parts[2], parts[1], parts[0])
} else {
(parts[0], parts[1], parts[2])
}
} else {
return Err("Not a valid date".to_string());
};
let (day, day_format) = parse_day(day_str)?;
let (month, month_format) = parse_month(month_str)?;
let (year, year_format) = parse_year(year_str)?;
let serial_number = match date_to_serial_number(day, month, year) {
Ok(n) => n,
Err(_) => return Err("Not a valid date".to_string()),
};
if is_iso_date {
Ok((
serial_number,
format!("yyyy{separator}{month_format}{separator}{day_format}"),
))
} else {
Ok((
serial_number,
format!("{day_format}{separator}{month_format}{separator}{year_format}"),
))
}
}
/// Parses a formatted number, returning the numeric value together with the format
/// Uses heuristics to guess the format string
/// "$ 123,345.678" => (123345.678, "$#,##0.00")
/// "30.34%" => (0.3034, "0.00%")
/// 100€ => (100, "100€")
pub(crate) fn parse_formatted_number(
value: &str,
currencies: &[&str],
) -> Result<(f64, Option<String>), String> {
let value = value.trim();
let scientific_format = "0.00E+00";
// Check if it is a percentage
if let Some(p) = value.strip_suffix('%') {
let (f, options) = parse_number(p.trim())?;
if options.is_scientific {
return Ok((f / 100.0, Some(scientific_format.to_string())));
}
// We ignore the separator
if options.decimal_digits > 0 {
// Percentage format with decimals
return Ok((f / 100.0, Some("#,##0.00%".to_string())));
}
// Percentage format standard
return Ok((f / 100.0, Some("#,##0%".to_string())));
}
// check if it is a currency in currencies
for currency in currencies {
if let Some(p) = value.strip_prefix(&format!("-{}", currency)) {
let (f, options) = parse_number(p.trim())?;
if options.is_scientific {
return Ok((f, Some(scientific_format.to_string())));
}
if options.decimal_digits > 0 {
return Ok((-f, Some(format!("{currency}#,##0.00"))));
}
return Ok((-f, Some(format!("{currency}#,##0"))));
} else if let Some(p) = value.strip_prefix(currency) {
let (f, options) = parse_number(p.trim())?;
if options.is_scientific {
return Ok((f, Some(scientific_format.to_string())));
}
if options.decimal_digits > 0 {
return Ok((f, Some(format!("{currency}#,##0.00"))));
}
return Ok((f, Some(format!("{currency}#,##0"))));
} else if let Some(p) = value.strip_suffix(currency) {
let (f, options) = parse_number(p.trim())?;
if options.is_scientific {
return Ok((f, Some(scientific_format.to_string())));
}
if options.decimal_digits > 0 {
let currency_format = &format!("#,##0.00{currency}");
return Ok((f, Some(currency_format.to_string())));
}
let currency_format = &format!("#,##0{currency}");
return Ok((f, Some(currency_format.to_string())));
}
}
if let Ok((serial_number, format)) = parse_date(value) {
return Ok((serial_number as f64, Some(format)));
}
// Lastly we check if it is a number
let (f, options) = parse_number(value)?;
if options.is_scientific {
return Ok((f, Some(scientific_format.to_string())));
}
if options.has_commas {
if options.decimal_digits > 0 {
// group separator and two decimal points
return Ok((f, Some("#,##0.00".to_string())));
}
// Group separator and no decimal points
return Ok((f, Some("#,##0".to_string())));
}
Ok((f, None))
}
struct NumberOptions {
has_commas: bool,
is_scientific: bool,
decimal_digits: usize,
}
// tries to parse 'value' as a number.
// If it is a number it either uses commas as thousands separator or it does not
fn parse_number(value: &str) -> Result<(f64, NumberOptions), String> {
let mut position = 0;
let bytes = value.as_bytes();
let len = bytes.len();
if len == 0 {
return Err("Cannot parse number".to_string());
}
let mut chars = String::from("");
let decimal_separator = b'.';
let group_separator = b',';
let mut group_separator_index = Vec::new();
// get the sign
let sign = if bytes[0] == b'-' {
position += 1;
-1.0
} else if bytes[0] == b'+' {
position += 1;
1.0
} else {
1.0
};
// numbers before the decimal point
while position < len {
let x = bytes[position];
if x.is_ascii_digit() {
chars.push(x as char);
} else if x == group_separator {
group_separator_index.push(chars.len());
} else {
break;
}
position += 1;
}
// Check the group separator is in multiples of three
for index in &group_separator_index {
if (chars.len() - index) % 3 != 0 {
return Err("Cannot parse number".to_string());
}
}
let mut decimal_digits = 0;
if position < len && bytes[position] == decimal_separator {
// numbers after the decimal point
chars.push('.');
position += 1;
let start_position = 0;
while position < len {
let x = bytes[position];
if x.is_ascii_digit() {
chars.push(x as char);
} else {
break;
}
position += 1;
}
decimal_digits = position - start_position;
}
let mut is_scientific = false;
if position + 1 < len && (bytes[position] == b'e' || bytes[position] == b'E') {
// exponential side
is_scientific = true;
let x = bytes[position + 1];
if x == b'-' || x == b'+' || x.is_ascii_digit() {
chars.push('e');
chars.push(x as char);
position += 2;
while position < len {
let x = bytes[position];
if x.is_ascii_digit() {
chars.push(x as char);
} else {
break;
}
position += 1;
}
}
}
if position != len {
return Err("Could not parse number".to_string());
};
match chars.parse::<f64>() {
Err(_) => Err("Failed to parse to double".to_string()),
Ok(v) => Ok((
sign * v,
NumberOptions {
has_commas: !group_separator_index.is_empty(),
is_scientific,
decimal_digits,
},
)),
}
}

408
base/src/formatter/lexer.rs Normal file
View File

@@ -0,0 +1,408 @@
pub struct Lexer {
position: usize,
len: usize,
chars: Vec<char>,
error_message: String,
error_position: usize,
}
#[derive(PartialEq, Debug)]
pub enum Token {
Color(i32), // [Red] or [Color 23]
Condition(Compare, f64), // [<=100] (Comparator, number)
Literal(char), // €, $, (, ), /, :, +, -, ^, ', {, }, <, =, !, ~, > and space or scaped \X
Spacer(char), // *X
Ghost(char), // _X
Text(String), // "Text"
Separator, // ;
Raw, // @
Percent, // %
Comma, // ,
Period, // .
Sharp, // #
Zero, // 0
QuestionMark, // ?
Scientific, // E+
ScientificMinus, // E-
General, // General
// Dates
Day, // d
DayPadded, // dd
DayNameShort, // ddd
DayName, // dddd+
Month, // m
MonthPadded, // mm
MonthNameShort, // mmm
MonthName, // mmmm or mmmmmm+
MonthLetter, // mmmmm
YearShort, // y or yy
Year, // yyy+
// TODO: Hours Minutes and Seconds
ILLEGAL,
EOF,
}
#[derive(PartialEq, Eq, Debug)]
pub enum Compare {
Equal,
LessThan,
GreaterThan,
LessOrEqualThan,
GreaterOrEqualThan,
}
impl Token {
pub fn is_digit(&self) -> bool {
(self == &Token::Zero) || (self == &Token::Sharp) || (self == &Token::QuestionMark)
}
pub fn is_date(&self) -> bool {
self == &Token::Day
|| self == &Token::DayPadded
|| self == &Token::DayNameShort
|| self == &Token::DayName
|| self == &Token::MonthName
|| self == &Token::MonthNameShort
|| self == &Token::Month
|| self == &Token::MonthPadded
|| self == &Token::MonthLetter
|| self == &Token::YearShort
|| self == &Token::Year
}
}
impl Lexer {
pub fn new(format: &str) -> Lexer {
let chars: Vec<char> = format.chars().collect();
let len = chars.len();
Lexer {
chars,
position: 0,
len,
error_message: "".to_string(),
error_position: 0,
}
}
fn peek_char(&mut self) -> Option<char> {
let position = self.position;
if position < self.len {
Some(self.chars[position])
} else {
None
}
}
fn read_next_char(&mut self) -> Option<char> {
let position = self.position;
if position < self.len {
self.position = position + 1;
Some(self.chars[position])
} else {
None
}
}
fn set_error(&mut self, error: &str) {
self.error_message = error.to_string();
self.error_position = self.position;
self.position = self.len;
}
fn consume_string(&mut self) -> Option<String> {
let mut position = self.position;
let len = self.len;
let mut chars = "".to_string();
while position < len {
let x = self.chars[position];
position += 1;
if x != '"' {
chars.push(x);
} else if position < len && self.chars[position] == '"' {
chars.push(x);
chars.push(self.chars[position]);
position += 1;
} else {
self.position = position;
return Some(chars);
}
}
None
}
fn consume_number(&mut self) -> Option<f64> {
let mut position = self.position;
let len = self.len;
let mut chars = "".to_string();
// numbers before the '.'
while position < len {
let x = self.chars[position];
if x.is_ascii_digit() {
chars.push(x);
} else {
break;
}
position += 1;
}
if position < len && self.chars[position] == '.' {
// numbers after the'.'
chars.push('.');
position += 1;
while position < len {
let x = self.chars[position];
if x.is_ascii_digit() {
chars.push(x);
} else {
break;
}
position += 1;
}
}
if position + 1 < len && self.chars[position] == 'e' {
// exponential side
let x = self.chars[position + 1];
if x == '-' || x == '+' || x.is_ascii_digit() {
chars.push('e');
chars.push(x);
position += 2;
while position < len {
let x = self.chars[position];
if x.is_ascii_digit() {
chars.push(x);
} else {
break;
}
position += 1;
}
}
}
self.position = position;
match chars.parse::<f64>() {
Err(_) => None,
Ok(v) => Some(v),
}
}
fn consume_condition(&mut self) -> Option<(Compare, f64)> {
let cmp;
match self.read_next_char() {
Some('<') => {
if let Some('=') = self.peek_char() {
self.read_next_char();
cmp = Compare::LessOrEqualThan;
} else {
cmp = Compare::LessThan;
}
}
Some('>') => {
if let Some('=') = self.peek_char() {
self.read_next_char();
cmp = Compare::GreaterOrEqualThan;
} else {
cmp = Compare::GreaterThan;
}
}
Some('=') => {
cmp = Compare::Equal;
}
_ => {
return None;
}
}
if let Some(v) = self.consume_number() {
return Some((cmp, v));
}
None
}
fn consume_color(&mut self) -> Option<i32> {
let colors = [
"black", "white", "red", "green", "blue", "yellow", "magenta",
];
let mut chars = "".to_string();
while let Some(ch) = self.read_next_char() {
if ch == ']' {
if let Some(index) = colors.iter().position(|&x| x == chars.to_lowercase()) {
return Some(index as i32);
}
if !chars.starts_with("Color") {
return None;
}
if let Ok(index) = chars[5..].trim().parse::<i32>() {
if index < 57 && index > 0 {
return Some(index);
} else {
return None;
}
}
return None;
} else {
chars.push(ch);
}
}
None
}
pub fn peek_token(&mut self) -> Token {
let position = self.position;
let token = self.next_token();
self.position = position;
token
}
pub fn next_token(&mut self) -> Token {
let ch = self.read_next_char();
match ch {
Some(x) => match x {
'$' | '€' | '(' | ')' | '/' | ':' | '+' | '-' | '^' | '\'' | '{' | '}' | '<'
| '=' | '!' | '~' | '>' | ' ' => Token::Literal(x),
'?' => Token::QuestionMark,
';' => Token::Separator,
'#' => Token::Sharp,
',' => Token::Comma,
'.' => Token::Period,
'0' => Token::Zero,
'@' => Token::Raw,
'%' => Token::Percent,
'[' => {
if let Some(c) = self.peek_char() {
if c == '<' || c == '>' || c == '=' {
// Condition
if let Some((cmp, value)) = self.consume_condition() {
Token::Condition(cmp, value)
} else {
self.set_error("Failed to parse condition");
Token::ILLEGAL
}
} else {
// Color
if let Some(index) = self.consume_color() {
return Token::Color(index);
}
self.set_error("Failed to parse color");
Token::ILLEGAL
}
} else {
self.set_error("Unexpected end of input");
Token::ILLEGAL
}
}
'_' => {
if let Some(y) = self.read_next_char() {
Token::Ghost(y)
} else {
self.set_error("Unexpected end of input");
Token::ILLEGAL
}
}
'*' => {
if let Some(y) = self.read_next_char() {
Token::Spacer(y)
} else {
self.set_error("Unexpected end of input");
Token::ILLEGAL
}
}
'\\' => {
if let Some(y) = self.read_next_char() {
Token::Literal(y)
} else {
self.set_error("Unexpected end of input");
Token::ILLEGAL
}
}
'"' => {
if let Some(s) = self.consume_string() {
Token::Text(s)
} else {
self.set_error("Did not find end of text string");
Token::ILLEGAL
}
}
'E' => {
if let Some(s) = self.read_next_char() {
if s == '+' {
Token::Scientific
} else if s == '-' {
Token::ScientificMinus
} else {
self.set_error(&format!("Unexpected char: {}. Expected + or -", s));
Token::ILLEGAL
}
} else {
self.set_error("Unexpected end of input");
Token::ILLEGAL
}
}
'd' => {
let mut d = 1;
while let Some('d') = self.peek_char() {
d += 1;
self.read_next_char();
}
match d {
1 => Token::Day,
2 => Token::DayPadded,
3 => Token::DayNameShort,
_ => Token::DayName,
}
}
'm' => {
let mut m = 1;
while let Some('m') = self.peek_char() {
m += 1;
self.read_next_char();
}
match m {
1 => Token::Month,
2 => Token::MonthPadded,
3 => Token::MonthNameShort,
4 => Token::MonthName,
5 => Token::MonthLetter,
_ => Token::MonthName,
}
}
'y' => {
let mut y = 1;
while let Some('y') = self.peek_char() {
y += 1;
self.read_next_char();
}
if y == 1 || y == 2 {
Token::YearShort
} else {
Token::Year
}
}
'g' | 'G' => {
for c in "eneral".chars() {
let cc = self.read_next_char();
if Some(c) != cc {
self.set_error(&format!("Unexpected character: {}", x));
return Token::ILLEGAL;
}
}
Token::General
}
_ => {
self.set_error(&format!("Unexpected character: {}", x));
Token::ILLEGAL
}
},
None => Token::EOF,
}
}
}
pub fn is_likely_date_number_format(format: &str) -> bool {
let mut lexer = Lexer::new(format);
loop {
let token = lexer.next_token();
if token == Token::EOF {
return false;
}
if token.is_date() {
return true;
}
}
}

105
base/src/formatter/mod.rs Normal file
View File

@@ -0,0 +1,105 @@
pub mod dates;
pub mod format;
pub mod lexer;
pub mod parser;
#[cfg(test)]
mod test;
// Excel formatting is extremely tricky and I think implementing all it's rules might be borderline impossible.
// But the essentials are easy to understand.
//
// A general Excel formatting string is divided iun four parts:
//
// <POSITIVE>;<NEGATIVE>;<ZERO>;<TEXT>
//
// * How many decimal digits do you need?
//
// 0.000 for exactly three
// 0.00??? for at least two and up to five
//
// * Do you need a thousands separator?
//
// #,##
// # will just write the number
// #, will write the number up to the thousand separator (if there is nothing else)
//
// But #,# and any number of '#' to the right will work just as good. So the following all produce the same results:
// #,##0.00 #,######0.00 #,0.00
//
// For us in IronCalc the most general format string for a number (non-scientific notation) will be:
//
// 1. Will have #,## at the beginning if we use the thousand separator
// 2. Then 0.0* with as many 0 as mandatory decimal places
// 3. Then ?* with as many question marks as possible decimal places
//
// Valid examples:
// #,##0.??? Thousand separator, up to three decimal digits
// 0.00 No thousand separator. Two mandatory decimal places
// 0.0? No thousand separator. One mandatory decimal digit and one extra if present.
//
// * Do you what the text in color?
//
// Use [RED] or any color in https://www.excelsupersite.com/what-are-the-56-colorindex-colors-in-excel/
// Weird things
// ============
//
// ####0.0E+00 of 12345467.890123 (changing the number of '#' produces results I do not understand)
// ?www??.????0220000 will format 1234567.890123 to 12345www67.89012223000
//
// Things we will not implement
// ============================
//
// 1.- The accounting format can leave white spaces of the size of a particular character. For instance:
//
// #,##0.00_);[Red](#,##0.00)
//
// Will leave a white space to the right of positive numbers so that they are always aligned with negative numbers
//
// 2.- Excel can repeat a character as many times as needed to fill the cell:
//
// _($* #,##0_);_($* (#,##0))
//
// This will put a '$' sign to the left most (leaving a space the size of '(') and then as many empty spaces as possible
// and then the number:
// | $ 234 |
// | $ 1234 |
// We can't do this easily in IronCalc
//
// 3.- You can use ?/? to format fractions in Excel (this is probably not too hard)
// TOKENs
// ======
//
// * Color [Red] or [Color 23] or [Color23]
// * Conditions [<100]
// * Space _X when X is any given char
// * A spacer of chars: *X where X is repeated as much as possible
// * Literals: $, (, ), :, +, - and space
// * Text: "Some Text"
// * Escaped char: \X where X is anything
// * % appears literal and multiplies number by 100
// * , If it's in between digit characters it uses the thousand separator. If it is after the digit characters it multiplies by 1000
// * Digit characters: 0, #, ?
// * ; Types formatter divider
// * @ inserts raw text
// * Scientific literals E+, E-, e+, e-
// * . period. First one is the decimal point, subsequent are literals.
// d day of the month
// dd day of the month (padded i.e 05)
// ddd day of the week abbreviation
// dddd+ day of the week
// mmm Abbreviation month
// mmmm Month name
// mmmmm First letter of the month
// y or yy 2-digit year
// yyy+ 4 digit year
// References
// ==========
//
// [1] https://support.microsoft.com/en-us/office/number-format-codes-5026bbd6-04bc-48cd-bf33-80f18b4eae68?ui=en-us&rs=en-us&ad=us
// [2] https://developers.google.com/sheets/api/guides/formats
// [3] https://docs.microsoft.com/en-us/openspecs/office_standards/ms-oe376/0e59abdb-7f4e-48fc-9b89-67832fa11789

View File

@@ -0,0 +1,297 @@
use super::lexer::{Compare, Lexer, Token};
pub struct Digit {
pub kind: char, // '#' | '?' | '0'
pub index: i32,
pub number: char, // 'i' | 'd' | 'e' (integer, decimal or exponent)
}
pub enum TextToken {
Literal(char),
Text(String),
Ghost(char),
Spacer(char),
// Text
Raw,
Digit(Digit),
Period,
// Dates
Day,
DayPadded,
DayNameShort,
DayName,
Month,
MonthPadded,
MonthNameShort,
MonthName,
MonthLetter,
YearShort,
Year,
}
pub struct NumberPart {
pub color: Option<i32>,
pub condition: Option<(Compare, f64)>,
pub use_thousands: bool,
pub percent: i32, // multiply number by 100^percent
pub comma: i32, // divide number by 1000^comma
pub tokens: Vec<TextToken>,
pub digit_count: i32, // number of digit tokens (#, 0 or ?) to the left of the decimal point
pub precision: i32, // number of digits to the right of the decimal point
pub is_scientific: bool,
pub scientific_minus: bool,
pub exponent_digit_count: i32,
}
pub struct DatePart {
pub color: Option<i32>,
pub tokens: Vec<TextToken>,
}
pub struct ErrorPart {}
pub struct GeneralPart {}
pub enum ParsePart {
Number(NumberPart),
Date(DatePart),
Error(ErrorPart),
General(GeneralPart),
}
pub struct Parser {
pub parts: Vec<ParsePart>,
lexer: Lexer,
}
impl ParsePart {
pub fn is_error(&self) -> bool {
match &self {
ParsePart::Date(..) => false,
ParsePart::Number(..) => false,
ParsePart::Error(..) => true,
ParsePart::General(..) => false,
}
}
pub fn is_date(&self) -> bool {
match &self {
ParsePart::Date(..) => true,
ParsePart::Number(..) => false,
ParsePart::Error(..) => false,
ParsePart::General(..) => false,
}
}
}
impl Parser {
pub fn new(format: &str) -> Self {
let lexer = Lexer::new(format);
let parts = vec![];
Parser { parts, lexer }
}
pub fn parse(&mut self) {
while self.lexer.peek_token() != Token::EOF {
let part = self.parse_part();
self.parts.push(part);
}
}
fn parse_part(&mut self) -> ParsePart {
let mut token = self.lexer.next_token();
let mut digit_count = 0;
let mut precision = 0;
let mut is_date = false;
let mut is_number = false;
let mut found_decimal_dot = false;
let mut use_thousands = false;
let mut comma = 0;
let mut percent = 0;
let mut last_token_is_digit = false;
let mut color = None;
let mut condition = None;
let mut tokens = vec![];
let mut is_scientific = false;
let mut scientific_minus = false;
let mut exponent_digit_count = 0;
let mut number = 'i';
let mut index = 0;
while token != Token::EOF && token != Token::Separator {
let next_token = self.lexer.next_token();
let token_is_digit = token.is_digit();
is_number = is_number || token_is_digit;
let next_token_is_digit = next_token.is_digit();
if token_is_digit {
if is_scientific {
exponent_digit_count += 1;
} else if found_decimal_dot {
precision += 1;
} else {
digit_count += 1;
}
}
match token {
Token::General => {
if tokens.is_empty() {
return ParsePart::General(GeneralPart {});
} else {
return ParsePart::Error(ErrorPart {});
}
}
Token::Comma => {
// If it is in between digit token then we use the thousand separator
if last_token_is_digit && next_token_is_digit {
use_thousands = true;
} else if digit_count > 0 {
comma += 1;
} else {
// Before the number is just a literal.
tokens.push(TextToken::Literal(','));
}
}
Token::Percent => {
tokens.push(TextToken::Literal('%'));
percent += 1;
}
Token::Period => {
if !found_decimal_dot {
tokens.push(TextToken::Period);
found_decimal_dot = true;
if number == 'i' {
number = 'd';
index = 0;
}
} else {
tokens.push(TextToken::Literal('.'));
}
}
Token::Color(index) => {
color = Some(index);
}
Token::Condition(cmp, value) => {
condition = Some((cmp, value));
}
Token::QuestionMark => {
tokens.push(TextToken::Digit(Digit {
kind: '?',
index,
number,
}));
index += 1;
}
Token::Sharp => {
tokens.push(TextToken::Digit(Digit {
kind: '#',
index,
number,
}));
index += 1;
}
Token::Zero => {
tokens.push(TextToken::Digit(Digit {
kind: '0',
index,
number,
}));
index += 1;
}
Token::Literal(value) => {
tokens.push(TextToken::Literal(value));
}
Token::Text(value) => {
tokens.push(TextToken::Text(value));
}
Token::Ghost(value) => {
tokens.push(TextToken::Ghost(value));
}
Token::Spacer(value) => {
tokens.push(TextToken::Spacer(value));
}
Token::Day => {
is_date = true;
tokens.push(TextToken::Day);
}
Token::DayPadded => {
is_date = true;
tokens.push(TextToken::DayPadded);
}
Token::DayNameShort => {
is_date = true;
tokens.push(TextToken::DayNameShort);
}
Token::DayName => {
is_date = true;
tokens.push(TextToken::DayName);
}
Token::MonthNameShort => {
is_date = true;
tokens.push(TextToken::MonthNameShort);
}
Token::MonthName => {
is_date = true;
tokens.push(TextToken::MonthName);
}
Token::Month => {
is_date = true;
tokens.push(TextToken::Month);
}
Token::MonthPadded => {
is_date = true;
tokens.push(TextToken::MonthPadded);
}
Token::MonthLetter => {
is_date = true;
tokens.push(TextToken::MonthLetter);
}
Token::YearShort => {
is_date = true;
tokens.push(TextToken::YearShort);
}
Token::Year => {
is_date = true;
tokens.push(TextToken::Year);
}
Token::Scientific => {
if !is_scientific {
index = 0;
number = 'e';
}
is_scientific = true;
}
Token::ScientificMinus => {
is_scientific = true;
scientific_minus = true;
}
Token::Separator => {}
Token::Raw => {
tokens.push(TextToken::Raw);
}
Token::ILLEGAL => {
return ParsePart::Error(ErrorPart {});
}
Token::EOF => {}
}
last_token_is_digit = token_is_digit;
token = next_token;
}
if is_date {
if is_number {
return ParsePart::Error(ErrorPart {});
}
ParsePart::Date(DatePart { color, tokens })
} else {
ParsePart::Number(NumberPart {
color,
condition,
use_thousands,
percent,
comma,
tokens,
digit_count,
precision,
is_scientific,
scientific_minus,
exponent_digit_count,
})
}
}
}

View File

@@ -0,0 +1,2 @@
mod test_general;
mod test_parse_formatted_number;

View File

@@ -0,0 +1,196 @@
#![allow(clippy::unwrap_used)]
use crate::{
formatter::format::format_number,
locale::{get_locale, Locale},
};
fn get_default_locale() -> &'static Locale {
get_locale("en").unwrap()
}
#[test]
fn simple_test() {
let locale = get_default_locale();
let bond_james_bond = format_number(7.0, "000", locale);
assert_eq!(bond_james_bond.text, "007");
}
#[test]
fn test_general() {
let locale = get_default_locale();
assert_eq!(format_number(7.0, "General", locale).text, "7");
}
#[test]
fn simple_test_comma() {
let locale = get_default_locale();
assert_eq!(format_number(1007.0, "000", locale).text, "1007");
assert_eq!(format_number(1008.0, "#", locale).text, "1008");
assert_eq!(format_number(1009.0, "#,#", locale).text, "1,009");
assert_eq!(
format_number(12_345_678.0, "#,#", locale).text,
"12,345,678"
);
assert_eq!(
format_number(12_345_678.0, "0,0", locale).text,
"12,345,678"
);
assert_eq!(format_number(1005.0, "00-00", locale).text, "10-05");
assert_eq!(format_number(7.0, "0?0", locale).text, "0 7");
assert_eq!(format_number(7.0, "0#0", locale).text, "07");
assert_eq!(
format_number(1234.0, "000 \"Millions\"", locale).text,
"1234 Millions"
);
assert_eq!(
format_number(1235.0, "#,000 \"Millions\"", locale).text,
"1,235 Millions"
);
assert_eq!(format_number(1007.0, "0,00", locale).text, "1,007");
assert_eq!(
format_number(10_000_007.0, "0,00", locale).text,
"10,000,007"
);
}
#[test]
fn test_negative_numbers() {
let locale = get_default_locale();
assert_eq!(format_number(-123.0, "0.0", locale).text, "-123.0");
assert_eq!(format_number(-3.0, "000.0", locale).text, "-003.0");
assert_eq!(format_number(-0.00001, "000.0", locale).text, "000.0");
}
#[test]
fn test_decimal_part() {
let locale = get_default_locale();
assert_eq!(format_number(3.1, "0.00", locale).text, "3.10");
assert_eq!(format_number(3.1, "00-.-0?0", locale).text, "03-.-1 0");
}
#[test]
fn test_color() {
let locale = get_default_locale();
assert_eq!(format_number(3.1, "[blue]0.00", locale).text, "3.10");
assert_eq!(format_number(3.1, "[blue]0.00", locale).color, Some(4));
}
#[test]
fn test_parts() {
let locale = get_default_locale();
assert_eq!(format_number(3.1, "0.00;(0.00);(-)", locale).text, "3.10");
assert_eq!(
format_number(-3.1, "0.00;(0.00);(-)", locale).text,
"(3.10)"
);
assert_eq!(format_number(0.0, "0.00;(0.00);(-)", locale).text, "(-)");
}
#[test]
fn test_zero() {
let locale = get_default_locale();
assert_eq!(format_number(0.0, "$#,##0", locale).text, "$0");
assert_eq!(format_number(-1.0 / 3.0, "0", locale).text, "0");
assert_eq!(format_number(-1.0 / 3.0, "0;(0)", locale).text, "(0)");
}
#[test]
fn test_negative_currencies() {
let locale = get_default_locale();
assert_eq!(format_number(-23.0, "$#,##0", locale).text, "-$23");
}
#[test]
fn test_percent() {
let locale = get_default_locale();
assert_eq!(format_number(0.12, "0.00%", locale).text, "12.00%");
assert_eq!(format_number(0.12, "0.00%%", locale).text, "1200.00%%");
}
#[test]
fn test_percent_correct_rounding() {
let locale = get_default_locale();
// Formatting does Excel rounding (15 significant digits)
assert_eq!(
format_number(0.1399999999999999, "0.0%", locale).text,
"14.0%"
);
assert_eq!(
format_number(-0.1399999999999999, "0.0%", locale).text,
"-14.0%"
);
// Formatting does proper rounding
assert_eq!(format_number(0.1399, "0.0%", locale).text, "14.0%");
assert_eq!(format_number(-0.1399, "0.0%", locale).text, "-14.0%");
assert_eq!(format_number(0.02666, "0.00%", locale).text, "2.67%");
assert_eq!(format_number(0.0266, "0.00%", locale).text, "2.66%");
assert_eq!(format_number(0.0233, "0.00%", locale).text, "2.33%");
assert_eq!(format_number(0.02666, "0%", locale).text, "3%");
assert_eq!(format_number(-0.02666, "0.00%", locale).text, "-2.67%");
assert_eq!(format_number(-0.02666, "0%", locale).text, "-3%");
// precision 0
assert_eq!(format_number(0.135, "0%", locale).text, "14%");
assert_eq!(format_number(0.13499, "0%", locale).text, "13%");
assert_eq!(format_number(-0.135, "0%", locale).text, "-14%");
assert_eq!(format_number(-0.13499, "0%", locale).text, "-13%");
// precision 1
assert_eq!(format_number(0.1345, "0.0%", locale).text, "13.5%");
assert_eq!(format_number(0.1343, "0.0%", locale).text, "13.4%");
assert_eq!(format_number(-0.1345, "0.0%", locale).text, "-13.5%");
assert_eq!(format_number(-0.134499, "0.0%", locale).text, "-13.4%");
}
#[test]
fn test_scientific() {
let locale = get_default_locale();
assert_eq!(format_number(2.5e-14, "0.00E+0", locale).text, "2.50E-14");
assert_eq!(format_number(3e-4, "0.00E+00", locale).text, "3.00E-04");
}
#[test]
fn test_currency() {
let locale = get_default_locale();
assert_eq!(format_number(123.1, "$#,##0", locale).text, "$123");
assert_eq!(format_number(123.1, "#,##0 €", locale).text, "123 €");
}
#[test]
fn test_date() {
let locale = get_default_locale();
assert_eq!(
format_number(41181.0, "dd/mm/yyyy", locale).text,
"29/09/2012"
);
assert_eq!(
format_number(41181.0, "dd-mm-yyyy", locale).text,
"29-09-2012"
);
assert_eq!(
format_number(41304.0, "dd-mm-yyyy", locale).text,
"30-01-2013"
);
assert_eq!(
format_number(42657.0, "dd-mm-yyyy", locale).text,
"14-10-2016"
);
assert_eq!(
format_number(41181.0, "dddd-mmmm-yyyy", locale).text,
"Saturday-September-2012"
);
assert_eq!(
format_number(41181.0, "ddd-mmm-yy", locale).text,
"Sat-Sep-12"
);
assert_eq!(
format_number(41181.0, "ddd-mmmmm-yy", locale).text,
"Sat-S-12"
);
assert_eq!(
format_number(41181.0, "ddd-mmmmmmm-yy", locale).text,
"Sat-September-12"
);
}

View File

@@ -0,0 +1,206 @@
#![allow(clippy::unwrap_used)]
use crate::formatter::format::parse_formatted_number as parse;
const PARSE_ERROR_MSG: &str = "Could not parse number";
#[test]
fn numbers() {
// whole numbers
assert_eq!(parse("400", &["$"]), Ok((400.0, None)));
// decimal numbers
assert_eq!(parse("4.456", &["$"]), Ok((4.456, None)));
// scientific notation
assert_eq!(
parse("23e-12", &["$"]),
Ok((2.3e-11, Some("0.00E+00".to_string())))
);
assert_eq!(
parse("2.123456789e-11", &["$"]),
Ok((2.123456789e-11, Some("0.00E+00".to_string())))
);
assert_eq!(
parse("4.5E-9", &["$"]),
Ok((4.5e-9, Some("0.00E+00".to_string())))
);
assert_eq!(
parse("23e+2", &["$"]),
Ok((2300.0, Some("0.00E+00".to_string())))
);
assert_eq!(
parse("4.5E9", &["$"]),
Ok((4.5e9, Some("0.00E+00".to_string())))
);
// negative numbers
assert_eq!(parse("-400", &["$"]), Ok((-400.0, None)));
assert_eq!(parse("-4.456", &["$"]), Ok((-4.456, None)));
assert_eq!(
parse("-23e-12", &["$"]),
Ok((-2.3e-11, Some("0.00E+00".to_string())))
);
// trims space
assert_eq!(parse(" 400 ", &["$"]), Ok((400.0, None)));
}
#[test]
fn percentage() {
// whole numbers
assert_eq!(parse("400%", &["$"]), Ok((4.0, Some("#,##0%".to_string()))));
// decimal numbers
assert_eq!(
parse("4.456$", &["$"]),
Ok((4.456, Some("#,##0.00$".to_string())))
);
// Percentage in scientific notation will not be formatted as percentage
assert_eq!(
parse("23e-12%", &["$"]),
Ok((23e-12 / 100.0, Some("0.00E+00".to_string())))
);
assert_eq!(
parse("2.3E4%", &["$"]),
Ok((230.0, Some("0.00E+00".to_string())))
);
}
#[test]
fn currency() {
// whole numbers
assert_eq!(
parse("400$", &["$"]),
Ok((400.0, Some("#,##0$".to_string())))
);
// decimal numbers
assert_eq!(
parse("4.456$", &["$"]),
Ok((4.456, Some("#,##0.00$".to_string())))
);
// Currencies in scientific notation will not be formatted as currencies
assert_eq!(
parse("23e-12$", &["$"]),
Ok((2.3e-11, Some("0.00E+00".to_string())))
);
assert_eq!(
parse("2.3e-12$", &["$"]),
Ok((2.3e-12, Some("0.00E+00".to_string())))
);
assert_eq!(
parse("€23e-12", &[""]),
Ok((2.3e-11, Some("0.00E+00".to_string())))
);
// switch side of currencies
assert_eq!(
parse("$400", &["$"]),
Ok((400.0, Some("$#,##0".to_string())))
);
assert_eq!(
parse("$4.456", &["$"]),
Ok((4.456, Some("$#,##0.00".to_string())))
);
assert_eq!(
parse("$23e-12", &["$"]),
Ok((2.3e-11, Some("0.00E+00".to_string())))
);
assert_eq!(
parse("$2.3e-12", &["$"]),
Ok((2.3e-12, Some("0.00E+00".to_string())))
);
assert_eq!(
parse("23e-12€", &[""]),
Ok((2.3e-11, Some("0.00E+00".to_string())))
);
}
#[test]
fn negative_currencies() {
assert_eq!(
parse("-400$", &["$"]),
Ok((-400.0, Some("#,##0$".to_string())))
);
assert_eq!(
parse("-$400", &["$"]),
Ok((-400.0, Some("$#,##0".to_string())))
);
assert_eq!(
parse("$-400", &["$"]),
Ok((-400.0, Some("$#,##0".to_string())))
);
}
#[test]
fn errors() {
// Strings are not numbers
assert_eq!(parse("One", &["$"]), Err(PARSE_ERROR_MSG.to_string()));
// Not partial parsing
assert_eq!(parse("23 Hello", &["$"]), Err(PARSE_ERROR_MSG.to_string()));
assert_eq!(parse("Hello 23", &["$"]), Err(PARSE_ERROR_MSG.to_string()));
assert_eq!(parse("2 3", &["$"]), Err(PARSE_ERROR_MSG.to_string()));
// No space between
assert_eq!(parse("- 23", &["$"]), Err(PARSE_ERROR_MSG.to_string()));
}
#[test]
fn errors_wrong_currency() {
assert_eq!(parse("123€", &["$"]), Err(PARSE_ERROR_MSG.to_string()));
}
#[test]
fn long_dates() {
assert_eq!(
parse("02/03/2024", &["$"]),
Ok((45353.0, Some("dd/mm/yyyy".to_string())))
);
assert_eq!(
parse("02/3/2024", &["$"]),
Ok((45353.0, Some("dd/m/yyyy".to_string())))
);
assert_eq!(
parse("02/Mar/2024", &["$"]),
Ok((45353.0, Some("dd/mmm/yyyy".to_string())))
);
assert_eq!(
parse("02/March/2024", &["$"]),
Ok((45353.0, Some("dd/mmmm/yyyy".to_string())))
);
assert_eq!(
parse("2/3/24", &["$"]),
Ok((45353.0, Some("d/m/yy".to_string())))
);
assert_eq!(
parse("10-02-1975", &["$"]),
Ok((27435.0, Some("dd-mm-yyyy".to_string())))
);
assert_eq!(
parse("10-2-1975", &["$"]),
Ok((27435.0, Some("dd-m-yyyy".to_string())))
);
assert_eq!(
parse("10-Feb-1975", &["$"]),
Ok((27435.0, Some("dd-mmm-yyyy".to_string())))
);
assert_eq!(
parse("10-February-1975", &["$"]),
Ok((27435.0, Some("dd-mmmm-yyyy".to_string())))
);
assert_eq!(
parse("10-2-75", &["$"]),
Ok((27435.0, Some("dd-m-yy".to_string())))
);
}
#[test]
fn iso_dates() {
assert_eq!(
parse("2024/03/02", &["$"]),
Ok((45353.0, Some("yyyy/mm/dd".to_string())))
);
assert_eq!(
parse("2024/March/02", &["$"]),
Err(PARSE_ERROR_MSG.to_string())
);
}

View File

@@ -0,0 +1,210 @@
use std::cmp::Ordering;
use crate::{
calc_result::{CalcResult, CellReference},
model::Model,
};
use super::util::compare_values;
// NOTE: We don't know how Excel exactly implements binary search internally.
// This means that if the values on the lookup range are not in order our results and Excel's will differ
// Assumes values are in ascending order, returns matching index or the largest value smaller than target.
// Returns None if target is smaller than the smaller value.
pub(crate) fn binary_search_or_smaller<T: Ord>(target: &T, array: &[T]) -> Option<i32> {
// We apply binary search leftmost for value in the range
let n = array.len();
let mut l = 0;
let mut r = n;
while l < r {
let m = (l + r) / 2;
if &array[m] < target {
l = m + 1;
} else {
r = m;
}
}
if l == n {
return Some((l - 1) as i32);
}
// Now l points to the leftmost element
if &array[l] == target {
return Some(l as i32);
}
// If target is less than the minimum return None
if l == 0 {
return None;
}
Some((l - 1) as i32)
}
// Assumes values are in ascending order, returns matching index or the smaller value larger than target.
// Returns None if target is smaller than the smaller value.
pub(crate) fn binary_search_or_greater<T: Ord>(target: &T, array: &[T]) -> Option<i32> {
let mut l = 0;
let mut r = array.len();
while l < r {
let mut m = (l + r) / 2;
match &array[m].cmp(target) {
Ordering::Less => {
l = m + 1;
}
Ordering::Greater => {
r = m;
}
Ordering::Equal => {
while m > 1 {
if &array[m - 1] == target {
m -= 1;
} else {
break;
}
}
return Some(m as i32);
}
}
}
// If target is larger than the maximum return None
if r == array.len() {
return None;
}
// Now r points to the rightmost element
Some(r as i32)
}
// Assumes values are in descending order
pub(crate) fn binary_search_descending_or_smaller<T: Ord>(target: &T, array: &[T]) -> Option<i32> {
let n = array.len();
let mut l = 0;
let mut r = n;
while l < r {
let m = (l + r) / 2;
let mut index = n - m - 1;
match &array[index].cmp(target) {
Ordering::Less => {
l = m + 1;
}
Ordering::Greater => {
r = m;
}
Ordering::Equal => {
while index < n - 1 {
if &array[index + 1] == target {
index += 1;
} else {
break;
}
}
return Some(index as i32);
}
}
}
if l == 0 {
return None;
}
Some((n - l) as i32)
}
// Assumes values are in descending order, returns matching index or the smaller value larger than target.
// Returns None if target is smaller than the smaller value.
pub(crate) fn binary_search_descending_or_greater<T: Ord>(target: &T, array: &[T]) -> Option<i32> {
let n = array.len();
let mut l = 0;
let mut r = n;
while l < r {
let m = (l + r) / 2;
let mut index = n - m - 1;
match &array[index].cmp(target) {
Ordering::Less => {
l = m + 1;
}
Ordering::Greater => {
r = m;
}
Ordering::Equal => {
while index < n - 1 {
if &array[index + 1] == target {
index += 1;
} else {
break;
}
}
return Some(index as i32);
}
}
}
if r == n {
return None;
}
Some((n - r - 1) as i32)
}
impl Model {
/// Returns an array with the list of cell values in the range
pub(crate) fn prepare_array(
&mut self,
left: &CellReference,
right: &CellReference,
is_row_vector: bool,
) -> Vec<CalcResult> {
let n = if is_row_vector {
right.row - left.row
} else {
right.column - left.column
} + 1;
let mut result = vec![];
for index in 0..n {
let row;
let column;
if is_row_vector {
row = left.row + index;
column = left.column;
} else {
column = left.column + index;
row = left.row;
}
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
});
result.push(value);
}
result
}
/// Old style binary search. Used in HLOOKUP, etc
pub(crate) fn binary_search(
&mut self,
target: &CalcResult,
left: &CellReference,
right: &CellReference,
is_row_vector: bool,
) -> i32 {
let array = self.prepare_array(left, right, is_row_vector);
// We apply binary search leftmost for value in the range
let mut l = 0;
let mut r = array.len();
while l < r {
let m = (l + r) / 2;
match compare_values(&array[m], target) {
-1 => {
l = m + 1;
}
1 => {
r = m;
}
_ => {
return m as i32;
}
}
}
// If target is less than the minimum return #N/A
if l == 0 {
return -2;
}
// Now l points to the leftmost element
(l - 1) as i32
}
}

View File

@@ -0,0 +1,314 @@
use chrono::Datelike;
use chrono::Months;
use chrono::NaiveDateTime;
use chrono::TimeZone;
use chrono::Timelike;
use crate::formatter::dates::date_to_serial_number;
use crate::model::get_milliseconds_since_epoch;
use crate::{
calc_result::{CalcResult, CellReference},
constants::EXCEL_DATE_BASE,
expressions::parser::Node,
expressions::token::Error,
formatter::dates::from_excel_date,
model::Model,
};
impl Model {
pub(crate) fn fn_day(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 1 {
return CalcResult::new_args_number_error(cell);
}
let serial_number = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i64;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Function DAY parameter 1 value is negative. It should be positive or zero.".to_string(),
};
}
t
}
Err(s) => return s,
};
let date = from_excel_date(serial_number);
let day = date.day() as f64;
CalcResult::Number(day)
}
pub(crate) fn fn_month(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 1 {
return CalcResult::new_args_number_error(cell);
}
let serial_number = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i64;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Function MONTH parameter 1 value is negative. It should be positive or zero.".to_string(),
};
}
t
}
Err(s) => return s,
};
let date = from_excel_date(serial_number);
let month = date.month() as f64;
CalcResult::Number(month)
}
pub(crate) fn fn_eomonth(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 2 {
return CalcResult::new_args_number_error(cell);
}
let serial_number = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i64;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Function EOMONTH parameter 1 value is negative. It should be positive or zero.".to_string(),
};
}
t
}
Err(s) => return s,
};
let months = match self.get_number_no_bools(&args[1], cell) {
Ok(c) => {
let t = c.trunc();
t as i32
}
Err(s) => return s,
};
let months_abs = months.unsigned_abs();
let native_date = if months > 0 {
from_excel_date(serial_number) + Months::new(months_abs)
} else {
from_excel_date(serial_number) - Months::new(months_abs)
};
// Instead of calculating the end of month we compute the first day of the following month
// and take one day.
let mut month = native_date.month() + 1;
let mut year = native_date.year();
if month == 13 {
month = 1;
year += 1;
}
match date_to_serial_number(1, month, year) {
Ok(serial_number) => CalcResult::Number(serial_number as f64 - 1.0),
Err(message) => CalcResult::Error {
error: Error::NUM,
origin: cell,
message,
},
}
}
// year, month, day
pub(crate) fn fn_date(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 3 {
return CalcResult::new_args_number_error(cell);
}
let year = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i32;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Out of range parameters for date".to_string(),
};
}
t
}
Err(s) => return s,
};
let month = match self.get_number(&args[1], cell) {
Ok(c) => {
let t = c.floor();
if t < 0.0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Out of range parameters for date".to_string(),
};
}
t as u32
}
Err(s) => return s,
};
let day = match self.get_number(&args[2], cell) {
Ok(c) => {
let t = c.floor();
if t < 0.0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Out of range parameters for date".to_string(),
};
}
t as u32
}
Err(s) => return s,
};
match date_to_serial_number(day, month, year) {
Ok(serial_number) => CalcResult::Number(serial_number as f64),
Err(message) => CalcResult::Error {
error: Error::NUM,
origin: cell,
message,
},
}
}
pub(crate) fn fn_year(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 1 {
return CalcResult::new_args_number_error(cell);
}
let serial_number = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i64;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Function YEAR parameter 1 value is negative. It should be positive or zero.".to_string(),
};
}
t
}
Err(s) => return s,
};
let date = from_excel_date(serial_number);
let year = date.year() as f64;
CalcResult::Number(year)
}
// date, months
pub(crate) fn fn_edate(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 2 {
return CalcResult::new_args_number_error(cell);
}
let serial_number = match self.get_number(&args[0], cell) {
Ok(c) => {
let t = c.floor() as i64;
if t < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Parameter 1 value is negative. It should be positive or zero."
.to_string(),
};
}
t
}
Err(s) => return s,
};
let months = match self.get_number(&args[1], cell) {
Ok(c) => {
let t = c.trunc();
t as i32
}
Err(s) => return s,
};
let months_abs = months.unsigned_abs();
let native_date = if months > 0 {
from_excel_date(serial_number) + Months::new(months_abs)
} else {
from_excel_date(serial_number) - Months::new(months_abs)
};
let serial_number = native_date.num_days_from_ce() - EXCEL_DATE_BASE;
if serial_number < 0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "EDATE out of bounds".to_string(),
};
}
CalcResult::Number(serial_number as f64)
}
pub(crate) fn fn_today(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 0 {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Wrong number of arguments".to_string(),
};
}
// milliseconds since January 1, 1970 00:00:00 UTC.
let milliseconds = get_milliseconds_since_epoch();
let seconds = milliseconds / 1000;
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
Some(dt) => dt,
None => {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Invalid date".to_string(),
}
}
};
let local_time = self.tz.from_utc_datetime(&dt);
// 693_594 is computed as:
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
// The 2 days offset is because of Excel 1900 bug
let days_from_1900 = local_time.num_days_from_ce() - 693_594;
CalcResult::Number(days_from_1900 as f64)
}
pub(crate) fn fn_now(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count != 0 {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Wrong number of arguments".to_string(),
};
}
// milliseconds since January 1, 1970 00:00:00 UTC.
let milliseconds = get_milliseconds_since_epoch();
let seconds = milliseconds / 1000;
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
Some(dt) => dt,
None => {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Invalid date".to_string(),
}
}
};
let local_time = self.tz.from_utc_datetime(&dt);
// 693_594 is computed as:
// NaiveDate::from_ymd(1900, 1, 1).num_days_from_ce() - 2
// The 2 days offset is because of Excel 1900 bug
let days_from_1900 = local_time.num_days_from_ce() - 693_594;
let days = (local_time.num_seconds_from_midnight() as f64) / (60.0 * 60.0 * 24.0);
CalcResult::Number(days_from_1900 as f64 + days.fract())
}
}

View File

@@ -0,0 +1,176 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::{parser::Node, token::Error},
model::Model,
};
use super::transcendental::{bessel_i, bessel_j, bessel_k, bessel_y, erf};
// https://root.cern/doc/v610/TMath_8cxx_source.html
// Notice that the parameters for Bessel functions in Excel and here have inverted order
// EXCEL_BESSEL(x, n) => bessel(n, x)
impl Model {
pub(crate) fn fn_besseli(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = n.trunc() as i32;
let result = bessel_i(n, x);
if result.is_infinite() || result.is_nan() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_besselj(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = n.trunc() as i32;
if n < 0 {
// In Excel this ins #NUM!
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
let result = bessel_j(n, x);
if result.is_infinite() || result.is_nan() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_besselk(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = n.trunc() as i32;
let result = bessel_k(n, x);
if result.is_infinite() || result.is_nan() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_bessely(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
let n = n.trunc() as i32;
if n < 0 {
// In Excel this ins #NUM!
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
let result = bessel_y(n, x);
if result.is_infinite() || result.is_nan() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid parameter for Bessel function".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_erf(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
if args.len() == 2 {
let y = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
CalcResult::Number(erf(y) - erf(x))
} else {
CalcResult::Number(erf(x))
}
}
pub(crate) fn fn_erfprecise(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
};
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
CalcResult::Number(erf(x))
}
pub(crate) fn fn_erfc(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
};
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
CalcResult::Number(1.0 - erf(x))
}
pub(crate) fn fn_erfcprecise(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
};
let x = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
CalcResult::Number(1.0 - erf(x))
}
}

View File

@@ -0,0 +1,233 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
// 2^48-1
const MAX: f64 = 281474976710655.0;
impl Model {
// BITAND( number1, number2)
pub(crate) fn fn_bitand(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let number1 = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let number2 = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if number1.trunc() != number1 || number2.trunc() != number2 {
return CalcResult::new_error(Error::NUM, cell, "numbers must be integers".to_string());
}
if number1 < 0.0 || number2 < 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be positive or zero".to_string(),
);
}
if number1 > MAX || number2 > MAX {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be less than 2^48-1".to_string(),
);
}
let number1 = number1.trunc() as i64;
let number2 = number2.trunc() as i64;
let result = number1 & number2;
CalcResult::Number(result as f64)
}
// BITOR(number1, number2)
pub(crate) fn fn_bitor(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let number1 = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let number2 = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if number1.trunc() != number1 || number2.trunc() != number2 {
return CalcResult::new_error(Error::NUM, cell, "numbers must be integers".to_string());
}
if number1 < 0.0 || number2 < 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be positive or zero".to_string(),
);
}
if number1 > MAX || number2 > MAX {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be less than 2^48-1".to_string(),
);
}
let number1 = number1.trunc() as i64;
let number2 = number2.trunc() as i64;
let result = number1 | number2;
CalcResult::Number(result as f64)
}
// BITXOR(number1, number2)
pub(crate) fn fn_bitxor(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let number1 = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let number2 = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if number1.trunc() != number1 || number2.trunc() != number2 {
return CalcResult::new_error(Error::NUM, cell, "numbers must be integers".to_string());
}
if number1 < 0.0 || number2 < 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be positive or zero".to_string(),
);
}
if number1 > MAX || number2 > MAX {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be less than 2^48-1".to_string(),
);
}
let number1 = number1.trunc() as i64;
let number2 = number2.trunc() as i64;
let result = number1 ^ number2;
CalcResult::Number(result as f64)
}
// BITLSHIFT(number, shift_amount)
pub(crate) fn fn_bitlshift(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let number = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let shift = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if number.trunc() != number {
return CalcResult::new_error(Error::NUM, cell, "numbers must be integers".to_string());
}
if number < 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be positive or zero".to_string(),
);
}
if number > MAX {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be less than 2^48-1".to_string(),
);
}
if shift.abs() > 53.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"shift amount must be less than 53".to_string(),
);
}
let number = number.trunc() as i64;
let shift = shift.trunc() as i64;
let result = if shift > 0 {
number << shift
} else {
number >> -shift
};
let result = result as f64;
if result.abs() > MAX {
return CalcResult::new_error(Error::NUM, cell, "BITLSHIFT overflow".to_string());
}
CalcResult::Number(result)
}
// BITRSHIFT(number, shift_amount)
pub(crate) fn fn_bitrshift(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let number = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let shift = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if number.trunc() != number {
return CalcResult::new_error(Error::NUM, cell, "numbers must be integers".to_string());
}
if number < 0.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be positive or zero".to_string(),
);
}
if number > MAX {
return CalcResult::new_error(
Error::NUM,
cell,
"numbers must be less than 2^48-1".to_string(),
);
}
if shift.abs() > 53.0 {
return CalcResult::new_error(
Error::NUM,
cell,
"shift amount must be less than 53".to_string(),
);
}
let number = number.trunc() as i64;
let shift = shift.trunc() as i64;
let result = if shift > 0 {
number >> shift
} else {
number << -shift
};
let result = result as f64;
if result.abs() > MAX {
return CalcResult::new_error(Error::NUM, cell, "BITRSHIFT overflow".to_string());
}
CalcResult::Number(result)
}
}

View File

@@ -0,0 +1,793 @@
use std::fmt;
use crate::{
calc_result::{CalcResult, CellReference},
expressions::{
lexer::util::get_tokens,
parser::Node,
token::{Error, OpSum, TokenType},
},
model::Model,
number_format::to_precision,
};
/// This implements all functions with complex arguments in the standard
/// NOTE: If performance is ever needed we should have a new entry in CalcResult,
/// So this functions will return CalcResult::Complex(x,y, Suffix)
/// and not having to parse it over and over again.
#[derive(PartialEq, Debug)]
enum Suffix {
I,
J,
}
impl fmt::Display for Suffix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Suffix::I => write!(f, "i"),
Suffix::J => write!(f, "j"),
}
}
}
struct Complex {
x: f64,
y: f64,
suffix: Suffix,
}
impl fmt::Display for Complex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let x = to_precision(self.x, 15);
let y = to_precision(self.y, 15);
let suffix = &self.suffix;
// it is a bit weird what Excel does but it seems it uses general notation for
// numbers > 1e-20 and scientific notation for the rest
let y_str = if y.abs() <= 9e-20 {
format!("{:E}", y)
} else if y == 1.0 {
"".to_string()
} else if y == -1.0 {
"-".to_string()
} else {
format!("{}", y)
};
let x_str = if x.abs() <= 9e-20 {
format!("{:E}", x)
} else {
format!("{}", x)
};
if y == 0.0 && x == 0.0 {
write!(f, "0")
} else if y == 0.0 {
write!(f, "{x_str}")
} else if x == 0.0 {
write!(f, "{y_str}{suffix}")
} else if y > 0.0 {
write!(f, "{x_str}+{y_str}{suffix}")
} else {
write!(f, "{x_str}{y_str}{suffix}")
}
}
}
fn parse_complex_number(s: &str) -> Result<(f64, f64, Suffix), String> {
// Check for i, j, -i, -j
let (sign, s) = match s.strip_prefix('-') {
Some(r) => (-1.0, r),
None => (1.0, s),
};
match s {
"i" => return Ok((0.0, sign * 1.0, Suffix::I)),
"j" => return Ok((0.0, sign * 1.0, Suffix::J)),
_ => {
// Let it go
}
};
// TODO: This is an overuse
let tokens = get_tokens(s);
// There has to be 1, 2 3, or 4 tokens
// number
// number suffix
// number1+suffix
// number1+number2 suffix
match tokens.len() {
1 => {
// Real number
let number1 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
// i is the default
Ok((sign * number1, 0.0, Suffix::I))
}
2 => {
// number2 i
let number2 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let suffix = match &tokens[1].token {
TokenType::Ident(w) => match w.as_str() {
"i" => Suffix::I,
"j" => Suffix::J,
_ => return Err(format!("Not a complex number: {s}")),
},
_ => {
return Err(format!("Not a complex number: {s}"));
}
};
Ok((0.0, sign * number2, suffix))
}
3 => {
let number1 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let operation = match &tokens[1].token {
TokenType::Addition(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let suffix = match &tokens[2].token {
TokenType::Ident(w) => match w.as_str() {
"i" => Suffix::I,
"j" => Suffix::J,
_ => return Err(format!("Not a complex number: {s}")),
},
_ => {
return Err(format!("Not a complex number: {s}"));
}
};
let number2 = if matches!(operation, OpSum::Minus) {
-1.0
} else {
1.0
};
Ok((sign * number1, number2, suffix))
}
4 => {
let number1 = match tokens[0].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let operation = match &tokens[1].token {
TokenType::Addition(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let mut number2 = match tokens[2].token {
TokenType::Number(f) => f,
_ => return Err(format!("Not a complex number: {s}")),
};
let suffix = match &tokens[3].token {
TokenType::Ident(w) => match w.as_str() {
"i" => Suffix::I,
"j" => Suffix::J,
_ => return Err(format!("Not a complex number: {s}")),
},
_ => {
return Err(format!("Not a complex number: {s}"));
}
};
if matches!(operation, OpSum::Minus) {
number2 = -number2
}
Ok((sign * number1, number2, suffix))
}
_ => Err(format!("Not a complex number: {s}")),
}
}
impl Model {
fn get_complex_number(
&mut self,
node: &Node,
cell: CellReference,
) -> Result<(f64, f64, Suffix), CalcResult> {
let value = match self.get_string(node, cell) {
Ok(s) => s,
Err(s) => return Err(s),
};
if value.is_empty() {
return Ok((0.0, 0.0, Suffix::I));
}
match parse_complex_number(&value) {
Ok(s) => Ok(s),
Err(message) => Err(CalcResult::new_error(Error::NUM, cell, message)),
}
}
// COMPLEX(real_num, i_num, [suffix])
pub(crate) fn fn_complex(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(2..=3).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let y = match self.get_number(&args[1], cell) {
Ok(s) => s,
Err(s) => return s,
};
let suffix = if args.len() == 3 {
match self.get_string(&args[2], cell) {
Ok(s) => {
if s == "i" || s.is_empty() {
Suffix::I
} else if s == "j" {
Suffix::J
} else {
return CalcResult::new_error(
Error::VALUE,
cell,
"Invalid suffix".to_string(),
);
}
}
Err(s) => return s,
}
} else {
Suffix::I
};
let complex = Complex { x, y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imabs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
CalcResult::Number(f64::sqrt(x * x + y * y))
}
pub(crate) fn fn_imaginary(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (_, y, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
CalcResult::Number(y)
}
pub(crate) fn fn_imargument(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
if x == 0.0 && y == 0.0 {
return CalcResult::new_error(Error::DIV, cell, "Division by zero".to_string());
}
let angle = f64::atan2(y, x);
CalcResult::Number(angle)
}
pub(crate) fn fn_imconjugate(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex { x, y: -y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcos(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let complex = Complex {
x: x.cos() * y.cosh(),
y: -x.sin() * y.sinh(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcosh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let complex = Complex {
x: x.cosh() * y.cos(),
y: x.sinh() * y.sin(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcot(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
if x == 0.0 && y != 0.0 {
let complex = Complex {
x: 0.0,
y: -1.0 / y.tanh(),
suffix,
};
return CalcResult::String(complex.to_string());
} else if y == 0.0 {
let complex = Complex {
x: 1.0 / x.tan(),
y: 0.0,
suffix,
};
return CalcResult::String(complex.to_string());
}
let x_cot = 1.0 / x.tan();
let y_coth = 1.0 / y.tanh();
let t = x_cot * x_cot + y_coth * y_coth;
let x = (x_cot * y_coth * y_coth - x_cot) / t;
let y = (-x_cot * x_cot * y_coth - y_coth) / t;
if x.is_infinite() || y.is_infinite() || x.is_nan() || y.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid operation".to_string());
}
let complex = Complex { x, y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcsc(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let x_cos = x.cos();
let x_sin = x.sin();
let y_cosh = y.cosh();
let y_sinh = y.sinh();
let t = x_sin * x_sin * y_cosh * y_cosh + x_cos * x_cos * y_sinh * y_sinh;
let complex = Complex {
x: x_sin * y_cosh / t,
y: -x_cos * y_sinh / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imcsch(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let (x, y, suffix) = match parse_complex_number(&value) {
Ok(s) => s,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
let x_cosh = x.cosh();
let x_sinh = x.sinh();
let y_cos = y.cos();
let y_sin = y.sin();
let t = x_sinh * x_sinh * y_cos * y_cos + x_cosh * x_cosh * y_sin * y_sin;
let complex = Complex {
x: x_sinh * y_cos / t,
y: -x_cosh * y_sin / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imdiv(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let t = x2 * x2 + y2 * y2;
if t == 0.0 {
return CalcResult::new_error(Error::NUM, cell, "Invalid".to_string());
}
let complex = Complex {
x: (x1 * x2 + y1 * y2) / t,
y: (-x1 * y2 + y1 * x2) / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imexp(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex {
x: x.exp() * y.cos(),
y: x.exp() * y.sin(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imln(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let complex = Complex {
x: r.ln(),
y: a,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imlog10(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let complex = Complex {
x: r.log10(),
y: a * f64::log10(f64::exp(1.0)),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imlog2(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let complex = Complex {
x: r.log2(),
y: a * f64::log2(f64::exp(1.0)),
suffix,
};
CalcResult::String(complex.to_string())
}
// IMPOWER(imnumber, power)
// If $(r, \theta)$ is the polar representation the formula is:
// $$ x = r^n*\cos(n\dot\theta), y = r^n*\csin(n\dot\theta) $
pub(crate) fn fn_impower(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let n = match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
// if n == n.trunc() && n < 10.0 {
// // for small powers we compute manually
// let (mut x0, mut y0) = (x, y);
// for _ in 1..(n.trunc() as i32) {
// (x0, y0) = (x0 * x - y0 * y, x0 * y + y0 * x);
// }
// let complex = Complex {
// x: x0,
// y: y0,
// suffix,
// };
// return CalcResult::String(complex.to_string());
// };
let r = f64::sqrt(x * x + y * y);
let a = f64::atan2(y, x);
let x = r.powf(n) * f64::cos(a * n);
let y = r.powf(n) * f64::sin(a * n);
if x.is_infinite() || y.is_infinite() || x.is_nan() || y.is_nan() {
return CalcResult::new_error(Error::NUM, cell, "Invalid operation".to_string());
}
let complex = Complex { x, y, suffix };
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_improduct(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let complex = Complex {
x: x1 * x2 - y1 * y2,
y: x1 * y2 + y1 * x2,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imreal(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, _, _) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
CalcResult::Number(x)
}
pub(crate) fn fn_imsec(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let x_cos = x.cos();
let x_sin = x.sin();
let y_cosh = y.cosh();
let y_sinh = y.sinh();
let t = x_cos * x_cos * y_cosh * y_cosh + x_sin * x_sin * y_sinh * y_sinh;
let complex = Complex {
x: x_cos * y_cosh / t,
y: x_sin * y_sinh / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsech(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let x_cosh = x.cosh();
let x_sinh = x.sinh();
let y_cos = y.cos();
let y_sin = y.sin();
let t = x_cosh * x_cosh * y_cos * y_cos + x_sinh * x_sinh * y_sin * y_sin;
let complex = Complex {
x: x_cosh * y_cos / t,
y: -x_sinh * y_sin / t,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex {
x: x.sin() * y.cosh(),
y: x.cos() * y.sinh(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsinh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let complex = Complex {
x: x.sinh() * y.cos(),
y: x.cosh() * y.sin(),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsqrt(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let r = f64::sqrt(x * x + y * y).sqrt();
let a = f64::atan2(y, x);
let complex = Complex {
x: r * f64::cos(a / 2.0),
y: r * f64::sin(a / 2.0),
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsub(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let complex = Complex {
x: x1 - x2,
y: y1 - y2,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imsum(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let (x1, y1, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let (x2, y2, suffix2) = match self.get_complex_number(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
if suffix != suffix2 {
return CalcResult::new_error(Error::VALUE, cell, "Different suffixes".to_string());
}
let complex = Complex {
x: x1 + x2,
y: y1 + y2,
suffix,
};
CalcResult::String(complex.to_string())
}
pub(crate) fn fn_imtan(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let (x, y, suffix) = match self.get_complex_number(&args[0], cell) {
Ok(s) => s,
Err(error) => return error,
};
let x_tan = x.tan();
let y_tanh = y.tanh();
let t = 1.0 + x_tan * x_tan * y_tanh * y_tanh;
let complex = Complex {
x: (x_tan - x_tan * y_tanh * y_tanh) / t,
y: (y_tanh + x_tan * x_tan * y_tanh) / t,
suffix,
};
CalcResult::String(complex.to_string())
}
}
#[cfg(test)]
mod tests {
use crate::functions::engineering::complex::Suffix;
use super::parse_complex_number as parse;
#[test]
fn test_parse_complex() {
assert_eq!(parse("1+2i"), Ok((1.0, 2.0, Suffix::I)));
assert_eq!(parse("2i"), Ok((0.0, 2.0, Suffix::I)));
assert_eq!(parse("7.5"), Ok((7.5, 0.0, Suffix::I)));
assert_eq!(parse("-7.5"), Ok((-7.5, 0.0, Suffix::I)));
assert_eq!(parse("7-5i"), Ok((7.0, -5.0, Suffix::I)));
assert_eq!(parse("i"), Ok((0.0, 1.0, Suffix::I)));
assert_eq!(parse("7+i"), Ok((7.0, 1.0, Suffix::I)));
assert_eq!(parse("7-i"), Ok((7.0, -1.0, Suffix::I)));
assert_eq!(parse("-i"), Ok((0.0, -1.0, Suffix::I)));
assert_eq!(parse("0"), Ok((0.0, 0.0, Suffix::I)));
}
}

View File

@@ -0,0 +1,418 @@
use std::collections::HashMap;
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
enum Temperature {
Kelvin,
Celsius,
Rankine,
Reaumur,
Fahrenheit,
}
// To Kelvin
// T_K = T_C + 273.15
// T_K = 5/9 * T_rank
// T_K = (T_R-273.15)*4/5
// T_K = 5/9 ( T_F + 459.67)
fn convert_temperature(
value: f64,
from_temperature: Temperature,
to_temperature: Temperature,
) -> f64 {
let from_t_kelvin = match from_temperature {
Temperature::Kelvin => value,
Temperature::Celsius => value + 273.15,
Temperature::Rankine => 5.0 * value / 9.0,
Temperature::Reaumur => 5.0 * value / 4.0 + 273.15,
Temperature::Fahrenheit => 5.0 / 9.0 * (value + 459.67),
};
match to_temperature {
Temperature::Kelvin => from_t_kelvin,
Temperature::Celsius => from_t_kelvin - 273.5,
Temperature::Rankine => 9.0 * from_t_kelvin / 5.0,
Temperature::Reaumur => 4.0 * (from_t_kelvin - 273.15) / 5.0,
Temperature::Fahrenheit => 9.0 * from_t_kelvin / 5.0 - 459.67,
}
}
impl Model {
// CONVERT(number, from_unit, to_unit)
pub(crate) fn fn_convert(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 3 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let from_unit = match self.get_string(&args[1], cell) {
Ok(s) => s,
Err(error) => return error,
};
let to_unit = match self.get_string(&args[2], cell) {
Ok(s) => s,
Err(error) => return error,
};
let prefix = HashMap::from([
("Y", 1E+24),
("Z", 1E+21),
("E", 1000000000000000000.0),
("P", 1000000000000000.0),
("T", 1000000000000.0),
("G", 1000000000.0),
("M", 1000000.0),
("k", 1000.0),
("h", 100.0),
("da", 10.0),
("e", 10.0),
("d", 0.1),
("c", 0.01),
("m", 0.001),
("u", 0.000001),
("n", 0.000000001),
("p", 1E-12),
("f", 1E-15),
("a", 1E-18),
("z", 1E-21),
("y", 1E-24),
("Yi", 2.0_f64.powf(80.0)),
("Ei", 2.0_f64.powf(70.0)),
("Yi", 2.0_f64.powf(80.0)),
("Zi", 2.0_f64.powf(70.0)),
("Ei", 2.0_f64.powf(60.0)),
("Pi", 2.0_f64.powf(50.0)),
("Ti", 2.0_f64.powf(40.0)),
("Gi", 2.0_f64.powf(30.0)),
("Mi", 2.0_f64.powf(20.0)),
("ki", 2.0_f64.powf(10.0)),
]);
let mut units = HashMap::new();
let weight = HashMap::from([
("g", 1.0),
("sg", 14593.9029372064),
("lbm", 453.59237),
("u", 1.660538782E-24),
("ozm", 28.349523125),
("grain", 0.06479891),
("cwt", 45359.237),
("shweight", 45359.237),
("uk_cwt", 50802.34544),
("lcwt", 50802.34544),
("stone", 6350.29318),
("ton", 907184.74),
("brton", 1016046.9088), // g-sheets has a different value for this
("LTON", 1016046.9088),
("uk_ton", 1016046.9088),
]);
units.insert("weight", weight);
let distance = HashMap::from([
("m", 1.0),
("mi", 1609.344),
("Nmi", 1852.0),
("in", 0.0254),
("ft", 0.3048),
("yd", 0.9144),
("ang", 0.0000000001),
("ell", 1.143),
("ly", 9460730472580800.0),
("parsec", 30856775812815500.0),
("pc", 30856775812815500.0),
("Picapt", 0.000352777777777778),
("Pica", 0.000352777777777778),
("pica", 0.00423333333333333),
("survey_mi", 1609.34721869444),
]);
units.insert("distance", distance);
let time = HashMap::from([
("yr", 31557600.0),
("day", 86400.0),
("d", 86400.0),
("hr", 3600.0),
("mn", 60.0),
("min", 60.0),
("sec", 1.0),
("s", 1.0),
]);
units.insert("time", time);
let pressure = HashMap::from([
("Pa", 1.0),
("p", 1.0),
("atm", 101325.0),
("at", 101325.0),
("mmHg", 133.322),
("psi", 6894.75729316836),
("Torr", 133.322368421053),
]);
units.insert("pressure", pressure);
let force = HashMap::from([
("N", 1.0),
("dyn", 0.00001),
("dy", 0.00001),
("lbf", 4.4482216152605),
("pond", 0.00980665),
]);
units.insert("force", force);
let energy = HashMap::from([
("J", 1.0),
("e", 0.0000001),
("c", 4.184),
("cal", 4.1868),
("eV", 1.602176487E-19),
("ev", 1.602176487E-19),
("HPh", 2684519.53769617),
("hh", 2684519.53769617),
("Wh", 3600.0),
("wh", 3600.0),
("flb", 1.3558179483314),
("BTU", 1055.05585262),
("btu", 1055.05585262),
]);
units.insert("energy", energy);
let power = HashMap::from([
("HP", 745.69987158227),
("h", 745.69987158227),
("PS", 735.49875),
("W", 1.0),
("w", 1.0),
]);
units.insert("power", power);
let magnetism = HashMap::from([("T", 1.0), ("ga", 0.0001)]);
units.insert("magnetism", magnetism);
let volume = HashMap::from([
("tsp", 0.00000492892159375),
("tspm", 0.000005),
("tbs", 0.00001478676478125),
("oz", 0.0000295735295625),
("cup", 0.0002365882365),
("pt", 0.000473176473),
("us_pt", 0.000473176473),
("uk_pt", 0.00056826125),
("qt", 0.000946352946),
("uk_qt", 0.0011365225),
("gal", 0.003785411784),
("uk_gal", 0.00454609),
("l", 0.001),
("L", 0.001),
("lt", 0.001),
("ang3", 1E-30),
("ang^3", 1E-30),
("barrel", 0.158987294928),
("bushel", 0.03523907016688),
("ft3", 0.028316846592),
("ft^3", 0.028316846592),
("in3", 0.000016387064),
("in^3", 0.000016387064),
("ly3", 8.46786664623715E+47),
("ly^3", 8.46786664623715E+47),
("m3", 1.0),
("m^3", 1.0),
("mi3", 4168181825.44058),
("mi^3", 4168181825.44058),
("yd3", 0.764554857984),
("yd^3", 0.764554857984),
("Nmi3", 6352182208.0),
("Nmi^3", 6352182208.0),
("Picapt3", 4.39039566186557E-11),
("Picapt^3", 4.39039566186557E-11),
("Pica3", 4.39039566186557E-11),
("Pica^3", 4.39039566186557E-11),
("GRT", 2.8316846592),
("regton", 2.8316846592),
("MTON", 1.13267386368),
]);
units.insert("volume", volume);
let area = HashMap::from([
("uk_acre", 4046.8564224),
("us_acre", 4046.87260987425),
("ang2", 1E-20),
("ang^2", 1E-20),
("ar", 100.0),
("ft2", 0.09290304),
("ft^2", 0.09290304),
("ha", 10000.0),
("in2", 0.00064516),
("in^2", 0.00064516),
("ly2", 8.95054210748189E+31),
("ly^2", 8.95054210748189E+31),
("m2", 1.0),
("m^2", 1.0),
("Morgen", 2500.0),
("mi2", 2589988.110336),
("mi^2", 2589988.110336),
("Nmi2", 3429904.0),
("Nmi^2", 3429904.0),
("Picapt2", 0.000000124452160493827),
("Pica2", 0.000000124452160493827),
("Pica^2", 0.000000124452160493827),
("Picapt^2", 0.000000124452160493827),
("yd2", 0.83612736),
("yd^2", 0.83612736),
]);
units.insert("area", area);
let information = HashMap::from([("bit", 1.0), ("byte", 8.0)]);
units.insert("information", information);
let speed = HashMap::from([
("admkn", 0.514773333333333),
("kn", 0.514444444444444),
("m/h", 0.000277777777777778),
("m/hr", 0.000277777777777778),
("m/s", 1.0),
("m/sec", 1.0),
("mph", 0.44704),
]);
units.insert("speed", speed);
let temperature = HashMap::from([
("C", 1.0),
("cel", 1.0),
("F", 1.0),
("fah", 1.0),
("K", 1.0),
("kel", 1.0),
("Rank", 1.0),
("Reau", 1.0),
]);
units.insert("temperature", temperature);
// only some units admit prefixes (the is no kC, kilo Celsius, for instance)
let mks = [
"Pa", "p", "atm", "at", "mmHg", "g", "u", "m", "ang", "ly", "parsec", "pc", "ang2",
"ang^2", "ar", "m2", "m^2", "N", "dyn", "dy", "pond", "J", "e", "c", "cal", "eV", "ev",
"Wh", "wh", "W", "w", "T", "ga", "uk_pt", "l", "L", "lt", "ang3", "ang^3", "m3", "m^3",
"bit", "byte", "m/h", "m/hr", "m/s", "m/sec", "mph", "K", "kel",
];
let volumes = ["ang3", "ang^3", "m3", "m^3"];
// We need all_units to make sure tha pc is interpreted as parsec and not pico centimeters
// We could have this list hard coded, of course.
let mut all_units = Vec::new();
for unit in units.values() {
for &unit_name in unit.keys() {
all_units.push(unit_name);
}
}
let mut to_unit_prefix = 1.0;
let mut from_unit_prefix = 1.0;
// kind of units (weight, distance, time, ...)
let mut to_unit_kind = "";
let mut from_unit_kind = "";
let mut to_unit_name = "";
let mut from_unit_name = "";
for (&name, unit) in &units {
for (&unit_name, unit_value) in unit {
if let Some(pk) = from_unit.strip_suffix(unit_name) {
if pk.is_empty() {
from_unit_kind = name;
from_unit_prefix = 1.0 * unit_value;
from_unit_name = unit_name;
} else if let Some(modifier) = prefix.get(pk) {
if mks.contains(&unit_name) && !all_units.contains(&from_unit.as_str()) {
// We make sure:
// 1. It is a unit that admits a modifier (like metres or grams)
// 2. from_unit is not itself a unit
let scale = if name == "area" && unit_name != "ar" {
// 1 km2 is actually 10^6 m2
*modifier * modifier
} else if name == "volume" && volumes.contains(&unit_name) {
// don't look at me I don't make the rules!
*modifier * modifier * modifier
} else {
*modifier
};
from_unit_kind = name;
from_unit_prefix = scale * unit_value;
from_unit_name = unit_name;
}
}
}
if let Some(pk) = to_unit.strip_suffix(unit_name) {
if pk.is_empty() {
to_unit_kind = name;
to_unit_prefix = 1.0 * unit_value;
to_unit_name = unit_name;
} else if let Some(modifier) = prefix.get(pk) {
if mks.contains(&unit_name) && !all_units.contains(&to_unit.as_str()) {
let scale = if name == "area" && unit_name != "ar" {
*modifier * modifier
} else if name == "volume" && volumes.contains(&unit_name) {
*modifier * modifier * modifier
} else {
*modifier
};
to_unit_kind = name;
to_unit_prefix = scale * unit_value;
to_unit_name = unit_name;
}
}
}
if !from_unit_kind.is_empty() && !to_unit_kind.is_empty() {
break;
}
}
if !from_unit_kind.is_empty() && !to_unit_kind.is_empty() {
break;
}
}
if from_unit_kind != to_unit_kind {
return CalcResult::new_error(Error::NA, cell, "Different units".to_string());
}
// Let's check if it is temperature;
if from_unit_kind.is_empty() {
return CalcResult::new_error(Error::NA, cell, "Unit not found".to_string());
}
if from_unit_kind == "temperature" {
// Temperature requires formula conversion
// Kelvin (K, k), Celsius (C,cel), Rankine (Rank), Réaumur (Reau)
let to_temperature = match to_unit_name {
"K" | "kel" => Temperature::Kelvin,
"C" | "cel" => Temperature::Celsius,
"Rank" => Temperature::Rankine,
"Reau" => Temperature::Reaumur,
"F" | "fah" => Temperature::Fahrenheit,
_ => {
return CalcResult::new_error(Error::ERROR, cell, "Internal error".to_string());
}
};
let from_temperature = match from_unit_name {
"K" | "kel" => Temperature::Kelvin,
"C" | "cel" => Temperature::Celsius,
"Rank" => Temperature::Rankine,
"Reau" => Temperature::Reaumur,
"F" | "fah" => Temperature::Fahrenheit,
_ => {
return CalcResult::new_error(Error::ERROR, cell, "Internal error".to_string());
}
};
let t = convert_temperature(value * from_unit_prefix, from_temperature, to_temperature)
/ to_unit_prefix;
return CalcResult::Number(t);
}
CalcResult::Number(value * from_unit_prefix / to_unit_prefix)
}
}

View File

@@ -0,0 +1,59 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
model::Model,
number_format::to_precision,
};
impl Model {
// DELTA(number1, [number2])
pub(crate) fn fn_delta(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let arg_count = args.len();
if !(1..=2).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let number1 = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(error) => return error,
};
let number2 = if arg_count > 1 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(error) => return error,
}
} else {
0.0
};
if to_precision(number1, 16) == to_precision(number2, 16) {
CalcResult::Number(1.0)
} else {
CalcResult::Number(0.0)
}
}
// GESTEP(number, [step])
pub(crate) fn fn_gestep(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let arg_count = args.len();
if !(1..=2).contains(&arg_count) {
return CalcResult::new_args_number_error(cell);
}
let number = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(error) => return error,
};
let step = if arg_count > 1 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => f,
Err(error) => return error,
}
} else {
0.0
};
if to_precision(number, 16) >= to_precision(step, 16) {
CalcResult::Number(1.0)
} else {
CalcResult::Number(0.0)
}
}
}

View File

@@ -0,0 +1,7 @@
mod bessel;
mod bit_operations;
mod complex;
mod convert;
mod misc;
mod number_basis;
mod transcendental;

View File

@@ -0,0 +1,546 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
// 8_i64.pow(10);
const OCT_MAX: i64 = 1_073_741_824;
const OCT_MAX_HALF: i64 = 536_870_912;
// 16_i64.pow(10)
const HEX_MAX: i64 = 1_099_511_627_776;
const HEX_MAX_HALF: i64 = 549_755_813_888;
// Binary numbers are 10 bits and the most significant bit is the sign
fn from_binary_to_decimal(value: f64) -> Result<i64, String> {
let value = format!("{value}");
let result = match i64::from_str_radix(&value, 2) {
Ok(b) => b,
Err(_) => {
return Err("cannot parse into binary".to_string());
}
};
if !(0..=1023).contains(&result) {
// 2^10
return Err("too large".to_string());
} else if result > 511 {
// 2^9
return Ok(result - 1024);
};
Ok(result)
}
impl Model {
// BIN2DEC(number)
pub(crate) fn fn_bin2dec(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
match from_binary_to_decimal(value) {
Ok(n) => CalcResult::Number(n as f64),
Err(message) => CalcResult::new_error(Error::NUM, cell, message),
}
}
// BIN2HEX(number, [places])
pub(crate) fn fn_bin2hex(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let value = match from_binary_to_decimal(value) {
Ok(n) => n,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
if value < 0 {
CalcResult::String(format!("{:0width$X}", HEX_MAX + value, width = 9))
} else {
let result = format!("{:X}", value);
if let Some(places) = places {
if places < result.len() as i32 {
return CalcResult::new_error(
Error::NUM,
cell,
"Not enough places".to_string(),
);
}
return CalcResult::String(format!("{:0width$X}", value, width = places as usize));
}
CalcResult::String(result)
}
}
// BIN2OCT(number, [places])
pub(crate) fn fn_bin2oct(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
let value = match from_binary_to_decimal(value) {
Ok(n) => n,
Err(message) => return CalcResult::new_error(Error::NUM, cell, message),
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
if value < 0 {
CalcResult::String(format!("{:0width$o}", OCT_MAX + value, width = 9))
} else {
let result = format!("{:o}", value);
if let Some(places) = places {
if places < result.len() as i32 {
return CalcResult::new_error(
Error::NUM,
cell,
"Not enough places".to_string(),
);
}
return CalcResult::String(format!("{:0width$o}", value, width = places as usize));
}
CalcResult::String(result)
}
}
pub(crate) fn fn_dec2bin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value_raw = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = value_raw.trunc() as i64;
if !(-512..=511).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += 1024;
}
let result = format!("{:b}", value);
if let Some(places) = places {
if value_raw > 0.0 && places < result.len() as i32 {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$b}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
pub(crate) fn fn_dec2hex(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value_raw = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f.trunc(),
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = value_raw.trunc() as i64;
if !(-HEX_MAX_HALF..=HEX_MAX_HALF - 1).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += HEX_MAX;
}
let result = format!("{:X}", value);
if let Some(places) = places {
if value_raw > 0.0 && places < result.len() as i32 {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$X}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
pub(crate) fn fn_dec2oct(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value_raw = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = value_raw.trunc() as i64;
if !(-OCT_MAX_HALF..=OCT_MAX_HALF - 1).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += OCT_MAX;
}
let result = format!("{:o}", value);
if let Some(places) = places {
if value_raw > 0.0 && places < result.len() as i32 {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$o}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
// HEX2BIN(number, [places])
pub(crate) fn fn_hex2bin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if value.len() > 10 {
return CalcResult::new_error(Error::NUM, cell, "Value too large".to_string());
}
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = match i64::from_str_radix(&value, 16) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value >= HEX_MAX_HALF {
value -= HEX_MAX;
}
if !(-512..=511).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += 1024;
}
let result = format!("{:b}", value);
if let Some(places) = places {
if places <= 0 || (value > 0 && places < result.len() as i32) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$b}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
// HEX2DEC(number)
pub(crate) fn fn_hex2dec(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
if value.len() > 10 {
return CalcResult::new_error(Error::NUM, cell, "Value too large".to_string());
}
let mut value = match i64::from_str_radix(&value, 16) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value >= HEX_MAX_HALF {
value -= HEX_MAX;
}
CalcResult::Number(value as f64)
}
pub(crate) fn fn_hex2oct(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
if value.len() > 10 {
return CalcResult::new_error(Error::NUM, cell, "Value too large".to_string());
}
let mut value = match i64::from_str_radix(&value, 16) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value > HEX_MAX_HALF {
value -= HEX_MAX;
}
if !(-OCT_MAX_HALF..=OCT_MAX_HALF - 1).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += OCT_MAX;
}
let result = format!("{:o}", value);
if let Some(places) = places {
if places <= 0 || (value > 0 && places < result.len() as i32) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$o}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
pub(crate) fn fn_oct2bin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = match i64::from_str_radix(&value, 8) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value >= OCT_MAX_HALF {
value -= OCT_MAX;
}
if !(-512..=511).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += 1024;
}
let result = format!("{:b}", value);
if let Some(places) = places {
if value < 512 && places < result.len() as i32 {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$b}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
pub(crate) fn fn_oct2dec(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let mut value = match i64::from_str_radix(&value, 8) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value >= OCT_MAX_HALF {
value -= OCT_MAX
}
CalcResult::Number(value as f64)
}
pub(crate) fn fn_oct2hex(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !(1..=2).contains(&args.len()) {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_string(&args[0], cell) {
Ok(s) => s,
Err(s) => return s,
};
let places = if args.len() == 2 {
match self.get_number_no_bools(&args[1], cell) {
Ok(f) => Some(f.trunc() as i32),
Err(s) => return s,
}
} else {
None
};
// There is not a default value for places
// But if there is a value it needs to be positive and less than 11
if let Some(p) = places {
if p <= 0 || p > 10 {
return CalcResult::new_error(Error::NUM, cell, "Not enough places".to_string());
}
}
let mut value = match i64::from_str_radix(&value, 8) {
Ok(f) => f,
Err(_) => {
return CalcResult::new_error(
Error::NUM,
cell,
"Error parsing hex number".to_string(),
);
}
};
if value < 0 {
return CalcResult::new_error(Error::NUM, cell, "Negative value".to_string());
}
if value >= OCT_MAX_HALF {
value -= OCT_MAX;
}
if !(-HEX_MAX_HALF..=HEX_MAX_HALF - 1).contains(&value) {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
if value < 0 {
value += HEX_MAX;
}
let result = format!("{:X}", value);
if let Some(places) = places {
if value < HEX_MAX_HALF && places < result.len() as i32 {
return CalcResult::new_error(Error::NUM, cell, "Out of bounds".to_string());
}
let result = format!("{:0width$X}", value, width = places as usize);
return CalcResult::String(result);
}
CalcResult::String(result)
}
}

View File

@@ -0,0 +1,29 @@
# Creating tests from transcendental functions
Excel supports a number of transcendental functions like the error functions, gamma nad beta functions.
In this folder we have tests for the Bessel functions.
Some other platform's implementations of those functions are remarkably poor (including Excel), sometimes failing on the third decimal digit. We strive to do better.
To properly test you need to compute some known values with established arbitrary precision arithmetic.
I use for this purpose Arb[1], created by the unrivalled Fredrik Johansson[2]. You might find some python bindings, but I use Julia's Nemo[3]:
```julia
julia> using Nemo
julia> CC = AcbField(200)
julia> besseli(CC("17"), CC("5.6"))
```
If you are new to Julia, just install Julia and in the Julia terminal run:
```
julia> using Pkg; Pkg.add("Nemo")
```
You only need to do that once (like the R philosophy)
Will give you any Bessel function of any order (integer or not) of any value real or complex
[1]: https://arblib.org/
[2]: https://fredrikj.net/
[3]: https://nemocas.github.io/Nemo.jl/latest/

View File

@@ -0,0 +1,144 @@
// This are somewhat lower precision than the BesselJ and BesselY
// needed for BesselK
pub(crate) fn bessel_i0(x: f64) -> f64 {
// Parameters of the polynomial approximation
let p1 = 1.0;
let p2 = 3.5156229;
let p3 = 3.0899424;
let p4 = 1.2067492;
let p5 = 0.2659732;
let p6 = 3.60768e-2;
let p7 = 4.5813e-3;
let q1 = 0.39894228;
let q2 = 1.328592e-2;
let q3 = 2.25319e-3;
let q4 = -1.57565e-3;
let q5 = 9.16281e-3;
let q6 = -2.057706e-2;
let q7 = 2.635537e-2;
let q8 = -1.647633e-2;
let q9 = 3.92377e-3;
let k1 = 3.75;
let ax = x.abs();
if x.is_infinite() {
return 0.0;
}
if ax < k1 {
// let xx = x / k1;
// let y = xx * xx;
// let mut result = 1.0;
// let max_iter = 50;
// let mut term = 1.0;
// for i in 1..max_iter {
// term = term * k1*k1*y /(2.0*i as f64).powi(2);
// result += term;
// };
// result
let xx = x / k1;
let y = xx * xx;
p1 + y * (p2 + y * (p3 + y * (p4 + y * (p5 + y * (p6 + y * p7)))))
} else {
let y = k1 / ax;
((ax).exp() / (ax).sqrt())
* (q1
+ y * (q2
+ y * (q3 + y * (q4 + y * (q5 + y * (q6 + y * (q7 + y * (q8 + y * q9))))))))
}
}
// needed for BesselK
pub(crate) fn bessel_i1(x: f64) -> f64 {
let p1 = 0.5;
let p2 = 0.87890594;
let p3 = 0.51498869;
let p4 = 0.15084934;
let p5 = 2.658733e-2;
let p6 = 3.01532e-3;
let p7 = 3.2411e-4;
let q1 = 0.39894228;
let q2 = -3.988024e-2;
let q3 = -3.62018e-3;
let q4 = 1.63801e-3;
let q5 = -1.031555e-2;
let q6 = 2.282967e-2;
let q7 = -2.895312e-2;
let q8 = 1.787654e-2;
let q9 = -4.20059e-3;
let k1 = 3.75;
let ax = x.abs();
if ax < k1 {
let xx = x / k1;
let y = xx * xx;
x * (p1 + y * (p2 + y * (p3 + y * (p4 + y * (p5 + y * (p6 + y * p7))))))
} else {
let y = k1 / ax;
let result = ((ax).exp() / (ax).sqrt())
* (q1
+ y * (q2
+ y * (q3 + y * (q4 + y * (q5 + y * (q6 + y * (q7 + y * (q8 + y * q9))))))));
if x < 0.0 {
return -result;
}
result
}
}
pub(crate) fn bessel_i(n: i32, x: f64) -> f64 {
let accuracy = 40;
let large_number = 1e10;
let small_number = 1e-10;
if n < 0 {
return f64::NAN;
}
if n == 0 {
return bessel_i0(x);
}
if x == 0.0 {
// I_n(0) = 0 for all n!= 0
return 0.0;
}
if n == 1 {
return bessel_i1(x);
}
if x.abs() > large_number {
return 0.0;
};
let tox = 2.0 / x.abs();
let mut bip = 0.0;
let mut bi = 1.0;
let mut result = 0.0;
let m = 2 * (((accuracy * n) as f64).sqrt().trunc() as i32 + n);
for j in (1..=m).rev() {
(bip, bi) = (bi, bip + (j as f64) * tox * bi);
// Prevent overflow
if bi.abs() > large_number {
result *= small_number;
bi *= small_number;
bip *= small_number;
}
if j == n {
result = bip;
}
}
result *= bessel_i0(x) / bi;
if (x < 0.0) && (n % 2 == 1) {
result = -result;
}
result
}

View File

@@ -0,0 +1,402 @@
/* @(#)e_j0.c 1.3 95/01/18 */
/*
* ====================================================
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
*
* Developed at SunSoft, a Sun Microsystems, Inc. business.
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
/* j0(x), y0(x)
* Bessel function of the first and second kinds of order zero.
* Method -- j0(x):
* 1. For tiny x, we use j0(x) = 1 - x^2/4 + x^4/64 - ...
* 2. Reduce x to |x| since j0(x)=j0(-x), and
* for x in (0,2)
* j0(x) = 1-z/4+ z^2*R0/S0, where z = x*x;
* (precision: |j0-1+z/4-z^2R0/S0 |<2**-63.67 )
* for x in (2,inf)
* j0(x) = sqrt(2/(pi*x))*(p0(x)*cos(x0)-q0(x)*sin(x0))
* where x0 = x-pi/4. It is better to compute sin(x0),cos(x0)
* as follow:
* cos(x0) = cos(x)cos(pi/4)+sin(x)sin(pi/4)
* = 1/sqrt(2) * (cos(x) + sin(x))
* sin(x0) = sin(x)cos(pi/4)-cos(x)sin(pi/4)
* = 1/sqrt(2) * (sin(x) - cos(x))
* (To avoid cancellation, use
* sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x))
* to compute the worse 1.)
*
* 3 Special cases
* j0(nan)= nan
* j0(0) = 1
* j0(inf) = 0
*
* Method -- y0(x):
* 1. For x<2.
* Since
* y0(x) = 2/pi*(j0(x)*(ln(x/2)+Euler) + x^2/4 - ...)
* therefore y0(x)-2/pi*j0(x)*ln(x) is an even function.
* We use the following function to approximate y0,
* y0(x) = U(z)/V(z) + (2/pi)*(j0(x)*ln(x)), z= x^2
* where
* U(z) = u00 + u01*z + ... + u06*z^6
* V(z) = 1 + v01*z + ... + v04*z^4
* with absolute approximation error bounded by 2**-72.
* Note: For tiny x, U/V = u0 and j0(x)~1, hence
* y0(tiny) = u0 + (2/pi)*ln(tiny), (choose tiny<2**-27)
* 2. For x>=2.
* y0(x) = sqrt(2/(pi*x))*(p0(x)*cos(x0)+q0(x)*sin(x0))
* where x0 = x-pi/4. It is better to compute sin(x0),cos(x0)
* by the method menti1d above.
* 3. Special cases: y0(0)=-inf, y0(x<0)=NaN, y0(inf)=0.
*/
use std::f64::consts::FRAC_2_PI;
use super::bessel_util::{high_word, split_words, FRAC_2_SQRT_PI, HUGE};
// R0/S0 on [0, 2.00]
const R02: f64 = 1.562_499_999_999_999_5e-2; // 0x3F8FFFFF, 0xFFFFFFFD
const R03: f64 = -1.899_792_942_388_547_2e-4; // 0xBF28E6A5, 0xB61AC6E9
const R04: f64 = 1.829_540_495_327_006_7e-6; // 0x3EBEB1D1, 0x0C503919
const R05: f64 = -4.618_326_885_321_032e-9; // 0xBE33D5E7, 0x73D63FCE
const S01: f64 = 1.561_910_294_648_900_1e-2; // 0x3F8FFCE8, 0x82C8C2A4
const S02: f64 = 1.169_267_846_633_374_5e-4; // 0x3F1EA6D2, 0xDD57DBF4
const S03: f64 = 5.135_465_502_073_181e-7; // 0x3EA13B54, 0xCE84D5A9
const S04: f64 = 1.166_140_033_337_9e-9; // 0x3E1408BC, 0xF4745D8F
/* The asymptotic expansions of pzero is
* 1 - 9/128 s^2 + 11025/98304 s^4 - ..., where s = 1/x.
* For x >= 2, We approximate pzero by
* pzero(x) = 1 + (R/S)
* where R = pR0 + pR1*s^2 + pR2*s^4 + ... + pR5*s^10
* S = 1 + pS0*s^2 + ... + pS4*s^10
* and
* | pzero(x)-1-R/S | <= 2 ** ( -60.26)
*/
const P_R8: [f64; 6] = [
/* for x in [inf, 8]=1/[0,0.125] */
0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */
-7.031_249_999_999_004e-2, /* 0xBFB1FFFF, 0xFFFFFD32 */
-8.081_670_412_753_498, /* 0xC02029D0, 0xB44FA779 */
-2.570_631_056_797_048_5e2, /* 0xC0701102, 0x7B19E863 */
-2.485_216_410_094_288e3, /* 0xC0A36A6E, 0xCD4DCAFC */
-5.253_043_804_907_295e3, /* 0xC0B4850B, 0x36CC643D */
];
const P_S8: [f64; 5] = [
1.165_343_646_196_681_8e2, /* 0x405D2233, 0x07A96751 */
3.833_744_753_641_218_3e3, /* 0x40ADF37D, 0x50596938 */
4.059_785_726_484_725_5e4, /* 0x40E3D2BB, 0x6EB6B05F */
1.167_529_725_643_759_2e5, /* 0x40FC810F, 0x8F9FA9BD */
4.762_772_841_467_309_6e4, /* 0x40E74177, 0x4F2C49DC */
];
const P_R5: [f64; 6] = [
/* for x in [8,4.5454]=1/[0.125,0.22001] */
-1.141_254_646_918_945e-11, /* 0xBDA918B1, 0x47E495CC */
-7.031_249_408_735_993e-2, /* 0xBFB1FFFF, 0xE69AFBC6 */
-4.159_610_644_705_878, /* 0xC010A370, 0xF90C6BBF */
-6.767_476_522_651_673e1, /* 0xC050EB2F, 0x5A7D1783 */
-3.312_312_996_491_729_7e2, /* 0xC074B3B3, 0x6742CC63 */
-3.464_333_883_656_049e2, /* 0xC075A6EF, 0x28A38BD7 */
];
const P_S5: [f64; 5] = [
6.075_393_826_923_003_4e1, /* 0x404E6081, 0x0C98C5DE */
1.051_252_305_957_045_8e3, /* 0x40906D02, 0x5C7E2864 */
5.978_970_943_338_558e3, /* 0x40B75AF8, 0x8FBE1D60 */
9.625_445_143_577_745e3, /* 0x40C2CCB8, 0xFA76FA38 */
2.406_058_159_229_391e3, /* 0x40A2CC1D, 0xC70BE864 */
];
const P_R3: [f64; 6] = [
/* for x in [4.547,2.8571]=1/[0.2199,0.35001] */
-2.547_046_017_719_519e-9, /* 0xBE25E103, 0x6FE1AA86 */
-7.031_196_163_814_817e-2, /* 0xBFB1FFF6, 0xF7C0E24B */
-2.409_032_215_495_296, /* 0xC00345B2, 0xAEA48074 */
-2.196_597_747_348_831e1, /* 0xC035F74A, 0x4CB94E14 */
-5.807_917_047_017_376e1, /* 0xC04D0A22, 0x420A1A45 */
-3.144_794_705_948_885e1, /* 0xC03F72AC, 0xA892D80F */
];
const P_S3: [f64; 5] = [
3.585_603_380_552_097e1, /* 0x4041ED92, 0x84077DD3 */
3.615_139_830_503_038_6e2, /* 0x40769839, 0x464A7C0E */
1.193_607_837_921_115_3e3, /* 0x4092A66E, 0x6D1061D6 */
1.127_996_798_569_074_1e3, /* 0x40919FFC, 0xB8C39B7E */
1.735_809_308_133_357_5e2, /* 0x4065B296, 0xFC379081 */
];
const P_R2: [f64; 6] = [
/* for x in [2.8570,2]=1/[0.3499,0.5] */
-8.875_343_330_325_264e-8, /* 0xBE77D316, 0xE927026D */
-7.030_309_954_836_247e-2, /* 0xBFB1FF62, 0x495E1E42 */
-1.450_738_467_809_529_9, /* 0xBFF73639, 0x8A24A843 */
-7.635_696_138_235_278, /* 0xC01E8AF3, 0xEDAFA7F3 */
-1.119_316_688_603_567_5e1, /* 0xC02662E6, 0xC5246303 */
-3.233_645_793_513_353_4, /* 0xC009DE81, 0xAF8FE70F */
];
const P_S2: [f64; 5] = [
2.222_029_975_320_888e1, /* 0x40363865, 0x908B5959 */
1.362_067_942_182_152e2, /* 0x4061069E, 0x0EE8878F */
2.704_702_786_580_835e2, /* 0x4070E786, 0x42EA079B */
1.538_753_942_083_203_3e2, /* 0x40633C03, 0x3AB6FAFF */
1.465_761_769_482_562e1, /* 0x402D50B3, 0x44391809 */
];
// Note: This function is only called for ix>=0x40000000 (see above)
fn pzero(x: f64) -> f64 {
let ix = high_word(x) & 0x7fffffff;
// ix>=0x40000000 && ix<=0x48000000);
let (p, q) = if ix >= 0x40200000 {
(P_R8, P_S8)
} else if ix >= 0x40122E8B {
(P_R5, P_S5)
} else if ix >= 0x4006DB6D {
(P_R3, P_S3)
} else {
(P_R2, P_S2)
};
let z = 1.0 / (x * x);
let r = p[0] + z * (p[1] + z * (p[2] + z * (p[3] + z * (p[4] + z * p[5]))));
let s = 1.0 + z * (q[0] + z * (q[1] + z * (q[2] + z * (q[3] + z * q[4]))));
1.0 + r / s
}
/* For x >= 8, the asymptotic expansions of qzero is
* -1/8 s + 75/1024 s^3 - ..., where s = 1/x.
* We approximate pzero by
* qzero(x) = s*(-1.25 + (R/S))
* where R = qR0 + qR1*s^2 + qR2*s^4 + ... + qR5*s^10
* S = 1 + qS0*s^2 + ... + qS5*s^12
* and
* | qzero(x)/s +1.25-R/S | <= 2 ** ( -61.22)
*/
const Q_R8: [f64; 6] = [
/* for x in [inf, 8]=1/[0,0.125] */
0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */
7.324_218_749_999_35e-2, /* 0x3FB2BFFF, 0xFFFFFE2C */
1.176_820_646_822_527e1, /* 0x40278952, 0x5BB334D6 */
5.576_733_802_564_019e2, /* 0x40816D63, 0x15301825 */
8.859_197_207_564_686e3, /* 0x40C14D99, 0x3E18F46D */
3.701_462_677_768_878e4, /* 0x40E212D4, 0x0E901566 */
];
const Q_S8: [f64; 6] = [
1.637_760_268_956_898_2e2, /* 0x406478D5, 0x365B39BC */
8.098_344_946_564_498e3, /* 0x40BFA258, 0x4E6B0563 */
1.425_382_914_191_204_8e5, /* 0x41016652, 0x54D38C3F */
8.033_092_571_195_144e5, /* 0x412883DA, 0x83A52B43 */
8.405_015_798_190_605e5, /* 0x4129A66B, 0x28DE0B3D */
-3.438_992_935_378_666e5, /* 0xC114FD6D, 0x2C9530C5 */
];
const Q_R5: [f64; 6] = [
/* for x in [8,4.5454]=1/[0.125,0.22001] */
1.840_859_635_945_155_3e-11, /* 0x3DB43D8F, 0x29CC8CD9 */
7.324_217_666_126_848e-2, /* 0x3FB2BFFF, 0xD172B04C */
5.835_635_089_620_569_5, /* 0x401757B0, 0xB9953DD3 */
1.351_115_772_864_498_3e2, /* 0x4060E392, 0x0A8788E9 */
1.027_243_765_961_641e3, /* 0x40900CF9, 0x9DC8C481 */
1.989_977_858_646_053_8e3, /* 0x409F17E9, 0x53C6E3A6 */
];
const Q_S5: [f64; 6] = [
8.277_661_022_365_378e1, /* 0x4054B1B3, 0xFB5E1543 */
2.077_814_164_213_93e3, /* 0x40A03BA0, 0xDA21C0CE */
1.884_728_877_857_181e4, /* 0x40D267D2, 0x7B591E6D */
5.675_111_228_949_473e4, /* 0x40EBB5E3, 0x97E02372 */
3.597_675_384_251_145e4, /* 0x40E19118, 0x1F7A54A0 */
-5.354_342_756_019_448e3, /* 0xC0B4EA57, 0xBEDBC609 */
];
const Q_R3: [f64; 6] = [
/* for x in [4.547,2.8571]=1/[0.2199,0.35001] */
4.377_410_140_897_386e-9, /* 0x3E32CD03, 0x6ADECB82 */
7.324_111_800_429_114e-2, /* 0x3FB2BFEE, 0x0E8D0842 */
3.344_231_375_161_707, /* 0x400AC0FC, 0x61149CF5 */
4.262_184_407_454_126_5e1, /* 0x40454F98, 0x962DAEDD */
1.708_080_913_405_656e2, /* 0x406559DB, 0xE25EFD1F */
1.667_339_486_966_511_7e2, /* 0x4064D77C, 0x81FA21E0 */
];
const Q_S3: [f64; 6] = [
4.875_887_297_245_872e1, /* 0x40486122, 0xBFE343A6 */
7.096_892_210_566_06e2, /* 0x40862D83, 0x86544EB3 */
3.704_148_226_201_113_6e3, /* 0x40ACF04B, 0xE44DFC63 */
6.460_425_167_525_689e3, /* 0x40B93C6C, 0xD7C76A28 */
2.516_333_689_203_689_6e3, /* 0x40A3A8AA, 0xD94FB1C0 */
-1.492_474_518_361_564e2, /* 0xC062A7EB, 0x201CF40F */
];
const Q_R2: [f64; 6] = [
/* for x in [2.8570,2]=1/[0.3499,0.5] */
1.504_444_448_869_832_7e-7, /* 0x3E84313B, 0x54F76BDB */
7.322_342_659_630_793e-2, /* 0x3FB2BEC5, 0x3E883E34 */
1.998_191_740_938_16, /* 0x3FFFF897, 0xE727779C */
1.449_560_293_478_857_4e1, /* 0x402CFDBF, 0xAAF96FE5 */
3.166_623_175_047_815_4e1, /* 0x403FAA8E, 0x29FBDC4A */
1.625_270_757_109_292_7e1, /* 0x403040B1, 0x71814BB4 */
];
const Q_S2: [f64; 6] = [
3.036_558_483_552_192e1, /* 0x403E5D96, 0xF7C07AED */
2.693_481_186_080_498_4e2, /* 0x4070D591, 0xE4D14B40 */
8.447_837_575_953_201e2, /* 0x408A6645, 0x22B3BF22 */
8.829_358_451_124_886e2, /* 0x408B977C, 0x9C5CC214 */
2.126_663_885_117_988_3e2, /* 0x406A9553, 0x0E001365 */
-5.310_954_938_826_669_5, /* 0xC0153E6A, 0xF8B32931 */
];
fn qzero(x: f64) -> f64 {
let ix = high_word(x) & 0x7fffffff;
let (p, q) = if ix >= 0x40200000 {
(Q_R8, Q_S8)
} else if ix >= 0x40122E8B {
(Q_R5, Q_S5)
} else if ix >= 0x4006DB6D {
(Q_R3, Q_S3)
} else {
(Q_R2, Q_S2)
};
let z = 1.0 / (x * x);
let r = p[0] + z * (p[1] + z * (p[2] + z * (p[3] + z * (p[4] + z * p[5]))));
let s = 1.0 + z * (q[0] + z * (q[1] + z * (q[2] + z * (q[3] + z * (q[4] + z * q[5])))));
(-0.125 + r / s) / x
}
const U00: f64 = -7.380_429_510_868_723e-2; /* 0xBFB2E4D6, 0x99CBD01F */
const U01: f64 = 1.766_664_525_091_811_2e-1; /* 0x3FC69D01, 0x9DE9E3FC */
const U02: f64 = -1.381_856_719_455_969e-2; /* 0xBF8C4CE8, 0xB16CFA97 */
const U03: f64 = 3.474_534_320_936_836_5e-4; /* 0x3F36C54D, 0x20B29B6B */
const U04: f64 = -3.814_070_537_243_641_6e-6; /* 0xBECFFEA7, 0x73D25CAD */
const U05: f64 = 1.955_901_370_350_229_2e-8; /* 0x3E550057, 0x3B4EABD4 */
const U06: f64 = -3.982_051_941_321_034e-11; /* 0xBDC5E43D, 0x693FB3C8 */
const V01: f64 = 1.273_048_348_341_237e-2; /* 0x3F8A1270, 0x91C9C71A */
const V02: f64 = 7.600_686_273_503_533e-5; /* 0x3F13ECBB, 0xF578C6C1 */
const V03: f64 = 2.591_508_518_404_578e-7; /* 0x3E91642D, 0x7FF202FD */
const V04: f64 = 4.411_103_113_326_754_7e-10; /* 0x3DFE5018, 0x3BD6D9EF */
pub(crate) fn y0(x: f64) -> f64 {
let (lx, hx) = split_words(x);
let ix = 0x7fffffff & hx;
// Y0(NaN) is NaN, y0(-inf) is Nan, y0(inf) is 0
if ix >= 0x7ff00000 {
return 1.0 / (x + x * x);
}
if (ix | lx) == 0 {
return f64::NEG_INFINITY;
}
if hx < 0 {
return f64::NAN;
}
if ix >= 0x40000000 {
// |x| >= 2.0
// y0(x) = sqrt(2/(pi*x))*(p0(x)*sin(x0)+q0(x)*cos(x0))
// where x0 = x-pi/4
// Better formula:
// cos(x0) = cos(x)cos(pi/4)+sin(x)sin(pi/4)
// = 1/sqrt(2) * (sin(x) + cos(x))
// sin(x0) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
// = 1/sqrt(2) * (sin(x) - cos(x))
// To avoid cancellation, use
// sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x))
// to compute the worse 1.
let s = x.sin();
let c = x.cos();
let mut ss = s - c;
let mut cc = s + c;
// j0(x) = 1/sqrt(pi) * (P(0,x)*cc - Q(0,x)*ss) / sqrt(x)
// y0(x) = 1/sqrt(pi) * (P(0,x)*ss + Q(0,x)*cc) / sqrt(x)
if ix < 0x7fe00000 {
// make sure x+x not overflow
let z = -(x + x).cos();
if (s * c) < 0.0 {
cc = z / ss;
} else {
ss = z / cc;
}
}
return if ix > 0x48000000 {
FRAC_2_SQRT_PI * ss / x.sqrt()
} else {
let u = pzero(x);
let v = qzero(x);
FRAC_2_SQRT_PI * (u * ss + v * cc) / x.sqrt()
};
}
if ix <= 0x3e400000 {
// x < 2^(-27)
return U00 + FRAC_2_PI * x.ln();
}
let z = x * x;
let u = U00 + z * (U01 + z * (U02 + z * (U03 + z * (U04 + z * (U05 + z * U06)))));
let v = 1.0 + z * (V01 + z * (V02 + z * (V03 + z * V04)));
u / v + FRAC_2_PI * (j0(x) * x.ln())
}
pub(crate) fn j0(x: f64) -> f64 {
let hx = high_word(x);
let ix = hx & 0x7fffffff;
if x.is_nan() {
return x;
} else if x.is_infinite() {
return 0.0;
}
// the function is even
let x = x.abs();
if ix >= 0x40000000 {
// |x| >= 2.0
// let (s, c) = x.sin_cos()
let s = x.sin();
let c = x.cos();
let mut ss = s - c;
let mut cc = s + c;
// makes sure that x+x does not overflow
if ix < 0x7fe00000 {
// |x| < f64::MAX / 2.0
let z = -(x + x).cos();
if s * c < 0.0 {
cc = z / ss;
} else {
ss = z / cc;
}
}
// j0(x) = 1/sqrt(pi) * (P(0,x)*cc - Q(0,x)*ss) / sqrt(x)
// y0(x) = 1/sqrt(pi) * (P(0,x)*ss + Q(0,x)*cc) / sqrt(x)
return if ix > 0x48000000 {
// x < 5.253807105661922e-287 (2^(-951))
FRAC_2_SQRT_PI * cc / (x.sqrt())
} else {
let u = pzero(x);
let v = qzero(x);
FRAC_2_SQRT_PI * (u * cc - v * ss) / x.sqrt()
};
};
if ix < 0x3f200000 {
// |x| < 2^(-13)
if HUGE + x > 1.0 {
// raise inexact if x != 0
if ix < 0x3e400000 {
return 1.0; // |x|<2^(-27)
} else {
return 1.0 - 0.25 * x * x;
}
}
}
let z = x * x;
let r = z * (R02 + z * (R03 + z * (R04 + z * R05)));
let s = 1.0 + z * (S01 + z * (S02 + z * (S03 + z * S04)));
if ix < 0x3FF00000 {
// |x| < 1.00
1.0 + z * (-0.25 + (r / s))
} else {
let u = 0.5 * x;
(1.0 + u) * (1.0 - u) + z * (r / s)
}
}

View File

@@ -0,0 +1,391 @@
/*
* ====================================================
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
*
* Developed at SunSoft, a Sun Microsystems, Inc. business.
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
/* __ieee754_j1(x), __ieee754_y1(x)
* Bessel function of the first and second kinds of order zero.
* Method -- j1(x):
* 1. For tiny x, we use j1(x) = x/2 - x^3/16 + x^5/384 - ...
* 2. Reduce x to |x| since j1(x)=-j1(-x), and
* for x in (0,2)
* j1(x) = x/2 + x*z*R0/S0, where z = x*x;
* (precision: |j1/x - 1/2 - R0/S0 |<2**-61.51 )
* for x in (2,inf)
* j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
* y1(x) = sqrt(2/(pi*x))*(p1(x)*sin(x1)+q1(x)*cos(x1))
* where x1 = x-3*pi/4. It is better to compute sin(x1),cos(x1)
* as follow:
* cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
* = 1/sqrt(2) * (sin(x) - cos(x))
* sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
* = -1/sqrt(2) * (sin(x) + cos(x))
* (To avoid cancellation, use
* sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x))
* to compute the worse one.)
*
* 3 Special cases
* j1(nan)= nan
* j1(0) = 0
* j1(inf) = 0
*
* Method -- y1(x):
* 1. screen out x<=0 cases: y1(0)=-inf, y1(x<0)=NaN
* 2. For x<2.
* Since
* y1(x) = 2/pi*(j1(x)*(ln(x/2)+Euler)-1/x-x/2+5/64*x^3-...)
* therefore y1(x)-2/pi*j1(x)*ln(x)-1/x is an odd function.
* We use the following function to approximate y1,
* y1(x) = x*U(z)/V(z) + (2/pi)*(j1(x)*ln(x)-1/x), z= x^2
* where for x in [0,2] (abs err less than 2**-65.89)
* U(z) = U0[0] + U0[1]*z + ... + U0[4]*z^4
* V(z) = 1 + v0[0]*z + ... + v0[4]*z^5
* Note: For tiny x, 1/x dominate y1 and hence
* y1(tiny) = -2/pi/tiny, (choose tiny<2**-54)
* 3. For x>=2.
* y1(x) = sqrt(2/(pi*x))*(p1(x)*sin(x1)+q1(x)*cos(x1))
* where x1 = x-3*pi/4. It is better to compute sin(x1),cos(x1)
* by method mentioned above.
*/
use std::f64::consts::FRAC_2_PI;
use super::bessel_util::{high_word, split_words, FRAC_2_SQRT_PI, HUGE};
// R0/S0 on [0,2]
const R00: f64 = -6.25e-2; // 0xBFB00000, 0x00000000
const R01: f64 = 1.407_056_669_551_897e-3; // 0x3F570D9F, 0x98472C61
const R02: f64 = -1.599_556_310_840_356e-5; // 0xBEF0C5C6, 0xBA169668
const R03: f64 = 4.967_279_996_095_844_5e-8; // 0x3E6AAAFA, 0x46CA0BD9
const S01: f64 = 1.915_375_995_383_634_6e-2; // 0x3F939D0B, 0x12637E53
const S02: f64 = 1.859_467_855_886_309_2e-4; // 0x3F285F56, 0xB9CDF664
const S03: f64 = 1.177_184_640_426_236_8e-6; // 0x3EB3BFF8, 0x333F8498
const S04: f64 = 5.046_362_570_762_170_4e-9; // 0x3E35AC88, 0xC97DFF2C
const S05: f64 = 1.235_422_744_261_379_1e-11; // 0x3DAB2ACF, 0xCFB97ED8
pub(crate) fn j1(x: f64) -> f64 {
let hx = high_word(x);
let ix = hx & 0x7fffffff;
if ix >= 0x7ff00000 {
return 1.0 / x;
}
let y = x.abs();
if ix >= 0x40000000 {
/* |x| >= 2.0 */
let s = y.sin();
let c = y.cos();
let mut ss = -s - c;
let mut cc = s - c;
if ix < 0x7fe00000 {
/* make sure y+y not overflow */
let z = (y + y).cos();
if s * c > 0.0 {
cc = z / ss;
} else {
ss = z / cc;
}
}
// j1(x) = 1/sqrt(pi) * (P(1,x)*cc - Q(1,x)*ss) / sqrt(x)
// y1(x) = 1/sqrt(pi) * (P(1,x)*ss + Q(1,x)*cc) / sqrt(x)
let z = if ix > 0x48000000 {
FRAC_2_SQRT_PI * cc / y.sqrt()
} else {
let u = pone(y);
let v = qone(y);
FRAC_2_SQRT_PI * (u * cc - v * ss) / y.sqrt()
};
if hx < 0 {
return -z;
} else {
return z;
}
}
if ix < 0x3e400000 {
/* |x|<2**-27 */
if HUGE + x > 1.0 {
return 0.5 * x; /* inexact if x!=0 necessary */
}
}
let z = x * x;
let mut r = z * (R00 + z * (R01 + z * (R02 + z * R03)));
let s = 1.0 + z * (S01 + z * (S02 + z * (S03 + z * (S04 + z * S05))));
r *= x;
x * 0.5 + r / s
}
const U0: [f64; 5] = [
-1.960_570_906_462_389_4e-1, /* 0xBFC91866, 0x143CBC8A */
5.044_387_166_398_113e-2, /* 0x3FA9D3C7, 0x76292CD1 */
-1.912_568_958_757_635_5e-3, /* 0xBF5F55E5, 0x4844F50F */
2.352_526_005_616_105e-5, /* 0x3EF8AB03, 0x8FA6B88E */
-9.190_991_580_398_789e-8, /* 0xBE78AC00, 0x569105B8 */
];
const V0: [f64; 5] = [
1.991_673_182_366_499e-2, /* 0x3F94650D, 0x3F4DA9F0 */
2.025_525_810_251_351_7e-4, /* 0x3F2A8C89, 0x6C257764 */
1.356_088_010_975_162_3e-6, /* 0x3EB6C05A, 0x894E8CA6 */
6.227_414_523_646_215e-9, /* 0x3E3ABF1D, 0x5BA69A86 */
1.665_592_462_079_920_8e-11, /* 0x3DB25039, 0xDACA772A */
];
pub(crate) fn y1(x: f64) -> f64 {
let (lx, hx) = split_words(x);
let ix = 0x7fffffff & hx;
// if Y1(NaN) is NaN, Y1(-inf) is NaN, Y1(inf) is 0
if ix >= 0x7ff00000 {
return 1.0 / (x + x * x);
}
if (ix | lx) == 0 {
return f64::NEG_INFINITY;
}
if hx < 0 {
return f64::NAN;
}
if ix >= 0x40000000 {
// |x| >= 2.0
let s = x.sin();
let c = x.cos();
let mut ss = -s - c;
let mut cc = s - c;
if ix < 0x7fe00000 {
// make sure x+x not overflow
let z = (x + x).cos();
if s * c > 0.0 {
cc = z / ss;
} else {
ss = z / cc;
}
}
/* y1(x) = sqrt(2/(pi*x))*(p1(x)*sin(x0)+q1(x)*cos(x0))
* where x0 = x-3pi/4
* Better formula:
* cos(x0) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
* = 1/sqrt(2) * (sin(x) - cos(x))
* sin(x0) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
* = -1/sqrt(2) * (cos(x) + sin(x))
* To avoid cancellation, use
* sin(x) +- cos(x) = -cos(2x)/(sin(x) -+ cos(x))
* to compute the worse one.
*/
return if ix > 0x48000000 {
FRAC_2_SQRT_PI * ss / x.sqrt()
} else {
let u = pone(x);
let v = qone(x);
FRAC_2_SQRT_PI * (u * ss + v * cc) / x.sqrt()
};
}
if ix <= 0x3c900000 {
// x < 2^(-54)
return -FRAC_2_PI / x;
}
let z = x * x;
let u = U0[0] + z * (U0[1] + z * (U0[2] + z * (U0[3] + z * U0[4])));
let v = 1.0 + z * (V0[0] + z * (V0[1] + z * (V0[2] + z * (V0[3] + z * V0[4]))));
x * (u / v) + FRAC_2_PI * (j1(x) * x.ln() - 1.0 / x)
}
/* For x >= 8, the asymptotic expansions of pone is
* 1 + 15/128 s^2 - 4725/2^15 s^4 - ..., where s = 1/x.
* We approximate pone by
* pone(x) = 1 + (R/S)
* where R = pr0 + pr1*s^2 + pr2*s^4 + ... + pr5*s^10
* S = 1 + ps0*s^2 + ... + ps4*s^10
* and
* | pone(x)-1-R/S | <= 2 ** ( -60.06)
*/
const PR8: [f64; 6] = [
/* for x in [inf, 8]=1/[0,0.125] */
0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */
1.171_874_999_999_886_5e-1, /* 0x3FBDFFFF, 0xFFFFFCCE */
1.323_948_065_930_735_8e1, /* 0x402A7A9D, 0x357F7FCE */
4.120_518_543_073_785_6e2, /* 0x4079C0D4, 0x652EA590 */
3.874_745_389_139_605_3e3, /* 0x40AE457D, 0xA3A532CC */
7.914_479_540_318_917e3, /* 0x40BEEA7A, 0xC32782DD */
];
const PS8: [f64; 5] = [
1.142_073_703_756_784_1e2, /* 0x405C8D45, 0x8E656CAC */
3.650_930_834_208_534_6e3, /* 0x40AC85DC, 0x964D274F */
3.695_620_602_690_334_6e4, /* 0x40E20B86, 0x97C5BB7F */
9.760_279_359_349_508e4, /* 0x40F7D42C, 0xB28F17BB */
3.080_427_206_278_888e4, /* 0x40DE1511, 0x697A0B2D */
];
const PR5: [f64; 6] = [
/* for x in [8,4.5454]=1/[0.125,0.22001] */
1.319_905_195_562_435_2e-11, /* 0x3DAD0667, 0xDAE1CA7D */
1.171_874_931_906_141e-1, /* 0x3FBDFFFF, 0xE2C10043 */
6.802_751_278_684_329, /* 0x401B3604, 0x6E6315E3 */
1.083_081_829_901_891_1e2, /* 0x405B13B9, 0x452602ED */
5.176_361_395_331_998e2, /* 0x40802D16, 0xD052D649 */
5.287_152_013_633_375e2, /* 0x408085B8, 0xBB7E0CB7 */
];
const PS5: [f64; 5] = [
5.928_059_872_211_313e1, /* 0x404DA3EA, 0xA8AF633D */
9.914_014_187_336_144e2, /* 0x408EFB36, 0x1B066701 */
5.353_266_952_914_88e3, /* 0x40B4E944, 0x5706B6FB */
7.844_690_317_495_512e3, /* 0x40BEA4B0, 0xB8A5BB15 */
1.504_046_888_103_610_6e3, /* 0x40978030, 0x036F5E51 */
];
const PR3: [f64; 6] = [
3.025_039_161_373_736e-9, /* 0x3E29FC21, 0xA7AD9EDD */
1.171_868_655_672_535_9e-1, /* 0x3FBDFFF5, 0x5B21D17B */
3.932_977_500_333_156_4, /* 0x400F76BC, 0xE85EAD8A */
3.511_940_355_916_369e1, /* 0x40418F48, 0x9DA6D129 */
9.105_501_107_507_813e1, /* 0x4056C385, 0x4D2C1837 */
4.855_906_851_973_649e1, /* 0x4048478F, 0x8EA83EE5 */
];
const PS3: [f64; 5] = [
3.479_130_950_012_515e1, /* 0x40416549, 0xA134069C */
3.367_624_587_478_257_5e2, /* 0x40750C33, 0x07F1A75F */
1.046_871_399_757_751_3e3, /* 0x40905B7C, 0x5037D523 */
8.908_113_463_982_564e2, /* 0x408BD67D, 0xA32E31E9 */
1.037_879_324_396_392_8e2, /* 0x4059F26D, 0x7C2EED53 */
];
const PR2: [f64; 6] = [
/* for x in [2.8570,2]=1/[0.3499,0.5] */
1.077_108_301_068_737_4e-7, /* 0x3E7CE9D4, 0xF65544F4 */
1.171_762_194_626_833_5e-1, /* 0x3FBDFF42, 0xBE760D83 */
2.368_514_966_676_088, /* 0x4002F2B7, 0xF98FAEC0 */
1.224_261_091_482_612_3e1, /* 0x40287C37, 0x7F71A964 */
1.769_397_112_716_877_3e1, /* 0x4031B1A8, 0x177F8EE2 */
5.073_523_125_888_185, /* 0x40144B49, 0xA574C1FE */
];
const PS2: [f64; 5] = [
2.143_648_593_638_214e1, /* 0x40356FBD, 0x8AD5ECDC */
1.252_902_271_684_027_5e2, /* 0x405F5293, 0x14F92CD5 */
2.322_764_690_571_628e2, /* 0x406D08D8, 0xD5A2DBD9 */
1.176_793_732_871_471e2, /* 0x405D6B7A, 0xDA1884A9 */
8.364_638_933_716_183, /* 0x4020BAB1, 0xF44E5192 */
];
/* Note: This function is only called for ix>=0x40000000 (see above) */
fn pone(x: f64) -> f64 {
let ix = high_word(x) & 0x7fffffff;
// ix>=0x40000000 && ix<=0x48000000)
let (p, q) = if ix >= 0x40200000 {
(PR8, PS8)
} else if ix >= 0x40122E8B {
(PR5, PS5)
} else if ix >= 0x4006DB6D {
(PR3, PS3)
} else {
(PR2, PS2)
};
let z = 1.0 / (x * x);
let r = p[0] + z * (p[1] + z * (p[2] + z * (p[3] + z * (p[4] + z * p[5]))));
let s = 1.0 + z * (q[0] + z * (q[1] + z * (q[2] + z * (q[3] + z * q[4]))));
1.0 + r / s
}
/* For x >= 8, the asymptotic expansions of qone is
* 3/8 s - 105/1024 s^3 - ..., where s = 1/x.
* We approximate pone by
* qone(x) = s*(0.375 + (R/S))
* where R = qr1*s^2 + qr2*s^4 + ... + qr5*s^10
* S = 1 + qs1*s^2 + ... + qs6*s^12
* and
* | qone(x)/s -0.375-R/S | <= 2 ** ( -61.13)
*/
const QR8: [f64; 6] = [
/* for x in [inf, 8]=1/[0,0.125] */
0.00000000000000000000e+00, /* 0x00000000, 0x00000000 */
-1.025_390_624_999_927_1e-1, /* 0xBFBA3FFF, 0xFFFFFDF3 */
-1.627_175_345_445_9e1, /* 0xC0304591, 0xA26779F7 */
-7.596_017_225_139_501e2, /* 0xC087BCD0, 0x53E4B576 */
-1.184_980_667_024_295_9e4, /* 0xC0C724E7, 0x40F87415 */
-4.843_851_242_857_503_5e4, /* 0xC0E7A6D0, 0x65D09C6A */
];
const QS8: [f64; 6] = [
1.613_953_697_007_229e2, /* 0x40642CA6, 0xDE5BCDE5 */
7.825_385_999_233_485e3, /* 0x40BE9162, 0xD0D88419 */
1.338_753_362_872_495_8e5, /* 0x4100579A, 0xB0B75E98 */
7.196_577_236_832_409e5, /* 0x4125F653, 0x72869C19 */
6.666_012_326_177_764e5, /* 0x412457D2, 0x7719AD5C */
-2.944_902_643_038_346_4e5, /* 0xC111F969, 0x0EA5AA18 */
];
const QR5: [f64; 6] = [
/* for x in [8,4.5454]=1/[0.125,0.22001] */
-2.089_799_311_417_641e-11, /* 0xBDB6FA43, 0x1AA1A098 */
-1.025_390_502_413_754_3e-1, /* 0xBFBA3FFF, 0xCB597FEF */
-8.056_448_281_239_36, /* 0xC0201CE6, 0xCA03AD4B */
-1.836_696_074_748_883_8e2, /* 0xC066F56D, 0x6CA7B9B0 */
-1.373_193_760_655_081_6e3, /* 0xC09574C6, 0x6931734F */
-2.612_444_404_532_156_6e3, /* 0xC0A468E3, 0x88FDA79D */
];
const QS5: [f64; 6] = [
8.127_655_013_843_358e1, /* 0x405451B2, 0xFF5A11B2 */
1.991_798_734_604_859_6e3, /* 0x409F1F31, 0xE77BF839 */
1.746_848_519_249_089e4, /* 0x40D10F1F, 0x0D64CE29 */
4.985_142_709_103_523e4, /* 0x40E8576D, 0xAABAD197 */
2.794_807_516_389_181_2e4, /* 0x40DB4B04, 0xCF7C364B */
-4.719_183_547_951_285e3, /* 0xC0B26F2E, 0xFCFFA004 */
];
const QR3: [f64; 6] = [
-5.078_312_264_617_666e-9, /* 0xBE35CFA9, 0xD38FC84F */
-1.025_378_298_208_370_9e-1, /* 0xBFBA3FEB, 0x51AEED54 */
-4.610_115_811_394_734, /* 0xC01270C2, 0x3302D9FF */
-5.784_722_165_627_836_4e1, /* 0xC04CEC71, 0xC25D16DA */
-2.282_445_407_376_317e2, /* 0xC06C87D3, 0x4718D55F */
-2.192_101_284_789_093_3e2, /* 0xC06B66B9, 0x5F5C1BF6 */
];
const QS3: [f64; 6] = [
4.766_515_503_237_295e1, /* 0x4047D523, 0xCCD367E4 */
6.738_651_126_766_997e2, /* 0x40850EEB, 0xC031EE3E */
3.380_152_866_795_263_4e3, /* 0x40AA684E, 0x448E7C9A */
5.547_729_097_207_228e3, /* 0x40B5ABBA, 0xA61D54A6 */
1.903_119_193_388_108e3, /* 0x409DBC7A, 0x0DD4DF4B */
-1.352_011_914_443_073_4e2, /* 0xC060E670, 0x290A311F */
];
const QR2: [f64; 6] = [
/* for x in [2.8570,2]=1/[0.3499,0.5] */
-1.783_817_275_109_588_7e-7, /* 0xBE87F126, 0x44C626D2 */
-1.025_170_426_079_855_5e-1, /* 0xBFBA3E8E, 0x9148B010 */
-2.752_205_682_781_874_6, /* 0xC0060484, 0x69BB4EDA */
-1.966_361_626_437_037_2e1, /* 0xC033A9E2, 0xC168907F */
-4.232_531_333_728_305e1, /* 0xC04529A3, 0xDE104AAA */
-2.137_192_117_037_040_6e1, /* 0xC0355F36, 0x39CF6E52 */
];
const QS2: [f64; 6] = [
2.953_336_290_605_238_5e1, /* 0x403D888A, 0x78AE64FF */
2.529_815_499_821_905_3e2, /* 0x406F9F68, 0xDB821CBA */
7.575_028_348_686_454e2, /* 0x4087AC05, 0xCE49A0F7 */
7.393_932_053_204_672e2, /* 0x40871B25, 0x48D4C029 */
1.559_490_033_366_661_2e2, /* 0x40637E5E, 0x3C3ED8D4 */
-4.959_498_988_226_282, /* 0xC013D686, 0xE71BE86B */
];
// Note: This function is only called for ix>=0x40000000 (see above)
fn qone(x: f64) -> f64 {
let ix = high_word(x) & 0x7fffffff;
// ix>=0x40000000 && ix<=0x48000000
let (p, q) = if ix >= 0x40200000 {
(QR8, QS8)
} else if ix >= 0x40122E8B {
(QR5, QS5)
} else if ix >= 0x4006DB6D {
(QR3, QS3)
} else {
(QR2, QS2)
};
let z = 1.0 / (x * x);
let r = p[0] + z * (p[1] + z * (p[2] + z * (p[3] + z * (p[4] + z * p[5]))));
let s = 1.0 + z * (q[0] + z * (q[1] + z * (q[2] + z * (q[3] + z * (q[4] + z * q[5])))));
(0.375 + r / s) / x
}

View File

@@ -0,0 +1,329 @@
// https://github.com/JuliaLang/openlibm/blob/master/src/e_jn.c
/*
* ====================================================
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
*
* Developed at SunSoft, a Sun Microsystems, Inc. business.
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
/*
* __ieee754_jn(n, x), __ieee754_yn(n, x)
* floating point Bessel's function of the 1st and 2nd kind
* of order n
*
* Special cases:
* y0(0)=y1(0)=yn(n,0) = -inf with division by 0 signal;
* y0(-ve)=y1(-ve)=yn(n,-ve) are NaN with invalid signal.
* Note 2. About jn(n,x), yn(n,x)
* For n=0, j0(x) is called,
* for n=1, j1(x) is called,
* for n<x, forward recursion us used starting
* from values of j0(x) and j1(x).
* for n>x, a continued fraction approximation to
* j(n,x)/j(n-1,x) is evaluated and then backward
* recursion is used starting from a supposed value
* for j(n,x). The resulting value of j(0,x) is
* compared with the actual value to correct the
* supposed value of j(n,x).
*
* yn(n,x) is similar in all respects, except
* that forward recursion is used for all
* values of n>1.
*
*/
use super::{
bessel_j0_y0::{j0, y0},
bessel_j1_y1::{j1, y1},
bessel_util::{split_words, FRAC_2_SQRT_PI},
};
// Special cases are:
//
// $ J_n(n, ±\Infinity) = 0$
// $ J_n(n, NaN} = NaN $
// $ J_n(n, 0) = 0 $
pub(crate) fn jn(n: i32, x: f64) -> f64 {
let (lx, mut hx) = split_words(x);
let ix = 0x7fffffff & hx;
// if J(n,NaN) is NaN
if x.is_nan() {
return x;
}
// if (ix | (/*(u_int32_t)*/(lx | -lx)) >> 31) > 0x7ff00000 {
// return x + x;
// }
let (n, x) = if n < 0 {
// hx ^= 0x80000000;
hx = -hx;
(-n, -x)
} else {
(n, x)
};
if n == 0 {
return j0(x);
}
if n == 1 {
return j1(x);
}
let sign = (n & 1) & (hx >> 31); /* even n -- 0, odd n -- sign(x) */
// let sign = if x < 0.0 { -1 } else { 1 };
let x = x.abs();
let b = if (ix | lx) == 0 || ix >= 0x7ff00000 {
// if x is 0 or inf
0.0
} else if n as f64 <= x {
/* Safe to use J(n+1,x)=2n/x *J(n,x)-J(n-1,x) */
if ix >= 0x52D00000 {
/* x > 2**302 */
/* (x >> n**2)
* Jn(x) = cos(x-(2n+1)*pi/4)*sqrt(2/x*pi)
* Yn(x) = sin(x-(2n+1)*pi/4)*sqrt(2/x*pi)
* Let s=x.sin(), c=x.cos(),
* xn=x-(2n+1)*pi/4, sqt2 = sqrt(2),then
*
* n sin(xn)*sqt2 cos(xn)*sqt2
* ----------------------------------
* 0 s-c c+s
* 1 -s-c -c+s
* 2 -s+c -c-s
* 3 s+c c-s
*/
let temp = match n & 3 {
0 => x.cos() + x.sin(),
1 => -x.cos() + x.sin(),
2 => -x.cos() - x.sin(),
3 => x.cos() - x.sin(),
_ => {
// Impossible: FIXME!
// panic!("")
0.0
}
};
FRAC_2_SQRT_PI * temp / x.sqrt()
} else {
let mut a = j0(x);
let mut b = j1(x);
for i in 1..n {
let temp = b;
b = b * (((i + i) as f64) / x) - a; /* avoid underflow */
a = temp;
}
b
}
} else {
// x < 2^(-29)
if ix < 0x3e100000 {
// x is tiny, return the first Taylor expansion of J(n,x)
// J(n,x) = 1/n!*(x/2)^n - ...
if n > 33 {
// underflow
0.0
} else {
let temp = x * 0.5;
let mut b = temp;
let mut a = 1;
for i in 2..=n {
a *= i; /* a = n! */
b *= temp; /* b = (x/2)^n */
}
b / (a as f64)
}
} else {
/* use backward recurrence */
/* x x^2 x^2
* J(n,x)/J(n-1,x) = ---- ------ ------ .....
* 2n - 2(n+1) - 2(n+2)
*
* 1 1 1
* (for large x) = ---- ------ ------ .....
* 2n 2(n+1) 2(n+2)
* -- - ------ - ------ -
* x x x
*
* Let w = 2n/x and h=2/x, then the above quotient
* is equal to the continued fraction:
* 1
* = -----------------------
* 1
* w - -----------------
* 1
* w+h - ---------
* w+2h - ...
*
* To determine how many terms needed, let
* Q(0) = w, Q(1) = w(w+h) - 1,
* Q(k) = (w+k*h)*Q(k-1) - Q(k-2),
* When Q(k) > 1e4 good for single
* When Q(k) > 1e9 good for double
* When Q(k) > 1e17 good for quadruple
*/
let w = ((n + n) as f64) / x;
let h = 2.0 / x;
let mut q0 = w;
let mut z = w + h;
let mut q1 = w * z - 1.0;
let mut k = 1;
while q1 < 1.0e9 {
k += 1;
z += h;
let tmp = z * q1 - q0;
q0 = q1;
q1 = tmp;
}
let m = n + n;
let mut t = 0.0;
for i in (m..2 * (n + k)).step_by(2).rev() {
t = 1.0 / ((i as f64) / x - t);
}
// for (t=0, i = 2*(n+k); i>=m; i -= 2) t = 1/(i/x-t);
let mut a = t;
let mut b = 1.0;
/* estimate log((2/x)^n*n!) = n*log(2/x)+n*ln(n)
* Hence, if n*(log(2n/x)) > ...
* single 8.8722839355e+01
* double 7.09782712893383973096e+02
* long double 1.1356523406294143949491931077970765006170e+04
* then recurrent value may overflow and the result is
* likely underflow to 0
*/
let mut tmp = n as f64;
let v = 2.0 / x;
tmp = tmp * f64::ln(f64::abs(v * tmp));
if tmp < 7.097_827_128_933_84e2 {
// for(i=n-1, di=(i+i); i>0; i--){
let mut di = 2.0 * ((n - 1) as f64);
for _ in (1..=n - 1).rev() {
let temp = b;
b *= di;
b = b / x - a;
a = temp;
di -= 2.0;
}
} else {
// for(i=n-1, di=(i+i); i>0; i--) {
let mut di = 2.0 * ((n - 1) as f64);
for _ in (1..=n - 1).rev() {
let temp = b;
b *= di;
b = b / x - a;
a = temp;
di -= 2.0;
/* scale b to avoid spurious overflow */
if b > 1e100 {
a /= b;
t /= b;
b = 1.0;
}
}
}
let z = j0(x);
let w = j1(x);
if z.abs() >= w.abs() {
t * z / b
} else {
t * w / a
}
}
};
if sign == 1 {
-b
} else {
b
}
}
// Yn returns the order-n Bessel function of the second kind.
//
// Special cases are:
//
// Y(n, +Inf) = 0
// Y(n ≥ 0, 0) = -Inf
// Y(n < 0, 0) = +Inf if n is odd, -Inf if n is even
// Y(n, x < 0) = NaN
// Y(n, NaN) = NaN
pub(crate) fn yn(n: i32, x: f64) -> f64 {
let (lx, hx) = split_words(x);
let ix = 0x7fffffff & hx;
// if Y(n, NaN) is NaN
if x.is_nan() {
return x;
}
// if (ix | (/*(u_int32_t)*/(lx | -lx)) >> 31) > 0x7ff00000 {
// return x + x;
// }
if (ix | lx) == 0 {
return f64::NEG_INFINITY;
}
if hx < 0 {
return f64::NAN;
}
let (n, sign) = if n < 0 {
(-n, 1 - ((n & 1) << 1))
} else {
(n, 1)
};
if n == 0 {
return y0(x);
}
if n == 1 {
return (sign as f64) * y1(x);
}
if ix == 0x7ff00000 {
return 0.0;
}
let b = if ix >= 0x52D00000 {
// x > 2^302
/* (x >> n**2)
* Jn(x) = cos(x-(2n+1)*pi/4)*sqrt(2/x*pi)
* Yn(x) = sin(x-(2n+1)*pi/4)*sqrt(2/x*pi)
* Let s=x.sin(), c=x.cos(),
* xn=x-(2n+1)*pi/4, sqt2 = sqrt(2),then
*
* n sin(xn)*sqt2 cos(xn)*sqt2
* ----------------------------------
* 0 s-c c+s
* 1 -s-c -c+s
* 2 -s+c -c-s
* 3 s+c c-s
*/
let temp = match n & 3 {
0 => x.sin() - x.cos(),
1 => -x.sin() - x.cos(),
2 => -x.sin() + x.cos(),
3 => x.sin() + x.cos(),
_ => {
// unreachable
0.0
}
};
FRAC_2_SQRT_PI * temp / x.sqrt()
} else {
let mut a = y0(x);
let mut b = y1(x);
for i in 1..n {
if b.is_infinite() {
break;
}
// if high_word(b) != 0xfff00000 {
// break;
// }
(a, b) = (b, ((2.0 * i as f64) / x) * b - a);
}
b
};
if sign > 0 {
b
} else {
-b
}
}

View File

@@ -0,0 +1,90 @@
// This are somewhat lower precision than the BesselJ and BesselY
use super::bessel_i::bessel_i0;
use super::bessel_i::bessel_i1;
fn bessel_k0(x: f64) -> f64 {
let p1 = -0.57721566;
let p2 = 0.42278420;
let p3 = 0.23069756;
let p4 = 3.488590e-2;
let p5 = 2.62698e-3;
let p6 = 1.0750e-4;
let p7 = 7.4e-6;
let q1 = 1.25331414;
let q2 = -7.832358e-2;
let q3 = 2.189568e-2;
let q4 = -1.062446e-2;
let q5 = 5.87872e-3;
let q6 = -2.51540e-3;
let q7 = 5.3208e-4;
if x <= 0.0 {
return 0.0;
}
if x <= 2.0 {
let y = x * x / 4.0;
(-(x / 2.0).ln() * bessel_i0(x))
+ (p1 + y * (p2 + y * (p3 + y * (p4 + y * (p5 + y * (p6 + y * p7))))))
} else {
let y = 2.0 / x;
((-x).exp() / x.sqrt())
* (q1 + y * (q2 + y * (q3 + y * (q4 + y * (q5 + y * (q6 + y * q7))))))
}
}
fn bessel_k1(x: f64) -> f64 {
let p1 = 1.0;
let p2 = 0.15443144;
let p3 = -0.67278579;
let p4 = -0.18156897;
let p5 = -1.919402e-2;
let p6 = -1.10404e-3;
let p7 = -4.686e-5;
let q1 = 1.25331414;
let q2 = 0.23498619;
let q3 = -3.655620e-2;
let q4 = 1.504268e-2;
let q5 = -7.80353e-3;
let q6 = 3.25614e-3;
let q7 = -6.8245e-4;
if x <= 0.0 {
return f64::NAN;
}
if x <= 2.0 {
let y = x * x / 4.0;
((x / 2.0).ln() * bessel_i1(x))
+ (1. / x) * (p1 + y * (p2 + y * (p3 + y * (p4 + y * (p5 + y * (p6 + y * p7))))))
} else {
let y = 2.0 / x;
((-x).exp() / x.sqrt())
* (q1 + y * (q2 + y * (q3 + y * (q4 + y * (q5 + y * (q6 + y * q7))))))
}
}
pub(crate) fn bessel_k(n: i32, x: f64) -> f64 {
if x <= 0.0 || n < 0 {
return f64::NAN;
}
if n == 0 {
return bessel_k0(x);
}
if n == 1 {
return bessel_k1(x);
}
// Perform upward recurrence for all x
let tox = 2.0 / x;
let mut bkm = bessel_k0(x);
let mut bk = bessel_k1(x);
for j in 1..n {
(bkm, bk) = (bk, bkm + (j as f64) * tox * bk);
}
bk
}

View File

@@ -0,0 +1,19 @@
pub(crate) const HUGE: f64 = 1e300;
pub(crate) const FRAC_2_SQRT_PI: f64 = 5.641_895_835_477_563e-1;
pub(crate) fn high_word(x: f64) -> i32 {
let [_, _, _, _, a1, a2, a3, a4] = x.to_ne_bytes();
// let binding = x.to_ne_bytes();
// let high = <&[u8; 4]>::try_from(&binding[4..8]).expect("");
i32::from_ne_bytes([a1, a2, a3, a4])
}
pub(crate) fn split_words(x: f64) -> (i32, i32) {
let [a1, a2, a3, a4, b1, b2, b3, b4] = x.to_ne_bytes();
// let binding = x.to_ne_bytes();
// let high = <&[u8; 4]>::try_from(&binding[4..8]).expect("");
(
i32::from_ne_bytes([a1, a2, a3, a4]),
i32::from_ne_bytes([b1, b2, b3, b4]),
)
}

View File

@@ -0,0 +1,14 @@
# Example file creating testing cases for BesselI
using Nemo
CC = AcbField(100)
values = [1, 2, 3, -2, 5, 30, 2e-8]
for value in values
y_acb = besseli(CC(1), CC(value))
real64 = convert(Float64, real(y_acb))
im64 = convert(Float64, real(y_acb))
println("(", value, ", ", real64, "),")
end

View File

@@ -0,0 +1,53 @@
pub(crate) fn erf(x: f64) -> f64 {
let cof = vec![
-1.3026537197817094,
6.419_697_923_564_902e-1,
1.9476473204185836e-2,
-9.561_514_786_808_63e-3,
-9.46595344482036e-4,
3.66839497852761e-4,
4.2523324806907e-5,
-2.0278578112534e-5,
-1.624290004647e-6,
1.303655835580e-6,
1.5626441722e-8,
-8.5238095915e-8,
6.529054439e-9,
5.059343495e-9,
-9.91364156e-10,
-2.27365122e-10,
9.6467911e-11,
2.394038e-12,
-6.886027e-12,
8.94487e-13,
3.13092e-13,
-1.12708e-13,
3.81e-16,
7.106e-15,
-1.523e-15,
-9.4e-17,
1.21e-16,
-2.8e-17,
];
let mut d = 0.0;
let mut dd = 0.0;
let x_abs = x.abs();
let t = 2.0 / (2.0 + x_abs);
let ty = 4.0 * t - 2.0;
for j in (1..=cof.len() - 1).rev() {
let tmp = d;
d = ty * d - dd + cof[j];
dd = tmp;
}
let res = t * f64::exp(-x_abs * x_abs + 0.5 * (cof[0] + ty * d) - dd);
if x < 0.0 {
res - 1.0
} else {
1.0 - res
}
}

View File

@@ -0,0 +1,16 @@
mod bessel_i;
mod bessel_j0_y0;
mod bessel_j1_y1;
mod bessel_jn_yn;
mod bessel_k;
mod bessel_util;
mod erf;
#[cfg(test)]
mod test_bessel;
pub(crate) use bessel_i::bessel_i;
pub(crate) use bessel_jn_yn::jn as bessel_j;
pub(crate) use bessel_jn_yn::yn as bessel_y;
pub(crate) use bessel_k::bessel_k;
pub(crate) use erf::erf;

View File

@@ -0,0 +1,183 @@
use crate::functions::engineering::transcendental::bessel_k;
use super::{
bessel_i::bessel_i,
bessel_j0_y0::{j0, y0},
bessel_j1_y1::j1,
bessel_jn_yn::{jn, yn},
};
const EPS: f64 = 1e-13;
const EPS_LOW: f64 = 1e-6;
// Known values computed with Arb via Nemo.jl in Julia
// You can also use Mathematica
/// But please do not use Excel or any other software without arbitrary precision
fn numbers_are_close(a: f64, b: f64) -> bool {
if a == b {
// avoid underflow if a = b = 0.0
return true;
}
(a - b).abs() / ((a * a + b * b).sqrt()) < EPS
}
fn numbers_are_somewhat_close(a: f64, b: f64) -> bool {
if a == b {
// avoid underflow if a = b = 0.0
return true;
}
(a - b).abs() / ((a * a + b * b).sqrt()) < EPS_LOW
}
#[test]
fn bessel_j0_known_values() {
let cases = [
(2.4, 0.002507683297243813),
(0.5, 0.9384698072408129),
(1.0, 0.7651976865579666),
(1.12345, 0.7084999488947348),
(27.0, 0.07274191800588709),
(33.0, 0.09727067223550946),
(2e-4, 0.9999999900000001),
(0.0, 1.0),
(1e10, 2.175591750246892e-6),
];
for (value, known) in cases {
let f = j0(value);
assert!(
numbers_are_close(f, known),
"Got: {f}, expected: {known} for j0({value})"
);
}
}
#[test]
fn bessel_y0_known_values() {
let cases = [
(2.4, 0.5104147486657438),
(0.5, -0.4445187335067065),
(1.0, 0.08825696421567692),
(1.12345, 0.1783162909790613),
(27.0, 0.1352149762078722),
(33.0, 0.0991348255208796),
(2e-4, -5.496017824512429),
(1e10, -7.676508175792937e-6),
(1e-300, -439.8351636227653),
];
for (value, known) in cases {
let f = y0(value);
assert!(
numbers_are_close(f, known),
"Got: {f}, expected: {known} for y0({value})"
);
}
assert!(y0(0.0).is_infinite());
}
#[test]
fn bessel_j1_known_values() {
// Values computed with Maxima, the computer algebra system
// TODO: Recompute
let cases = [
(2.4, 0.5201852681819311),
(0.5, 0.2422684576748738),
(1.0, 0.4400505857449335),
(1.17232, 0.4910665691824317),
(27.5, 0.1521418932046569),
(42.0, -0.04599388822188721),
(3e-5, 1.499999999831249E-5),
(350.0, -0.02040531295214455),
(0.0, 0.0),
(1e12, -7.913802683850441e-7),
];
for (value, known) in cases {
let f = j1(value);
assert!(
numbers_are_close(f, known),
"Got: {f}, expected: {known} for j1({value})"
);
}
}
#[test]
fn bessel_jn_known_values() {
// Values computed with Maxima, the computer algebra system
// TODO: Recompute
let cases = [
(3, 0.5, 0.002_563_729_994_587_244),
(4, 0.5, 0.000_160_736_476_364_287_6),
(-3, 0.5, -0.002_563_729_994_587_244),
(-4, 0.5, 0.000_160_736_476_364_287_6),
(3, 30.0, 0.129211228759725),
(-3, 30.0, -0.129211228759725),
(4, 30.0, -0.052609000321320355),
(20, 30.0, 0.0048310199934040645),
(7, 0.0, 0.0),
];
for (n, value, known) in cases {
let f = jn(n, value);
assert!(
numbers_are_close(f, known),
"Got: {f}, expected: {known} for jn({n}, {value})"
);
}
}
#[test]
fn bessel_yn_known_values() {
let cases = [
(3, 0.5, -42.059494304723883),
(4, 0.5, -499.272_560_819_512_3),
(-3, 0.5, 42.059494304723883),
(-4, 0.5, -499.272_560_819_512_3),
(3, 35.0, -0.13191405300596323),
(-12, 12.2, -0.310438011314211),
(7, 1e12, 1.016_712_505_197_956_3e-7),
(35, 3.0, -6.895_879_073_343_495e31),
];
for (n, value, known) in cases {
let f = yn(n, value);
assert!(
numbers_are_close(f, known),
"Got: {f}, expected: {known} for yn({n}, {value})"
);
}
}
#[test]
fn bessel_in_known_values() {
let cases = [
(1, 0.5, 0.2578943053908963),
(3, 0.5, 0.002645111968990286),
(7, 0.2, 1.986608521182497e-11),
(7, 0.0, 0.0),
(0, -0.5, 1.0634833707413236),
// worse case scenario
(0, 3.7499, 9.118167894541882),
(0, 3.7501, 9.119723897590003),
];
for (n, value, known) in cases {
let f = bessel_i(n, value);
assert!(
numbers_are_somewhat_close(f, known),
"Got: {f}, expected: {known} for in({n}, {value})"
);
}
}
#[test]
fn bessel_kn_known_values() {
let cases = [
(1, 0.5, 1.656441120003301),
(0, 0.5, 0.9244190712276659),
(3, 0.5, 62.05790952993026),
];
for (n, value, known) in cases {
let f = bessel_k(n, value);
assert!(
numbers_are_somewhat_close(f, known),
"Got: {f}, expected: {known} for kn({n}, {value})"
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,255 @@
use crate::expressions::token::Error;
// Here we use some numerical routines to solve for some functions:
// RATE, IRR, XIRR
// We use a combination of heuristics, bisection and Newton-Raphson
// If you want to improve on this methods you probably would want to find failing tests first
// From Microsoft docs:
// https://support.microsoft.com/en-us/office/rate-function-9f665657-4a7e-4bb7-a030-83fc59e748ce
// Returns the interest rate per period of an annuity.
// RATE is calculated by iteration (using Newton-Raphson) and can have zero or more solutions.
// If the successive results of RATE do not converge to within 0.0000001 after 20 iterations,
// RATE returns the #NUM! error value.
// NOTE: We need a better algorithm here
pub(crate) fn compute_rate(
pv: f64,
fv: f64,
nper: f64,
pmt: f64,
annuity_type: i32,
guess: f64,
) -> Result<f64, (Error, String)> {
let mut rate = guess;
// Excel _claims_ to do 20 iterations, but that will have tests failing
let max_iterations = 50;
let eps = 0.0000001;
let annuity_type = annuity_type as f64;
if guess <= -1.0 {
return Err((Error::VALUE, "Rate initial guess must be > -1".to_string()));
}
for _ in 1..=max_iterations {
let t = (1.0 + rate).powf(nper - 1.0);
let tt = t * (1.0 + rate);
let f = pv * tt + pmt * (1.0 + rate * annuity_type) * (tt - 1.0) / rate + fv;
let f_prime = pv * nper * t - pmt * (tt - 1.0) / (rate * rate)
+ pmt * (1.0 + rate * annuity_type) * t * nper / rate;
let new_rate = rate - f / f_prime;
if new_rate <= -1.0 {
return Err((Error::NUM, "Failed to converge".to_string()));
}
if (new_rate - rate).abs() < eps {
return Ok(new_rate);
}
rate = new_rate;
}
Err((Error::NUM, "Failed to converge".to_string()))
}
pub(crate) fn compute_npv(rate: f64, values: &[f64]) -> Result<f64, (Error, String)> {
let mut npv = 0.0;
for (i, item) in values.iter().enumerate() {
npv += item / (1.0 + rate).powi(i as i32 + 1)
}
Ok(npv)
}
// Tries to solve npv(r, values) = 0 for r given values
// Uses a bit of heuristics:
// * First tries Newton-Raphson around the guess
// * Failing that uses bisection and bracketing
// * If that fails (no root found of the interval) uses Newton-Raphson around the edges
// Values for x1, x2 and the guess for N-R are fine tuned using heuristics
pub(crate) fn compute_irr(values: &[f64], guess: f64) -> Result<f64, (Error, String)> {
if guess <= -1.0 {
return Err((Error::VALUE, "Rate initial guess must be > -1".to_string()));
}
// The values cannot be all positive or all negative
if values.iter().all(|&x| x >= 0.0) || values.iter().all(|&x| x <= 0.0) {
return Err((Error::NUM, "Failed to converge".to_string()));
}
if let Ok(f) = compute_irr_newton_raphson(values, guess) {
return Ok(f);
};
// We try bisection
let max_iterations = 50;
let eps = 1e-10;
let x1 = -0.99999;
let x2 = 100.0;
let f1 = compute_npv(x1, values)?;
let f2 = compute_npv(x2, values)?;
if f1 * f2 > 0.0 {
// The root is not within the limits or there are two roots
// We try Newton-Raphson a bit above the upper limit and a bit below the lower limit
if let Ok(f) = compute_irr_newton_raphson(values, 200.0) {
return Ok(f);
};
if let Ok(f) = compute_irr_newton_raphson(values, -2.0) {
return Ok(f);
};
return Err((Error::NUM, "Failed to converge".to_string()));
}
let (mut rtb, mut dx) = if f1 < 0.0 {
(x1, x2 - x1)
} else {
(x2, x1 - x2)
};
for _ in 1..max_iterations {
dx *= 0.5;
let x_mid = rtb + dx;
let f_mid = compute_npv(x_mid, values)?;
if f_mid <= 0.0 {
rtb = x_mid;
}
if f_mid.abs() < eps || dx.abs() < eps {
return Ok(x_mid);
}
}
Err((Error::NUM, "Failed to converge".to_string()))
}
fn compute_npv_prime(rate: f64, values: &[f64]) -> Result<f64, (Error, String)> {
let mut npv = 0.0;
for (i, item) in values.iter().enumerate() {
npv += -item * (i as f64 + 1.0) / (1.0 + rate).powi(i as i32 + 2)
}
if npv.is_infinite() || npv.is_nan() {
return Err((Error::NUM, "NaN".to_string()));
}
Ok(npv)
}
fn compute_irr_newton_raphson(values: &[f64], guess: f64) -> Result<f64, (Error, String)> {
let mut irr = guess;
let max_iterations = 50;
let eps = 1e-8;
for _ in 1..=max_iterations {
let f = compute_npv(irr, values)?;
let f_prime = compute_npv_prime(irr, values)?;
let new_irr = irr - f / f_prime;
if (new_irr - irr).abs() < eps {
return Ok(new_irr);
}
irr = new_irr;
}
Err((Error::NUM, "Failed to converge".to_string()))
}
// Formula is:
// $$\sum_{i=1}^n\frac{v_i}{(1+r)^{\frac{(d_j-d_1)}{365}}}$$
// where $v_i$ is the ith-1 value and $d_i$ is the ith-1 date
pub(crate) fn compute_xnpv(
rate: f64,
values: &[f64],
dates: &[f64],
) -> Result<f64, (Error, String)> {
let mut xnpv = values[0];
let d0 = dates[0];
let n = values.len();
for i in 1..n {
let vi = values[i];
let di = dates[i];
xnpv += vi / ((1.0 + rate).powf((di - d0) / 365.0))
}
if xnpv.is_infinite() || xnpv.is_nan() {
return Err((Error::NUM, "NaN".to_string()));
}
Ok(xnpv)
}
fn compute_xnpv_prime(rate: f64, values: &[f64], dates: &[f64]) -> Result<f64, (Error, String)> {
let mut xnpv = 0.0;
let d0 = dates[0];
let n = values.len();
for i in 1..n {
let vi = values[i];
let di = dates[i];
let ratio = (di - d0) / 365.0;
let power = (1.0 + rate).powf(ratio + 1.0);
xnpv -= vi * ratio / power;
}
if xnpv.is_infinite() || xnpv.is_nan() {
return Err((Error::NUM, "NaN".to_string()));
}
Ok(xnpv)
}
fn compute_xirr_newton_raphson(
values: &[f64],
dates: &[f64],
guess: f64,
) -> Result<f64, (Error, String)> {
let mut xirr = guess;
let max_iterations = 100;
let eps = 1e-7;
for _ in 1..=max_iterations {
let f = compute_xnpv(xirr, values, dates)?;
let f_prime = compute_xnpv_prime(xirr, values, dates)?;
let new_xirr = xirr - f / f_prime;
if (new_xirr - xirr).abs() < eps {
return Ok(new_xirr);
}
xirr = new_xirr;
}
Err((Error::NUM, "Failed to converge".to_string()))
}
// NOTES:
// 1. If the cash-flows (value[i] for i > 0) are always of the same sign, the function is monotonous
// 3. Say (d_max, v_max) are the pair where d_max is the largest date,
// then if v_max and v_0 have different signs, it's guaranteed there is a zero
// The algorithm needs to be improved but it works so far in all practical cases
pub(crate) fn compute_xirr(
values: &[f64],
dates: &[f64],
guess: f64,
) -> Result<f64, (Error, String)> {
if guess <= -1.0 {
return Err((Error::VALUE, "Rate initial guess must be > -1".to_string()));
}
// The values cannot be all positive or all negative
if values.iter().all(|&x| x >= 0.0) || values.iter().all(|&x| x <= 0.0) {
return Err((Error::NUM, "Failed to converge".to_string()));
}
if let Ok(f) = compute_xirr_newton_raphson(values, dates, guess) {
return Ok(f);
};
// We try bisection
let max_iterations = 50;
let eps = 1e-8;
// This will miss 0's very close to -1
let x1 = -0.9999;
let x2 = 100.0;
let f1 = compute_xnpv(x1, values, dates)?;
let f2 = compute_xnpv(x2, values, dates)?;
if f1 * f2 > 0.0 {
// The root is not within the limits (or there are two roots)
// We try Newton-Raphson above the upper limit
// (we cannot go to the left of -1)
if let Ok(f) = compute_xirr_newton_raphson(values, dates, 200.0) {
return Ok(f);
};
return Err((Error::NUM, "Failed to converge".to_string()));
}
let (mut rtb, mut dx) = if f1 < 0.0 {
(x1, x2 - x1)
} else {
(x2, x1 - x2)
};
for _ in 1..max_iterations {
dx *= 0.5;
let x_mid = rtb + dx;
let f_mid = compute_xnpv(x_mid, values, dates)?;
if f_mid <= 0.0 {
rtb = x_mid;
}
if f_mid.abs() < eps || dx.abs() < eps {
return Ok(x_mid);
}
}
Err((Error::NUM, "Failed to converge".to_string()))
}

View File

@@ -0,0 +1,296 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::{Model, ParsedDefinedName},
};
impl Model {
pub(crate) fn fn_isnumber(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Number(_) => return CalcResult::Boolean(true),
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_istext(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::String(_) => return CalcResult::Boolean(true),
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_isnontext(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::String(_) => return CalcResult::Boolean(false),
_ => {
return CalcResult::Boolean(true);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_islogical(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Boolean(_) => return CalcResult::Boolean(true),
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_isblank(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::EmptyCell => return CalcResult::Boolean(true),
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_iserror(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Error { .. } => return CalcResult::Boolean(true),
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_iserr(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Error { error, .. } => {
if Error::NA == error {
return CalcResult::Boolean(false);
} else {
return CalcResult::Boolean(true);
}
}
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_isna(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Error { error, .. } => {
if error == Error::NA {
return CalcResult::Boolean(true);
} else {
return CalcResult::Boolean(false);
}
}
_ => {
return CalcResult::Boolean(false);
}
};
}
CalcResult::new_args_number_error(cell)
}
// Returns true if it is a reference or evaluates to a reference
// But DOES NOT evaluate
pub(crate) fn fn_isref(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match &args[0] {
Node::ReferenceKind { .. } | Node::RangeKind { .. } | Node::OpRangeKind { .. } => {
CalcResult::Boolean(true)
}
Node::FunctionKind { kind, args: _ } => CalcResult::Boolean(kind.returns_reference()),
_ => CalcResult::Boolean(false),
}
}
pub(crate) fn fn_isodd(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f.abs().trunc() as i64,
Err(s) => return s,
};
CalcResult::Boolean(value % 2 == 1)
}
pub(crate) fn fn_iseven(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number_no_bools(&args[0], cell) {
Ok(f) => f.abs().trunc() as i64,
Err(s) => return s,
};
CalcResult::Boolean(value % 2 == 0)
}
// ISFORMULA arg needs to be a reference or something that evaluates to a reference
pub(crate) fn fn_isformula(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
if let CalcResult::Range { left, right } = self.evaluate_node_with_reference(&args[0], cell)
{
if left.sheet != right.sheet {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "3D ranges not supported".to_string(),
};
}
if left.row != right.row && left.column != right.column {
// FIXME: Implicit intersection or dynamic arrays
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "argument must be a reference to a single cell".to_string(),
};
}
let is_formula = if let Ok(f) = self.cell_formula(left.sheet, left.row, left.column) {
f.is_some()
} else {
false
};
CalcResult::Boolean(is_formula)
} else {
CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Argument must be a reference".to_string(),
}
}
}
pub(crate) fn fn_errortype(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Error { error, .. } => {
match error {
Error::NULL => CalcResult::Number(1.0),
Error::DIV => CalcResult::Number(2.0),
Error::VALUE => CalcResult::Number(3.0),
Error::REF => CalcResult::Number(4.0),
Error::NAME => CalcResult::Number(5.0),
Error::NUM => CalcResult::Number(6.0),
Error::NA => CalcResult::Number(7.0),
Error::SPILL => CalcResult::Number(9.0),
Error::CALC => CalcResult::Number(14.0),
// IronCalc specific
Error::ERROR => CalcResult::Number(101.0),
Error::NIMPL => CalcResult::Number(102.0),
Error::CIRC => CalcResult::Number(104.0),
// Missing from Excel
// #GETTING_DATA => 8
// #CONNECT => 10
// #BLOCKED => 11
// #UNKNOWN => 12
// #FIELD => 13
// #EXTERNAL => 19
}
}
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not an error".to_string(),
},
}
}
// Excel believes for some reason that TYPE(A1:A7) is an array formula
// Although we evaluate the same as Excel we cannot, ATM import this from excel
pub(crate) fn fn_type(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::String(_) => CalcResult::Number(2.0),
CalcResult::Number(_) => CalcResult::Number(1.0),
CalcResult::Boolean(_) => CalcResult::Number(4.0),
CalcResult::Error { .. } => CalcResult::Number(16.0),
CalcResult::Range { .. } => CalcResult::Number(64.0),
CalcResult::EmptyCell => CalcResult::Number(1.0),
CalcResult::EmptyArg => {
// This cannot happen
CalcResult::Number(1.0)
}
}
}
pub(crate) fn fn_sheet(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let arg_count = args.len();
if arg_count > 1 {
return CalcResult::new_args_number_error(cell);
}
if arg_count == 0 {
// Sheets are 0-indexed`
return CalcResult::Number(cell.sheet as f64 + 1.0);
}
// The arg could be a defined name or a table
let arg = &args[0];
if let Node::VariableKind(name) = arg {
// Let's see if it is a defined name
if let Some(defined_name) = self.parsed_defined_names.get(&(None, name.to_lowercase()))
{
match defined_name {
ParsedDefinedName::CellReference(reference) => {
return CalcResult::Number(reference.sheet as f64 + 1.0)
}
ParsedDefinedName::RangeReference(range) => {
return CalcResult::Number(range.left.sheet as f64 + 1.0)
}
ParsedDefinedName::InvalidDefinedNameFormula => {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Invalid name".to_string(),
};
}
}
}
// Now let's see if it is a table
for (table_name, table) in &self.workbook.tables {
if table_name == name {
if let Some(sheet_index) = self.get_sheet_index_by_name(&table.sheet_name) {
return CalcResult::Number(sheet_index as f64 + 1.0);
} else {
break;
}
}
}
}
// Now it should be the name of a sheet
let sheet_name = match self.get_string(arg, cell) {
Ok(s) => s,
Err(e) => return e,
};
if let Some(sheet_index) = self.get_sheet_index_by_name(&sheet_name) {
return CalcResult::Number(sheet_index as f64 + 1.0);
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Invalid name".to_string(),
}
}
}

View File

@@ -0,0 +1,321 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
use super::util::compare_values;
impl Model {
pub(crate) fn fn_if(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 2 || args.len() == 3 {
let cond_result = self.get_boolean(&args[0], cell);
let cond = match cond_result {
Ok(f) => f,
Err(s) => {
return s;
}
};
if cond {
return self.evaluate_node_in_context(&args[1], cell);
} else if args.len() == 3 {
return self.evaluate_node_in_context(&args[2], cell);
} else {
return CalcResult::Boolean(false);
}
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_iferror(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 2 {
let value = self.evaluate_node_in_context(&args[0], cell);
match value {
CalcResult::Error { .. } => {
return self.evaluate_node_in_context(&args[1], cell);
}
_ => return value,
}
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_ifna(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 2 {
let value = self.evaluate_node_in_context(&args[0], cell);
if let CalcResult::Error { error, .. } = &value {
if error == &Error::NA {
return self.evaluate_node_in_context(&args[1], cell);
}
}
return value;
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_not(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 1 {
match self.get_boolean(&args[0], cell) {
Ok(f) => return CalcResult::Boolean(!f),
Err(s) => {
return s;
}
};
}
CalcResult::new_args_number_error(cell)
}
pub(crate) fn fn_and(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut true_count = 0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Boolean(b) => {
if !b {
return CalcResult::Boolean(false);
}
true_count += 1;
}
CalcResult::Number(value) => {
if value == 0.0 {
return CalcResult::Boolean(false);
}
true_count += 1;
}
CalcResult::String(_value) => {
true_count += 1;
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
for row in left.row..(right.row + 1) {
for column in left.column..(right.column + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Boolean(b) => {
if !b {
return CalcResult::Boolean(false);
}
true_count += 1;
}
CalcResult::Number(value) => {
if value == 0.0 {
return CalcResult::Boolean(false);
}
true_count += 1;
}
CalcResult::String(_value) => {
true_count += 1;
}
error @ CalcResult::Error { .. } => return error,
CalcResult::Range { .. } => {}
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
}
}
}
}
error @ CalcResult::Error { .. } => return error,
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
};
}
if true_count == 0 {
return CalcResult::new_error(
Error::VALUE,
cell,
"Boolean values not found".to_string(),
);
}
CalcResult::Boolean(true)
}
pub(crate) fn fn_or(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut result = false;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Boolean(value) => result = value || result,
CalcResult::Number(value) => {
if value != 0.0 {
return CalcResult::Boolean(true);
}
}
CalcResult::String(_value) => {
return CalcResult::Boolean(true);
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
for row in left.row..(right.row + 1) {
for column in left.column..(right.column + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Boolean(value) => {
result = value || result;
}
CalcResult::Number(value) => {
if value != 0.0 {
return CalcResult::Boolean(true);
}
}
CalcResult::String(_value) => {
return CalcResult::Boolean(true);
}
error @ CalcResult::Error { .. } => return error,
CalcResult::Range { .. } => {}
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
}
}
}
}
error @ CalcResult::Error { .. } => return error,
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
};
}
CalcResult::Boolean(result)
}
/// XOR(logical1, [logical]*,...)
/// Logical1 is required, subsequent logical values are optional. Can be logical values, arrays, or references.
/// The result of XOR is TRUE when the number of TRUE inputs is odd and FALSE when the number of TRUE inputs is even.
pub(crate) fn fn_xor(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut true_count = 0;
let mut false_count = 0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Boolean(b) => {
if b {
true_count += 1;
} else {
false_count += 1;
}
}
CalcResult::Number(value) => {
if value != 0.0 {
true_count += 1;
} else {
false_count += 1;
}
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
for row in left.row..(right.row + 1) {
for column in left.column..(right.column + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Boolean(b) => {
if b {
true_count += 1;
} else {
false_count += 1;
}
}
CalcResult::Number(value) => {
if value != 0.0 {
true_count += 1;
} else {
false_count += 1;
}
}
_ => {}
}
}
}
}
_ => {}
};
}
if true_count == 0 && false_count == 0 {
return CalcResult::new_error(Error::VALUE, cell, "No booleans found".to_string());
}
CalcResult::Boolean(true_count % 2 == 1)
}
/// =SWITCH(expression, case1, value1, [case, value]*, [default])
pub(crate) fn fn_switch(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count < 3 {
return CalcResult::new_args_number_error(cell);
}
// TODO add implicit intersection
let expr = self.evaluate_node_in_context(&args[0], cell);
if expr.is_error() {
return expr;
}
// How many cases we have?
// 3, 4 args -> 1 case
let case_count = (args_count - 1) / 2;
for case_index in 0..case_count {
let case = self.evaluate_node_in_context(&args[2 * case_index + 1], cell);
if case.is_error() {
return case;
}
if compare_values(&expr, &case) == 0 {
return self.evaluate_node_in_context(&args[2 * case_index + 2], cell);
}
}
// None of the cases matched so we return the default
// If there is an even number of args is the last one otherwise is #N/A
if args_count % 2 == 0 {
return self.evaluate_node_in_context(&args[args_count - 1], cell);
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Did not find a match".to_string(),
}
}
/// =IFS(condition1, value, [condition, value]*)
pub(crate) fn fn_ifs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count < 2 {
return CalcResult::new_args_number_error(cell);
}
if args_count % 2 != 0 {
// Missing value for last condition
return CalcResult::new_args_number_error(cell);
}
let case_count = args_count / 2;
for case_index in 0..case_count {
let value = self.get_boolean(&args[2 * case_index], cell);
match value {
Ok(b) => {
if b {
return self.evaluate_node_in_context(&args[2 * case_index + 1], cell);
}
}
Err(s) => return s,
}
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Did not find a match".to_string(),
}
}
}

View File

@@ -0,0 +1,843 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
utils::ParsedReference,
};
use super::util::{compare_values, from_wildcard_to_regex, result_matches_regex, values_are_equal};
impl Model {
pub(crate) fn fn_index(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let row_num;
let col_num;
if args.len() == 3 {
row_num = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => {
return s;
}
};
if row_num < 1.0 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument must be >= 1".to_string(),
};
}
col_num = match self.get_number(&args[2], cell) {
Ok(f) => f,
Err(s) => {
return s;
}
};
if col_num < 1.0 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument must be >= 1".to_string(),
};
}
} else if args.len() == 2 {
row_num = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => {
return s;
}
};
if row_num < 1.0 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument must be >= 1".to_string(),
};
}
col_num = -1.0;
} else {
return CalcResult::new_args_number_error(cell);
}
match self.evaluate_node_in_context(&args[0], cell) {
CalcResult::Range { left, right } => {
let row;
let column;
if (col_num + 1.0).abs() < f64::EPSILON {
if left.row == right.row {
column = left.column + (row_num as i32) - 1;
row = left.row;
} else {
column = left.column;
row = left.row + (row_num as i32) - 1;
}
} else {
row = left.row + (row_num as i32) - 1;
column = left.column + (col_num as i32) - 1;
}
if row > right.row {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Wrong reference".to_string(),
};
}
if column > right.column {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Wrong reference".to_string(),
};
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Expecting a Range".to_string(),
},
}
}
// MATCH(lookup_value, lookup_array, [match_type])
// The MATCH function syntax has the following arguments:
// * lookup_value Required. The value that you want to match in lookup_array.
// The lookup_value argument can be a value (number, text, or logical value)
// or a cell reference to a number, text, or logical value.
// * lookup_array Required. The range of cells being searched.
// * match_type Optional. The number -1, 0, or 1.
// The match_type argument specifies how Excel matches lookup_value
// with values in lookup_array. The default value for this argument is 1.
// NOTE: Please read the caveat above in binary search
pub(crate) fn fn_match(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 3 || args.len() < 2 {
return CalcResult::new_args_number_error(cell);
}
let target = self.evaluate_node_in_context(&args[0], cell);
if target.is_error() {
return target;
}
if matches!(target, CalcResult::EmptyCell) {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Cannot match empty cell".to_string(),
};
}
let match_type = if args.len() == 3 {
match self.get_number(&args[2], cell) {
Ok(v) => v as i32,
Err(s) => return s,
}
} else {
1
};
let match_range = self.evaluate_node_in_context(&args[1], cell);
match match_range {
CalcResult::Range { left, right } => {
match match_type {
-1 => {
// We apply binary search leftmost for value in the range
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Argument must be a vector".to_string(),
};
}
let n = if is_row_vector {
right.row - left.row
} else {
right.column - left.column
} + 1;
let mut l = 0;
let mut r = n;
while l < r {
let m = (l + r) / 2;
let row;
let column;
if is_row_vector {
row = left.row + m;
column = left.column;
} else {
column = left.column + m;
row = left.row;
}
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
});
if compare_values(&value, &target) >= 0 {
l = m + 1;
} else {
r = m;
}
}
// r is the number of elements less than target in the vector
// If target is less than the minimum return #N/A
if l == 0 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
// Now l points to the leftmost element
CalcResult::Number(l as f64)
}
0 => {
// We apply linear search
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Argument must be a vector".to_string(),
};
}
let n = if is_row_vector {
right.row - left.row
} else {
right.column - left.column
} + 1;
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
if let CalcResult::String(s) = &target {
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| values_are_equal(x, &target))
};
for l in 0..n {
let row;
let column;
if is_row_vector {
row = left.row + l;
column = left.column;
} else {
column = left.column + l;
row = left.row;
}
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
});
if result_matches(&value) {
return CalcResult::Number(l as f64 + 1.0);
}
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
}
_ => {
// l is the number of elements less than target in the vector
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Argument must be a vector".to_string(),
};
}
let l = self.binary_search(&target, &left, &right, is_row_vector);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
CalcResult::Number(l as f64 + 1.0)
}
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Invalid".to_string(),
},
}
}
/// HLOOKUP(lookup_value, table_array, row_index, [is_sorted])
/// We look for `lookup_value` in the first row of table array
/// We return the value in row `row_index` of the same column in `table_array`
/// `is_sorted` is true by default and assumes that values in first row are ordered
pub(crate) fn fn_hlookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 4 || args.len() < 3 {
return CalcResult::new_args_number_error(cell);
}
let lookup_value = self.evaluate_node_in_context(&args[0], cell);
if lookup_value.is_error() {
return lookup_value;
}
let row_index = match self.get_number(&args[2], cell) {
Ok(v) => v.floor() as i32,
Err(s) => return s,
};
let is_sorted = if args.len() == 4 {
match self.get_boolean(&args[3], cell) {
Ok(v) => v,
Err(s) => return s,
}
} else {
true
};
let range = self.evaluate_node_in_context(&args[1], cell);
match range {
CalcResult::Range { left, right } => {
if is_sorted {
// This assumes the values in row are in order
let l = self.binary_search(&lookup_value, &left, &right, false);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
let row = left.row + row_index - 1;
let column = left.column + l;
if row > right.row {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
} else {
// Linear search for exact match
let n = right.column - left.column + 1;
let row = left.row + row_index - 1;
if row > right.row {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
if let CalcResult::String(s) = &lookup_value {
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| compare_values(x, &lookup_value) == 0)
};
for l in 0..n {
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row,
column: left.column + l,
});
if result_matches(&value) {
return self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column: left.column + l,
});
}
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
}
}
error @ CalcResult::Error { .. } => error,
CalcResult::String(_) => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Range expected".to_string(),
},
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
/// VLOOKUP(lookup_value, table_array, row_index, [is_sorted])
/// We look for `lookup_value` in the first column of table array
/// We return the value in column `column_index` of the same row in `table_array`
/// `is_sorted` is true by default and assumes that values in first column are ordered
pub(crate) fn fn_vlookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 4 || args.len() < 3 {
return CalcResult::new_args_number_error(cell);
}
let lookup_value = self.evaluate_node_in_context(&args[0], cell);
if lookup_value.is_error() {
return lookup_value;
}
let column_index = match self.get_number(&args[2], cell) {
Ok(v) => v.floor() as i32,
Err(s) => return s,
};
let is_sorted = if args.len() == 4 {
match self.get_boolean(&args[3], cell) {
Ok(v) => v,
Err(s) => return s,
}
} else {
true
};
let range = self.evaluate_node_in_context(&args[1], cell);
match range {
CalcResult::Range { left, right } => {
if is_sorted {
// This assumes the values in column are in order
let l = self.binary_search(&lookup_value, &left, &right, true);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
let row = left.row + l;
let column = left.column + column_index - 1;
if column > right.column {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
} else {
// Linear search for exact match
let n = right.row - left.row + 1;
let column = left.column + column_index - 1;
if column > right.column {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
if let CalcResult::String(s) = &lookup_value {
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| compare_values(x, &lookup_value) == 0)
};
for l in 0..n {
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row + l,
column: left.column,
});
if result_matches(&value) {
return self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row + l,
column,
});
}
}
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
}
}
error @ CalcResult::Error { .. } => error,
CalcResult::String(_) => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Range expected".to_string(),
},
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
// LOOKUP(lookup_value, lookup_vector, [result_vector])
// Important: The values in lookup_vector must be placed in ascending order:
// ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE;
// otherwise, LOOKUP might not return the correct value.
// Uppercase and lowercase text are equivalent.
// TODO: Implement the other form of INDEX:
// INDEX(reference, row_num, [column_num], [area_num])
// NOTE: Please read the caveat above in binary search
pub(crate) fn fn_lookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 3 || args.len() < 2 {
return CalcResult::new_args_number_error(cell);
}
let target = self.evaluate_node_in_context(&args[0], cell);
if target.is_error() {
return target;
}
let value = self.evaluate_node_in_context(&args[1], cell);
match value {
CalcResult::Range { left, right } => {
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Second argument must be a vector".to_string(),
};
}
let l = self.binary_search(&target, &left, &right, is_row_vector);
if l == -2 {
return CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
};
}
if args.len() == 3 {
let target_range = self.evaluate_node_in_context(&args[2], cell);
match target_range {
CalcResult::Range {
left: l1,
right: _r1,
} => {
let row;
let column;
if is_row_vector {
row = l1.row + l;
column = l1.column;
} else {
column = l1.column + l;
row = l1.row;
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
} else {
let row;
let column;
if is_row_vector {
row = left.row + l;
column = left.column;
} else {
column = left.column + l;
row = left.row;
}
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
// ROW([reference])
// If reference is not present returns the row of the present cell.
// Otherwise returns the row number of reference
pub(crate) fn fn_row(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 1 {
return CalcResult::new_args_number_error(cell);
}
if args.is_empty() {
return CalcResult::Number(cell.row as f64);
}
match self.get_reference(&args[0], cell) {
Ok(c) => CalcResult::Number(c.left.row as f64),
Err(s) => s,
}
}
// ROWS(range)
// Returns the number of rows in range
pub(crate) fn fn_rows(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match self.get_reference(&args[0], cell) {
Ok(c) => CalcResult::Number((c.right.row - c.left.row + 1) as f64),
Err(s) => s,
}
}
// COLUMN([reference])
// If reference is not present returns the column of the present cell.
// Otherwise returns the column number of reference
pub(crate) fn fn_column(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 1 {
return CalcResult::new_args_number_error(cell);
}
if args.is_empty() {
return CalcResult::Number(cell.column as f64);
}
match self.get_reference(&args[0], cell) {
Ok(range) => CalcResult::Number(range.left.column as f64),
Err(s) => s,
}
}
/// CHOOSE(index_num, value1, [value2], ...)
/// Uses index_num to return a value from the list of value arguments.
pub(crate) fn fn_choose(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() < 2 {
return CalcResult::new_args_number_error(cell);
}
let index_num = match self.get_number(&args[0], cell) {
Ok(index_num) => index_num as usize,
Err(calc_err) => return calc_err,
};
if index_num < 1 || index_num > (args.len() - 1) {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Invalid index".to_string(),
};
}
self.evaluate_node_with_reference(&args[index_num], cell)
}
// COLUMNS(range)
// Returns the number of columns in range
pub(crate) fn fn_columns(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
match self.get_reference(&args[0], cell) {
Ok(c) => CalcResult::Number((c.right.column - c.left.column + 1) as f64),
Err(s) => s,
}
}
// INDIRECT(ref_tex)
// Returns the reference specified by 'ref_text'
pub(crate) fn fn_indirect(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() > 2 || args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
let value = self.get_string(&args[0], cell);
match value {
Ok(s) => {
if args.len() == 2 {
return CalcResult::Error {
error: Error::NIMPL,
origin: cell,
message: "Not implemented".to_string(),
};
}
let parsed_reference = ParsedReference::parse_reference_formula(
Some(cell.sheet),
&s,
&self.locale,
|name| self.get_sheet_index_by_name(name),
);
let parsed_reference = match parsed_reference {
Ok(reference) => reference,
Err(message) => {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message,
};
}
};
match parsed_reference {
ParsedReference::CellReference(reference) => CalcResult::Range {
left: reference,
right: reference,
},
ParsedReference::Range(left, right) => CalcResult::Range { left, right },
}
}
Err(v) => v,
}
}
// OFFSET(reference, rows, cols, [height], [width])
// Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
// The reference that is returned can be a single cell or a range of cells.
// You can specify the number of rows and the number of columns to be returned.
pub(crate) fn fn_offset(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let l = args.len();
if !(3..=5).contains(&l) {
return CalcResult::new_args_number_error(cell);
}
let reference = match self.get_reference(&args[0], cell) {
Ok(c) => c,
Err(s) => return s,
};
let rows = match self.get_number(&args[1], cell) {
Ok(c) => {
if c < 0.0 {
c.ceil() as i32
} else {
c.floor() as i32
}
}
Err(s) => return s,
};
let cols = match self.get_number(&args[2], cell) {
Ok(c) => {
if c < 0.0 {
c.ceil() as i32
} else {
c.floor() as i32
}
}
Err(s) => return s,
};
let row_start = reference.left.row + rows;
let column_start = reference.left.column + cols;
let width;
let height;
if l == 4 {
height = match self.get_number(&args[3], cell) {
Ok(c) => {
if c < 1.0 {
c.ceil() as i32 - 1
} else {
c.floor() as i32 - 1
}
}
Err(s) => return s,
};
width = reference.right.column - reference.left.column;
} else if l == 5 {
height = match self.get_number(&args[3], cell) {
Ok(c) => {
if c < 1.0 {
c.ceil() as i32 - 1
} else {
c.floor() as i32 - 1
}
}
Err(s) => return s,
};
width = match self.get_number(&args[4], cell) {
Ok(c) => {
if c < 1.0 {
c.ceil() as i32 - 1
} else {
c.floor() as i32 - 1
}
}
Err(s) => return s,
};
} else {
width = reference.right.column - reference.left.column;
height = reference.right.row - reference.left.row;
}
// This is what Excel does
if width == -1 || height == -1 {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
// NB: Excel documentation says that negative values of width and height are not valid
// but in practice they are valid. We follow the documentation and not Excel
if width < -1 || height < -1 {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "width and height cannot be negative".to_string(),
};
}
let column_end = column_start + width;
let row_end = row_start + height;
if row_start < 1 || row_end > LAST_ROW || column_start < 1 || column_end > LAST_COLUMN {
return CalcResult::Error {
error: Error::REF,
origin: cell,
message: "Invalid reference".to_string(),
};
}
let left = CellReference {
sheet: reference.left.sheet,
row: row_start,
column: column_start,
};
let right = CellReference {
sheet: reference.right.sheet,
row: row_end,
column: column_end,
};
CalcResult::Range { left, right }
}
}

View File

@@ -0,0 +1,671 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
use std::f64::consts::PI;
#[cfg(not(target_arch = "wasm32"))]
pub fn random() -> f64 {
rand::random()
}
#[cfg(target_arch = "wasm32")]
pub fn random() -> f64 {
use js_sys::Math;
Math::random()
}
impl Model {
pub(crate) fn fn_min(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut result = f64::NAN;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Number(value) => result = value.min(result),
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
for row in left.row..(right.row + 1) {
for column in left.column..(right.column + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Number(value) => {
result = value.min(result);
}
error @ CalcResult::Error { .. } => return error,
_ => {
// We ignore booleans and strings
}
}
}
}
}
error @ CalcResult::Error { .. } => return error,
_ => {
// We ignore booleans and strings
}
};
}
if result.is_nan() || result.is_infinite() {
return CalcResult::Number(0.0);
}
CalcResult::Number(result)
}
pub(crate) fn fn_max(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut result = f64::NAN;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Number(value) => result = value.max(result),
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
for row in left.row..(right.row + 1) {
for column in left.column..(right.column + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Number(value) => {
result = value.max(result);
}
error @ CalcResult::Error { .. } => return error,
_ => {
// We ignore booleans and strings
}
}
}
}
}
error @ CalcResult::Error { .. } => return error,
_ => {
// We ignore booleans and strings
}
};
}
if result.is_nan() || result.is_infinite() {
return CalcResult::Number(0.0);
}
CalcResult::Number(result)
}
pub(crate) fn fn_sum(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
let mut result = 0.0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Number(value) => result += value,
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
// TODO: We should do this for all functions that run through ranges
// Running cargo test for the ironcalc takes around .8 seconds with this speedup
// and ~ 3.5 seconds without it. Note that once properly in place sheet.dimension should be almost a noop
let row1 = left.row;
let mut row2 = right.row;
let column1 = left.column;
let mut column2 = right.column;
if row1 == 1 && row2 == LAST_ROW {
row2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_row;
}
if column1 == 1 && column2 == LAST_COLUMN {
column2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_column;
}
for row in row1..row2 + 1 {
for column in column1..(column2 + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Number(value) => {
result += value;
}
error @ CalcResult::Error { .. } => return error,
_ => {
// We ignore booleans and strings
}
}
}
}
}
error @ CalcResult::Error { .. } => return error,
_ => {
// We ignore booleans and strings
}
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_product(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
let mut result = 1.0;
let mut seen_value = false;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Number(value) => {
seen_value = true;
result *= value;
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
let row1 = left.row;
let mut row2 = right.row;
let column1 = left.column;
let mut column2 = right.column;
if row1 == 1 && row2 == LAST_ROW {
row2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_row;
}
if column1 == 1 && column2 == LAST_COLUMN {
column2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_column;
}
for row in row1..row2 + 1 {
for column in column1..(column2 + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Number(value) => {
seen_value = true;
result *= value;
}
error @ CalcResult::Error { .. } => return error,
_ => {
// We ignore booleans and strings
}
}
}
}
}
error @ CalcResult::Error { .. } => return error,
_ => {
// We ignore booleans and strings
}
};
}
if !seen_value {
return CalcResult::Number(0.0);
}
CalcResult::Number(result)
}
/// SUMIF(criteria_range, criteria, [sum_range])
/// if sum_rage is missing then criteria_range will be used
pub(crate) fn fn_sumif(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 2 {
let arguments = vec![args[0].clone(), args[0].clone(), args[1].clone()];
self.fn_sumifs(&arguments, cell)
} else if args.len() == 3 {
let arguments = vec![args[2].clone(), args[0].clone(), args[1].clone()];
self.fn_sumifs(&arguments, cell)
} else {
CalcResult::new_args_number_error(cell)
}
}
/// SUMIFS(sum_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...)
pub(crate) fn fn_sumifs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut total = 0.0;
let sum = |value| total += value;
if let Err(e) = self.apply_ifs(args, cell, sum) {
return e;
}
CalcResult::Number(total)
}
pub(crate) fn fn_round(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
// Incorrect number of arguments
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let number_of_digits = match self.get_number(&args[1], cell) {
Ok(f) => {
if f > 0.0 {
f.floor()
} else {
f.ceil()
}
}
Err(s) => return s,
};
let scale = 10.0_f64.powf(number_of_digits);
CalcResult::Number((value * scale).round() / scale)
}
pub(crate) fn fn_roundup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let number_of_digits = match self.get_number(&args[1], cell) {
Ok(f) => {
if f > 0.0 {
f.floor()
} else {
f.ceil()
}
}
Err(s) => return s,
};
let scale = 10.0_f64.powf(number_of_digits);
if value > 0.0 {
CalcResult::Number((value * scale).ceil() / scale)
} else {
CalcResult::Number((value * scale).floor() / scale)
}
}
pub(crate) fn fn_rounddown(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let number_of_digits = match self.get_number(&args[1], cell) {
Ok(f) => {
if f > 0.0 {
f.floor()
} else {
f.ceil()
}
}
Err(s) => return s,
};
let scale = 10.0_f64.powf(number_of_digits);
if value > 0.0 {
CalcResult::Number((value * scale).floor() / scale)
} else {
CalcResult::Number((value * scale).ceil() / scale)
}
}
pub(crate) fn fn_sin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.sin();
CalcResult::Number(result)
}
pub(crate) fn fn_cos(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.cos();
CalcResult::Number(result)
}
pub(crate) fn fn_tan(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.tan();
CalcResult::Number(result)
}
pub(crate) fn fn_sinh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.sinh();
CalcResult::Number(result)
}
pub(crate) fn fn_cosh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.cosh();
CalcResult::Number(result)
}
pub(crate) fn fn_tanh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.tanh();
CalcResult::Number(result)
}
pub(crate) fn fn_asin(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.asin();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for ASIN".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_acos(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.acos();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for COS".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_atan(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.atan();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for ATAN".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_asinh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.asinh();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for ASINH".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_acosh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.acosh();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for ACOSH".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_atanh(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let result = value.atanh();
if result.is_nan() || result.is_infinite() {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid argument for ATANH".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_pi(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
CalcResult::Number(PI)
}
pub(crate) fn fn_abs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
CalcResult::Number(value.abs())
}
pub(crate) fn fn_sqrtpi(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
if value < 0.0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Argument of SQRTPI should be >= 0".to_string(),
};
}
CalcResult::Number((value * PI).sqrt())
}
pub(crate) fn fn_sqrt(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
if value < 0.0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Argument of SQRT should be >= 0".to_string(),
};
}
CalcResult::Number(value.sqrt())
}
pub(crate) fn fn_atan2(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let y = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if x == 0.0 && y == 0.0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Arguments can't be both zero".to_string(),
};
}
CalcResult::Number(f64::atan2(y, x))
}
pub(crate) fn fn_power(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number(&args[0], cell) {
Ok(f) => f,
Err(s) => return s,
};
let y = match self.get_number(&args[1], cell) {
Ok(f) => f,
Err(s) => return s,
};
if x == 0.0 && y == 0.0 {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Arguments can't be both zero".to_string(),
};
}
if y == 0.0 {
return CalcResult::Number(1.0);
}
let result = x.powf(y);
if result.is_infinite() {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "POWER returned infinity".to_string(),
};
}
if result.is_nan() {
// This might happen for some combinations of negative base and exponent
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: "Invalid arguments for POWER".to_string(),
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_rand(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if !args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
CalcResult::Number(random())
}
// TODO: Add tests for RANDBETWEEN
pub(crate) fn fn_randbetween(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() != 2 {
return CalcResult::new_args_number_error(cell);
}
let x = match self.get_number(&args[0], cell) {
Ok(f) => f.floor(),
Err(s) => return s,
};
let y = match self.get_number(&args[1], cell) {
Ok(f) => f.ceil() + 1.0,
Err(s) => return s,
};
if x > y {
return CalcResult::Error {
error: Error::NUM,
origin: cell,
message: format!("{x}>{y}"),
};
}
CalcResult::Number((x + random() * (y - x)).floor())
}
}

930
base/src/functions/mod.rs Normal file
View File

@@ -0,0 +1,930 @@
use core::fmt;
use crate::{
calc_result::{CalcResult, CellReference},
expressions::{parser::Node, token::Error},
model::Model,
};
pub(crate) mod binary_search;
mod date_and_time;
mod engineering;
mod financial;
mod financial_util;
mod information;
mod logical;
mod lookup_and_reference;
mod mathematical;
mod statistical;
mod subtotal;
mod text;
mod text_util;
pub(crate) mod util;
mod xlookup;
/// List of all implemented functions
#[derive(PartialEq, Clone, Debug)]
pub enum Function {
// Logical
And,
False,
If,
Iferror,
Ifna,
Ifs,
Not,
Or,
Switch,
True,
Xor,
// Mathematical and trigonometry
Abs,
Acos,
Acosh,
Asin,
Asinh,
Atan,
Atan2,
Atanh,
Choose,
Column,
Columns,
Cos,
Cosh,
Max,
Min,
Pi,
Power,
Product,
Rand,
Randbetween,
Round,
Rounddown,
Roundup,
Sin,
Sinh,
Sqrt,
Sqrtpi,
Sum,
Sumif,
Sumifs,
Tan,
Tanh,
// Information
ErrorType,
Isblank,
Iserr,
Iserror,
Iseven,
Isformula,
Islogical,
Isna,
Isnontext,
Isnumber,
Isodd,
Isref,
Istext,
Na,
Sheet,
Type,
// Lookup and reference
Hlookup,
Index,
Indirect,
Lookup,
Match,
Offset,
Row,
Rows,
Vlookup,
Xlookup,
// Text
Concat,
Concatenate,
Exact,
Find,
Left,
Len,
Lower,
Mid,
Rept,
Right,
Search,
Substitute,
T,
Text,
Textafter,
Textbefore,
Textjoin,
Trim,
Upper,
Value,
Valuetotext,
// Statistical
Average,
Averagea,
Averageif,
Averageifs,
Count,
Counta,
Countblank,
Countif,
Countifs,
Maxifs,
Minifs,
// Date and time
Date,
Day,
Edate,
Eomonth,
Month,
Now,
Today,
Year,
// Financial
Cumipmt,
Cumprinc,
Db,
Ddb,
Dollarde,
Dollarfr,
Effect,
Fv,
Ipmt,
Irr,
Ispmt,
Mirr,
Nominal,
Nper,
Npv,
Pduration,
Pmt,
Ppmt,
Pv,
Rate,
Rri,
Sln,
Syd,
Tbilleq,
Tbillprice,
Tbillyield,
Xirr,
Xnpv,
// Engineering: Bessel and transcendental functions
Besseli,
Besselj,
Besselk,
Bessely,
Erf,
Erfc,
ErfcPrecise,
ErfPrecise,
// Engineering: Number systems
Bin2dec,
Bin2hex,
Bin2oct,
Dec2Bin,
Dec2hex,
Dec2oct,
Hex2bin,
Hex2dec,
Hex2oct,
Oct2bin,
Oct2dec,
Oct2hex,
// Engineering: Bit functions
Bitand,
Bitlshift,
Bitor,
Bitrshift,
Bitxor,
// Engineering: Complex functions
Complex,
Imabs,
Imaginary,
Imargument,
Imconjugate,
Imcos,
Imcosh,
Imcot,
Imcsc,
Imcsch,
Imdiv,
Imexp,
Imln,
Imlog10,
Imlog2,
Impower,
Improduct,
Imreal,
Imsec,
Imsech,
Imsin,
Imsinh,
Imsqrt,
Imsub,
Imsum,
Imtan,
// Engineering: Misc function
Convert,
Delta,
Gestep,
Subtotal,
}
impl Function {
/// Some functions in Excel like CONCAT are stringified as `_xlfn.CONCAT`.
pub fn to_xlsx_string(&self) -> String {
match self {
Function::Concat => "_xlfn.CONCAT".to_string(),
Function::Ifna => "_xlfn.IFNA".to_string(),
Function::Ifs => "_xlfn.IFS".to_string(),
Function::Maxifs => "_xlfn.MAXIFS".to_string(),
Function::Minifs => "_xlfn.MINIFS".to_string(),
Function::Switch => "_xlfn.SWITCH".to_string(),
Function::Xlookup => "_xlfn.XLOOKUP".to_string(),
Function::Xor => "_xlfn.XOR".to_string(),
Function::Textbefore => "_xlfn.TEXTBEFORE".to_string(),
Function::Textafter => "_xlfn.TEXTAFTER".to_string(),
Function::Textjoin => "_xlfn.TEXTJOIN".to_string(),
Function::Rri => "_xlfn.RRI".to_string(),
Function::Pduration => "_xlfn.PDURATION".to_string(),
Function::Bitand => "_xlfn.BITAND".to_string(),
Function::Bitor => "_xlfn.BITOR".to_string(),
Function::Bitxor => "_xlfn.BITXOR".to_string(),
Function::Bitlshift => "_xlfn.BITLSHIFT".to_string(),
Function::Bitrshift => "_xlfn.BITRSHIFT".to_string(),
Function::Imtan => "_xlfn.IMTAN".to_string(),
Function::Imsinh => "_xlfn.IMSINH".to_string(),
Function::Imcosh => "_xlfn.IMCOSH".to_string(),
Function::Imcot => "_xlfn.IMCOT".to_string(),
Function::Imcsc => "_xlfn.IMCSC".to_string(),
Function::Imcsch => "_xlfn.IMCSCH".to_string(),
Function::Imsec => "_xlfn.IMSEC".to_string(),
Function::ErfcPrecise => "_xlfn.ERFC.PRECISE".to_string(),
Function::ErfPrecise => "_xlfn.ERF.PRECISE".to_string(),
Function::Valuetotext => "_xlfn.VALUETOTEXT".to_string(),
Function::Isformula => "_xlfn.ISFORMULA".to_string(),
Function::Sheet => "_xlfn.SHEET".to_string(),
_ => self.to_string(),
}
}
pub(crate) fn returns_reference(&self) -> bool {
matches!(self, Function::Indirect | Function::Offset)
}
/// Gets the function from the name.
/// Note that in Excel some (modern) functions are prefixed by `_xlfn.`
pub fn get_function(name: &str) -> Option<Function> {
match name.to_ascii_uppercase().as_str() {
"AND" => Some(Function::And),
"FALSE" => Some(Function::False),
"IF" => Some(Function::If),
"IFERROR" => Some(Function::Iferror),
"IFNA" | "_XLFN.IFNA" => Some(Function::Ifna),
"IFS" | "_XLFN.IFS" => Some(Function::Ifs),
"NOT" => Some(Function::Not),
"OR" => Some(Function::Or),
"SWITCH" | "_XLFN.SWITCH" => Some(Function::Switch),
"TRUE" => Some(Function::True),
"XOR" | "_XLFN.XOR" => Some(Function::Xor),
"SIN" => Some(Function::Sin),
"COS" => Some(Function::Cos),
"TAN" => Some(Function::Tan),
"ASIN" => Some(Function::Asin),
"ACOS" => Some(Function::Acos),
"ATAN" => Some(Function::Atan),
"SINH" => Some(Function::Sinh),
"COSH" => Some(Function::Cosh),
"TANH" => Some(Function::Tanh),
"ASINH" => Some(Function::Asinh),
"ACOSH" => Some(Function::Acosh),
"ATANH" => Some(Function::Atanh),
"PI" => Some(Function::Pi),
"ABS" => Some(Function::Abs),
"SQRT" => Some(Function::Sqrt),
"SQRTPI" => Some(Function::Sqrtpi),
"POWER" => Some(Function::Power),
"ATAN2" => Some(Function::Atan2),
"MAX" => Some(Function::Max),
"MIN" => Some(Function::Min),
"PRODUCT" => Some(Function::Product),
"RAND" => Some(Function::Rand),
"RANDBETWEEN" => Some(Function::Randbetween),
"ROUND" => Some(Function::Round),
"ROUNDDOWN" => Some(Function::Rounddown),
"ROUNDUP" => Some(Function::Roundup),
"SUM" => Some(Function::Sum),
"SUMIF" => Some(Function::Sumif),
"SUMIFS" => Some(Function::Sumifs),
// Lookup and Reference
"CHOOSE" => Some(Function::Choose),
"COLUMN" => Some(Function::Column),
"COLUMNS" => Some(Function::Columns),
"INDEX" => Some(Function::Index),
"INDIRECT" => Some(Function::Indirect),
"HLOOKUP" => Some(Function::Hlookup),
"LOOKUP" => Some(Function::Lookup),
"MATCH" => Some(Function::Match),
"OFFSET" => Some(Function::Offset),
"ROW" => Some(Function::Row),
"ROWS" => Some(Function::Rows),
"VLOOKUP" => Some(Function::Vlookup),
"XLOOKUP" | "_XLFN.XLOOKUP" => Some(Function::Xlookup),
"CONCATENATE" => Some(Function::Concatenate),
"EXACT" => Some(Function::Exact),
"VALUE" => Some(Function::Value),
"T" => Some(Function::T),
"VALUETOTEXT" | "_XLFN.VALUETOTEXT" => Some(Function::Valuetotext),
"CONCAT" | "_XLFN.CONCAT" => Some(Function::Concat),
"FIND" => Some(Function::Find),
"LEFT" => Some(Function::Left),
"LEN" => Some(Function::Len),
"LOWER" => Some(Function::Lower),
"MID" => Some(Function::Mid),
"RIGHT" => Some(Function::Right),
"SEARCH" => Some(Function::Search),
"TEXT" => Some(Function::Text),
"TRIM" => Some(Function::Trim),
"UPPER" => Some(Function::Upper),
"REPT" => Some(Function::Rept),
"TEXTAFTER" | "_XLFN.TEXTAFTER" => Some(Function::Textafter),
"TEXTBEFORE" | "_XLFN.TEXTBEFORE" => Some(Function::Textbefore),
"TEXTJOIN" | "_XLFN.TEXTJOIN" => Some(Function::Textjoin),
"SUBSTITUTE" => Some(Function::Substitute),
"ISNUMBER" => Some(Function::Isnumber),
"ISNONTEXT" => Some(Function::Isnontext),
"ISTEXT" => Some(Function::Istext),
"ISLOGICAL" => Some(Function::Islogical),
"ISBLANK" => Some(Function::Isblank),
"ISERR" => Some(Function::Iserr),
"ISERROR" => Some(Function::Iserror),
"ISNA" => Some(Function::Isna),
"NA" => Some(Function::Na),
"ISREF" => Some(Function::Isref),
"ISODD" => Some(Function::Isodd),
"ISEVEN" => Some(Function::Iseven),
"ERROR.TYPE" => Some(Function::ErrorType),
"ISFORMULA" | "_XLFN.ISFORMULA" => Some(Function::Isformula),
"TYPE" => Some(Function::Type),
"SHEET" | "_XLFN.SHEET" => Some(Function::Sheet),
"AVERAGE" => Some(Function::Average),
"AVERAGEA" => Some(Function::Averagea),
"AVERAGEIF" => Some(Function::Averageif),
"AVERAGEIFS" => Some(Function::Averageifs),
"COUNT" => Some(Function::Count),
"COUNTA" => Some(Function::Counta),
"COUNTBLANK" => Some(Function::Countblank),
"COUNTIF" => Some(Function::Countif),
"COUNTIFS" => Some(Function::Countifs),
"MAXIFS" | "_XLFN.MAXIFS" => Some(Function::Maxifs),
"MINIFS" | "_XLFN.MINIFS" => Some(Function::Minifs),
// Date and Time
"YEAR" => Some(Function::Year),
"DAY" => Some(Function::Day),
"EOMONTH" => Some(Function::Eomonth),
"MONTH" => Some(Function::Month),
"DATE" => Some(Function::Date),
"EDATE" => Some(Function::Edate),
"TODAY" => Some(Function::Today),
"NOW" => Some(Function::Now),
// Financial
"PMT" => Some(Function::Pmt),
"PV" => Some(Function::Pv),
"RATE" => Some(Function::Rate),
"NPER" => Some(Function::Nper),
"FV" => Some(Function::Fv),
"PPMT" => Some(Function::Ppmt),
"IPMT" => Some(Function::Ipmt),
"NPV" => Some(Function::Npv),
"XNPV" => Some(Function::Xnpv),
"MIRR" => Some(Function::Mirr),
"IRR" => Some(Function::Irr),
"XIRR" => Some(Function::Xirr),
"ISPMT" => Some(Function::Ispmt),
"RRI" | "_XLFN.RRI" => Some(Function::Rri),
"SLN" => Some(Function::Sln),
"SYD" => Some(Function::Syd),
"NOMINAL" => Some(Function::Nominal),
"EFFECT" => Some(Function::Effect),
"PDURATION" | "_XLFN.PDURATION" => Some(Function::Pduration),
"TBILLYIELD" => Some(Function::Tbillyield),
"TBILLPRICE" => Some(Function::Tbillprice),
"TBILLEQ" => Some(Function::Tbilleq),
"DOLLARDE" => Some(Function::Dollarde),
"DOLLARFR" => Some(Function::Dollarfr),
"DDB" => Some(Function::Ddb),
"DB" => Some(Function::Db),
"CUMPRINC" => Some(Function::Cumprinc),
"CUMIPMT" => Some(Function::Cumipmt),
"BESSELI" => Some(Function::Besseli),
"BESSELJ" => Some(Function::Besselj),
"BESSELK" => Some(Function::Besselk),
"BESSELY" => Some(Function::Bessely),
"ERF" => Some(Function::Erf),
"ERF.PRECISE" | "_XLFN.ERF.PRECISE" => Some(Function::ErfPrecise),
"ERFC" => Some(Function::Erfc),
"ERFC.PRECISE" | "_XLFN.ERFC.PRECISE" => Some(Function::ErfcPrecise),
"BIN2DEC" => Some(Function::Bin2dec),
"BIN2HEX" => Some(Function::Bin2hex),
"BIN2OCT" => Some(Function::Bin2oct),
"DEC2BIN" => Some(Function::Dec2Bin),
"DEC2HEX" => Some(Function::Dec2hex),
"DEC2OCT" => Some(Function::Dec2oct),
"HEX2BIN" => Some(Function::Hex2bin),
"HEX2DEC" => Some(Function::Hex2dec),
"HEX2OCT" => Some(Function::Hex2oct),
"OCT2BIN" => Some(Function::Oct2bin),
"OCT2DEC" => Some(Function::Oct2dec),
"OCT2HEX" => Some(Function::Oct2hex),
"BITAND" | "_XLFN.BITAND" => Some(Function::Bitand),
"BITLSHIFT" | "_XLFN.BITLSHIFT" => Some(Function::Bitlshift),
"BITOR" | "_XLFN.BITOR" => Some(Function::Bitor),
"BITRSHIFT" | "_XLFN.BITRSHIFT" => Some(Function::Bitrshift),
"BITXOR" | "_XLFN.BITXOR" => Some(Function::Bitxor),
"COMPLEX" => Some(Function::Complex),
"IMABS" => Some(Function::Imabs),
"IMAGINARY" => Some(Function::Imaginary),
"IMARGUMENT" => Some(Function::Imargument),
"IMCONJUGATE" => Some(Function::Imconjugate),
"IMCOS" => Some(Function::Imcos),
"IMCOSH" | "_XLFN.IMCOSH" => Some(Function::Imcosh),
"IMCOT" | "_XLFN.IMCOT" => Some(Function::Imcot),
"IMCSC" | "_XLFN.IMCSC" => Some(Function::Imcsc),
"IMCSCH" | "_XLFN.IMCSCH" => Some(Function::Imcsch),
"IMDIV" => Some(Function::Imdiv),
"IMEXP" => Some(Function::Imexp),
"IMLN" => Some(Function::Imln),
"IMLOG10" => Some(Function::Imlog10),
"IMLOG2" => Some(Function::Imlog2),
"IMPOWER" => Some(Function::Impower),
"IMPRODUCT" => Some(Function::Improduct),
"IMREAL" => Some(Function::Imreal),
"IMSEC" | "_XLFN.IMSEC" => Some(Function::Imsec),
"IMSECH" | "_XLFN.IMSECH" => Some(Function::Imsech),
"IMSIN" => Some(Function::Imsin),
"IMSINH" | "_XLFN.IMSINH" => Some(Function::Imsinh),
"IMSQRT" => Some(Function::Imsqrt),
"IMSUB" => Some(Function::Imsub),
"IMSUM" => Some(Function::Imsum),
"IMTAN" | "_XLFN.IMTAN" => Some(Function::Imtan),
"CONVERT" => Some(Function::Convert),
"DELTA" => Some(Function::Delta),
"GESTEP" => Some(Function::Gestep),
"SUBTOTAL" => Some(Function::Subtotal),
_ => None,
}
}
}
impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Function::And => write!(f, "AND"),
Function::False => write!(f, "FALSE"),
Function::If => write!(f, "IF"),
Function::Iferror => write!(f, "IFERROR"),
Function::Ifna => write!(f, "IFNA"),
Function::Ifs => write!(f, "IFS"),
Function::Not => write!(f, "NOT"),
Function::Or => write!(f, "OR"),
Function::Switch => write!(f, "SWITCH"),
Function::True => write!(f, "TRUE"),
Function::Xor => write!(f, "XOR"),
Function::Sin => write!(f, "SIN"),
Function::Cos => write!(f, "COS"),
Function::Tan => write!(f, "TAN"),
Function::Asin => write!(f, "ASIN"),
Function::Acos => write!(f, "ACOS"),
Function::Atan => write!(f, "ATAN"),
Function::Sinh => write!(f, "SINH"),
Function::Cosh => write!(f, "COSH"),
Function::Tanh => write!(f, "TANH"),
Function::Asinh => write!(f, "ASINH"),
Function::Acosh => write!(f, "ACOSH"),
Function::Atanh => write!(f, "ATANH"),
Function::Abs => write!(f, "ABS"),
Function::Pi => write!(f, "PI"),
Function::Sqrt => write!(f, "SQRT"),
Function::Sqrtpi => write!(f, "SQRTPI"),
Function::Atan2 => write!(f, "ATAN2"),
Function::Power => write!(f, "POWER"),
Function::Max => write!(f, "MAX"),
Function::Min => write!(f, "MIN"),
Function::Product => write!(f, "PRODUCT"),
Function::Rand => write!(f, "RAND"),
Function::Randbetween => write!(f, "RANDBETWEEN"),
Function::Round => write!(f, "ROUND"),
Function::Rounddown => write!(f, "ROUNDDOWN"),
Function::Roundup => write!(f, "ROUNDUP"),
Function::Sum => write!(f, "SUM"),
Function::Sumif => write!(f, "SUMIF"),
Function::Sumifs => write!(f, "SUMIFS"),
Function::Choose => write!(f, "CHOOSE"),
Function::Column => write!(f, "COLUMN"),
Function::Columns => write!(f, "COLUMNS"),
Function::Index => write!(f, "INDEX"),
Function::Indirect => write!(f, "INDIRECT"),
Function::Hlookup => write!(f, "HLOOKUP"),
Function::Lookup => write!(f, "LOOKUP"),
Function::Match => write!(f, "MATCH"),
Function::Offset => write!(f, "OFFSET"),
Function::Row => write!(f, "ROW"),
Function::Rows => write!(f, "ROWS"),
Function::Vlookup => write!(f, "VLOOKUP"),
Function::Xlookup => write!(f, "XLOOKUP"),
Function::Concatenate => write!(f, "CONCATENATE"),
Function::Exact => write!(f, "EXACT"),
Function::Value => write!(f, "VALUE"),
Function::T => write!(f, "T"),
Function::Valuetotext => write!(f, "VALUETOTEXT"),
Function::Concat => write!(f, "CONCAT"),
Function::Find => write!(f, "FIND"),
Function::Left => write!(f, "LEFT"),
Function::Len => write!(f, "LEN"),
Function::Lower => write!(f, "LOWER"),
Function::Mid => write!(f, "MID"),
Function::Right => write!(f, "RIGHT"),
Function::Search => write!(f, "SEARCH"),
Function::Text => write!(f, "TEXT"),
Function::Trim => write!(f, "TRIM"),
Function::Upper => write!(f, "UPPER"),
Function::Isnumber => write!(f, "ISNUMBER"),
Function::Isnontext => write!(f, "ISNONTEXT"),
Function::Istext => write!(f, "ISTEXT"),
Function::Islogical => write!(f, "ISLOGICAL"),
Function::Isblank => write!(f, "ISBLANK"),
Function::Iserr => write!(f, "ISERR"),
Function::Iserror => write!(f, "ISERROR"),
Function::Isna => write!(f, "ISNA"),
Function::Na => write!(f, "NA"),
Function::Isref => write!(f, "ISREF"),
Function::Isodd => write!(f, "ISODD"),
Function::Iseven => write!(f, "ISEVEN"),
Function::ErrorType => write!(f, "ERROR.TYPE"),
Function::Isformula => write!(f, "ISFORMULA"),
Function::Type => write!(f, "TYPE"),
Function::Sheet => write!(f, "SHEET"),
Function::Average => write!(f, "AVERAGE"),
Function::Averagea => write!(f, "AVERAGEA"),
Function::Averageif => write!(f, "AVERAGEIF"),
Function::Averageifs => write!(f, "AVERAGEIFS"),
Function::Count => write!(f, "COUNT"),
Function::Counta => write!(f, "COUNTA"),
Function::Countblank => write!(f, "COUNTBLANK"),
Function::Countif => write!(f, "COUNTIF"),
Function::Countifs => write!(f, "COUNTIFS"),
Function::Maxifs => write!(f, "MAXIFS"),
Function::Minifs => write!(f, "MINIFS"),
Function::Year => write!(f, "YEAR"),
Function::Day => write!(f, "DAY"),
Function::Month => write!(f, "MONTH"),
Function::Eomonth => write!(f, "EOMONTH"),
Function::Date => write!(f, "DATE"),
Function::Edate => write!(f, "EDATE"),
Function::Today => write!(f, "TODAY"),
Function::Now => write!(f, "NOW"),
Function::Pmt => write!(f, "PMT"),
Function::Pv => write!(f, "PV"),
Function::Rate => write!(f, "RATE"),
Function::Nper => write!(f, "NPER"),
Function::Fv => write!(f, "FV"),
Function::Ppmt => write!(f, "PPMT"),
Function::Ipmt => write!(f, "IPMT"),
Function::Npv => write!(f, "NPV"),
Function::Mirr => write!(f, "MIRR"),
Function::Irr => write!(f, "IRR"),
Function::Xirr => write!(f, "XIRR"),
Function::Xnpv => write!(f, "XNPV"),
Function::Rept => write!(f, "REPT"),
Function::Textafter => write!(f, "TEXTAFTER"),
Function::Textbefore => write!(f, "TEXTBEFORE"),
Function::Textjoin => write!(f, "TEXTJOIN"),
Function::Substitute => write!(f, "SUBSTITUTE"),
Function::Ispmt => write!(f, "ISPMT"),
Function::Rri => write!(f, "RRI"),
Function::Sln => write!(f, "SLN"),
Function::Syd => write!(f, "SYD"),
Function::Nominal => write!(f, "NOMINAL"),
Function::Effect => write!(f, "EFFECT"),
Function::Pduration => write!(f, "PDURATION"),
Function::Tbillyield => write!(f, "TBILLYIELD"),
Function::Tbillprice => write!(f, "TBILLPRICE"),
Function::Tbilleq => write!(f, "TBILLEQ"),
Function::Dollarde => write!(f, "DOLLARDE"),
Function::Dollarfr => write!(f, "DOLLARFR"),
Function::Ddb => write!(f, "DDB"),
Function::Db => write!(f, "DB"),
Function::Cumprinc => write!(f, "CUMPRINC"),
Function::Cumipmt => write!(f, "CUMIPMT"),
Function::Besseli => write!(f, "BESSELI"),
Function::Besselj => write!(f, "BESSELJ"),
Function::Besselk => write!(f, "BESSELK"),
Function::Bessely => write!(f, "BESSELY"),
Function::Erf => write!(f, "ERF"),
Function::ErfPrecise => write!(f, "ERF.PRECISE"),
Function::Erfc => write!(f, "ERFC"),
Function::ErfcPrecise => write!(f, "ERFC.PRECISE"),
Function::Bin2dec => write!(f, "BIN2DEC"),
Function::Bin2hex => write!(f, "BIN2HEX"),
Function::Bin2oct => write!(f, "BIN2OCT"),
Function::Dec2Bin => write!(f, "DEC2BIN"),
Function::Dec2hex => write!(f, "DEC2HEX"),
Function::Dec2oct => write!(f, "DEC2OCT"),
Function::Hex2bin => write!(f, "HEX2BIN"),
Function::Hex2dec => write!(f, "HEX2DEC"),
Function::Hex2oct => write!(f, "HEX2OCT"),
Function::Oct2bin => write!(f, "OCT2BIN"),
Function::Oct2dec => write!(f, "OCT2DEC"),
Function::Oct2hex => write!(f, "OCT2HEX"),
Function::Bitand => write!(f, "BITAND"),
Function::Bitlshift => write!(f, "BITLSHIFT"),
Function::Bitor => write!(f, "BITOR"),
Function::Bitrshift => write!(f, "BITRSHIFT"),
Function::Bitxor => write!(f, "BITXOR"),
Function::Complex => write!(f, "COMPLEX"),
Function::Imabs => write!(f, "IMABS"),
Function::Imaginary => write!(f, "IMAGINARY"),
Function::Imargument => write!(f, "IMARGUMENT"),
Function::Imconjugate => write!(f, "IMCONJUGATE"),
Function::Imcos => write!(f, "IMCOS"),
Function::Imcosh => write!(f, "IMCOSH"),
Function::Imcot => write!(f, "IMCOT"),
Function::Imcsc => write!(f, "IMCSC"),
Function::Imcsch => write!(f, "IMCSCH"),
Function::Imdiv => write!(f, "IMDIV"),
Function::Imexp => write!(f, "IMEXP"),
Function::Imln => write!(f, "IMLN"),
Function::Imlog10 => write!(f, "IMLOG10"),
Function::Imlog2 => write!(f, "IMLOG2"),
Function::Impower => write!(f, "IMPOWER"),
Function::Improduct => write!(f, "IMPRODUCT"),
Function::Imreal => write!(f, "IMREAL"),
Function::Imsec => write!(f, "IMSEC"),
Function::Imsech => write!(f, "IMSECH"),
Function::Imsin => write!(f, "IMSIN"),
Function::Imsinh => write!(f, "IMSINH"),
Function::Imsqrt => write!(f, "IMSQRT"),
Function::Imsub => write!(f, "IMSUB"),
Function::Imsum => write!(f, "IMSUM"),
Function::Imtan => write!(f, "IMTAN"),
Function::Convert => write!(f, "CONVERT"),
Function::Delta => write!(f, "DELTA"),
Function::Gestep => write!(f, "GESTEP"),
Function::Subtotal => write!(f, "SUBTOTAL"),
}
}
}
impl Model {
pub(crate) fn evaluate_function(
&mut self,
kind: &Function,
args: &[Node],
cell: CellReference,
) -> CalcResult {
match kind {
// Logical
Function::And => self.fn_and(args, cell),
Function::False => CalcResult::Boolean(false),
Function::If => self.fn_if(args, cell),
Function::Iferror => self.fn_iferror(args, cell),
Function::Ifna => self.fn_ifna(args, cell),
Function::Ifs => self.fn_ifs(args, cell),
Function::Not => self.fn_not(args, cell),
Function::Or => self.fn_or(args, cell),
Function::Switch => self.fn_switch(args, cell),
Function::True => CalcResult::Boolean(true),
Function::Xor => self.fn_xor(args, cell),
// Math and trigonometry
Function::Sin => self.fn_sin(args, cell),
Function::Cos => self.fn_cos(args, cell),
Function::Tan => self.fn_tan(args, cell),
Function::Asin => self.fn_asin(args, cell),
Function::Acos => self.fn_acos(args, cell),
Function::Atan => self.fn_atan(args, cell),
Function::Sinh => self.fn_sinh(args, cell),
Function::Cosh => self.fn_cosh(args, cell),
Function::Tanh => self.fn_tanh(args, cell),
Function::Asinh => self.fn_asinh(args, cell),
Function::Acosh => self.fn_acosh(args, cell),
Function::Atanh => self.fn_atanh(args, cell),
Function::Pi => self.fn_pi(args, cell),
Function::Abs => self.fn_abs(args, cell),
Function::Sqrt => self.fn_sqrt(args, cell),
Function::Sqrtpi => self.fn_sqrtpi(args, cell),
Function::Atan2 => self.fn_atan2(args, cell),
Function::Power => self.fn_power(args, cell),
Function::Max => self.fn_max(args, cell),
Function::Min => self.fn_min(args, cell),
Function::Product => self.fn_product(args, cell),
Function::Rand => self.fn_rand(args, cell),
Function::Randbetween => self.fn_randbetween(args, cell),
Function::Round => self.fn_round(args, cell),
Function::Rounddown => self.fn_rounddown(args, cell),
Function::Roundup => self.fn_roundup(args, cell),
Function::Sum => self.fn_sum(args, cell),
Function::Sumif => self.fn_sumif(args, cell),
Function::Sumifs => self.fn_sumifs(args, cell),
// Lookup and Reference
Function::Choose => self.fn_choose(args, cell),
Function::Column => self.fn_column(args, cell),
Function::Columns => self.fn_columns(args, cell),
Function::Index => self.fn_index(args, cell),
Function::Indirect => self.fn_indirect(args, cell),
Function::Hlookup => self.fn_hlookup(args, cell),
Function::Lookup => self.fn_lookup(args, cell),
Function::Match => self.fn_match(args, cell),
Function::Offset => self.fn_offset(args, cell),
Function::Row => self.fn_row(args, cell),
Function::Rows => self.fn_rows(args, cell),
Function::Vlookup => self.fn_vlookup(args, cell),
Function::Xlookup => self.fn_xlookup(args, cell),
// Text
Function::Concatenate => self.fn_concatenate(args, cell),
Function::Exact => self.fn_exact(args, cell),
Function::Value => self.fn_value(args, cell),
Function::T => self.fn_t(args, cell),
Function::Valuetotext => self.fn_valuetotext(args, cell),
Function::Concat => self.fn_concat(args, cell),
Function::Find => self.fn_find(args, cell),
Function::Left => self.fn_left(args, cell),
Function::Len => self.fn_len(args, cell),
Function::Lower => self.fn_lower(args, cell),
Function::Mid => self.fn_mid(args, cell),
Function::Right => self.fn_right(args, cell),
Function::Search => self.fn_search(args, cell),
Function::Text => self.fn_text(args, cell),
Function::Trim => self.fn_trim(args, cell),
Function::Upper => self.fn_upper(args, cell),
// Information
Function::Isnumber => self.fn_isnumber(args, cell),
Function::Isnontext => self.fn_isnontext(args, cell),
Function::Istext => self.fn_istext(args, cell),
Function::Islogical => self.fn_islogical(args, cell),
Function::Isblank => self.fn_isblank(args, cell),
Function::Iserr => self.fn_iserr(args, cell),
Function::Iserror => self.fn_iserror(args, cell),
Function::Isna => self.fn_isna(args, cell),
Function::Na => CalcResult::new_error(Error::NA, cell, "".to_string()),
Function::Isref => self.fn_isref(args, cell),
Function::Isodd => self.fn_isodd(args, cell),
Function::Iseven => self.fn_iseven(args, cell),
Function::ErrorType => self.fn_errortype(args, cell),
Function::Isformula => self.fn_isformula(args, cell),
Function::Type => self.fn_type(args, cell),
Function::Sheet => self.fn_sheet(args, cell),
// Statistical
Function::Average => self.fn_average(args, cell),
Function::Averagea => self.fn_averagea(args, cell),
Function::Averageif => self.fn_averageif(args, cell),
Function::Averageifs => self.fn_averageifs(args, cell),
Function::Count => self.fn_count(args, cell),
Function::Counta => self.fn_counta(args, cell),
Function::Countblank => self.fn_countblank(args, cell),
Function::Countif => self.fn_countif(args, cell),
Function::Countifs => self.fn_countifs(args, cell),
Function::Maxifs => self.fn_maxifs(args, cell),
Function::Minifs => self.fn_minifs(args, cell),
// Date and Time
Function::Year => self.fn_year(args, cell),
Function::Day => self.fn_day(args, cell),
Function::Eomonth => self.fn_eomonth(args, cell),
Function::Month => self.fn_month(args, cell),
Function::Date => self.fn_date(args, cell),
Function::Edate => self.fn_edate(args, cell),
Function::Today => self.fn_today(args, cell),
Function::Now => self.fn_now(args, cell),
// Financial
Function::Pmt => self.fn_pmt(args, cell),
Function::Pv => self.fn_pv(args, cell),
Function::Rate => self.fn_rate(args, cell),
Function::Nper => self.fn_nper(args, cell),
Function::Fv => self.fn_fv(args, cell),
Function::Ppmt => self.fn_ppmt(args, cell),
Function::Ipmt => self.fn_ipmt(args, cell),
Function::Npv => self.fn_npv(args, cell),
Function::Mirr => self.fn_mirr(args, cell),
Function::Irr => self.fn_irr(args, cell),
Function::Xirr => self.fn_xirr(args, cell),
Function::Xnpv => self.fn_xnpv(args, cell),
Function::Rept => self.fn_rept(args, cell),
Function::Textafter => self.fn_textafter(args, cell),
Function::Textbefore => self.fn_textbefore(args, cell),
Function::Textjoin => self.fn_textjoin(args, cell),
Function::Substitute => self.fn_substitute(args, cell),
Function::Ispmt => self.fn_ispmt(args, cell),
Function::Rri => self.fn_rri(args, cell),
Function::Sln => self.fn_sln(args, cell),
Function::Syd => self.fn_syd(args, cell),
Function::Nominal => self.fn_nominal(args, cell),
Function::Effect => self.fn_effect(args, cell),
Function::Pduration => self.fn_pduration(args, cell),
Function::Tbillyield => self.fn_tbillyield(args, cell),
Function::Tbillprice => self.fn_tbillprice(args, cell),
Function::Tbilleq => self.fn_tbilleq(args, cell),
Function::Dollarde => self.fn_dollarde(args, cell),
Function::Dollarfr => self.fn_dollarfr(args, cell),
Function::Ddb => self.fn_ddb(args, cell),
Function::Db => self.fn_db(args, cell),
Function::Cumprinc => self.fn_cumprinc(args, cell),
Function::Cumipmt => self.fn_cumipmt(args, cell),
// Engineering
Function::Besseli => self.fn_besseli(args, cell),
Function::Besselj => self.fn_besselj(args, cell),
Function::Besselk => self.fn_besselk(args, cell),
Function::Bessely => self.fn_bessely(args, cell),
Function::Erf => self.fn_erf(args, cell),
Function::ErfPrecise => self.fn_erfprecise(args, cell),
Function::Erfc => self.fn_erfc(args, cell),
Function::ErfcPrecise => self.fn_erfcprecise(args, cell),
Function::Bin2dec => self.fn_bin2dec(args, cell),
Function::Bin2hex => self.fn_bin2hex(args, cell),
Function::Bin2oct => self.fn_bin2oct(args, cell),
Function::Dec2Bin => self.fn_dec2bin(args, cell),
Function::Dec2hex => self.fn_dec2hex(args, cell),
Function::Dec2oct => self.fn_dec2oct(args, cell),
Function::Hex2bin => self.fn_hex2bin(args, cell),
Function::Hex2dec => self.fn_hex2dec(args, cell),
Function::Hex2oct => self.fn_hex2oct(args, cell),
Function::Oct2bin => self.fn_oct2bin(args, cell),
Function::Oct2dec => self.fn_oct2dec(args, cell),
Function::Oct2hex => self.fn_oct2hex(args, cell),
Function::Bitand => self.fn_bitand(args, cell),
Function::Bitlshift => self.fn_bitlshift(args, cell),
Function::Bitor => self.fn_bitor(args, cell),
Function::Bitrshift => self.fn_bitrshift(args, cell),
Function::Bitxor => self.fn_bitxor(args, cell),
Function::Complex => self.fn_complex(args, cell),
Function::Imabs => self.fn_imabs(args, cell),
Function::Imaginary => self.fn_imaginary(args, cell),
Function::Imargument => self.fn_imargument(args, cell),
Function::Imconjugate => self.fn_imconjugate(args, cell),
Function::Imcos => self.fn_imcos(args, cell),
Function::Imcosh => self.fn_imcosh(args, cell),
Function::Imcot => self.fn_imcot(args, cell),
Function::Imcsc => self.fn_imcsc(args, cell),
Function::Imcsch => self.fn_imcsch(args, cell),
Function::Imdiv => self.fn_imdiv(args, cell),
Function::Imexp => self.fn_imexp(args, cell),
Function::Imln => self.fn_imln(args, cell),
Function::Imlog10 => self.fn_imlog10(args, cell),
Function::Imlog2 => self.fn_imlog2(args, cell),
Function::Impower => self.fn_impower(args, cell),
Function::Improduct => self.fn_improduct(args, cell),
Function::Imreal => self.fn_imreal(args, cell),
Function::Imsec => self.fn_imsec(args, cell),
Function::Imsech => self.fn_imsech(args, cell),
Function::Imsin => self.fn_imsin(args, cell),
Function::Imsinh => self.fn_imsinh(args, cell),
Function::Imsqrt => self.fn_imsqrt(args, cell),
Function::Imsub => self.fn_imsub(args, cell),
Function::Imsum => self.fn_imsum(args, cell),
Function::Imtan => self.fn_imtan(args, cell),
Function::Convert => self.fn_convert(args, cell),
Function::Delta => self.fn_delta(args, cell),
Function::Gestep => self.fn_gestep(args, cell),
Function::Subtotal => self.fn_subtotal(args, cell),
}
}
}

View File

@@ -0,0 +1,624 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::{
calc_result::{CalcResult, CellReference, Range},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
use super::util::build_criteria;
impl Model {
pub(crate) fn fn_average(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
let mut count = 0.0;
let mut sum = 0.0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Number(value) => {
count += 1.0;
sum += value;
}
CalcResult::Boolean(b) => {
if let Node::ReferenceKind { .. } = arg {
} else {
sum += if b { 1.0 } else { 0.0 };
count += 1.0;
}
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
for row in left.row..(right.row + 1) {
for column in left.column..(right.column + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Number(value) => {
count += 1.0;
sum += value;
}
error @ CalcResult::Error { .. } => return error,
CalcResult::Range { .. } => {
return CalcResult::new_error(
Error::ERROR,
cell,
"Unexpected Range".to_string(),
);
}
_ => {}
}
}
}
}
error @ CalcResult::Error { .. } => return error,
CalcResult::String(s) => {
if let Node::ReferenceKind { .. } = arg {
// Do nothing
} else if let Ok(t) = s.parse::<f64>() {
sum += t;
count += 1.0;
} else {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument cannot be cast into number".to_string(),
};
}
}
_ => {
// Ignore everything else
}
};
}
if count == 0.0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by Zero".to_string(),
};
}
CalcResult::Number(sum / count)
}
pub(crate) fn fn_averagea(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
let mut count = 0.0;
let mut sum = 0.0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
for row in left.row..(right.row + 1) {
for column in left.column..(right.column + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::String(_) => count += 1.0,
CalcResult::Number(value) => {
count += 1.0;
sum += value;
}
CalcResult::Boolean(b) => {
if b {
sum += 1.0;
}
count += 1.0;
}
error @ CalcResult::Error { .. } => return error,
CalcResult::Range { .. } => {
return CalcResult::new_error(
Error::ERROR,
cell,
"Unexpected Range".to_string(),
);
}
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
}
}
}
}
CalcResult::Number(value) => {
count += 1.0;
sum += value;
}
CalcResult::String(s) => {
if let Node::ReferenceKind { .. } = arg {
// Do nothing
count += 1.0;
} else if let Ok(t) = s.parse::<f64>() {
sum += t;
count += 1.0;
} else {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Argument cannot be cast into number".to_string(),
};
}
}
CalcResult::Boolean(b) => {
count += 1.0;
if b {
sum += 1.0;
}
}
error @ CalcResult::Error { .. } => return error,
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
};
}
if count == 0.0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by Zero".to_string(),
};
}
CalcResult::Number(sum / count)
}
pub(crate) fn fn_count(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
let mut result = 0.0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::Number(_) => {
result += 1.0;
}
CalcResult::Boolean(_) => {
if !matches!(arg, Node::ReferenceKind { .. }) {
result += 1.0;
}
}
CalcResult::String(s) => {
if !matches!(arg, Node::ReferenceKind { .. }) && s.parse::<f64>().is_ok() {
result += 1.0;
}
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
for row in left.row..(right.row + 1) {
for column in left.column..(right.column + 1) {
if let CalcResult::Number(_) = self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
result += 1.0;
}
}
}
}
_ => {
// Ignore everything else
}
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_counta(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.is_empty() {
return CalcResult::new_args_number_error(cell);
}
let mut result = 0.0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
for row in left.row..(right.row + 1) {
for column in left.column..(right.column + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::EmptyCell | CalcResult::EmptyArg => {}
_ => {
result += 1.0;
}
}
}
}
}
_ => {
result += 1.0;
}
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_countblank(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
// COUNTBLANK requires only one argument
if args.len() != 1 {
return CalcResult::new_args_number_error(cell);
}
let mut result = 0.0;
for arg in args {
match self.evaluate_node_in_context(arg, cell) {
CalcResult::EmptyCell | CalcResult::EmptyArg => result += 1.0,
CalcResult::String(s) => {
if s.is_empty() {
result += 1.0
}
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
for row in left.row..(right.row + 1) {
for column in left.column..(right.column + 1) {
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::EmptyCell | CalcResult::EmptyArg => result += 1.0,
CalcResult::String(s) => {
if s.is_empty() {
result += 1.0
}
}
_ => {}
}
}
}
}
_ => {}
};
}
CalcResult::Number(result)
}
pub(crate) fn fn_countif(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 2 {
let arguments = vec![args[0].clone(), args[1].clone()];
self.fn_countifs(&arguments, cell)
} else {
CalcResult::new_args_number_error(cell)
}
}
/// AVERAGEIF(criteria_range, criteria, [average_range])
/// if average_rage is missing then criteria_range will be used
pub(crate) fn fn_averageif(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() == 2 {
let arguments = vec![args[0].clone(), args[0].clone(), args[1].clone()];
self.fn_averageifs(&arguments, cell)
} else if args.len() == 3 {
let arguments = vec![args[2].clone(), args[0].clone(), args[1].clone()];
self.fn_averageifs(&arguments, cell)
} else {
CalcResult::new_args_number_error(cell)
}
}
// FIXME: This function shares a lot of code with apply_ifs. Can we merge them?
pub(crate) fn fn_countifs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let args_count = args.len();
if args_count < 2 || args_count % 2 == 1 {
return CalcResult::new_args_number_error(cell);
}
let case_count = args_count / 2;
// NB: this is a beautiful example of the borrow checker
// The order of these two definitions cannot be swapped.
let mut criteria = Vec::new();
let mut fn_criteria = Vec::new();
let ranges = &mut Vec::new();
for case_index in 0..case_count {
let criterion = self.evaluate_node_in_context(&args[case_index * 2 + 1], cell);
criteria.push(criterion);
// NB: We cannot do:
// fn_criteria.push(build_criteria(&criterion));
// because criterion doesn't live long enough
let result = self.evaluate_node_in_context(&args[case_index * 2], cell);
if result.is_error() {
return result;
}
if let CalcResult::Range { left, right } = result {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
// TODO test ranges are of the same size as sum_range
ranges.push(Range { left, right });
} else {
return CalcResult::new_error(Error::VALUE, cell, "Expected a range".to_string());
}
}
for criterion in criteria.iter() {
fn_criteria.push(build_criteria(criterion));
}
let mut total = 0.0;
let first_range = &ranges[0];
let left_row = first_range.left.row;
let left_column = first_range.left.column;
let right_row = first_range.right.row;
let right_column = first_range.right.column;
let dimension = self
.workbook
.worksheet(first_range.left.sheet)
.expect("Sheet expected during evaluation.")
.dimension();
let max_row = dimension.max_row;
let max_column = dimension.max_column;
let open_row = left_row == 1 && right_row == LAST_ROW;
let open_column = left_column == 1 && right_column == LAST_COLUMN;
for row in left_row..right_row + 1 {
if open_row && row > max_row {
// If the row is larger than the max row in the sheet then all cells are empty.
// We compute it only once
let mut is_true = true;
for fn_criterion in fn_criteria.iter() {
if !fn_criterion(&CalcResult::EmptyCell) {
is_true = false;
break;
}
}
if is_true {
total += ((LAST_ROW - max_row) * (right_column - left_column + 1)) as f64;
}
break;
}
for column in left_column..right_column + 1 {
if open_column && column > max_column {
// If the column is larger than the max column in the sheet then all cells are empty.
// We compute it only once
let mut is_true = true;
for fn_criterion in fn_criteria.iter() {
if !fn_criterion(&CalcResult::EmptyCell) {
is_true = false;
break;
}
}
if is_true {
total += (LAST_COLUMN - max_column) as f64;
}
break;
}
let mut is_true = true;
for case_index in 0..case_count {
// We check if value in range n meets criterion n
let range = &ranges[case_index];
let fn_criterion = &fn_criteria[case_index];
let value = self.evaluate_cell(CellReference {
sheet: range.left.sheet,
row: range.left.row + row - first_range.left.row,
column: range.left.column + column - first_range.left.column,
});
if !fn_criterion(&value) {
is_true = false;
break;
}
}
if is_true {
total += 1.0;
}
}
}
CalcResult::Number(total)
}
pub(crate) fn apply_ifs<F>(
&mut self,
args: &[Node],
cell: CellReference,
mut apply: F,
) -> Result<(), CalcResult>
where
F: FnMut(f64),
{
let args_count = args.len();
if args_count < 3 || args_count % 2 == 0 {
return Err(CalcResult::new_args_number_error(cell));
}
let arg_0 = self.evaluate_node_in_context(&args[0], cell);
if arg_0.is_error() {
return Err(arg_0);
}
let sum_range = if let CalcResult::Range { left, right } = arg_0 {
if left.sheet != right.sheet {
return Err(CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
));
}
Range { left, right }
} else {
return Err(CalcResult::new_error(
Error::VALUE,
cell,
"Expected a range".to_string(),
));
};
let case_count = (args_count - 1) / 2;
// NB: this is a beautiful example of the borrow checker
// The order of these two definitions cannot be swapped.
let mut criteria = Vec::new();
let mut fn_criteria = Vec::new();
let ranges = &mut Vec::new();
for case_index in 1..=case_count {
let criterion = self.evaluate_node_in_context(&args[case_index * 2], cell);
// NB: criterion might be an error. That's ok
criteria.push(criterion);
// NB: We cannot do:
// fn_criteria.push(build_criteria(&criterion));
// because criterion doesn't live long enough
let result = self.evaluate_node_in_context(&args[case_index * 2 - 1], cell);
if result.is_error() {
return Err(result);
}
if let CalcResult::Range { left, right } = result {
if left.sheet != right.sheet {
return Err(CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
));
}
// TODO test ranges are of the same size as sum_range
ranges.push(Range { left, right });
} else {
return Err(CalcResult::new_error(
Error::VALUE,
cell,
"Expected a range".to_string(),
));
}
}
for criterion in criteria.iter() {
fn_criteria.push(build_criteria(criterion));
}
let left_row = sum_range.left.row;
let left_column = sum_range.left.column;
let mut right_row = sum_range.right.row;
let mut right_column = sum_range.right.column;
if left_row == 1 && right_row == LAST_ROW {
right_row = self
.workbook
.worksheet(sum_range.left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_row;
}
if left_column == 1 && right_column == LAST_COLUMN {
right_column = self
.workbook
.worksheet(sum_range.left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_column;
}
for row in left_row..right_row + 1 {
for column in left_column..right_column + 1 {
let mut is_true = true;
for case_index in 0..case_count {
// We check if value in range n meets criterion n
let range = &ranges[case_index];
let fn_criterion = &fn_criteria[case_index];
let value = self.evaluate_cell(CellReference {
sheet: range.left.sheet,
row: range.left.row + row - sum_range.left.row,
column: range.left.column + column - sum_range.left.column,
});
if !fn_criterion(&value) {
is_true = false;
break;
}
}
if is_true {
let v = self.evaluate_cell(CellReference {
sheet: sum_range.left.sheet,
row,
column,
});
match v {
CalcResult::Number(n) => apply(n),
CalcResult::Error { .. } => return Err(v),
_ => {}
}
}
}
}
Ok(())
}
pub(crate) fn fn_averageifs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut total = 0.0;
let mut count = 0.0;
let average = |value: f64| {
total += value;
count += 1.0;
};
if let Err(e) = self.apply_ifs(args, cell, average) {
return e;
}
if count == 0.0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "division by 0".to_string(),
};
}
CalcResult::Number(total / count)
}
pub(crate) fn fn_minifs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut min = f64::INFINITY;
let apply_min = |value: f64| min = value.min(min);
if let Err(e) = self.apply_ifs(args, cell, apply_min) {
return e;
}
if min.is_infinite() {
min = 0.0;
}
CalcResult::Number(min)
}
pub(crate) fn fn_maxifs(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
let mut max = -f64::INFINITY;
let apply_max = |value: f64| max = value.max(max);
if let Err(e) = self.apply_ifs(args, cell, apply_max) {
return e;
}
if max.is_infinite() {
max = 0.0;
}
CalcResult::Number(max)
}
}

View File

@@ -0,0 +1,584 @@
use crate::{
calc_result::{CalcResult, CellReference},
expressions::{
parser::{parse_range, Node},
token::Error,
},
functions::Function,
model::Model,
};
/// Excel has a complicated way of filtering + hidden rows
/// As a first a approximation a table can either have filtered rows or hidden rows, but not both.
/// Internally hey both will be marked as hidden rows. Hidden rows
/// The behaviour is the same for SUBTOTAL(100s,) => ignore those
/// But changes for the SUBTOTAL(1-11, ) those ignore filtered but take hidden into account.
/// In Excel filters are non-dynamic. Once you apply filters in a table (say value in column 1 should > 20) they
/// stay that way, even if you change the value of the values in the table after the fact.
/// If you try to hide rows in a table with filtered rows they will behave as if filtered
/// // Also subtotals ignore subtotals
///
#[derive(PartialEq)]
enum SubTotalMode {
Full,
SkipHidden,
}
#[derive(PartialEq, Debug)]
pub enum CellTableStatus {
Normal,
Hidden,
Filtered,
}
impl Model {
fn get_table_for_cell(&self, sheet_index: u32, row: i32, column: i32) -> bool {
let worksheet = match self.workbook.worksheet(sheet_index) {
Ok(ws) => ws,
Err(_) => return false,
};
for table in self.workbook.tables.values() {
if worksheet.name != table.sheet_name {
continue;
}
// (column, row, column, row)
if let Ok((column1, row1, column2, row2)) = parse_range(&table.reference) {
if ((column >= column1) && (column <= column2)) && ((row >= row1) && (row <= row2))
{
return table.has_filters;
}
}
}
false
}
fn cell_hidden_status(&self, sheet_index: u32, row: i32, column: i32) -> CellTableStatus {
let worksheet = self.workbook.worksheet(sheet_index).expect("");
let mut hidden = false;
for row_style in &worksheet.rows {
if row_style.r == row {
hidden = row_style.hidden;
break;
}
}
if !hidden {
return CellTableStatus::Normal;
}
// The row is hidden we need to know if the table has filters
if self.get_table_for_cell(sheet_index, row, column) {
CellTableStatus::Filtered
} else {
CellTableStatus::Hidden
}
}
// FIXME(TD): This is too much
fn cell_is_subtotal(&self, sheet_index: u32, row: i32, column: i32) -> bool {
let row_data = match self.workbook.worksheets[sheet_index as usize]
.sheet_data
.get(&row)
{
Some(r) => r,
None => return false,
};
let cell = match row_data.get(&column) {
Some(c) => c,
None => {
return false;
}
};
match cell.get_formula() {
Some(f) => {
let node = &self.parsed_formulas[sheet_index as usize][f as usize];
matches!(
node,
Node::FunctionKind {
kind: Function::Subtotal,
args: _
}
)
}
None => false,
}
}
fn subtotal_get_values(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> Result<Vec<f64>, CalcResult> {
let mut result: Vec<f64> = Vec::new();
for arg in args {
match arg {
Node::FunctionKind {
kind: Function::Subtotal,
args: _,
} => {
// skip
}
_ => {
match self.evaluate_node_with_reference(arg, cell) {
CalcResult::String(_) | CalcResult::Boolean(_) => {
// Skip
}
CalcResult::Number(f) => result.push(f),
error @ CalcResult::Error { .. } => {
return Err(error);
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return Err(CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
));
}
// We are not expecting subtotal to have open ranges
let row1 = left.row;
let row2 = right.row;
let column1 = left.column;
let column2 = right.column;
for row in row1..=row2 {
let cell_status = self.cell_hidden_status(left.sheet, row, column1);
if cell_status == CellTableStatus::Filtered {
continue;
}
if mode == SubTotalMode::SkipHidden
&& cell_status == CellTableStatus::Hidden
{
continue;
}
for column in column1..=column2 {
if self.cell_is_subtotal(left.sheet, row, column) {
continue;
}
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::Number(value) => {
result.push(value);
}
error @ CalcResult::Error { .. } => return Err(error),
_ => {
// We ignore booleans and strings
}
}
}
}
}
CalcResult::EmptyCell | CalcResult::EmptyArg => result.push(0.0),
}
}
}
}
Ok(result)
}
pub(crate) fn fn_subtotal(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() < 2 {
return CalcResult::new_args_number_error(cell);
}
let value = match self.get_number(&args[0], cell) {
Ok(f) => f.trunc() as i32,
Err(s) => return s,
};
match value {
1 => self.subtotal_average(&args[1..], cell, SubTotalMode::Full),
2 => self.subtotal_count(&args[1..], cell, SubTotalMode::Full),
3 => self.subtotal_counta(&args[1..], cell, SubTotalMode::Full),
4 => self.subtotal_max(&args[1..], cell, SubTotalMode::Full),
5 => self.subtotal_min(&args[1..], cell, SubTotalMode::Full),
6 => self.subtotal_product(&args[1..], cell, SubTotalMode::Full),
7 => self.subtotal_stdevs(&args[1..], cell, SubTotalMode::Full),
8 => self.subtotal_stdevp(&args[1..], cell, SubTotalMode::Full),
9 => self.subtotal_sum(&args[1..], cell, SubTotalMode::Full),
10 => self.subtotal_vars(&args[1..], cell, SubTotalMode::Full),
11 => self.subtotal_varp(&args[1..], cell, SubTotalMode::Full),
101 => self.subtotal_average(&args[1..], cell, SubTotalMode::SkipHidden),
102 => self.subtotal_count(&args[1..], cell, SubTotalMode::SkipHidden),
103 => self.subtotal_counta(&args[1..], cell, SubTotalMode::SkipHidden),
104 => self.subtotal_max(&args[1..], cell, SubTotalMode::SkipHidden),
105 => self.subtotal_min(&args[1..], cell, SubTotalMode::SkipHidden),
106 => self.subtotal_product(&args[1..], cell, SubTotalMode::SkipHidden),
107 => self.subtotal_stdevs(&args[1..], cell, SubTotalMode::SkipHidden),
108 => self.subtotal_stdevp(&args[1..], cell, SubTotalMode::SkipHidden),
109 => self.subtotal_sum(&args[1..], cell, SubTotalMode::SkipHidden),
110 => self.subtotal_vars(&args[1..], cell, SubTotalMode::Full),
111 => self.subtotal_varp(&args[1..], cell, SubTotalMode::Full),
_ => CalcResult::new_error(
Error::VALUE,
cell,
format!("Invalid value for SUBTOTAL: {value}"),
),
}
}
fn subtotal_vars(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
let l = values.len();
for value in &values {
result += value;
}
if l < 2 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by 0!".to_string(),
};
}
// average
let average = result / (l as f64);
let mut result = 0.0;
for value in &values {
result += (value - average).powi(2) / (l as f64 - 1.0)
}
CalcResult::Number(result)
}
fn subtotal_varp(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
let l = values.len();
for value in &values {
result += value;
}
if l == 0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by 0!".to_string(),
};
}
// average
let average = result / (l as f64);
let mut result = 0.0;
for value in &values {
result += (value - average).powi(2) / (l as f64)
}
CalcResult::Number(result)
}
fn subtotal_stdevs(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
let l = values.len();
for value in &values {
result += value;
}
if l < 2 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by 0!".to_string(),
};
}
// average
let average = result / (l as f64);
let mut result = 0.0;
for value in &values {
result += (value - average).powi(2) / (l as f64 - 1.0)
}
CalcResult::Number(result.sqrt())
}
fn subtotal_stdevp(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
let l = values.len();
for value in &values {
result += value;
}
if l == 0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by 0!".to_string(),
};
}
// average
let average = result / (l as f64);
let mut result = 0.0;
for value in &values {
result += (value - average).powi(2) / (l as f64)
}
CalcResult::Number(result.sqrt())
}
fn subtotal_counta(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let mut counta = 0;
for arg in args {
match arg {
Node::FunctionKind {
kind: Function::Subtotal,
args: _,
} => {
// skip
}
_ => {
match self.evaluate_node_with_reference(arg, cell) {
CalcResult::EmptyCell | CalcResult::EmptyArg => {
// skip
}
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
// We are not expecting subtotal to have open ranges
let row1 = left.row;
let row2 = right.row;
let column1 = left.column;
let column2 = right.column;
for row in row1..=row2 {
let cell_status = self.cell_hidden_status(left.sheet, row, column1);
if cell_status == CellTableStatus::Filtered {
continue;
}
if mode == SubTotalMode::SkipHidden
&& cell_status == CellTableStatus::Hidden
{
continue;
}
for column in column1..=column2 {
if self.cell_is_subtotal(left.sheet, row, column) {
continue;
}
match self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
}) {
CalcResult::EmptyCell | CalcResult::EmptyArg => {
// skip
}
_ => counta += 1,
}
}
}
}
CalcResult::String(_)
| CalcResult::Number(_)
| CalcResult::Boolean(_)
| CalcResult::Error { .. } => counta += 1,
}
}
}
}
CalcResult::Number(counta as f64)
}
fn subtotal_count(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let mut count = 0;
for arg in args {
match arg {
Node::FunctionKind {
kind: Function::Subtotal,
args: _,
} => {
// skip
}
_ => {
match self.evaluate_node_with_reference(arg, cell) {
CalcResult::Range { left, right } => {
if left.sheet != right.sheet {
return CalcResult::new_error(
Error::VALUE,
cell,
"Ranges are in different sheets".to_string(),
);
}
// We are not expecting subtotal to have open ranges
let row1 = left.row;
let row2 = right.row;
let column1 = left.column;
let column2 = right.column;
for row in row1..=row2 {
let cell_status = self.cell_hidden_status(left.sheet, row, column1);
if cell_status == CellTableStatus::Filtered {
continue;
}
if mode == SubTotalMode::SkipHidden
&& cell_status == CellTableStatus::Hidden
{
continue;
}
for column in column1..=column2 {
if self.cell_is_subtotal(left.sheet, row, column) {
continue;
}
if let CalcResult::Number(_) =
self.evaluate_cell(CellReference {
sheet: left.sheet,
row,
column,
})
{
count += 1;
}
}
}
}
// This hasn't been tested
CalcResult::Number(_) => count += 1,
_ => {}
}
}
}
}
CalcResult::Number(count as f64)
}
fn subtotal_average(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
let l = values.len();
for value in values {
result += value;
}
if l == 0 {
return CalcResult::Error {
error: Error::DIV,
origin: cell,
message: "Division by 0!".to_string(),
};
}
CalcResult::Number(result / (l as f64))
}
fn subtotal_sum(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 0.0;
for value in values {
result += value;
}
CalcResult::Number(result)
}
fn subtotal_product(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = 1.0;
for value in values {
result *= value;
}
CalcResult::Number(result)
}
fn subtotal_max(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = f64::NAN;
for value in values {
result = value.max(result);
}
if result.is_nan() {
return CalcResult::Number(0.0);
}
CalcResult::Number(result)
}
fn subtotal_min(
&mut self,
args: &[Node],
cell: CellReference,
mode: SubTotalMode,
) -> CalcResult {
let values = match self.subtotal_get_values(args, cell, mode) {
Ok(s) => s,
Err(s) => return s,
};
let mut result = f64::NAN;
for value in values {
result = value.min(result);
}
if result.is_nan() {
return CalcResult::Number(0.0);
}
CalcResult::Number(result)
}
}

1104
base/src/functions/text.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,196 @@
pub(crate) enum Case {
Sensitive,
Insensitive,
}
/// Finds the text after the occurrence instance of 'search_for' in text
pub(crate) fn text_after(
text: &str,
delimiter: &str,
instance_num: i32,
match_mode: Case,
) -> Option<String> {
if let Some((_, right)) = match_text(text, delimiter, instance_num, match_mode) {
return Some(text[right..].to_string());
};
None
}
pub(crate) fn text_before(
text: &str,
delimiter: &str,
instance_num: i32,
match_mode: Case,
) -> Option<String> {
if let Some((left, _)) = match_text(text, delimiter, instance_num, match_mode) {
return Some(text[..left].to_string());
};
None
}
pub(crate) fn substitute(text: &str, old_text: &str, new_text: &str, instance_num: i32) -> String {
if let Some((left, right)) = match_text(text, old_text, instance_num, Case::Sensitive) {
return format!("{}{}{}", &text[..left], new_text, &text[right..]);
};
text.to_string()
}
fn match_text(
text: &str,
delimiter: &str,
instance_num: i32,
match_mode: Case,
) -> Option<(usize, usize)> {
match match_mode {
Case::Sensitive => {
if instance_num > 0 {
text_sensitive(text, delimiter, instance_num)
} else {
text_sensitive_reverse(text, delimiter, -instance_num)
}
}
Case::Insensitive => {
if instance_num > 0 {
text_sensitive(
&text.to_lowercase(),
&delimiter.to_lowercase(),
instance_num,
)
} else {
text_sensitive_reverse(
&text.to_lowercase(),
&delimiter.to_lowercase(),
-instance_num,
)
}
}
}
}
fn text_sensitive(text: &str, delimiter: &str, instance_num: i32) -> Option<(usize, usize)> {
let mut byte_index = 0;
let mut local_index = 1;
// delimiter length in bytes
let delimiter_len = delimiter.len();
for c in text.chars() {
if text[byte_index..].starts_with(delimiter) {
if local_index == instance_num {
return Some((byte_index, byte_index + delimiter_len));
} else {
local_index += 1;
}
}
byte_index += c.len_utf8();
}
None
}
fn text_sensitive_reverse(
text: &str,
delimiter: &str,
instance_num: i32,
) -> Option<(usize, usize)> {
let text_len = text.len();
let mut byte_index = text_len;
let mut local_index = 1;
let delimiter_len = delimiter.len();
for c in text.chars().rev() {
if text[byte_index..].starts_with(delimiter) {
if local_index == instance_num {
return Some((byte_index, byte_index + delimiter_len));
} else {
local_index += 1;
}
}
byte_index -= c.len_utf8();
}
None
}
#[cfg(test)]
mod tests {
use crate::functions::text_util::Case;
use super::{text_after, text_before};
#[test]
fn test_text_after_sensitive() {
assert_eq!(
text_after("One element", "ele", 1, Case::Sensitive),
Some("ment".to_string())
);
assert_eq!(
text_after("One element", "e", 1, Case::Sensitive),
Some(" element".to_string())
);
assert_eq!(
text_after("One element", "e", 4, Case::Sensitive),
Some("nt".to_string())
);
assert_eq!(text_after("One element", "e", 5, Case::Sensitive), None);
assert_eq!(
text_after("長壽相等!", "", 1, Case::Sensitive),
Some("等!".to_string())
);
}
#[test]
fn test_text_before_sensitive() {
assert_eq!(
text_before("One element", "ele", 1, Case::Sensitive),
Some("One ".to_string())
);
assert_eq!(
text_before("One element", "e", 1, Case::Sensitive),
Some("On".to_string())
);
assert_eq!(
text_before("One element", "e", 4, Case::Sensitive),
Some("One elem".to_string())
);
assert_eq!(text_before("One element", "e", 5, Case::Sensitive), None);
assert_eq!(
text_before("長壽相等!", "", 1, Case::Sensitive),
Some("長壽".to_string())
);
}
#[test]
fn test_text_after_insensitive() {
assert_eq!(
text_after("One element", "eLe", 1, Case::Insensitive),
Some("ment".to_string())
);
assert_eq!(
text_after("One element", "E", 1, Case::Insensitive),
Some(" element".to_string())
);
assert_eq!(
text_after("One element", "E", 4, Case::Insensitive),
Some("nt".to_string())
);
assert_eq!(text_after("One element", "E", 5, Case::Insensitive), None);
assert_eq!(
text_after("長壽相等!", "", 1, Case::Insensitive),
Some("等!".to_string())
);
}
#[test]
fn test_text_before_insensitive() {
assert_eq!(
text_before("One element", "eLe", 1, Case::Insensitive),
Some("One ".to_string())
);
assert_eq!(
text_before("One element", "E", 1, Case::Insensitive),
Some("On".to_string())
);
assert_eq!(
text_before("One element", "E", 4, Case::Insensitive),
Some("One elem".to_string())
);
assert_eq!(text_before("One element", "E", 5, Case::Insensitive), None);
assert_eq!(
text_before("長壽相等!", "", 1, Case::Insensitive),
Some("長壽".to_string())
);
}
}

401
base/src/functions/util.rs Normal file
View File

@@ -0,0 +1,401 @@
use regex::{escape, Regex};
use crate::{calc_result::CalcResult, expressions::token::is_english_error_string};
/// This test for exact match (modulo case).
/// * strings are not cast into bools or numbers
/// * empty cell is not cast into empty string or zero
pub(crate) fn values_are_equal(left: &CalcResult, right: &CalcResult) -> bool {
match (left, right) {
(CalcResult::Number(value1), CalcResult::Number(value2)) => {
if (value2 - value1).abs() < f64::EPSILON {
return true;
}
false
}
(CalcResult::String(value1), CalcResult::String(value2)) => {
let value1 = value1.to_uppercase();
let value2 = value2.to_uppercase();
value1 == value2
}
(CalcResult::Boolean(value1), CalcResult::Boolean(value2)) => value1 == value2,
(CalcResult::EmptyCell, CalcResult::EmptyCell) => true,
// NOTE: Errors and Ranges are not covered
(_, _) => false,
}
}
/// In Excel there are two ways of comparing cell values.
/// The old school comparison valid in formulas like D3 < D4 or HLOOKUP,... cast empty cells into empty strings or 0
/// For the new formulas like XLOOKUP or SORT an empty cell is always larger than anything else.
// ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE;
pub(crate) fn compare_values(left: &CalcResult, right: &CalcResult) -> i32 {
match (left, right) {
(CalcResult::Number(value1), CalcResult::Number(value2)) => {
if (value2 - value1).abs() < f64::EPSILON {
return 0;
}
if value1 < value2 {
return -1;
}
1
}
(CalcResult::Number(_value1), CalcResult::String(_value2)) => -1,
(CalcResult::Number(_value1), CalcResult::Boolean(_value2)) => -1,
(CalcResult::String(value1), CalcResult::String(value2)) => {
let value1 = value1.to_uppercase();
let value2 = value2.to_uppercase();
match value1.cmp(&value2) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}
}
(CalcResult::String(_value1), CalcResult::Boolean(_value2)) => -1,
(CalcResult::Boolean(value1), CalcResult::Boolean(value2)) => {
if value1 == value2 {
return 0;
}
if *value1 {
return 1;
}
-1
}
(CalcResult::EmptyCell, CalcResult::String(_value2)) => {
compare_values(&CalcResult::String("".to_string()), right)
}
(CalcResult::String(_value1), CalcResult::EmptyCell) => {
compare_values(left, &CalcResult::String("".to_string()))
}
(CalcResult::EmptyCell, CalcResult::Number(_value2)) => {
compare_values(&CalcResult::Number(0.0), right)
}
(CalcResult::Number(_value1), CalcResult::EmptyCell) => {
compare_values(left, &CalcResult::Number(0.0))
}
(CalcResult::EmptyCell, CalcResult::EmptyCell) => 0,
// NOTE: Errors and Ranges are not covered
(_, _) => 1,
}
}
/// We convert an Excel wildcard into a Rust (Perl family) regex
pub(crate) fn from_wildcard_to_regex(
wildcard: &str,
exact: bool,
) -> Result<regex::Regex, regex::Error> {
// 1. Escape all
let reg = &escape(wildcard);
// 2. We convert the escaped '?' into '.' (matches a single character)
let reg = &reg.replace("\\?", ".");
// 3. We convert the escaped '*' into '.*' (matches anything)
let reg = &reg.replace("\\*", ".*");
// 4. We send '\\~\\~' to '??' that is an unescaped regular expression, therefore cannot be in reg
let reg = &reg.replace("\\~\\~", "??");
// 5. If the escaped and converted '*' is preceded by '~' then it's a raw '*'
let reg = &reg.replace("\\~.*", "\\*");
// 6. If the escaped and converted '.' is preceded by '~' then it's a raw '?'
let reg = &reg.replace("\\~.", "\\?");
// '~' is used in Excel to escape any other character.
// So ~x goes to x (whatever x is)
// 7. Remove all the others '\\~d' --> 'd'
let reg = &reg.replace("\\~", "");
// 8. Put back the '\\~\\~' as '\\~'
let reg = &reg.replace("??", "\\~");
// And we have a valid Perl regex! (As Kim Kardashian said before me: "I know, right?")
if exact {
return Regex::new(&format!("^{}$", reg));
}
Regex::new(reg)
}
/// NUMBERS ///
///*********///
// It could be either the number or a string representation of the number
// In the rest of the cases calc_result needs to be a number (cannot be the string "23", for instance)
fn result_is_equal_to_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => {
if (f - target).abs() < f64::EPSILON {
return true;
}
false
}
CalcResult::String(s) => {
if let Ok(f) = s.parse::<f64>() {
if (f - target).abs() < f64::EPSILON {
return true;
}
return false;
}
false
}
_ => false,
}
}
fn result_is_less_than_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => *f < target,
_ => false,
}
}
fn result_is_less_or_equal_than_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => *f <= target,
_ => false,
}
}
fn result_is_greater_than_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => *f > target,
_ => false,
}
}
fn result_is_greater_or_equal_than_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => *f >= target,
_ => false,
}
}
fn result_is_not_equal_to_number(calc_result: &CalcResult, target: f64) -> bool {
match calc_result {
CalcResult::Number(f) => {
if (f - target).abs() > f64::EPSILON {
return true;
}
false
}
_ => true,
}
}
/// BOOLEANS ///
///**********///
// Booleans have to be "exactly" equal
fn result_is_equal_to_bool(calc_result: &CalcResult, target: bool) -> bool {
match calc_result {
CalcResult::Boolean(f) => target == *f,
_ => false,
}
}
fn result_is_not_equal_to_bool(calc_result: &CalcResult, target: bool) -> bool {
match calc_result {
CalcResult::Boolean(f) => target != *f,
_ => true,
}
}
/// STRINGS ///
///*********///
/// Note that strings are case insensitive. `target` must always be lower case.
pub(crate) fn result_matches_regex(calc_result: &CalcResult, reg: &Regex) -> bool {
match calc_result {
CalcResult::String(s) => reg.is_match(&s.to_lowercase()),
_ => false,
}
}
fn result_is_equal_to_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => {
if target == s.to_lowercase() {
return true;
}
false
}
CalcResult::EmptyCell => target.is_empty(),
_ => false,
}
}
fn result_is_not_equal_to_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => {
if target != s.to_lowercase() {
return true;
}
false
}
_ => false,
}
}
fn result_is_less_than_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => target.cmp(&s.to_lowercase()) == std::cmp::Ordering::Greater,
_ => false,
}
}
fn result_is_less_or_equal_than_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => {
let lower_case = &s.to_lowercase();
target.cmp(lower_case) == std::cmp::Ordering::Less || lower_case == target
}
_ => false,
}
}
fn result_is_greater_than_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => target.cmp(&s.to_lowercase()) == std::cmp::Ordering::Less,
_ => false,
}
}
fn result_is_greater_or_equal_than_string(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::String(s) => {
let lower_case = &s.to_lowercase();
target.cmp(lower_case) == std::cmp::Ordering::Greater || lower_case == target
}
_ => false,
}
}
/// ERRORS ///
///********///
fn result_is_equal_to_error(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::Error { error, .. } => target == error.to_string(),
_ => false,
}
}
fn result_is_not_equal_to_error(calc_result: &CalcResult, target: &str) -> bool {
match calc_result {
CalcResult::Error { error, .. } => target != error.to_string(),
_ => true,
}
}
/// EMPTY ///
///*******///
// Note that these two are not inverse of each other.
// In particular, you can never match an empty cell.
fn result_is_not_equal_to_empty(calc_result: &CalcResult) -> bool {
!matches!(calc_result, CalcResult::EmptyCell)
}
fn result_is_equal_to_empty(calc_result: &CalcResult) -> bool {
match calc_result {
CalcResult::Number(f) => (f - 0.0).abs() < f64::EPSILON,
_ => false,
}
}
/// This returns a function (closure) of signature fn(&CalcResult) -> bool
/// It is Boxed because it returns different closures, so the size cannot be known at compile time
/// The lifetime (a) of value has to be longer or equal to the lifetime of the returned closure
pub(crate) fn build_criteria<'a>(value: &'a CalcResult) -> Box<dyn Fn(&CalcResult) -> bool + 'a> {
match value {
CalcResult::String(s) => {
if let Some(v) = s.strip_prefix("<=") {
// TODO: I am not implementing <= ERROR or <= BOOLEAN
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_less_or_equal_than_number(x, f))
} else if v.is_empty() {
Box::new(move |_x| false)
} else {
Box::new(move |x| result_is_less_or_equal_than_string(x, &v.to_lowercase()))
}
} else if let Some(v) = s.strip_prefix(">=") {
// TODO: I am not implementing >= ERROR or >= BOOLEAN
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_greater_or_equal_than_number(x, f))
} else if v.is_empty() {
Box::new(move |_x| false)
} else {
Box::new(move |x| result_is_greater_or_equal_than_string(x, &v.to_lowercase()))
}
} else if let Some(v) = s.strip_prefix("<>") {
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_not_equal_to_number(x, f))
} else if let Ok(b) = v.to_lowercase().parse::<bool>() {
Box::new(move |x| result_is_not_equal_to_bool(x, b))
} else if is_english_error_string(v) {
Box::new(move |x| result_is_not_equal_to_error(x, v))
} else if v.contains('*') || v.contains('?') {
if let Ok(reg) = from_wildcard_to_regex(&v.to_lowercase(), true) {
Box::new(move |x| !result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else if v.is_empty() {
Box::new(result_is_not_equal_to_empty)
} else {
Box::new(move |x| result_is_not_equal_to_string(x, &v.to_lowercase()))
}
} else if let Some(v) = s.strip_prefix('<') {
// TODO: I am not implementing < ERROR or < BOOLEAN
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_less_than_number(x, f))
} else if v.is_empty() {
Box::new(move |_x| false)
} else {
Box::new(move |x| result_is_less_than_string(x, &v.to_lowercase()))
}
} else if let Some(v) = s.strip_prefix('>') {
// TODO: I am not implementing > ERROR or > BOOLEAN
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_greater_than_number(x, f))
} else if v.is_empty() {
Box::new(move |_x| false)
} else {
Box::new(move |x| result_is_greater_than_string(x, &v.to_lowercase()))
}
} else {
let v = if let Some(a) = s.strip_prefix('=') {
a
} else {
s
};
if let Ok(f) = v.parse::<f64>() {
Box::new(move |x| result_is_equal_to_number(x, f))
} else if let Ok(b) = v.to_lowercase().parse::<bool>() {
Box::new(move |x| result_is_equal_to_bool(x, b))
} else if is_english_error_string(v) {
Box::new(move |x| result_is_equal_to_error(x, v))
} else if v.contains('*') || v.contains('?') {
if let Ok(reg) = from_wildcard_to_regex(&v.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| result_is_equal_to_string(x, &v.to_lowercase()))
}
}
}
CalcResult::Number(target) => Box::new(move |x| result_is_equal_to_number(x, *target)),
CalcResult::Boolean(b) => Box::new(move |x| result_is_equal_to_bool(x, *b)),
CalcResult::Error { error, .. } => {
// An error will match an error (never a string that is an error)
Box::new(move |x| result_is_equal_to_error(x, &error.to_string()))
}
CalcResult::Range { left: _, right: _ } => {
// TODO: Implicit Intersection
Box::new(move |_x| false)
}
CalcResult::EmptyCell | CalcResult::EmptyArg => Box::new(result_is_equal_to_empty),
}
}

View File

@@ -0,0 +1,384 @@
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::{
calc_result::{CalcResult, CellReference},
expressions::parser::Node,
expressions::token::Error,
model::Model,
};
use super::{
binary_search::{
binary_search_descending_or_greater, binary_search_descending_or_smaller,
binary_search_or_greater, binary_search_or_smaller,
},
util::{compare_values, from_wildcard_to_regex, result_matches_regex},
};
#[derive(PartialEq)]
enum SearchMode {
StartAtFirstItem = 1,
StartAtLastItem = -1,
BinarySearchDescending = -2,
BinarySearchAscending = 2,
}
#[derive(PartialEq)]
enum MatchMode {
ExactMatchSmaller = -1,
ExactMatch = 0,
ExactMatchLarger = 1,
WildcardMatch = 2,
}
// lookup_value in array, match_mode search_mode
fn linear_search(
lookup_value: &CalcResult,
array: &[CalcResult],
search_mode: SearchMode,
match_mode: MatchMode,
) -> Option<usize> {
let length = array.len();
match match_mode {
MatchMode::ExactMatch => {
// exact match
for l in 0..length {
let index = if search_mode == SearchMode::StartAtFirstItem {
l
} else {
length - l - 1
};
let value = &array[index];
if compare_values(value, lookup_value) == 0 {
return Some(index);
}
}
return None;
}
MatchMode::ExactMatchSmaller | MatchMode::ExactMatchLarger => {
// exact match, if none found return the next smaller/larger item
let mut found_index = 0;
let mut approx = None;
let m_mode = match_mode as i32;
for l in 0..length {
let index = if search_mode == SearchMode::StartAtFirstItem {
l
} else {
length - l - 1
};
let value = &array[index];
let c = compare_values(value, lookup_value);
if c == 0 {
return Some(index);
} else if c == m_mode {
match approx {
None => {
approx = Some(value.clone());
found_index = index;
}
Some(ref p) => {
if compare_values(p, value) == m_mode {
approx = Some(value.clone());
found_index = index;
}
}
}
}
}
if approx.is_none() {
return None;
} else {
return Some(found_index);
}
}
MatchMode::WildcardMatch => {
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
if let CalcResult::String(s) = &lookup_value {
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
Box::new(move |x| result_matches_regex(x, &reg))
} else {
Box::new(move |_| false)
}
} else {
Box::new(move |x| compare_values(x, lookup_value) == 0)
};
for l in 0..length {
let index = if search_mode == SearchMode::StartAtFirstItem {
l
} else {
length - l - 1
};
let value = &array[index];
if result_matches(value) {
return Some(index);
}
}
}
}
None
}
impl Model {
/// The XLOOKUP function searches a range or an array, and then returns the item corresponding
/// to the first match it finds. If no match exists, then XLOOKUP can return the closest (approximate) match.
/// =XLOOKUP(lookup_value, lookup_array, return_array, [if_not_found], [match_mode], [search_mode])
///
/// lookup_array and return_array must be column or row arrays and of the same dimension.
/// Otherwise #VALUE! is returned
/// [if_not_found]
/// Where a valid match is not found, return the [if_not_found] text you supply.
/// If a valid match is not found, and [if_not_found] is missing, #N/A is returned.
///
/// [match_mode]
/// Specify the match type:
/// * 0 - Exact match. If none found, return #N/A. This is the default.
/// * -1 - Exact match. If none found, return the next smaller item.
/// * 1 - Exact match. If none found, return the next larger item.
/// * 2 - A wildcard match where *, ?, and ~ have special meaning.
///
/// [search_mode]
/// Specify the search mode to use:
/// * 1 - Perform a search starting at the first item. This is the default.
/// * -1 - Perform a reverse search starting at the last item.
/// * 2 - Perform a binary search that relies on lookup_array being sorted
/// in ascending order. If not sorted, invalid results will be returned.
/// * -2 - Perform a binary search that relies on lookup_array being sorted
/// in descending order. If not sorted, invalid results will be returned.
pub(crate) fn fn_xlookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
if args.len() < 3 || args.len() > 6 {
return CalcResult::new_args_number_error(cell);
}
let lookup_value = self.evaluate_node_in_context(&args[0], cell);
if lookup_value.is_error() {
return lookup_value;
}
// Get optional arguments
let if_not_found = if args.len() >= 4 {
let v = self.evaluate_node_in_context(&args[3], cell);
match v {
CalcResult::EmptyArg => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
},
_ => v,
}
} else {
// default
CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Not found".to_string(),
}
};
let match_mode = if args.len() >= 5 {
match self.get_number(&args[4], cell) {
Ok(c) => match c.floor() as i32 {
-1 => MatchMode::ExactMatchSmaller,
1 => MatchMode::ExactMatchLarger,
0 => MatchMode::ExactMatch,
2 => MatchMode::WildcardMatch,
_ => {
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Unexpected number".to_string(),
};
}
},
Err(s) => return s,
}
} else {
// default
MatchMode::ExactMatch
};
let search_mode = if args.len() == 6 {
match self.get_number(&args[5], cell) {
Ok(c) => match c.floor() as i32 {
1 => SearchMode::StartAtFirstItem,
-1 => SearchMode::StartAtLastItem,
-2 => SearchMode::BinarySearchDescending,
2 => SearchMode::BinarySearchAscending,
_ => {
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Unexpected number".to_string(),
};
}
},
Err(s) => return s,
}
} else {
// default
SearchMode::StartAtFirstItem
};
// lookup_array
match self.evaluate_node_in_context(&args[1], cell) {
CalcResult::Range { left, right } => {
let is_row_vector;
if left.row == right.row {
is_row_vector = false;
} else if left.column == right.column {
is_row_vector = true;
} else {
// second argument must be a vector
return CalcResult::Error {
error: Error::ERROR,
origin: cell,
message: "Second argument must be a vector".to_string(),
};
}
// return array
match self.evaluate_node_in_context(&args[2], cell) {
CalcResult::Range {
left: result_left,
right: result_right,
} => {
if result_right.row - result_left.row != right.row - left.row
|| result_right.column - result_left.column
!= right.column - left.column
{
return CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Arrays must be of the same size".to_string(),
};
}
let mut row2 = right.row;
let row1 = left.row;
let mut column2 = right.column;
let column1 = left.column;
if row1 == 1 && row2 == LAST_ROW {
row2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_row;
}
if column1 == 1 && column2 == LAST_COLUMN {
column2 = self
.workbook
.worksheet(left.sheet)
.expect("Sheet expected during evaluation.")
.dimension()
.max_column;
}
let left = CellReference {
sheet: left.sheet,
column: column1,
row: row1,
};
let right = CellReference {
sheet: left.sheet,
column: column2,
row: row2,
};
match search_mode {
SearchMode::StartAtFirstItem | SearchMode::StartAtLastItem => {
let array = &self.prepare_array(&left, &right, is_row_vector);
match linear_search(&lookup_value, array, search_mode, match_mode) {
Some(index) => {
let row_index =
if is_row_vector { index as i32 } else { 0 };
let column_index =
if is_row_vector { 0 } else { index as i32 };
self.evaluate_cell(CellReference {
sheet: result_left.sheet,
row: result_left.row + row_index,
column: result_left.column + column_index,
})
}
None => if_not_found,
}
}
SearchMode::BinarySearchAscending
| SearchMode::BinarySearchDescending => {
let index = if match_mode == MatchMode::ExactMatchLarger {
if search_mode == SearchMode::BinarySearchAscending {
binary_search_or_greater(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
} else {
binary_search_descending_or_greater(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
}
} else if search_mode == SearchMode::BinarySearchAscending {
binary_search_or_smaller(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
} else {
binary_search_descending_or_smaller(
&lookup_value,
&self.prepare_array(&left, &right, is_row_vector),
)
};
match index {
None => if_not_found,
Some(l) => {
let row =
result_left.row + if is_row_vector { l } else { 0 };
let column =
result_left.column + if is_row_vector { 0 } else { l };
if match_mode == MatchMode::ExactMatch {
let value = self.evaluate_cell(CellReference {
sheet: left.sheet,
row: left.row + if is_row_vector { l } else { 0 },
column: left.column
+ if is_row_vector { 0 } else { l },
});
if compare_values(&value, &lookup_value) == 0 {
self.evaluate_cell(CellReference {
sheet: result_left.sheet,
row,
column,
})
} else {
if_not_found
}
} else if match_mode == MatchMode::ExactMatchSmaller
|| match_mode == MatchMode::ExactMatchLarger
{
self.evaluate_cell(CellReference {
sheet: result_left.sheet,
row,
column,
})
} else {
CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Cannot use wildcard in binary search"
.to_string(),
}
}
}
}
}
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::VALUE,
origin: cell,
message: "Range expected".to_string(),
},
}
}
error @ CalcResult::Error { .. } => error,
_ => CalcResult::Error {
error: Error::NA,
origin: cell,
message: "Range expected".to_string(),
},
}
}
}

View File

@@ -0,0 +1,48 @@
use crate::calc_result::{CellReference, Range};
/// It returns the closest cell from cell_reference to range in the same column/row
/// Examples
/// * i_i(B5, A2:A9) -> B5
/// * i_i(B5, A7:A9) -> None
/// * i_i(B5, A2:D2) -> B2
pub(crate) fn implicit_intersection(
cell_reference: &CellReference,
range: &Range,
) -> Option<CellReference> {
let left = &range.left;
let right = &range.right;
let sheet = cell_reference.sheet;
// If they are not all in the same sheet there is no intersection
if sheet != left.sheet && sheet != right.sheet {
return None;
}
let row = cell_reference.row;
let column = cell_reference.column;
if row >= left.row && row <= right.row {
if left.column != right.column {
return None;
}
return Some(CellReference {
sheet,
row,
column: left.column,
});
} else if column >= left.column && column <= right.column {
if left.row != right.row {
return None;
}
return Some(CellReference {
sheet,
row: left.row,
column,
});
} else if left.row == right.row && left.column == right.column {
// If the range is a single cell, then return it.
return Some(CellReference {
sheet,
row: left.row,
column: right.column,
});
}
None
}

View File

@@ -0,0 +1,82 @@
{
"en": {
"booleans": {
"true": "TRUE",
"false": "FALSE"
},
"errors":{
"ref": "#REF!",
"name": "#NAME?",
"value": "#VALUE!",
"div": "#DIV/0!",
"na": "#N/A",
"num": "#NUM!",
"error": "#ERROR!",
"nimpl": "#N/IMPL!",
"spill": "#SPILL!",
"null": "#NULL!",
"calc": "#CALC!",
"circ": "#CIRC!"
}
},
"de": {
"booleans": {
"true": "WAHR",
"false": "FALSCH"
},
"errors":{
"ref": "#BEZUG!",
"name": "#NAME?",
"value": "#WERT!",
"div": "#DIV/0!",
"na": "#NV",
"num": "#ZAHL!",
"error": "#ERROR!",
"nimpl": "#N/IMPL!",
"spill": "#ÜBERLAUF!",
"null": "#NULL!",
"calc": "#CALC!",
"circ": "#CIRC!"
}
},
"fr": {
"booleans": {
"true": "VRAI",
"false": "FAUX"
},
"errors":{
"ref": "#REF!",
"name": "#NOM?",
"value": "#VALEUR!",
"div": "#DIV/0!",
"na": "#N/A",
"num": "#NOMBRE!",
"error": "#ERROR!",
"nimpl": "#N/IMPL!",
"spill": "#SPILL!",
"null": "#NULL!",
"calc": "#CALC!",
"circ": "#CIRC!"
}
},
"es": {
"booleans": {
"true": "VERDADERO",
"false": "FALSO"
},
"errors": {
"ref": "#¡REF!",
"name": "#¿NOMBRE?",
"value": "#¡VALOR!",
"div": "#¡DIV/0!",
"na": "#N/A",
"num": "#¡NUM!",
"error": "#ERROR!",
"nimpl": "#N/IMPL!",
"spill": "#SPILL!",
"null": "#NULL!",
"calc": "#CALC!",
"circ": "#CIRC!"
}
}
}

46
base/src/language/mod.rs Normal file
View File

@@ -0,0 +1,46 @@
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Clone)]
pub struct Booleans {
#[serde(rename = "true")]
pub true_value: String,
#[serde(rename = "false")]
pub false_value: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Errors {
#[serde(rename = "ref")]
pub ref_value: String,
pub name: String,
pub value: String,
pub div: String,
pub na: String,
pub num: String,
pub nimpl: String,
pub spill: String,
pub calc: String,
pub circ: String,
pub error: String,
pub null: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Language {
pub booleans: Booleans,
pub errors: Errors,
}
static LANGUAGES: Lazy<HashMap<String, Language>> = Lazy::new(|| {
serde_json::from_str(include_str!("language.json")).expect("Failed parsing language file")
});
pub fn get_language(id: &str) -> Result<&Language, String> {
let language = LANGUAGES
.get(id)
.ok_or(format!("Language is not supported: '{}'", id))?;
Ok(language)
}

32
base/src/lib.rs Normal file
View File

@@ -0,0 +1,32 @@
#![deny(clippy::unwrap_used)]
pub mod calc_result;
pub mod cell;
pub mod expressions;
pub mod formatter;
pub mod language;
pub mod locale;
pub mod model;
pub mod new_empty;
pub mod number_format;
pub mod types;
pub mod worksheet;
mod functions;
mod actions;
mod cast;
mod constants;
mod styles;
mod diffs;
mod implicit_intersection;
mod units;
mod utils;
mod workbook;
#[cfg(test)]
mod test;
#[cfg(test)]
pub mod mock_time;

View File

@@ -0,0 +1 @@
{"en":{"dates":{"day_names":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"day_names_short":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"months":["January","February","March","April","May","June","July","August","September","October","November","December"],"months_short":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"months_letter":["J","F","M","A","M","J","J","A","S","O","N","D"]},"numbers":{"symbols-numberSystem-latn":{"decimal":".","group":",","list":";","percentSign":"%","plusSign":"+","minusSign":"-","approximatelySign":"~","exponential":"E","superscriptingExponent":"×","perMille":"‰","infinity":"∞","nan":"NaN","timeSeparator":":"},"decimalFormats-numberSystem-latn":{"standard":"#,##0.###"},"currencyFormats-numberSystem-latn":{"standard":"¤#,##0.00","standard-alphaNextToNumber":"¤ #,##0.00","standard-noCurrency":"#,##0.00","accounting":"¤#,##0.00;(¤#,##0.00)","accounting-alphaNextToNumber":"¤ #,##0.00;(¤ #,##0.00)","accounting-noCurrency":"#,##0.00;(#,##0.00)"}},"currency":{"iso":"USD","symbol":"$"}},"en-GB":{"dates":{"day_names":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"day_names_short":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],"months":["January","February","March","April","May","June","July","August","September","October","November","December"],"months_short":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"],"months_letter":["J","F","M","A","M","J","J","A","S","O","N","D"]},"numbers":{"symbols-numberSystem-latn":{"decimal":".","group":",","list":";","percentSign":"%","plusSign":"+","minusSign":"-","approximatelySign":"~","exponential":"E","superscriptingExponent":"×","perMille":"‰","infinity":"∞","nan":"NaN","timeSeparator":":"},"decimalFormats-numberSystem-latn":{"standard":"#,##0.###"},"currencyFormats-numberSystem-latn":{"standard":"¤#,##0.00","standard-alphaNextToNumber":"¤ #,##0.00","standard-noCurrency":"#,##0.00","accounting":"¤#,##0.00;(¤#,##0.00)","accounting-alphaNextToNumber":"¤ #,##0.00;(¤ #,##0.00)","accounting-noCurrency":"#,##0.00;(#,##0.00)"}},"currency":{"iso":"USD","symbol":"$"}},"es":{"dates":{"day_names":["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],"day_names_short":["dom","lun","mar","mié","jue","vie","sáb"],"months":["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],"months_short":["ene","feb","mar","abr","may","jun","jul","ago","sept","oct","nov","dic"],"months_letter":["E","F","M","A","M","J","J","A","S","O","N","D"]},"numbers":{"symbols-numberSystem-latn":{"decimal":",","group":".","list":";","percentSign":"%","plusSign":"+","minusSign":"-","approximatelySign":"~","exponential":"E","superscriptingExponent":"×","perMille":"‰","infinity":"∞","nan":"NaN","timeSeparator":":"},"decimalFormats-numberSystem-latn":{"standard":"#,##0.###"},"currencyFormats-numberSystem-latn":{"standard":"#,##0.00 ¤","standard-noCurrency":"#,##0.00","accounting":"#,##0.00 ¤","accounting-noCurrency":"#,##0.00"}},"currency":{"iso":"USD","symbol":"$"}},"de":{"dates":{"day_names":["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],"day_names_short":["So.","Mo.","Di.","Mi.","Do.","Fr.","Sa."],"months":["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],"months_short":["Jan.","Feb.","März","Apr.","Mai","Juni","Juli","Aug.","Sept.","Okt.","Nov.","Dez."],"months_letter":["J","F","M","A","M","J","J","A","S","O","N","D"]},"numbers":{"symbols-numberSystem-latn":{"decimal":",","group":".","list":";","percentSign":"%","plusSign":"+","minusSign":"-","approximatelySign":"≈","exponential":"E","superscriptingExponent":"·","perMille":"‰","infinity":"∞","nan":"NaN","timeSeparator":":"},"decimalFormats-numberSystem-latn":{"standard":"#,##0.###"},"currencyFormats-numberSystem-latn":{"standard":"#,##0.00 ¤","standard-noCurrency":"#,##0.00","accounting":"#,##0.00 ¤","accounting-noCurrency":"#,##0.00"}},"currency":{"iso":"USD","symbol":"$"}}}

93
base/src/locale/mod.rs Normal file
View File

@@ -0,0 +1,93 @@
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Clone)]
pub struct Locale {
pub dates: Dates,
pub numbers: NumbersProperties,
pub currency: Currency,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Currency {
pub iso: String,
pub symbol: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct NumbersProperties {
#[serde(rename = "symbols-numberSystem-latn")]
pub symbols: NumbersSymbols,
#[serde(rename = "decimalFormats-numberSystem-latn")]
pub decimal_formats: DecimalFormats,
#[serde(rename = "currencyFormats-numberSystem-latn")]
pub currency_formats: CurrencyFormats,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Dates {
pub day_names: Vec<String>,
pub day_names_short: Vec<String>,
pub months: Vec<String>,
pub months_short: Vec<String>,
pub months_letter: Vec<String>,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct NumbersSymbols {
pub decimal: String,
pub group: String,
pub list: String,
pub percent_sign: String,
pub plus_sign: String,
pub minus_sign: String,
pub approximately_sign: String,
pub exponential: String,
pub superscripting_exponent: String,
pub per_mille: String,
pub infinity: String,
pub nan: String,
pub time_separator: String,
}
// See: https://cldr.unicode.org/translation/number-currency-formats/number-and-currency-patterns
#[derive(Serialize, Deserialize, Clone)]
pub struct CurrencyFormats {
pub standard: String,
#[serde(rename = "standard-alphaNextToNumber")]
#[serde(skip_serializing_if = "Option::is_none")]
pub standard_alpha_next_to_number: Option<String>,
#[serde(rename = "standard-noCurrency")]
pub standard_no_currency: String,
pub accounting: String,
#[serde(rename = "accounting-alphaNextToNumber")]
#[serde(skip_serializing_if = "Option::is_none")]
pub accounting_alpha_next_to_number: Option<String>,
#[serde(rename = "accounting-noCurrency")]
pub accounting_no_currency: String,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DecimalFormats {
pub standard: String,
}
static LOCALES: Lazy<HashMap<String, Locale>> = Lazy::new(|| {
serde_json::from_str(include_str!("locales.json")).expect("Failed parsing locale")
});
pub fn get_locale(_id: &str) -> Result<&Locale, String> {
// TODO: pass the locale once we implement locales in Rust
let locale = LOCALES.get("en").ok_or("Invalid locale")?;
Ok(locale)
}
// TODO: Remove this function one we implement locales properly
pub fn get_locale_fix(id: &str) -> Result<&Locale, String> {
let locale = LOCALES.get(id).ok_or("Invalid locale")?;
Ok(locale)
}

22
base/src/mock_time.rs Normal file
View File

@@ -0,0 +1,22 @@
use std::cell::RefCell;
// main idea borrowed_mut from:
// https://blog.iany.me/2019/03/how-to-mock-time-in-rust-tests-and-cargo-gotchas-we-met/
// see also:
// https://docs.rs/mock_instant/latest/mock_instant/
// FIXME: This should be November 12 1955 06:38, of course
// (or maybe OCT 21, 2015 07:28)
// 8 November 2022 12:13 Berlin time
thread_local! {
static MOCK_TIME: RefCell<i64> = RefCell::new(1667906008578);
}
pub fn get_milliseconds_since_epoch() -> i64 {
MOCK_TIME.with(|t| *t.borrow())
}
pub fn set_mock_time(time: i64) {
MOCK_TIME.with(|cell| *cell.borrow_mut() = time);
}

1507
base/src/model.rs Normal file

File diff suppressed because it is too large Load Diff

395
base/src/new_empty.rs Normal file
View File

@@ -0,0 +1,395 @@
use chrono::NaiveDateTime;
use std::collections::HashMap;
use crate::{
calc_result::Range,
expressions::{
lexer::LexerMode,
parser::stringify::{rename_sheet_in_node, to_rc_format},
parser::Parser,
types::CellReferenceRC,
},
language::get_language,
locale::get_locale,
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
types::{Metadata, SheetState, Workbook, WorkbookSettings, Worksheet},
utils::ParsedReference,
};
pub use chrono_tz::Tz;
pub const APPLICATION: &str = "IronCalc Sheets";
pub const APP_VERSION: &str = "10.0000";
pub const IRONCALC_USER: &str = "IronCalc User";
/// Name cannot be blank, must be shorter than 31 characters.
/// You can use all alphanumeric characters but not the following special characters:
/// \ , / , * , ? , : , [ , ].
fn is_valid_sheet_name(name: &str) -> bool {
let invalid = ['\\', '/', '*', '?', ':', '[', ']'];
return !name.is_empty() && name.chars().count() <= 31 && !name.contains(&invalid[..]);
}
impl Model {
/// Creates a new worksheet. Note that it does not check if the name or the sheet_id exists
fn new_empty_worksheet(name: &str, sheet_id: u32) -> Worksheet {
Worksheet {
cols: vec![],
rows: vec![],
comments: vec![],
dimension: "A1".to_string(),
merge_cells: vec![],
name: name.to_string(),
shared_formulas: vec![],
sheet_data: Default::default(),
sheet_id,
state: SheetState::Visible,
color: Default::default(),
frozen_columns: 0,
frozen_rows: 0,
}
}
pub fn get_new_sheet_id(&self) -> u32 {
let mut index = 1;
let worksheets = &self.workbook.worksheets;
for worksheet in worksheets {
index = index.max(worksheet.sheet_id);
}
index + 1
}
pub(crate) fn parse_formulas(&mut self) {
self.parser.set_lexer_mode(LexerMode::R1C1);
let worksheets = &self.workbook.worksheets;
for worksheet in worksheets {
let shared_formulas = &worksheet.shared_formulas;
let cell_reference = &Some(CellReferenceRC {
sheet: worksheet.get_name(),
row: 1,
column: 1,
});
let mut parse_formula = Vec::new();
for formula in shared_formulas {
let t = self.parser.parse(formula, cell_reference);
parse_formula.push(t);
}
self.parsed_formulas.push(parse_formula);
}
self.parser.set_lexer_mode(LexerMode::A1);
}
pub(crate) fn parse_defined_names(&mut self) {
let mut parsed_defined_names = HashMap::new();
for defined_name in &self.workbook.defined_names {
let parsed_defined_name_formula = if let Ok(reference) =
ParsedReference::parse_reference_formula(
None,
&defined_name.formula,
&self.locale,
|name| self.get_sheet_index_by_name(name),
) {
match reference {
ParsedReference::CellReference(cell_reference) => {
ParsedDefinedName::CellReference(cell_reference)
}
ParsedReference::Range(left, right) => {
ParsedDefinedName::RangeReference(Range { left, right })
}
}
} else {
ParsedDefinedName::InvalidDefinedNameFormula
};
let local_sheet_index = if let Some(sheet_id) = defined_name.sheet_id {
if let Some(sheet_index) = self.get_sheet_index_by_sheet_id(sheet_id) {
Some(sheet_index)
} else {
// TODO: Error: Sheet with given sheet_id not found.
continue;
}
} else {
None
};
parsed_defined_names.insert(
(local_sheet_index, defined_name.name.to_lowercase()),
parsed_defined_name_formula,
);
}
self.parsed_defined_names = parsed_defined_names;
}
// Reparses all formulas and defined names
fn reset_parsed_structures(&mut self) {
self.parser
.set_worksheets(self.workbook.get_worksheet_names());
self.parsed_formulas = vec![];
self.parse_formulas();
self.parsed_defined_names = HashMap::new();
self.parse_defined_names();
self.evaluate();
}
/// Adds a sheet with a automatically generated name
pub fn new_sheet(&mut self) {
// First we find a name
// TODO: When/if we support i18n the name could depend on the locale
let base_name = "Sheet";
let base_name_uppercase = base_name.to_uppercase();
let mut index = 1;
while self
.workbook
.get_worksheet_names()
.iter()
.map(|s| s.to_uppercase())
.any(|x| x == format!("{}{}", base_name_uppercase, index))
{
index += 1;
}
let sheet_name = format!("{}{}", base_name, index);
// Now we need a sheet_id
let sheet_id = self.get_new_sheet_id();
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id);
self.workbook.worksheets.push(worksheet);
self.reset_parsed_structures();
}
/// Inserts a sheet with a particular index
/// Fails if a worksheet with that name already exists or the name is invalid
/// Fails if the index is too large
pub fn insert_sheet(
&mut self,
sheet_name: &str,
sheet_index: u32,
sheet_id: Option<u32>,
) -> Result<(), String> {
if !is_valid_sheet_name(sheet_name) {
return Err(format!("Invalid name for a sheet: '{}'", sheet_name));
}
if self
.workbook
.get_worksheet_names()
.iter()
.map(|s| s.to_uppercase())
.any(|x| x == sheet_name.to_uppercase())
{
return Err("A worksheet already exists with that name".to_string());
}
let sheet_id = match sheet_id {
Some(id) => id,
None => self.get_new_sheet_id(),
};
let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id);
if sheet_index as usize > self.workbook.worksheets.len() {
return Err("Sheet index out of range".to_string());
}
self.workbook
.worksheets
.insert(sheet_index as usize, worksheet);
self.reset_parsed_structures();
Ok(())
}
/// Adds a sheet with a specific name
/// Fails if a worksheet with that name already exists or the name is invalid
pub fn add_sheet(&mut self, sheet_name: &str) -> Result<(), String> {
self.insert_sheet(sheet_name, self.workbook.worksheets.len() as u32, None)
}
/// Renames a sheet and updates all existing references to that sheet.
/// It can fail if:
/// * The original sheet does not exists
/// * The target sheet already exists
/// * The target sheet name is invalid
pub fn rename_sheet(&mut self, old_name: &str, new_name: &str) -> Result<(), String> {
if let Some(sheet_index) = self.get_sheet_index_by_name(old_name) {
return self.rename_sheet_by_index(sheet_index, new_name);
}
Err(format!("Could not find sheet {}", old_name))
}
/// Renames a sheet and updates all existing references to that sheet.
/// It can fail if:
/// * The original index is too large
/// * The target sheet name already exists
/// * The target sheet name is invalid
pub fn rename_sheet_by_index(
&mut self,
sheet_index: u32,
new_name: &str,
) -> Result<(), String> {
if !is_valid_sheet_name(new_name) {
return Err(format!("Invalid name for a sheet: '{}'", new_name));
}
if self.get_sheet_index_by_name(new_name).is_some() {
return Err(format!("Sheet already exists: '{}'", new_name));
}
let worksheets = &self.workbook.worksheets;
let sheet_count = worksheets.len() as u32;
if sheet_index >= sheet_count {
return Err("Sheet index out of bounds".to_string());
}
// Parse all formulas with the old name
// All internal formulas are R1C1
self.parser.set_lexer_mode(LexerMode::R1C1);
// We use iter because the default would be a mut_iter and we don't need a mutable reference
let worksheets = &mut self.workbook.worksheets;
for worksheet in worksheets {
let cell_reference = &Some(CellReferenceRC {
sheet: worksheet.get_name(),
row: 1,
column: 1,
});
let mut formulas = Vec::new();
for formula in &worksheet.shared_formulas {
let mut t = self.parser.parse(formula, cell_reference);
rename_sheet_in_node(&mut t, sheet_index, new_name);
formulas.push(to_rc_format(&t));
}
worksheet.shared_formulas = formulas;
}
// Se the mode back to A1
self.parser.set_lexer_mode(LexerMode::A1);
// Update the name of the worksheet
let worksheets = &mut self.workbook.worksheets;
worksheets[sheet_index as usize].set_name(new_name);
self.reset_parsed_structures();
Ok(())
}
/// Deletes a sheet by index. Fails if:
/// * The sheet does not exists
/// * It is the last sheet
pub fn delete_sheet(&mut self, sheet_index: u32) -> Result<(), String> {
let worksheets = &self.workbook.worksheets;
let sheet_count = worksheets.len() as u32;
if sheet_count == 1 {
return Err("Cannot delete only sheet".to_string());
};
if sheet_index > sheet_count {
return Err("Sheet index too large".to_string());
}
self.workbook.worksheets.remove(sheet_index as usize);
self.reset_parsed_structures();
Ok(())
}
/// Deletes a sheet by name. Fails if:
/// * The sheet does not exists
/// * It is the last sheet
pub fn delete_sheet_by_name(&mut self, name: &str) -> Result<(), String> {
if let Some(sheet_index) = self.get_sheet_index_by_name(name) {
self.delete_sheet(sheet_index)
} else {
Err("Sheet not found".to_string())
}
}
/// Deletes a sheet by sheet_id. Fails if:
/// * The sheet by sheet_id does not exists
/// * It is the last sheet
pub fn delete_sheet_by_sheet_id(&mut self, sheet_id: u32) -> Result<(), String> {
if let Some(sheet_index) = self.get_sheet_index_by_sheet_id(sheet_id) {
self.delete_sheet(sheet_index)
} else {
Err("Sheet not found".to_string())
}
}
pub(crate) fn get_sheet_index_by_sheet_id(&self, sheet_id: u32) -> Option<u32> {
let worksheets = &self.workbook.worksheets;
for (index, worksheet) in worksheets.iter().enumerate() {
if worksheet.sheet_id == sheet_id {
return Some(index as u32);
}
}
None
}
/// Creates a new workbook with one empty sheet
pub fn new_empty(name: &str, locale_id: &str, timezone: &str) -> Result<Model, String> {
let tz: Tz = match &timezone.parse() {
Ok(tz) => *tz,
Err(_) => return Err(format!("Invalid timezone: {}", &timezone)),
};
let locale = match get_locale(locale_id) {
Ok(l) => l.clone(),
Err(_) => return Err(format!("Invalid locale: {}", locale_id)),
};
let milliseconds = get_milliseconds_since_epoch();
let seconds = milliseconds / 1000;
let dt = match NaiveDateTime::from_timestamp_opt(seconds, 0) {
Some(s) => s,
None => return Err(format!("Invalid timestamp: {}", milliseconds)),
};
// "2020-08-06T21:20:53Z
let now = dt.format("%Y-%m-%dT%H:%M:%SZ").to_string();
// String versions of the locale are added here to simplify the serialize/deserialize logic
let workbook = Workbook {
shared_strings: vec![],
defined_names: vec![],
worksheets: vec![Model::new_empty_worksheet("Sheet1", 1)],
styles: Default::default(),
name: name.to_string(),
settings: WorkbookSettings {
tz: timezone.to_string(),
locale: locale_id.to_string(),
},
metadata: Metadata {
application: APPLICATION.to_string(),
app_version: APP_VERSION.to_string(),
creator: IRONCALC_USER.to_string(),
last_modified_by: IRONCALC_USER.to_string(),
created: now.clone(),
last_modified: now,
},
tables: HashMap::new(),
};
let parsed_formulas = Vec::new();
let worksheets = &workbook.worksheets;
let worksheet_names = worksheets.iter().map(|s| s.get_name()).collect();
let parser = Parser::new(worksheet_names, HashMap::new());
let cells = HashMap::new();
// FIXME: Add support for display languages
let language = get_language("en").expect("").clone();
let mut model = Model {
workbook,
parsed_formulas,
parsed_defined_names: HashMap::new(),
parser,
cells,
locale,
language,
tz,
};
model.parse_formulas();
Ok(model)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_valid_sheet_name() {
assert!(is_valid_sheet_name("Sheet1"));
assert!(is_valid_sheet_name("Zażółć gęślą jaźń"));
assert!(is_valid_sheet_name(" "));
assert!(!is_valid_sheet_name(""));
assert!(is_valid_sheet_name("🙈"));
assert!(is_valid_sheet_name("AAAAAAAAAABBBBBBBBBBCCCCCCCCCCD")); // 31
assert!(!is_valid_sheet_name("AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDE")); // 32
}
}

157
base/src/number_format.rs Normal file
View File

@@ -0,0 +1,157 @@
use crate::{
formatter::{self, format::Formatted},
locale::get_locale,
types::NumFmt,
};
const DEFAULT_NUM_FMTS: &[&str] = &[
"general",
"0",
"0.00",
"#,## 0",
"#,## 0.00",
"$#,## 0; \\ - $#,## 0",
"$#,## 0; [Red] \\ - $#,## 0",
"$#,## 0.00; \\ - $#,## 0.00",
"$#,## 0.00; [Red] \\ - $#,## 0.00",
"0%",
"0.00%",
"0.00E + 00",
"#?/?",
"#?? / ??",
"mm-dd-yy",
"d-mmm-yy",
"d-mmm",
"mmm-yy",
"h:mm AM / PM",
"h:mm:ss AM / PM",
"h:mm",
"h:mm:ss",
"m / d / yy h:mm",
"#,## 0;()#,## 0)",
"#,## 0; [Red]()#,## 0)",
"#,## 0.00;()#,## 0.00)",
"#,## 0.00; [Red]()#,## 0.00)",
"_()$”*#,## 0.00 _); _()$”* \\()#,## 0.00\\); _()$”* - ?? _); _()@_)",
"mm:ss",
"[h]:mm:ss",
"mmss .0",
"## 0.0E + 0",
"@",
"[$ -404] e / m / d ",
"m / d / yy",
"[$ -404] e / m / d",
"[$ -404] e / / d",
"[$ -404] e / m / d",
"t0",
"t0.00",
"t#,## 0",
"t#,## 0.00",
"t0%",
"t0.00 %",
"t#?/?",
];
pub fn get_default_num_fmt_id(num_fmt: &str) -> Option<i32> {
for (index, default_num_fmt) in DEFAULT_NUM_FMTS.iter().enumerate() {
if default_num_fmt == &num_fmt {
return Some(index as i32);
};
}
None
}
pub fn get_num_fmt(num_fmt_id: i32, num_fmts: &[NumFmt]) -> String {
// Check if it defined
for num_fmt in num_fmts {
if num_fmt.num_fmt_id == num_fmt_id {
return num_fmt.format_code.clone();
}
}
// Return one of the default ones
if num_fmt_id < DEFAULT_NUM_FMTS.len() as i32 {
return DEFAULT_NUM_FMTS[num_fmt_id as usize].to_string();
}
// Return general
DEFAULT_NUM_FMTS[0].to_string()
}
pub fn get_new_num_fmt_index(num_fmts: &[NumFmt]) -> i32 {
let mut index = DEFAULT_NUM_FMTS.len() as i32;
let mut found = true;
while found {
found = false;
for num_fmt in num_fmts {
if num_fmt.num_fmt_id == index {
found = true;
index += 1;
break;
}
}
}
index
}
pub fn to_precision(value: f64, precision: usize) -> f64 {
if value.is_infinite() || value.is_nan() {
return value;
}
to_precision_str(value, precision)
.parse::<f64>()
.unwrap_or({
// TODO: do this in a way that does not require a possible error
0.0
})
}
/// It rounds a `f64` with `p` significant figures:
/// ```
/// use ironcalc_base::number_format;
/// assert_eq!(number_format::to_precision(0.1+0.2, 15), 0.3);
/// assert_eq!(number_format::to_excel_precision_str(0.1+0.2), "0.3");
/// ```
/// This intends to be equivalent to the js: `${parseFloat(value.toPrecision(precision)})`
/// See ([ecma](https://tc39.es/ecma262/#sec-number.prototype.toprecision)).
/// FIXME: There has to be a better algorithm :/
pub fn to_excel_precision_str(value: f64) -> String {
to_precision_str(value, 15)
}
pub fn to_precision_str(value: f64, precision: usize) -> String {
if value.is_infinite() {
return "inf".to_string();
}
if value.is_nan() {
return "NaN".to_string();
}
let exponent = value.abs().log10().floor();
let base = value / 10.0_f64.powf(exponent);
let base = format!("{0:.1$}", base, precision - 1);
let value = format!("{}e{}", base, exponent).parse::<f64>().unwrap_or({
// TODO: do this in a way that does not require a possible error
0.0
});
// I would love to use the std library. There is not a speed concern here
// problem is it doesn't do the right thing
// Also ryu is my favorite _modern_ algorithm
let mut buffer = ryu::Buffer::new();
let text = buffer.format(value);
// The above algorithm converts 2 to 2.0 regrettably
if let Some(stripped) = text.strip_suffix(".0") {
return stripped.to_string();
}
text.to_string()
}
pub fn format_number(value: f64, format_code: &str, locale: &str) -> Formatted {
let locale = match get_locale(locale) {
Ok(l) => l,
Err(_) => {
return Formatted {
text: "#ERROR!".to_owned(),
color: None,
error: Some("Invalid locale".to_string()),
}
}
};
formatter::format::format_number(value, format_code, locale)
}

291
base/src/styles.rs Normal file
View File

@@ -0,0 +1,291 @@
use crate::{
model::{Model, Style},
number_format::{get_default_num_fmt_id, get_new_num_fmt_index, get_num_fmt},
types::{Border, CellStyles, CellXfs, Fill, Font, NumFmt, Styles},
};
// TODO: Move Styles and all related types from crate::types here
// Not doing it right now to not have conflicts with exporter branch
impl Styles {
fn get_font_index(&self, font: &Font) -> Option<i32> {
for (font_index, item) in self.fonts.iter().enumerate() {
if item == font {
return Some(font_index as i32);
}
}
None
}
fn get_fill_index(&self, fill: &Fill) -> Option<i32> {
for (fill_index, item) in self.fills.iter().enumerate() {
if item == fill {
return Some(fill_index as i32);
}
}
None
}
fn get_border_index(&self, border: &Border) -> Option<i32> {
for (border_index, item) in self.borders.iter().enumerate() {
if item == border {
return Some(border_index as i32);
}
}
None
}
fn get_num_fmt_index(&self, format_code: &str) -> Option<i32> {
if let Some(index) = get_default_num_fmt_id(format_code) {
return Some(index);
}
for item in self.num_fmts.iter() {
if item.format_code == format_code {
return Some(item.num_fmt_id);
}
}
None
}
pub fn create_new_style(&mut self, style: &Style) -> i32 {
let font = &style.font;
let font_id = if let Some(index) = self.get_font_index(font) {
index
} else {
self.fonts.push(font.clone());
self.fonts.len() as i32 - 1
};
let fill = &style.fill;
let fill_id = if let Some(index) = self.get_fill_index(fill) {
index
} else {
self.fills.push(fill.clone());
self.fills.len() as i32 - 1
};
let border = &style.border;
let border_id = if let Some(index) = self.get_border_index(border) {
index
} else {
self.borders.push(border.clone());
self.borders.len() as i32 - 1
};
let num_fmt = &style.num_fmt;
let num_fmt_id;
if let Some(index) = self.get_num_fmt_index(num_fmt) {
num_fmt_id = index;
} else {
num_fmt_id = get_new_num_fmt_index(&self.num_fmts);
self.num_fmts.push(NumFmt {
format_code: num_fmt.to_string(),
num_fmt_id,
});
}
self.cell_xfs.push(CellXfs {
xf_id: 0,
num_fmt_id,
font_id,
fill_id,
border_id,
apply_number_format: false,
apply_border: false,
apply_alignment: false,
apply_protection: false,
apply_font: false,
apply_fill: false,
quote_prefix: style.quote_prefix,
alignment: style.alignment.clone(),
});
self.cell_xfs.len() as i32 - 1
}
pub fn get_style_index(&self, style: &Style) -> Option<i32> {
for (index, cell_xf) in self.cell_xfs.iter().enumerate() {
let border_id = cell_xf.border_id as usize;
let fill_id = cell_xf.fill_id as usize;
let font_id = cell_xf.font_id as usize;
let num_fmt_id = cell_xf.num_fmt_id;
let quote_prefix = cell_xf.quote_prefix;
if style
== &(Style {
alignment: cell_xf.alignment.clone(),
num_fmt: get_num_fmt(num_fmt_id, &self.num_fmts),
fill: self.fills[fill_id].clone(),
font: self.fonts[font_id].clone(),
border: self.borders[border_id].clone(),
quote_prefix,
})
{
return Some(index as i32);
}
}
None
}
pub(crate) fn get_style_index_or_create(&mut self, style: &Style) -> i32 {
// Check if style exist. If so sets style cell number to that otherwise create a new style.
if let Some(index) = self.get_style_index(style) {
index
} else {
self.create_new_style(style)
}
}
/// Adds a named cell style from an existing index
/// Fails if the named style already exists or if there is not a style with that index
pub fn add_named_cell_style(
&mut self,
style_name: &str,
style_index: i32,
) -> Result<(), String> {
if self.get_style_index_by_name(style_name).is_ok() {
return Err("A style with that name already exists".to_string());
}
if self.cell_xfs.len() < style_index as usize {
return Err("There is no style with that index".to_string());
}
let cell_style = CellStyles {
name: style_name.to_string(),
xf_id: style_index,
builtin_id: 0,
};
self.cell_styles.push(cell_style);
Ok(())
}
// Returns the index of the style or fails.
// NB: this method is case sensitive
pub fn get_style_index_by_name(&self, style_name: &str) -> Result<i32, String> {
for cell_style in &self.cell_styles {
if cell_style.name == style_name {
return Ok(cell_style.xf_id);
}
}
Err(format!("Style '{}' not found", style_name))
}
pub fn create_named_style(&mut self, style_name: &str, style: &Style) -> Result<(), String> {
let style_index = self.create_new_style(style);
self.add_named_cell_style(style_name, style_index)?;
Ok(())
}
pub(crate) fn get_style_with_quote_prefix(&mut self, index: i32) -> i32 {
let mut style = self.get_style(index);
style.quote_prefix = true;
self.get_style_index_or_create(&style)
}
pub(crate) fn get_style_with_format(&mut self, index: i32, num_fmt: &str) -> i32 {
let mut style = self.get_style(index);
style.num_fmt = num_fmt.to_string();
self.get_style_index_or_create(&style)
}
pub(crate) fn get_style_without_quote_prefix(&mut self, index: i32) -> i32 {
let mut style = self.get_style(index);
style.quote_prefix = false;
self.get_style_index_or_create(&style)
}
pub(crate) fn style_is_quote_prefix(&self, index: i32) -> bool {
let cell_xf = &self.cell_xfs[index as usize];
cell_xf.quote_prefix
}
pub(crate) fn get_style(&self, index: i32) -> Style {
let cell_xf = &self.cell_xfs[index as usize];
let border_id = cell_xf.border_id as usize;
let fill_id = cell_xf.fill_id as usize;
let font_id = cell_xf.font_id as usize;
let num_fmt_id = cell_xf.num_fmt_id;
let quote_prefix = cell_xf.quote_prefix;
let alignment = cell_xf.alignment.clone();
Style {
alignment,
num_fmt: get_num_fmt(num_fmt_id, &self.num_fmts),
fill: self.fills[fill_id].clone(),
font: self.fonts[font_id].clone(),
border: self.borders[border_id].clone(),
quote_prefix,
}
}
}
// TODO: Try to find a better spot for styles setters
impl Model {
pub fn set_cell_style(
&mut self,
sheet: u32,
row: i32,
column: i32,
style: &Style,
) -> Result<(), String> {
let style_index = self.workbook.styles.get_style_index_or_create(style);
self.workbook
.worksheet_mut(sheet)?
.set_cell_style(row, column, style_index);
Ok(())
}
pub fn copy_cell_style(
&mut self,
source_cell: (u32, i32, i32),
destination_cell: (u32, i32, i32),
) -> Result<(), String> {
let source_style_index = self
.workbook
.worksheet(source_cell.0)?
.get_style(source_cell.1, source_cell.2);
self.workbook
.worksheet_mut(destination_cell.0)?
.set_cell_style(destination_cell.1, destination_cell.2, source_style_index);
Ok(())
}
/// Sets the style "style_name" in cell
pub fn set_cell_style_by_name(
&mut self,
sheet: u32,
row: i32,
column: i32,
style_name: &str,
) -> Result<(), String> {
let style_index = self.workbook.styles.get_style_index_by_name(style_name)?;
self.workbook
.worksheet_mut(sheet)?
.set_cell_style(row, column, style_index);
Ok(())
}
pub fn set_sheet_style(&mut self, sheet: u32, style_name: &str) -> Result<(), String> {
let style_index = self.workbook.styles.get_style_index_by_name(style_name)?;
self.workbook.worksheet_mut(sheet)?.set_style(style_index)?;
Ok(())
}
pub fn set_sheet_row_style(
&mut self,
sheet: u32,
row: i32,
style_name: &str,
) -> Result<(), String> {
let style_index = self.workbook.styles.get_style_index_by_name(style_name)?;
self.workbook
.worksheet_mut(sheet)?
.set_row_style(row, style_index)?;
Ok(())
}
pub fn set_sheet_column_style(
&mut self,
sheet: u32,
column: i32,
style_name: &str,
) -> Result<(), String> {
let style_index = self.workbook.styles.get_style_index_by_name(style_name)?;
self.workbook
.worksheet_mut(sheet)?
.set_column_style(column, style_index)?;
Ok(())
}
}

View File

@@ -0,0 +1,6 @@
mod test_bessel;
mod test_bit_operations;
mod test_complex;
mod test_convert;
mod test_misc;
mod test_number_basis;

View File

@@ -0,0 +1,53 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_besseli() {
let mut model = new_empty_model();
model._set("B1", "=BESSELI()");
model._set("B2", "=BESSELI(1,2, 1)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_besselj() {
let mut model = new_empty_model();
model._set("B1", "=BESSELJ()");
model._set("B2", "=BESSELJ(1,2, 1)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_besselk() {
let mut model = new_empty_model();
model._set("B1", "=BESSELK()");
model._set("B2", "=BESSELK(1,2, 1)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bessely() {
let mut model = new_empty_model();
model._set("B1", "=BESSELY()");
model._set("B2", "=BESSELY(1,2, 1)");
model.evaluate();
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}

View File

@@ -0,0 +1,99 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_bitand() {
let mut model = new_empty_model();
model._set("A1", "=BITAND(1,5)");
model._set("A2", "=BITAND(13, 25");
model._set("B1", "=BITAND(1)");
model._set("B2", "=BITAND(1, 2, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "1");
assert_eq!(model._get_text("A2"), "9");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bitor() {
let mut model = new_empty_model();
model._set("A1", "=BITOR(1, 5)");
model._set("A2", "=BITOR(13, 10");
model._set("B1", "=BITOR(1)");
model._set("B2", "=BITOR(1, 2, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "5");
assert_eq!(model._get_text("A2"), "15");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bitxor() {
let mut model = new_empty_model();
model._set("A1", "=BITXOR(1, 5)");
model._set("A2", "=BITXOR(13, 25");
model._set("B1", "=BITXOR(1)");
model._set("B2", "=BITXOR(1, 2, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "4");
assert_eq!(model._get_text("A2"), "20");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bitlshift() {
let mut model = new_empty_model();
model._set("A1", "=BITLSHIFT(4, 2)");
model._set("A2", "=BITLSHIFT(13, 7");
model._set("B1", "=BITLSHIFT(1)");
model._set("B2", "=BITLSHIFT(1, 2, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "16");
assert_eq!(model._get_text("A2"), "1664");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bitrshift() {
let mut model = new_empty_model();
model._set("A1", "=BITRSHIFT(4, 2)");
model._set("A2", "=BITRSHIFT(13, 7");
model._set("A3", "=BITRSHIFT(145, -3");
model._set("B1", "=BITRSHIFT(1)");
model._set("B2", "=BITRSHIFT(1, 2, 3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "1");
assert_eq!(model._get_text("A2"), "0");
assert_eq!(model._get_text("A3"), "1160");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
// Excel does not pass this test (g sheets does)
#[test]
fn fn_bitshift_overflow() {
let mut model = new_empty_model();
model._set("A1", "=BITRSHIFT(12, -53)");
model._set("A2", "=BITLSHIFT(12, 53)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#NUM!");
assert_eq!(model._get_text("A2"), *"#NUM!");
}

View File

@@ -0,0 +1,162 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_complex() {
let mut model = new_empty_model();
model._set("A1", r#"=COMPLEX(3, 4.5, "i")"#);
model._set("A2", r#"=COMPLEX(3, -5)"#);
model._set("A3", r#"=COMPLEX(0, 42, "j")"#);
model._set("B1", "=COMPLEX()");
model._set("B2", r#"=COMPLEX(1,2, "i", 1)"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "3+4.5i");
assert_eq!(model._get_text("A2"), "3-5i");
assert_eq!(model._get_text("A3"), "42j");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_imabs() {
let mut model = new_empty_model();
model._set("A1", r#"=IMABS("3+4i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "5");
}
#[test]
fn fn_imaginary() {
let mut model = new_empty_model();
model._set("A1", r#"=IMAGINARY("3+4i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "4");
}
#[test]
fn fn_imreal() {
let mut model = new_empty_model();
model._set("A1", r#"=IMREAL("3+4i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "3");
}
#[test]
fn fn_imargument() {
let mut model = new_empty_model();
model._set("A1", r#"=IMARGUMENT("4+3i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "0.643501109");
}
#[test]
fn fn_imconjugate() {
let mut model = new_empty_model();
model._set("A1", r#"=IMCONJUGATE("3+4i")"#);
model._set("A2", r#"=IMCONJUGATE("12.7-32j")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "3-4i");
assert_eq!(model._get_text("A2"), "12.7+32j");
}
#[test]
fn fn_imcos() {
let mut model = new_empty_model();
model._set("A1", r#"=IMCOS("4+3i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "-6.58066304055116+7.58155274274654i");
}
#[test]
fn fn_imsin() {
let mut model = new_empty_model();
model._set("A1", r#"=IMSIN("4+3i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "-7.61923172032141-6.548120040911i");
}
#[test]
fn fn_imaginary_misc() {
let mut model = new_empty_model();
model._set("A1", r#"=IMAGINARY("3.4i")"#);
model._set("A2", r#"=IMAGINARY("-3.4")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "3.4");
assert_eq!(model._get_text("A2"), "0");
}
#[test]
fn fn_imcosh() {
let mut model = new_empty_model();
model._set("A1", r#"=IMCOSH("4+3i")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "-27.0349456030742+3.85115333481178i");
}
#[test]
fn fn_imcot() {
let mut model = new_empty_model();
model._set("A1", r#"=IMCOT("4+3i")"#);
model.evaluate();
assert_eq!(
model._get_text("A1"),
"0.0049011823943045-0.999266927805902i"
);
}
#[test]
fn fn_imtan() {
let mut model = new_empty_model();
model._set("A1", r#"=IMTAN("4+3i")"#);
model.evaluate();
assert_eq!(
model._get_text("A1"),
"0.00490825806749608+1.00070953606723i"
);
}
#[test]
fn fn_power() {
let mut model = new_empty_model();
model._set("A2", r#"=IMPOWER("4+3i", 3)"#);
model._set("A3", r#"=IMABS(IMSUB(IMPOWER("-i", -3), "-1"))<G1"#);
model._set("A3", r#"=IMABS(IMSUB(IMPOWER("-1", 0.5), "i"))<G1"#);
model._set("A1", r#"=IMABS(IMSUB(B1, "-1"))<G1"#);
model._set("B1", r#"=IMPOWER("i", 2)"#);
// small number
model._set("G1", "0.0000001");
model.evaluate();
assert_eq!(model._get_text("A1"), "TRUE");
assert_eq!(model._get_text("A2"), "-44+117i");
assert_eq!(model._get_text("A3"), "TRUE");
assert_eq!(model._get_text("A3"), "TRUE");
}

View File

@@ -0,0 +1,35 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_convert() {
let mut model = new_empty_model();
model._set("A1", r#"=CONVERT(1, "lbm", "kg")"#);
model._set("A2", r#"=CONVERT(68, "F", "C")"#);
model._set("A3", r#"=CONVERT(2.5, "ft", "sec")"#);
model._set("A4", r#"=CONVERT(CONVERT(100,"ft","m"),"ft","m""#);
model._set("B1", "6");
model._set("A5", r#"=CONVERT(B1,"C","F")"#);
model._set("A6", r#"=CONVERT(B1,"tsp","tbs")"#);
model._set("A7", r#"=CONVERT(B1,"gal","l")"#);
model._set("A8", r#"=CONVERT(B1,"mi","km")"#);
model._set("A9", r#"=CONVERT(B1,"km","mi")"#);
model._set("A10", r#"=CONVERT(B1,"in","ft")"#);
model._set("A11", r#"=CONVERT(B1,"cm","in")"#);
model.evaluate();
assert_eq!(model._get_text("A1"), "0.45359237");
assert_eq!(model._get_text("A2"), "19.65");
assert_eq!(model._get_text("A3"), "#N/A");
assert_eq!(model._get_text("A4"), "9.290304");
assert_eq!(model._get_text("A5"), "42.8");
assert_eq!(model._get_text("A6"), "2");
assert_eq!(model._get_text("A7"), "22.712470704"); //22.71741274");
assert_eq!(model._get_text("A8"), "9.656064");
assert_eq!(model._get_text("A9"), "3.728227153");
assert_eq!(model._get_text("A10"), "0.5");
assert_eq!(model._get_text("A11"), "2.362204724");
}

View File

@@ -0,0 +1,66 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_getstep() {
let mut model = new_empty_model();
model._set("A1", "=GESTEP(7, 4.6)");
model._set("A2", "=GESTEP(45, 45)");
model._set("A3", "=GESTEP(-7, -6)");
model._set("A4", "=GESTEP(0.1)");
model._set("A5", "=GESTEP(-0.1)");
model._set("B1", "=GESTEP()");
model._set("B2", "=GESTEP(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"1");
assert_eq!(model._get_text("A2"), *"1");
assert_eq!(model._get_text("A3"), *"0");
assert_eq!(model._get_text("A4"), *"1");
assert_eq!(model._get_text("A5"), *"0");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_delta() {
let mut model = new_empty_model();
model._set("A1", "=DELTA(7, 7)");
model._set("A2", "=DELTA(-7, -7)");
model._set("A3", "=DELTA(-7, 7)");
model._set("A4", "=DELTA(5, 0.5)");
model._set("A5", "=DELTA(-0, 0)");
model._set("B1", "=DELTA()");
model._set("B2", "=DELTA(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"1");
assert_eq!(model._get_text("A2"), *"1");
assert_eq!(model._get_text("A3"), *"0");
assert_eq!(model._get_text("A4"), *"0");
assert_eq!(model._get_text("A5"), *"1");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_delta_misc() {
let mut model = new_empty_model();
model._set("A1", "=3+1e-16");
model._set("A2", "=3");
model._set("A3", "=3+1e-15");
model._set("B1", "=DELTA(A1, A2)");
model._set("B2", "=DELTA(A1, A3)");
model._set("B1", "1");
model._set("B2", "0");
model.evaluate();
assert_eq!(model._get_text("B1"), *"1");
}

View File

@@ -0,0 +1,345 @@
use crate::test::util::new_empty_model;
#[test]
fn fn_bin2dec() {
let mut model = new_empty_model();
model._set("A1", "=BIN2DEC(1100100)");
model._set("A2", "=BIN2DEC(1111111111)");
model._set("B1", "=BIN2DEC()");
model._set("B2", "=BIN2DEC(1,2)");
model.evaluate();
assert_eq!(model._get_text("A1"), "100");
assert_eq!(model._get_text("A2"), "-1");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bin2hex() {
let mut model = new_empty_model();
model._set("A1", "=BIN2HEX(11111011, 4)");
model._set("A2", "=BIN2HEX(1110)");
model._set("A3", "=BIN2HEX(1111111111)");
model._set("A4", "=BIN2HEX(1100011011)");
model._set("B1", "=BIN2HEX()");
model._set("B2", "=BIN2HEX(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "00FB");
assert_eq!(model._get_text("A2"), "E");
assert_eq!(model._get_text("A3"), "FFFFFFFFFF");
assert_eq!(model._get_text("A4"), "FFFFFFFF1B");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bin2oct() {
let mut model = new_empty_model();
model._set("A1", "=BIN2OCT(11111011, 4)");
model._set("A2", "=BIN2OCT(1110)");
model._set("A3", "=BIN2OCT(1111111111)");
model._set("A4", "=BIN2OCT(1100011011)");
model._set("B1", "=BIN2OCT()");
model._set("B2", "=BIN2OCT(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "0373");
assert_eq!(model._get_text("A2"), "16");
assert_eq!(model._get_text("A3"), "7777777777");
assert_eq!(model._get_text("A4"), "7777777433");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_dec2bin() {
let mut model = new_empty_model();
model._set("A1", "=DEC2BIN(9, 4)");
model._set("A2", "=DEC2BIN(-100)");
model._set("A3", "=DEC2BIN(-1)");
model._set("A4", "=DEC2BIN(0, 3)");
model._set("B1", "=DEC2BIN()");
model._set("B2", "=DEC2BIN(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "1001");
assert_eq!(model._get_text("A2"), "1110011100");
assert_eq!(model._get_text("A3"), "1111111111");
assert_eq!(model._get_text("A4"), "000");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_dec2hex() {
let mut model = new_empty_model();
model._set("A1", "=DEC2HEX(100, 4)");
model._set("A2", "=DEC2HEX(-54)");
model._set("A3", "=DEC2HEX(28)");
model._set("A4", "=DEC2HEX(64, 1)");
model._set("B1", "=DEC2HEX()");
model._set("B2", "=DEC2HEX(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "0064");
assert_eq!(model._get_text("A2"), "FFFFFFFFCA");
assert_eq!(model._get_text("A3"), "1C");
assert_eq!(model._get_text("A4"), "#NUM!");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_dec2oct() {
let mut model = new_empty_model();
model._set("A1", "=DEC2OCT(58, 3)");
model._set("A2", "=DEC2OCT(-100)");
model._set("B1", "=DEC2OCT()");
model._set("B2", "=DEC2OCT(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "072");
assert_eq!(model._get_text("A2"), "7777777634");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_hex2bin() {
let mut model = new_empty_model();
model._set("A1", r#"=HEX2BIN("F", 8)"#);
model._set("A2", r#"=HEX2BIN("B7")"#);
model._set("A3", r#"=HEX2BIN("FFFFFFFFFF")"#);
model._set("B1", "=HEX2BIN()");
model._set("B2", "=HEX2BIN(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "00001111");
assert_eq!(model._get_text("A2"), "10110111");
assert_eq!(model._get_text("A3"), "1111111111");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_hex2dec() {
let mut model = new_empty_model();
model._set("A1", r#"=HEX2DEC("A5")"#);
model._set("A2", r#"=HEX2DEC("FFFFFFFF5B")"#);
model._set("A3", r#"=HEX2DEC("3DA408B9")"#);
model._set("A4", r#"=HEX2DEC("FE")"#);
model._set("B1", "=HEX2DEC()");
model._set("B2", "=HHEX2DEC(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "165");
assert_eq!(model._get_text("A2"), "-165");
assert_eq!(model._get_text("A3"), "1034160313");
assert_eq!(model._get_text("A4"), "254");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_hex2oct() {
let mut model = new_empty_model();
model._set("A1", r#"=HEX2OCT("F", 3)"#);
model._set("A2", r#"=HEX2OCT("3B4E")"#);
model._set("A3", r#"=HEX2OCT("FFFFFFFF00")"#);
model._set("B1", "=HEX2OCT()");
model._set("B2", "=HEX2OCT(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "017");
assert_eq!(model._get_text("A2"), "35516");
assert_eq!(model._get_text("A3"), "7777777400");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_oct2bin() {
let mut model = new_empty_model();
model._set("A1", r#"=OCT2BIN(3, 3)"#);
model._set("A2", r#"=OCT2BIN(7777777000)"#);
// bounds
model._set("G1", r#"=OCT2BIN(777)"#);
model._set("G2", r#"=OCT2BIN(778)"#);
model._set("B1", "=OCT2BIN()");
model._set("B2", "=OCT2BIN(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "011");
assert_eq!(model._get_text("A2"), "1000000000");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
assert_eq!(model._get_text("G1"), "111111111");
assert_eq!(model._get_text("G2"), "#NUM!");
}
#[test]
fn fn_oct2dec() {
let mut model = new_empty_model();
model._set("A1", r#"=OCT2DEC(54)"#);
model._set("A2", r#"=OCT2DEC(7777777533)"#);
model._set("B1", "=OCT2DEC()");
model._set("B2", "=OCT2DEC(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "44");
assert_eq!(model._get_text("A2"), "-165");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_oct2hex() {
let mut model = new_empty_model();
model._set("A1", r#"=OCT2HEX(100, 4)"#);
model._set("A2", r#"=OCT2HEX(7777777533)"#);
model._set("B1", "=OCT2HEX()");
model._set("B2", "=OCT2HEX(1,2,3)");
model.evaluate();
assert_eq!(model._get_text("A1"), "0040");
assert_eq!(model._get_text("A2"), "FFFFFFFF5B");
assert_eq!(model._get_text("B1"), *"#ERROR!");
assert_eq!(model._get_text("B2"), *"#ERROR!");
}
#[test]
fn fn_bin2hex_misc() {
let mut model = new_empty_model();
model._set("A1", "=BIN2HEX(1100011011, -2)");
model._set("A2", "=BIN2HEX(1100011011, 11)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#NUM!");
assert_eq!(model._get_text("A2"), *"#NUM!");
}
#[test]
fn fn_bin2oct_misc() {
let mut model = new_empty_model();
model._set("A1", "=BIN2OCT(1100011011, -2)");
model._set("A2", "=BIN2OCT(1100011011, 11)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"#NUM!");
assert_eq!(model._get_text("A2"), *"#NUM!");
}
#[test]
fn fn_dec2oct_misc() {
let mut model = new_empty_model();
model._set("A1", "=DEC2OCT(-1213, 1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"7777775503");
}
#[test]
fn fn_dec2bin_misc() {
let mut model = new_empty_model();
model._set("A1", "=DEC2BIN(-511, 4)");
model._set("A2", "=DEC2BIN(TRUE, -1)");
model._set("A3", "=DEC2OCT(TRUE, -1)");
model._set("A4", "=DEC2HEX(TRUE, -1)");
model.evaluate();
assert_eq!(model._get_text("A1"), *"1000000001");
// Note Excel here return #NUM! instead
assert_eq!(model._get_text("A2"), *"#VALUE!");
assert_eq!(model._get_text("A3"), *"#VALUE!");
assert_eq!(model._get_text("A4"), *"#VALUE!");
}
#[test]
fn fn_hex2whatever_misc() {
let mut model = new_empty_model();
model._set("A1", r#"=HEX2BIN(TRUE, 4)"#);
model._set("A2", r#"=HEX2DEC(TRUE, 4)"#);
model._set("A3", r#"=HEX2OCT(TRUE, 4)"#);
model.evaluate();
// Note Excel here return #VALUE! instead
assert_eq!(model._get_text("A1"), *"#NUM!");
assert_eq!(model._get_text("A2"), *"#NUM!");
assert_eq!(model._get_text("A3"), *"#NUM!");
}
#[test]
fn fn_oct2whatever_misc() {
let mut model = new_empty_model();
model._set("A1", r#"=OCT2BIN(TRUE, 4)"#);
model._set("A2", r#"=OCT2DEC(TRUE, 4)"#);
model._set("A3", r#"=OCT2HEX(TRUE, 4)"#);
model.evaluate();
// Note Excel here return #VALUE! instead
assert_eq!(model._get_text("A1"), *"#NUM!");
assert_eq!(model._get_text("A2"), *"#NUM!");
assert_eq!(model._get_text("A3"), *"#NUM!");
}
#[test]
fn fn_oct2dec_misc() {
let mut model = new_empty_model();
model._set("A1", r#"=OCT2DEC(777)"#);
model._set("A2", r#"=OCT2DEC("777")"#);
model._set("A3", r#"=OCT2DEC("-1")"#);
model._set("A4", r#"=OCT2BIN("-1")"#);
model._set("A5", r#"=OCT2HEX("-1")"#);
model._set("A6", r#"=OCT2DEC(4000000000)"#);
model.evaluate();
assert_eq!(model._get_text("A1"), *"511");
assert_eq!(model._get_text("A1"), *"511");
assert_eq!(model._get_text("A3"), *"#NUM!");
assert_eq!(model._get_text("A4"), *"#NUM!");
assert_eq!(model._get_text("A5"), *"#NUM!");
assert_eq!(model._get_text("A6"), *"-536870912");
}

51
base/src/test/mod.rs Normal file
View File

@@ -0,0 +1,51 @@
mod test_actions;
mod test_binary_search;
mod test_cell;
mod test_circular_references;
mod test_column_width;
mod test_criteria;
mod test_currency;
mod test_date_and_time;
mod test_error_propagation;
mod test_fn_average;
mod test_fn_averageifs;
mod test_fn_choose;
mod test_fn_concatenate;
mod test_fn_count;
mod test_fn_exact;
mod test_fn_financial;
mod test_fn_if;
mod test_fn_maxifs;
mod test_fn_minifs;
mod test_fn_product;
mod test_fn_rept;
mod test_fn_sum;
mod test_fn_sumifs;
mod test_fn_textbefore;
mod test_fn_textjoin;
mod test_forward_references;
mod test_frozen_rows_columns;
mod test_general;
mod test_math;
mod test_metadata;
mod test_model_delete_cell;
mod test_model_is_empty_cell;
mod test_model_set_cell_empty;
mod test_move_formula;
mod test_quote_prefix;
mod test_set_user_input;
mod test_sheet_markup;
mod test_sheets;
mod test_styles;
mod test_trigonometric;
mod test_worksheet;
pub(crate) mod util;
mod engineering;
mod test_fn_offset;
mod test_number_format;
mod test_escape_quotes;
mod test_fn_type;
mod test_percentage;
mod test_today;

View File

@@ -0,0 +1,287 @@
#![allow(clippy::unwrap_used)]
use crate::constants::LAST_COLUMN;
use crate::model::Model;
use crate::test::util::new_empty_model;
#[test]
fn test_insert_columns() {
let mut model = new_empty_model();
// We populate cells A1 to C1
model._set("A1", "1");
model._set("B1", "2");
model._set("C1", "=B1*2");
model._set("F1", "=B1");
model._set("L11", "300");
model._set("M11", "=L11*5");
model.evaluate();
assert_eq!(model._get_text("C1"), *"4");
// Let's insert 5 columns in column F (6)
let r = model.insert_columns(0, 6, 5);
assert!(r.is_ok());
model.evaluate();
// Check F1 is now empty
assert!(model.is_empty_cell(0, 1, 6).unwrap());
// The old F1 is K1
assert_eq!(model._get_formula("K1"), *"=B1");
// L11 and M11 are Q11 and R11
assert_eq!(model._get_text("Q11"), *"300");
assert_eq!(model._get_formula("R11"), *"=Q11*5");
assert_eq!(model._get_formula("C1"), "=B1*2");
assert_eq!(model._get_text("A1"), "1");
// inserting a negative number of columns fails:
let r = model.insert_columns(0, 6, -5);
assert!(r.is_err());
let r = model.insert_columns(0, 6, -5);
assert!(r.is_err());
// If you have data at the very ebd it fails
model._set("XFC12", "300");
let r = model.insert_columns(0, 6, 5);
assert!(r.is_err());
}
#[test]
fn test_insert_rows() {
let mut model = new_empty_model();
model._set("C4", "3");
model._set("C5", "7");
model._set("C6", "=C5");
model._set("H11", "=C4");
model._set("R10", "=C6");
model.evaluate();
// Let's insert 5 rows in row 6
let r = model.insert_rows(0, 6, 5);
assert!(r.is_ok());
model.evaluate();
// Check C6 is now empty
assert!(model.is_empty_cell(0, 6, 3).unwrap());
// Old C6 is now C11
assert_eq!(model._get_formula("C11"), *"=C5");
assert_eq!(model._get_formula("H16"), *"=C4");
assert_eq!(model._get_formula("R15"), *"=C11");
assert_eq!(model._get_text("C4"), *"3");
assert_eq!(model._get_text("C5"), *"7");
}
#[test]
fn test_insert_rows_styles() {
let mut model = new_empty_model();
assert!(
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// sets height 42 in row 10
model
.workbook
.worksheet_mut(0)
.unwrap()
.set_row_height(10, 42.0)
.unwrap();
assert!(
(42.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// Let's insert 5 rows in row 3
let r = model.insert_rows(0, 3, 5);
assert!(r.is_ok());
// Row 10 has the default height
assert!(
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// Row 10 is now row 15
assert!(
(42.0 - model.workbook.worksheet(0).unwrap().row_height(15).unwrap()).abs() < f64::EPSILON
);
}
#[test]
fn test_delete_rows_styles() {
let mut model = new_empty_model();
assert!(
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// sets height 42 in row 10
model
.workbook
.worksheet_mut(0)
.unwrap()
.set_row_height(10, 42.0)
.unwrap();
assert!(
(42.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// Let's delete 5 rows in row 3 (3-8)
let r = model.delete_rows(0, 3, 5);
assert!(r.is_ok());
// Row 10 has the default height
assert!(
(21.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
);
// Row 10 is now row 5
assert!(
(42.0 - model.workbook.worksheet(0).unwrap().row_height(5).unwrap()).abs() < f64::EPSILON
);
}
#[test]
fn test_delete_columns() {
let mut model = new_empty_model();
model._set("C4", "3");
model._set("D4", "7");
model._set("E4", "=D4");
model._set("F4", "=C4");
model._set("H11", "=D4");
model._set("R10", "=C6");
model._set("M5", "300");
model._set("N5", "=M5*6");
model._set("A1", "=SUM(M5:N5)");
model._set("A2", "=SUM(C4:M4)");
model._set("A3", "=SUM(E4:M4)");
model.evaluate();
// We delete columns D and E
let r = model.delete_columns(0, 4, 2);
assert!(r.is_ok());
model.evaluate();
// Old H11 will be F11 and contain =#REF!
assert_eq!(model._get_formula("F11"), *"=#REF!");
// Old F4 will be D4 now
assert_eq!(model._get_formula("D4"), *"=C4");
// Old N5 will be L5
assert_eq!(model._get_formula("L5"), *"=K5*6");
// Range in A1 is displaced correctly
assert_eq!(model._get_formula("A1"), *"=SUM(K5:L5)");
// Note that range in A2 would contain some of the deleted cells
// A long as the borders of the range are not included that's ok.
assert_eq!(model._get_formula("A2"), *"=SUM(C4:K4)");
// FIXME: In Excel this would be (lower limit won't change)
// assert_eq!(model._get_formula("A3"), *"=SUM(E4:K4)");
assert_eq!(model._get_formula("A3"), *"=SUM(#REF!:K4)");
}
#[test]
fn test_delete_rows() {
let mut model = new_empty_model();
model._set("C4", "4");
model._set("C5", "5");
model._set("C6", "6");
model._set("C7", "=C6*2");
model._set("C72", "=C1*3");
model.evaluate();
// We delete rows 5, 6
let r = model.delete_rows(0, 5, 2);
assert!(r.is_ok());
model.evaluate();
assert_eq!(model._get_formula("C5"), *"=#REF!*2");
assert_eq!(model._get_formula("C70"), *"=C1*3");
}
// E F G H I J K
// 3 1 1 2
// 4 2 5 8
// -2 3 6 7
fn populate_table(model: &mut Model) {
model._set("G1", "3");
model._set("H1", "1");
model._set("I1", "1");
model._set("J1", "2");
model._set("G2", "4");
model._set("H2", "2");
model._set("I2", "5");
model._set("J2", "8");
model._set("G3", "-2");
model._set("H3", "3");
model._set("I3", "6");
model._set("J3", "7");
}
#[test]
fn test_move_column_right() {
let mut model = new_empty_model();
populate_table(&mut model);
model._set("E3", "=G3");
model._set("E4", "=H3");
model._set("E5", "=SUM(G3:J7)");
model._set("E6", "=SUM(G3:G7)");
model._set("E7", "=SUM(H3:H7)");
model.evaluate();
// Wee swap column G with column H
let result = model.move_column_action(0, 7, 1);
assert!(result.is_ok());
model.evaluate();
assert_eq!(model._get_formula("E3"), "=H3");
assert_eq!(model._get_formula("E4"), "=G3");
assert_eq!(model._get_formula("E5"), "=SUM(H3:J7)");
assert_eq!(model._get_formula("E6"), "=SUM(H3:H7)");
assert_eq!(model._get_formula("E7"), "=SUM(G3:G7)");
}
#[test]
fn tets_move_column_error() {
let mut model = new_empty_model();
model.evaluate();
let result = model.move_column_action(0, 7, -10);
assert!(result.is_err());
let result = model.move_column_action(0, -7, 20);
assert!(result.is_err());
let result = model.move_column_action(0, LAST_COLUMN, 1);
assert!(result.is_err());
let result = model.move_column_action(0, LAST_COLUMN + 1, -10);
assert!(result.is_err());
// This works
let result = model.move_column_action(0, LAST_COLUMN, -1);
assert!(result.is_ok());
}
// A B C D E F G H I J K L M N O P Q R
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

View File

@@ -0,0 +1,28 @@
use crate::functions::binary_search::*;
#[test]
fn test_binary_search() {
let t = vec![1, 2, 3, 40, 55, 155];
assert_eq!(binary_search_or_smaller(&40, &t), Some(3));
assert_eq!(binary_search_or_greater(&40, &t), Some(3));
assert_eq!(binary_search_or_smaller(&45, &t), Some(3));
assert_eq!(binary_search_or_greater(&45, &t), Some(4));
}
#[test]
fn test_binary_search_descending() {
let t = vec![100, 33, 23, 14, 5, -155];
assert_eq!(binary_search_descending_or_smaller(&23, &t), Some(2));
assert_eq!(binary_search_descending_or_greater(&23, &t), Some(2));
assert_eq!(binary_search_descending_or_smaller(&25, &t), Some(2));
assert_eq!(binary_search_descending_or_greater(&25, &t), Some(1));
}
#[test]
fn test_binary_search_multiple() {
let t = vec![1, 2, 3, 40, 40, 40, 40, 55, 155];
assert_eq!(binary_search_or_smaller(&40, &t), Some(3));
assert_eq!(binary_search_or_smaller(&39, &t), Some(2));
assert_eq!(binary_search_or_greater(&40, &t), Some(3));
assert_eq!(binary_search_or_greater(&41, &t), Some(7));
}

View File

@@ -0,0 +1,35 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
use crate::types::CellType;
#[test]
fn test_cell_get_type() {
let mut model = new_empty_model();
model._set("A1", "");
model._set("A2", "42");
model._set("A3", "12.34");
model._set("A4", "foobar");
model._set("A5", "1+2");
model._set("A6", "TRUE");
model._set("A7", "#VALUE!");
model._set("A8", "=Z100"); // an empty cell, considered to be a CellType::Number
model._set("A9", "=2*3*7");
model._set("A10", "=\"foo\"");
model._set("A11", "=1/0");
model._set("A12", "=1>0");
model.evaluate();
assert_eq!(model._get_cell("A1").get_type(), CellType::Text);
assert_eq!(model._get_cell("A2").get_type(), CellType::Number);
assert_eq!(model._get_cell("A3").get_type(), CellType::Number);
assert_eq!(model._get_cell("A4").get_type(), CellType::Text);
assert_eq!(model._get_cell("A5").get_type(), CellType::Text);
assert_eq!(model._get_cell("A6").get_type(), CellType::LogicalValue);
assert_eq!(model._get_cell("A7").get_type(), CellType::ErrorValue);
assert_eq!(model._get_cell("A8").get_type(), CellType::Number);
assert_eq!(model._get_cell("A9").get_type(), CellType::Number);
assert_eq!(model._get_cell("A10").get_type(), CellType::Text);
assert_eq!(model._get_cell("A11").get_type(), CellType::ErrorValue);
assert_eq!(model._get_cell("A12").get_type(), CellType::LogicalValue);
}

View File

@@ -0,0 +1,27 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn test_simple_circ() {
let mut model = new_empty_model();
model._set("A1", "=A1+1");
model.evaluate();
assert_eq!(model._get_text("A1"), "#CIRC!");
}
#[test]
fn test_simple_circ_propagate() {
let mut model = new_empty_model();
model._set("A1", "=B6");
model._set("A2", "=A1+1");
model._set("A3", "=A2+1");
model._set("A4", "=A3+5");
model._set("B6", "=A4*7");
model.evaluate();
assert_eq!(model._get_text("A1"), "#CIRC!");
assert_eq!(model._get_text("A2"), "#CIRC!");
assert_eq!(model._get_text("A3"), "#CIRC!");
assert_eq!(model._get_text("A4"), "#CIRC!");
assert_eq!(model._get_text("B6"), "#CIRC!");
}

View File

@@ -0,0 +1,82 @@
#![allow(clippy::unwrap_used)]
use crate::constants::{COLUMN_WIDTH_FACTOR, DEFAULT_COLUMN_WIDTH};
use crate::test::util::new_empty_model;
use crate::types::Col;
#[test]
fn test_column_width() {
let mut model = new_empty_model();
let cols = vec![Col {
custom_width: false,
max: 16384,
min: 1,
style: Some(6),
width: 8.7,
}];
model.workbook.worksheets[0].cols = cols;
model
.workbook
.worksheet_mut(0)
.unwrap()
.set_column_width(2, 30.0)
.unwrap();
assert_eq!(model.workbook.worksheets[0].cols.len(), 3);
let worksheet = model.workbook.worksheet(0).unwrap();
assert!((worksheet.column_width(1).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert!((worksheet.column_width(2).unwrap() - 30.0).abs() < f64::EPSILON);
assert!((worksheet.column_width(3).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert_eq!(model.get_cell_style_index(0, 23, 2), 6);
}
#[test]
fn test_column_width_lower_edge() {
let mut model = new_empty_model();
let cols = vec![Col {
custom_width: true,
max: 16,
min: 5,
style: Some(1),
width: 10.0,
}];
model.workbook.worksheets[0].cols = cols;
model
.workbook
.worksheet_mut(0)
.unwrap()
.set_column_width(5, 30.0)
.unwrap();
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
let worksheet = model.workbook.worksheet(0).unwrap();
assert!((worksheet.column_width(4).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert!((worksheet.column_width(5).unwrap() - 30.0).abs() < f64::EPSILON);
assert!((worksheet.column_width(6).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON);
assert_eq!(model.get_cell_style_index(0, 23, 5), 1);
}
#[test]
fn test_column_width_higher_edge() {
let mut model = new_empty_model();
let cols = vec![Col {
custom_width: true,
max: 16,
min: 5,
style: Some(1),
width: 10.0,
}];
model.workbook.worksheets[0].cols = cols;
model
.workbook
.worksheet_mut(0)
.unwrap()
.set_column_width(16, 30.0)
.unwrap();
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
let worksheet = model.workbook.worksheet(0).unwrap();
assert!(
(worksheet.column_width(15).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON
);
assert!((worksheet.column_width(16).unwrap() - 30.0).abs() < f64::EPSILON);
assert!((worksheet.column_width(17).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert_eq!(model.get_cell_style_index(0, 23, 16), 1);
}

Some files were not shown because too many files have changed in this diff Show More