use std::collections::{BTreeMap, BTreeSet, VecDeque};
use chrono::{NaiveDate, TimeZone, Timelike, Utc};
use chrono_tz::Tz;
use icu_casemap::CaseMapperBorrowed;
use truecalc_core::{Engine, EngineFlavor, ErrorKind, Ref, Resolver, Value as CoreValue};
use crate::address::Address;
use crate::casefold::simple_fold;
use crate::cell::Cell;
use crate::depgraph::{CellRef, DependencyGraph, Precedent, RangeRef};
use crate::spill::{spill_rect, SpillRect, BLOCKED_SPILL_ERROR};
use crate::value::Value;
use crate::workbook::Workbook;
pub const CIRCULAR_ERROR: &str = "#REF!";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RecalcContext {
timestamp_ms: i64,
timezone: Tz,
rng_seed: u64,
}
impl RecalcContext {
pub fn new(timestamp_ms: i64, tz: &str, rng_seed: u64) -> Option<Self> {
let timezone: Tz = tz.parse().ok()?;
Some(Self {
timestamp_ms,
timezone,
rng_seed,
})
}
pub fn timestamp_ms(&self) -> i64 {
self.timestamp_ms
}
pub fn timezone(&self) -> Tz {
self.timezone
}
pub fn rng_seed(&self) -> u64 {
self.rng_seed
}
pub fn now_serial(&self) -> Option<f64> {
let utc = Utc.timestamp_millis_opt(self.timestamp_ms).single()?;
let local = utc.with_timezone(&self.timezone).naive_local();
let epoch = NaiveDate::from_ymd_opt(1899, 12, 30)?;
let days = local.date().signed_duration_since(epoch).num_days() as f64;
let secs = local.time().num_seconds_from_midnight() as f64;
Some(days + secs / 86_400.0)
}
pub fn now_utc_nanos(&self) -> Option<i64> {
self.timestamp_ms.checked_mul(1_000_000)
}
pub fn rng_key(&self, sheet_index: u32, row: u32, col: u32, draw_index: u32) -> u64 {
let mut h = self.rng_seed;
for part in [
sheet_index as u64,
row as u64,
col as u64,
draw_index as u64,
] {
h = mix64(h ^ mix64(part));
}
h
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Change {
pub sheet: String,
pub addr: Address,
pub old: Value,
pub new: Value,
}
impl Workbook {
pub fn recalc(&mut self, ctx: &RecalcContext) -> Vec<Change> {
let graph = DependencyGraph::build(self);
let to_eval: BTreeSet<CellRef> = graph.formula_cells().cloned().collect();
self.recompute(&graph, ctx, to_eval)
}
pub fn recalc_incremental(
&mut self,
ctx: &RecalcContext,
edited: &[(String, Address)],
) -> Vec<Change> {
let graph = DependencyGraph::build(self);
let folder = CaseMapperBorrowed::new();
let mut dirty: BTreeSet<CellRef> = BTreeSet::new();
let mut frontier: VecDeque<CellRef> = VecDeque::new();
for (sheet, addr) in edited {
let folded = simple_fold(&folder, sheet);
let seed = CellRef {
sheet: folded,
addr: *addr,
};
if graph.is_formula(&seed) && dirty.insert(seed.clone()) {
frontier.push_back(seed.clone());
}
for dep in graph.direct_dependents_of(&seed) {
if dirty.insert(dep.clone()) {
frontier.push_back(dep);
}
}
}
while let Some(cell) = frontier.pop_front() {
for dep in graph.direct_dependents_of(&cell) {
if dirty.insert(dep.clone()) {
frontier.push_back(dep);
}
}
}
for cell in graph.formula_cells() {
if self.is_volatile(cell) {
dirty.insert(cell.clone());
}
}
self.seed_spill_sensitive(&graph, &mut dirty);
let pre = self.snapshot_formula_values(&graph);
let max_widen = graph.formula_cells().count().saturating_add(2).max(1);
for _ in 0..max_widen {
let before = self.anchor_rectangles();
self.recompute(&graph, ctx, dirty.clone());
let after = self.anchor_rectangles();
let mut added = false;
for (sheet, addr) in changed_rectangle_cells(&before, &after) {
let spilled_ref = CellRef { sheet, addr };
for dep in graph.direct_dependents_of(&spilled_ref) {
if dirty.insert(dep) {
added = true;
}
}
}
if !added {
break;
}
}
self.diff_against_snapshot(pre)
}
fn recompute(
&mut self,
graph: &DependencyGraph,
ctx: &RecalcContext,
to_eval: BTreeSet<CellRef>,
) -> Vec<Change> {
let now_serial = ctx.now_serial();
let now_utc_nanos = ctx.now_utc_nanos();
let rng_seed = ctx.rng_seed();
let cycle = graph.cycle_cells();
let order = match graph.topological_order() {
Ok(order) => order,
Err(_) => {
graph.acyclic_order_excluding(&cycle)
}
};
let (mut new_values, mut spills) = self.seed_spills_from_grid();
let max_passes = order.len().saturating_add(2).max(1);
for _ in 0..max_passes {
let mut next_values: BTreeMap<CellRef, Value> = BTreeMap::new();
let mut next_spills: BTreeMap<CellRef, SpillRect> = BTreeMap::new();
for cell in &order {
if cycle.contains(cell) {
continue; }
if !to_eval.contains(cell) {
continue;
}
let raw = self.eval_formula_cell(
cell,
now_serial,
now_utc_nanos,
rng_seed,
&next_values,
&next_spills,
&new_values,
&spills,
&cycle,
&to_eval,
);
let stored = self.place_spill(cell, raw, &next_values, &mut next_spills);
next_values.insert(cell.clone(), stored);
}
let converged = next_values == new_values && next_spills == spills;
new_values = next_values;
spills = next_spills;
if converged {
break;
}
}
for cell in &to_eval {
if !new_values.contains_key(cell) {
new_values.insert(cell.clone(), Value::Error(CIRCULAR_ERROR.to_owned()));
}
}
self.apply_changes(new_values)
}
#[allow(clippy::too_many_arguments)]
fn eval_formula_cell(
&self,
cell: &CellRef,
now_serial: Option<f64>,
now_utc_nanos: Option<i64>,
rng_seed: u64,
new_values: &BTreeMap<CellRef, Value>,
spills: &BTreeMap<CellRef, SpillRect>,
prev_values: &BTreeMap<CellRef, Value>,
prev_spills: &BTreeMap<CellRef, SpillRect>,
cycle: &BTreeSet<CellRef>,
recomputed: &BTreeSet<CellRef>,
) -> Value {
let formula = match self.cell_at(cell).and_then(Cell::formula) {
Some(f) => f.to_owned(),
None => return Value::Empty,
};
let engine = match self.engine() {
EngineFlavor::Sheets => Engine::sheets(),
EngineFlavor::Excel => Engine::excel(),
};
let folder = CaseMapperBorrowed::new();
let sheet_index = self
.sheets()
.iter()
.position(|ws| simple_fold(&folder, ws.name()) == cell.sheet)
.unwrap_or(0) as u32;
let rng_cell = Some((rng_seed, sheet_index, cell.addr.row, cell.addr.column));
let mut resolver = GridResolver {
workbook: self,
own_sheet: &cell.sheet,
new_values,
spills,
prev_values,
prev_spills,
cycle,
recomputed,
};
let core = engine.evaluate_with_resolver_at_keyed(
&formula,
&mut resolver,
now_serial,
now_utc_nanos,
rng_cell,
);
core_to_workbook(core)
}
fn place_spill(
&self,
cell: &CellRef,
value: Value,
new_values: &BTreeMap<CellRef, Value>,
placed: &mut BTreeMap<CellRef, SpillRect>,
) -> Value {
let Value::Array(ref rows) = value else {
return value; };
let nrows = rows.len();
let ncols = rows.first().map_or(0, Vec::len);
let Some(rect) = spill_rect(cell.addr, nrows, ncols) else {
return Value::Error(BLOCKED_SPILL_ERROR.to_owned());
};
if self.spill_blocked(cell, &rect, new_values, placed) {
return Value::Error(BLOCKED_SPILL_ERROR.to_owned());
}
placed.insert(cell.clone(), rect);
value
}
fn spill_blocked(
&self,
cell: &CellRef,
rect: &SpillRect,
new_values: &BTreeMap<CellRef, Value>,
placed: &BTreeMap<CellRef, SpillRect>,
) -> bool {
for addr in rect.spilled_cells() {
let target = CellRef {
sheet: cell.sheet.clone(),
addr,
};
if self.cell_at(&target).is_some() {
return true;
}
if new_values.contains_key(&target) {
return true;
}
if placed
.values()
.any(|r| r.anchor != cell.addr && r.contains(addr))
{
return true;
}
}
false
}
fn seed_spills_from_grid(&self) -> (BTreeMap<CellRef, Value>, BTreeMap<CellRef, SpillRect>) {
let folder = CaseMapperBorrowed::new();
let mut values: BTreeMap<CellRef, Value> = BTreeMap::new();
let mut spills: BTreeMap<CellRef, SpillRect> = BTreeMap::new();
for sheet in self.sheets() {
let folded = simple_fold(&folder, sheet.name());
for (addr, cell) in sheet.iter() {
let Value::Array(rows) = cell.value() else {
continue;
};
let nrows = rows.len();
let ncols = rows.first().map_or(0, Vec::len);
if let Some(rect) = spill_rect(addr, nrows, ncols) {
let key = CellRef {
sheet: folded.clone(),
addr,
};
values.insert(key.clone(), cell.value().clone());
spills.insert(key, rect);
}
}
}
(values, spills)
}
fn apply_changes(&mut self, new_values: BTreeMap<CellRef, Value>) -> Vec<Change> {
let folder = CaseMapperBorrowed::new();
let mut changes: Vec<(usize, Change)> = Vec::new();
for (cell, new) in new_values {
let Some(idx) = self.sheet_index_folded(&folder, &cell.sheet) else {
continue; };
let sheet_name = self.sheets()[idx].name().to_owned();
let old = self.sheets()[idx]
.get(cell.addr)
.map(|c| c.value().clone())
.unwrap_or(Value::Empty);
if old == new {
continue;
}
let formula = self.sheets()[idx]
.get(cell.addr)
.and_then(|c| c.formula())
.map(str::to_owned);
if let Some(formula) = formula {
self.sheets_mut()[idx].set(cell.addr, Cell::with_formula(formula, new.clone()));
}
changes.push((
idx,
Change {
sheet: sheet_name,
addr: cell.addr,
old,
new,
},
));
}
changes.sort_by(|a, b| {
a.0.cmp(&b.0)
.then(a.1.addr.row.cmp(&b.1.addr.row))
.then(a.1.addr.column.cmp(&b.1.addr.column))
});
changes.into_iter().map(|(_, c)| c).collect()
}
fn is_volatile(&self, cell: &CellRef) -> bool {
let Some(formula) = self.cell_at(cell).and_then(Cell::formula) else {
return false;
};
let upper = formula.to_ascii_uppercase();
truecalc_core::Registry::VOLATILE_FUNCTIONS
.iter()
.any(|name| contains_call(&upper, name))
}
fn snapshot_formula_values(&self, graph: &DependencyGraph) -> BTreeMap<CellRef, Value> {
let mut snap = BTreeMap::new();
for cell in graph.formula_cells() {
let value = self
.cell_at(cell)
.map(|c| c.value().clone())
.unwrap_or(Value::Empty);
snap.insert(cell.clone(), value);
}
snap
}
fn seed_spill_sensitive(&self, graph: &DependencyGraph, dirty: &mut BTreeSet<CellRef>) {
let rects = self.anchor_rectangles();
for cell in graph.formula_cells() {
let is_spill_cell = match self.cell_at(cell).map(Cell::value) {
Some(Value::Array(_)) => true,
Some(Value::Error(code)) => code == BLOCKED_SPILL_ERROR,
_ => false,
};
let mut seed = is_spill_cell;
if !seed {
if let Some(precedents) = graph.precedents_of(cell) {
seed = precedents
.iter()
.any(|p| self.precedent_is_spill_sensitive(p, &rects));
}
}
if seed {
dirty.insert(cell.clone());
}
}
}
fn precedent_is_spill_sensitive(
&self,
precedent: &Precedent,
rects: &BTreeMap<CellRef, SpillRect>,
) -> bool {
match precedent {
Precedent::Cell(c) => self.cell_at(c).is_none(),
Precedent::Range(r) => {
rects
.iter()
.any(|(anchor, rect)| anchor.sheet == r.sheet && rect_overlaps_range(rect, r))
|| self.range_has_unauthored_cell(r)
}
Precedent::Name(_) => true,
Precedent::Unresolved(_) => false,
}
}
fn range_has_unauthored_cell(&self, r: &RangeRef) -> bool {
let folder = CaseMapperBorrowed::new();
let Some(sheet) = self
.sheets()
.iter()
.find(|s| simple_fold(&folder, s.name()) == r.sheet)
else {
return true;
};
let rows = (r.end.row - r.start.row + 1) as u64;
let cols = (r.end.column - r.start.column + 1) as u64;
let area = rows.saturating_mul(cols);
let authored_inside = sheet
.iter()
.filter(|(addr, _)| {
addr.row >= r.start.row
&& addr.row <= r.end.row
&& addr.column >= r.start.column
&& addr.column <= r.end.column
})
.count() as u64;
authored_inside < area
}
fn anchor_rectangles(&self) -> BTreeMap<CellRef, SpillRect> {
let folder = CaseMapperBorrowed::new();
let mut rects = BTreeMap::new();
for sheet in self.sheets() {
let folded = simple_fold(&folder, sheet.name());
for (addr, cell) in sheet.iter() {
let Value::Array(rows) = cell.value() else {
continue;
};
let nrows = rows.len();
let ncols = rows.first().map_or(0, Vec::len);
if let Some(rect) = spill_rect(addr, nrows, ncols) {
rects.insert(
CellRef {
sheet: folded.clone(),
addr,
},
rect,
);
}
}
}
rects
}
fn diff_against_snapshot(&self, snapshot: BTreeMap<CellRef, Value>) -> Vec<Change> {
let folder = CaseMapperBorrowed::new();
let mut changes: Vec<(usize, Change)> = Vec::new();
for (cell, old) in snapshot {
let Some(idx) = self.sheet_index_folded(&folder, &cell.sheet) else {
continue;
};
let new = self.sheets()[idx]
.get(cell.addr)
.map(|c| c.value().clone())
.unwrap_or(Value::Empty);
if old == new {
continue;
}
changes.push((
idx,
Change {
sheet: self.sheets()[idx].name().to_owned(),
addr: cell.addr,
old,
new,
},
));
}
changes.sort_by(|a, b| {
a.0.cmp(&b.0)
.then(a.1.addr.row.cmp(&b.1.addr.row))
.then(a.1.addr.column.cmp(&b.1.addr.column))
});
changes.into_iter().map(|(_, c)| c).collect()
}
fn cell_at(&self, cell: &CellRef) -> Option<&Cell> {
let folder = CaseMapperBorrowed::new();
let idx = self.sheet_index_folded(&folder, &cell.sheet)?;
self.sheets()[idx].get(cell.addr)
}
fn sheet_index_folded(
&self,
folder: &CaseMapperBorrowed<'static>,
folded: &str,
) -> Option<usize> {
self.sheets()
.iter()
.position(|s| simple_fold(folder, s.name()) == folded)
}
}
struct GridResolver<'a> {
workbook: &'a Workbook,
own_sheet: &'a str,
new_values: &'a BTreeMap<CellRef, Value>,
spills: &'a BTreeMap<CellRef, SpillRect>,
prev_values: &'a BTreeMap<CellRef, Value>,
prev_spills: &'a BTreeMap<CellRef, SpillRect>,
cycle: &'a BTreeSet<CellRef>,
recomputed: &'a BTreeSet<CellRef>,
}
impl GridResolver<'_> {
fn cell_value(&self, sheet_folded: &str, addr: Address) -> CoreValue {
let key = CellRef {
sheet: sheet_folded.to_owned(),
addr,
};
if self.cycle.contains(&key) {
return CoreValue::Error(ErrorKind::Ref);
}
if let Some(v) = self.new_values.get(&key) {
return workbook_to_core(v);
}
let folder = CaseMapperBorrowed::new();
if let Some(c) = self
.workbook
.sheets()
.iter()
.find(|s| simple_fold(&folder, s.name()) == sheet_folded)
.and_then(|s| s.get(addr))
{
return workbook_to_core(c.value());
}
if let Some(v) = self.spilled_value(sheet_folded, addr, self.spills, self.new_values) {
return workbook_to_core(&v);
}
if let Some(v) = self.spilled_value(sheet_folded, addr, self.prev_spills, self.prev_values)
{
return workbook_to_core(&v);
}
if let Some(v) = self.prev_values.get(&key) {
return workbook_to_core(v);
}
if let Some(v) = self.grid_spilled_value(sheet_folded, addr) {
return workbook_to_core(&v);
}
CoreValue::Empty
}
fn grid_spilled_value(&self, sheet_folded: &str, addr: Address) -> Option<Value> {
let folder = CaseMapperBorrowed::new();
let sheet = self
.workbook
.sheets()
.iter()
.find(|s| simple_fold(&folder, s.name()) == sheet_folded)?;
for (anchor_addr, cell) in sheet.iter() {
if anchor_addr == addr {
continue;
}
let anchor_key = CellRef {
sheet: sheet_folded.to_owned(),
addr: anchor_addr,
};
if self.recomputed.contains(&anchor_key) {
continue;
}
let Value::Array(rows) = cell.value() else {
continue;
};
let nrows = rows.len();
let ncols = rows.first().map_or(0, Vec::len);
let Some(rect) = crate::spill::spill_rect(anchor_addr, nrows, ncols) else {
continue;
};
if let Some((i, j)) = rect.offset_of(addr) {
return rows.get(i).and_then(|r| r.get(j)).cloned();
}
}
None
}
fn spilled_value(
&self,
sheet_folded: &str,
addr: Address,
spills: &BTreeMap<CellRef, SpillRect>,
values: &BTreeMap<CellRef, Value>,
) -> Option<Value> {
for (anchor, rect) in spills {
if anchor.sheet != sheet_folded {
continue;
}
if anchor.addr == addr {
continue; }
let Some((i, j)) = rect.offset_of(addr) else {
continue;
};
if let Some(Value::Array(rows)) = values.get(anchor) {
return rows.get(i).and_then(|r| r.get(j)).cloned();
}
}
None
}
fn target_sheet(&self, sheet: &Option<String>) -> Option<String> {
let folder = CaseMapperBorrowed::new();
match sheet {
None => Some(self.own_sheet.to_owned()),
Some(name) => self
.workbook
.sheet(name)
.map(|s| simple_fold(&folder, s.name())),
}
}
}
impl Resolver for GridResolver<'_> {
fn resolve(&mut self, r: &Ref) -> CoreValue {
match r {
Ref::Cell { sheet, addr } => {
let Some(target) = self.target_sheet(sheet) else {
return CoreValue::Error(ErrorKind::Ref);
};
match Address::new(addr.row, addr.col) {
Some(a) => self.cell_value(&target, a),
None => CoreValue::Error(ErrorKind::Ref),
}
}
Ref::Range { sheet, start, end } => {
let Some(target) = self.target_sheet(sheet) else {
return CoreValue::Error(ErrorKind::Ref);
};
self.resolve_range(&target, start, end)
}
Ref::Name(name) => {
let folder = CaseMapperBorrowed::new();
let folded = simple_fold(&folder, name);
let target = self
.workbook
.names()
.iter()
.find(|nr| simple_fold(&folder, &nr.name) == folded);
match target {
None => CoreValue::Error(ErrorKind::Name),
Some(nr) => self.resolve_name_ref(&nr.r#ref),
}
}
}
}
}
impl GridResolver<'_> {
fn resolve_range(
&self,
sheet_folded: &str,
start: &truecalc_core::CellAddr,
end: &truecalc_core::CellAddr,
) -> CoreValue {
let (r0, r1) = (start.row.min(end.row), start.row.max(end.row));
let (c0, c1) = (start.col.min(end.col), start.col.max(end.col));
let mut cells: Vec<CoreValue> = Vec::new();
for r in r0..=r1 {
for c in c0..=c1 {
match Address::new(r, c) {
Some(a) => {
let v = self.cell_value(sheet_folded, a);
let scalar = match v {
CoreValue::Array(ref rows) => match rows.first() {
Some(CoreValue::Array(ref cols)) => {
cols.first().cloned().unwrap_or(CoreValue::Empty)
}
Some(other) => other.clone(),
None => CoreValue::Empty,
},
other => other,
};
cells.push(scalar);
}
None => cells.push(CoreValue::Error(ErrorKind::Ref)),
}
}
}
CoreValue::Array(cells)
}
fn resolve_name_ref(&mut self, r: &str) -> CoreValue {
let engine = match self.workbook.engine() {
EngineFlavor::Sheets => Engine::sheets(),
EngineFlavor::Excel => Engine::excel(),
};
let formula = format!("={r}");
match engine.parse(&formula) {
Ok(expr) => {
let refs = truecalc_core::extract_refs(&expr);
match refs.first() {
Some(first) => self.resolve(first),
None => CoreValue::Error(ErrorKind::Ref),
}
}
Err(_) => CoreValue::Error(ErrorKind::Ref),
}
}
}
fn core_to_workbook(v: CoreValue) -> Value {
match v {
CoreValue::Number(n) => Value::Number(n),
CoreValue::Text(s) => Value::Text(s),
CoreValue::Bool(b) => Value::Boolean(b),
CoreValue::Error(e) => Value::Error(e.to_string()),
CoreValue::Empty => Value::Empty,
CoreValue::Date(n) => Value::Date(n),
CoreValue::Zoned(z) => Value::Zoned(z),
CoreValue::Array(elems) => core_array_to_workbook(elems),
}
}
fn core_array_to_workbook(elems: Vec<CoreValue>) -> Value {
if elems.is_empty() {
return Value::Error("#REF!".to_owned());
}
let nested = elems.iter().all(|e| matches!(e, CoreValue::Array(_)));
let rows: Vec<Vec<Value>> = if nested {
elems
.into_iter()
.map(|row| match row {
CoreValue::Array(cells) => cells.into_iter().map(core_to_workbook).collect(),
other => vec![core_to_workbook(other)],
})
.collect()
} else {
vec![elems.into_iter().map(core_to_workbook).collect()]
};
if rows.len() == 1 && rows[0].len() == 1 {
return rows.into_iter().next().unwrap().into_iter().next().unwrap();
}
Value::Array(rows)
}
fn workbook_to_core(v: &Value) -> CoreValue {
match v {
Value::Number(n) => CoreValue::Number(*n),
Value::Text(s) => CoreValue::Text(s.clone()),
Value::Boolean(b) => CoreValue::Bool(*b),
Value::Error(code) => CoreValue::Error(error_kind_from_code(code)),
Value::Empty => CoreValue::Empty,
Value::Date(n) => CoreValue::Date(*n),
Value::Zoned(z) => CoreValue::Zoned(z.clone()),
Value::Array(rows) => CoreValue::Array(
rows.iter()
.map(|row| CoreValue::Array(row.iter().map(workbook_to_core).collect()))
.collect(),
),
}
}
fn error_kind_from_code(code: &str) -> ErrorKind {
match code {
"#DIV/0!" => ErrorKind::DivByZero,
"#VALUE!" => ErrorKind::Value,
"#REF!" => ErrorKind::Ref,
"#NAME?" => ErrorKind::Name,
"#NUM!" => ErrorKind::Num,
"#N/A" => ErrorKind::NA,
"#NULL!" => ErrorKind::Null,
_ => ErrorKind::Ref,
}
}
fn contains_call(upper: &str, name: &str) -> bool {
let bytes = upper.as_bytes();
let nb = name.as_bytes();
let mut i = 0;
while let Some(pos) = find_from(bytes, nb, i) {
let before_ok = pos == 0 || !is_ident_byte(bytes[pos - 1]);
let mut j = pos + nb.len();
while j < bytes.len() && bytes[j] == b' ' {
j += 1;
}
let after_ok = j < bytes.len() && bytes[j] == b'(';
if before_ok && after_ok {
return true;
}
i = pos + 1;
}
false
}
fn find_from(haystack: &[u8], needle: &[u8], from: usize) -> Option<usize> {
if needle.is_empty() || from + needle.len() > haystack.len() {
return None;
}
(from..=haystack.len() - needle.len()).find(|&i| &haystack[i..i + needle.len()] == needle)
}
fn is_ident_byte(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}
fn changed_rectangle_cells(
before: &BTreeMap<CellRef, SpillRect>,
after: &BTreeMap<CellRef, SpillRect>,
) -> BTreeSet<(String, Address)> {
let mut out: BTreeSet<(String, Address)> = BTreeSet::new();
let mut consider = |anchor: &CellRef, rect: &SpillRect| {
for addr in rect.spilled_cells() {
out.insert((anchor.sheet.clone(), addr));
}
};
for (anchor, rect) in before {
match after.get(anchor) {
Some(same) if same == rect => {}
_ => consider(anchor, rect),
}
}
for (anchor, rect) in after {
match before.get(anchor) {
Some(same) if same == rect => {}
_ => consider(anchor, rect),
}
}
out
}
fn rect_overlaps_range(rect: &SpillRect, range: &RangeRef) -> bool {
let rect_r0 = rect.anchor.row;
let rect_r1 = rect.anchor.row + rect.rows - 1;
let rect_c0 = rect.anchor.column;
let rect_c1 = rect.anchor.column + rect.cols - 1;
rect_r0 <= range.end.row
&& rect_r1 >= range.start.row
&& rect_c0 <= range.end.column
&& rect_c1 >= range.start.column
}
fn mix64(mut z: u64) -> u64 {
z = z.wrapping_add(0x9E37_79B9_7F4A_7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}