Merge pull request #600 from MarekSpodBiedry:issue-564

This commit is contained in:
Brandon Thomas 2026-10-13 00:49:40 -04:00
commit e381a8fd01
5 changed files with 47 additions and 16 deletions

View File

@ -407,6 +407,7 @@ impl<'h> Evaluator<'h> {
};
}
let v = self.value(x);
let sys = self.host.workbook().date_system;
Ev::V(map_unary(
&v,
&|n| match op {
@ -416,6 +417,7 @@ impl<'h> Evaluator<'h> {
UnOp::At | UnOp::Spill | UnOp::Trim(_) => Value::number(n),
},
op == UnOp::Plus,
sys,
))
}
@ -502,7 +504,7 @@ impl<'h> Evaluator<'h> {
let mut acc = self.value(left);
while let Some((o, r)) = rest.pop() {
let y = self.value(r);
acc = binary_values(o, &acc, &y);
acc = binary_values(o, &acc, &y, self.host.workbook().date_system);
}
Ev::V(acc)
}
@ -1593,12 +1595,12 @@ impl Acc {
// ---------------------------------------------------------------- operators
fn map_unary(v: &Value, f: &dyn Fn(f64) -> Value, plus: bool) -> Value {
fn map_unary(v: &Value, f: &dyn Fn(f64) -> Value, plus: bool, sys: gridcraft_core::DateSystem) -> Value {
match v {
Value::Array(a) => Value::from(Array { rows: a.rows, cols: a.cols, data: a.data.iter().map(|x| map_unary(x, f, plus)).collect() }),
Value::Array(a) => Value::from(Array { rows: a.rows, cols: a.cols, data: a.data.iter().map(|x| map_unary(x, f, plus, sys)).collect() }),
Value::Error(e) => Value::Error(*e),
Value::Text(_) if plus => v.clone(),
other => match other.to_number() {
other => match other.to_number_in(sys) {
Ok(n) => f(n),
Err(e) => Value::Error(e),
},
@ -1651,18 +1653,18 @@ fn zip3(c: &Array, t: &Value, f: &Value, g: impl Fn(&Value, &Value, &Value) -> V
Value::from(Array { rows, cols, data })
}
pub fn binary_values(op: BinOp, x: &Value, y: &Value) -> Value {
zip2(x, y, |a, b| binary_scalar(op, a, b))
pub fn binary_values(op: BinOp, x: &Value, y: &Value, sys: gridcraft_core::DateSystem) -> Value {
zip2(x, y, |a, b| binary_scalar(op, a, b, sys))
}
fn binary_scalar(op: BinOp, a: &Value, b: &Value) -> Value {
fn binary_scalar(op: BinOp, a: &Value, b: &Value, sys: gridcraft_core::DateSystem) -> Value {
if let Value::Error(e) = a {
return Value::Error(*e);
}
if let Value::Error(e) = b {
return Value::Error(*e);
}
let num = |f: fn(f64, f64) -> Value| match (a.to_number(), b.to_number()) {
let num = |f: fn(f64, f64) -> Value| match (a.to_number_in(sys), b.to_number_in(sys)) {
(Ok(x), Ok(y)) => f(x, y),
(Err(e), _) | (_, Err(e)) => Value::Error(e),
};

View File

@ -290,6 +290,26 @@ fn circular_references_without_iterative_calculation() {
assert_eq!(t.get("A1"), Value::Error(CellError::Circ));
}
#[test]
fn date_text_coerces_in_the_workbook_date_system() {
for (sys, serial) in [(gridcraft_core::DateSystem::D1900, 1462.0), (gridcraft_core::DateSystem::D1904, 0.0)] {
let mut t = T::new();
t.wb.date_system = sys;
t.set("A1", "'1/1/1904");
t.set("B1", "=A1+0");
t.set("C1", "=VALUE(A1)");
t.set("D1", "=-A1");
t.set("E1", "=DATEVALUE(A1)");
t.set("F1", "=NUMBERVALUE(A1)");
assert_eq!(t.get("A1"), Value::from("1/1/1904"));
assert_eq!(t.num("B1"), serial, "{sys:?}");
assert_eq!(t.num("C1"), serial, "{sys:?}");
assert_eq!(t.num("D1"), -serial, "{sys:?}");
assert_eq!(t.num("E1"), serial, "{sys:?}");
assert_eq!(t.num("F1"), serial, "{sys:?}");
}
}
#[test]
fn operators() {
let mut t = T::new();

View File

@ -41,7 +41,12 @@ pub fn parse_input(s: &str, sys: DateSystem) -> Parsed {
/// Numeric text as arithmetic coerces it ("1,000", "50%", "$3", "1/2/2020", "10:00").
pub fn parse_number_text(s: &str) -> Option<f64> {
parse_numeric(s.trim(), DateSystem::D1900).map(|(n, _)| n)
parse_number_text_in(s, DateSystem::D1900)
}
/// Like [`parse_number_text`], with date text read in the given workbook date system.
pub fn parse_number_text_in(s: &str, sys: DateSystem) -> Option<f64> {
parse_numeric(s.trim(), sys).map(|(n, _)| n)
}
fn parse_numeric(t: &str, sys: DateSystem) -> Option<(f64, Option<&'static str>)> {

View File

@ -224,13 +224,17 @@ impl Value {
/// Coercion to a number as arithmetic does it: empty = 0, TRUE = 1, numeric text parses
/// (including dates, times, percentages and currency), other text = `#VALUE!`.
pub fn to_number(&self) -> Result<f64, CellError> {
self.to_number_in(crate::DateSystem::D1900)
}
/// [`Value::to_number`] with date text read in the given workbook date system.
pub fn to_number_in(&self, sys: crate::DateSystem) -> Result<f64, CellError> {
match self {
Value::Empty => Ok(0.0),
Value::Number(n) => Ok(*n),
Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
Value::Text(t) => crate::parse::parse_number_text(t).ok_or(CellError::Value),
Value::Text(t) => crate::parse::parse_number_text_in(t, sys).ok_or(CellError::Value),
Value::Error(e) => Err(*e),
Value::Array(a) => a.data.first().map(|v| v.to_number()).unwrap_or(Ok(0.0)),
Value::Array(a) => a.data.first().map(|v| v.to_number_in(sys)).unwrap_or(Ok(0.0)),
}
}
/// Coercion to text as `&` does it.

View File

@ -1,6 +1,6 @@
//! Text functions.
use gridcraft_core::parse::parse_number_text;
use gridcraft_core::parse::parse_number_text_in;
use gridcraft_core::{CellError, Value, number_to_text};
use crate::criteria::wildcard_find;
@ -295,17 +295,17 @@ fn unicode(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
/// VALUE: a blank cell reads as 0, but empty or blank text is not a number (observed:
/// `VALUE("")` is `#VALUE!`).
fn value(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
fn value(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
match scalar(arg(a, 0)?) {
Value::Number(n) => num_val(n),
Value::Empty => num_val(0.0),
Value::Error(e) => Err(e),
Value::Text(t) => parse_number_text(&t).ok_or(CellError::Value).and_then(num_val),
Value::Text(t) => parse_number_text_in(&t, c.date_system()).ok_or(CellError::Value).and_then(num_val),
_ => Err(CellError::Value),
}
}
fn numbervalue(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
fn numbervalue(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
let s = text(a, 0)?;
let dec = if a.len() > 1 { text(a, 1)?.chars().next().ok_or(CellError::Value)? } else { '.' };
let grp = if a.len() > 2 { text(a, 2)?.chars().next().ok_or(CellError::Value)? } else { ',' };
@ -338,7 +338,7 @@ fn numbervalue(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
out.push(c);
}
}
let mut n = parse_number_text(&out).ok_or(CellError::Value)?;
let mut n = parse_number_text_in(&out, c.date_system()).ok_or(CellError::Value)?;
for _ in 0..pct {
n /= 100.0;
}