1use crate::SheetId as EngineSheetId;
2use crate::args::CoercionPolicy;
3use crate::arrow_store;
4use crate::engine::DependencyGraph;
5use crate::stripes::NumericChunk;
6use formualizer_common::{ExcelError, LiteralValue};
7
8pub struct RangeView<'a> {
10 backing: RangeBacking<'a>,
11 rows: usize,
12 cols: usize,
13}
14
15enum RangeBacking<'a> {
16 Borrowed2D(&'a [Vec<LiteralValue>]),
18 GraphSlice {
20 graph: &'a DependencyGraph,
21 sheet_id: EngineSheetId,
22 sr: u32,
23 sc: u32,
24 er: u32,
25 ec: u32,
26 },
27 Arrow(arrow_store::ArrowRangeView<'a>),
29 Hybrid {
31 graph: &'a DependencyGraph,
32 sheet_id: EngineSheetId,
33 sr: u32,
34 sc: u32,
35 arrow: arrow_store::ArrowRangeView<'a>,
36 },
37}
38
39impl<'a> core::fmt::Debug for RangeView<'a> {
40 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41 f.debug_struct("RangeView")
42 .field("rows", &self.rows)
43 .field("cols", &self.cols)
44 .field("kind", &self.kind_probe())
45 .finish()
46 }
47}
48
49#[derive(Copy, Clone, Debug, Eq, PartialEq)]
50pub enum RangeKind {
51 Empty,
52 NumericOnly,
53 TextOnly,
54 Mixed,
55}
56
57impl<'a> RangeView<'a> {
58 pub fn from_borrowed(rows: &'a [Vec<LiteralValue>]) -> Self {
59 let r = rows.len();
60 let c = rows.first().map(|r| r.len()).unwrap_or(0);
61 Self {
62 backing: RangeBacking::Borrowed2D(rows),
63 rows: r,
64 cols: c,
65 }
66 }
67 pub fn from_arrow(av: arrow_store::ArrowRangeView<'a>) -> Self {
68 let (rows, cols) = av.dims();
69 Self {
70 backing: RangeBacking::Arrow(av),
71 rows,
72 cols,
73 }
74 }
75
76 pub fn from_graph(
77 graph: &'a DependencyGraph,
78 sheet_id: EngineSheetId,
79 sr: u32,
80 sc: u32,
81 er: u32,
82 ec: u32,
83 ) -> Self {
84 let (rows, cols) = if er < sr || ec < sc {
86 (0, 0)
87 } else {
88 ((er - sr + 1) as usize, (ec - sc + 1) as usize)
89 };
90 Self {
91 backing: RangeBacking::GraphSlice {
92 graph,
93 sheet_id,
94 sr,
95 sc,
96 er,
97 ec,
98 },
99 rows,
100 cols,
101 }
102 }
103
104 #[inline]
105 pub fn dims(&self) -> (usize, usize) {
106 (self.rows, self.cols)
107 }
108
109 #[inline]
110 pub fn is_empty(&self) -> bool {
111 self.rows == 0 || self.cols == 0
112 }
113
114 pub fn kind_probe(&self) -> RangeKind {
115 match &self.backing {
116 RangeBacking::Borrowed2D(rows) => {
117 if rows.is_empty() || self.is_empty() {
118 RangeKind::Empty
119 } else {
120 let mut has_num = false;
122 let mut has_text = false;
123 let mut has_other = false;
124 'outer: for r in rows.iter() {
125 for v in r.iter() {
126 match v {
127 LiteralValue::Number(_)
128 | LiteralValue::Int(_)
129 | LiteralValue::Boolean(_)
130 | LiteralValue::Empty => has_num = true,
131 LiteralValue::Text(_) => has_text = true,
132 _ => {
133 has_other = true;
134 break 'outer;
135 }
136 }
137 }
138 }
139 if has_other || (has_num && has_text) {
140 RangeKind::Mixed
141 } else if has_text {
142 RangeKind::TextOnly
143 } else if has_num {
144 RangeKind::NumericOnly
145 } else {
146 RangeKind::Empty
147 }
148 }
149 }
150 RangeBacking::GraphSlice { .. } => RangeKind::Mixed, RangeBacking::Arrow(_) => RangeKind::Mixed, RangeBacking::Hybrid { .. } => RangeKind::Mixed,
153 }
154 }
155
156 pub fn as_1x1(&self) -> Option<LiteralValue> {
157 if self.rows == 0 || self.cols == 0 {
158 return None;
159 }
160 if self.rows == 1 && self.cols == 1 {
161 let mut out: Option<LiteralValue> = None;
162 let _ = self.for_each_cell(&mut |v| {
163 out = Some(v.clone());
164 Ok(())
165 });
166 return out;
167 }
168 None
169 }
170
171 pub fn get_cell(&self, row: usize, col: usize) -> LiteralValue {
174 if row >= self.rows || col >= self.cols {
175 return LiteralValue::Empty;
176 }
177
178 match &self.backing {
179 RangeBacking::Borrowed2D(rows) => rows[row][col].clone(),
180 RangeBacking::GraphSlice {
181 graph,
182 sheet_id,
183 sr,
184 sc,
185 ..
186 } => {
187 let actual_row = sr + row as u32;
188 let actual_col = sc + col as u32;
189 let coord = crate::reference::Coord::new(actual_row, actual_col, true, true);
190 let addr = crate::reference::CellRef::new(*sheet_id, coord);
191 graph
192 .get_vertex_id_for_address(&addr)
193 .and_then(|id| graph.get_value(*id))
194 .unwrap_or(LiteralValue::Empty)
195 }
196 RangeBacking::Arrow(av) => av.get_cell(row, col),
197 RangeBacking::Hybrid {
198 graph,
199 sheet_id,
200 sr,
201 sc,
202 arrow,
203 } => {
204 let abs_row = sr.saturating_add(row as u32);
206 let abs_col = sc.saturating_add(col as u32);
207 let coord = crate::reference::Coord::new(abs_row, abs_col, true, true);
208 let addr = crate::reference::CellRef::new(*sheet_id, coord);
209 if let Some(vid) = graph.get_vertex_id_for_address(&addr) {
210 if let Some(v) = graph.get_value(*vid) {
211 return v;
212 }
213 }
214 arrow.get_cell(row, col)
215 }
216 }
217 }
218
219 pub fn for_each_cell(
221 &self,
222 f: &mut dyn FnMut(&LiteralValue) -> Result<(), ExcelError>,
223 ) -> Result<(), ExcelError> {
224 match &self.backing {
225 RangeBacking::Borrowed2D(rows) => {
226 for r in rows.iter() {
227 for v in r.iter() {
228 f(v)?;
229 }
230 }
231 }
232 RangeBacking::GraphSlice {
233 graph,
234 sheet_id,
235 sr,
236 sc,
237 er,
238 ec,
239 } => {
240 if *er < *sr || *ec < *sc {
241 return Ok(());
242 }
243 let mut row = *sr;
244 while row <= *er {
245 let mut col = *sc;
246 while col <= *ec {
247 let coord = crate::reference::Coord::new(row, col, true, true);
248 let addr = crate::reference::CellRef::new(*sheet_id, coord);
249 let v = graph
250 .get_vertex_id_for_address(&addr)
251 .and_then(|id| graph.get_value(*id))
252 .unwrap_or(LiteralValue::Empty);
253 f(&v)?;
254 col += 1;
255 }
256 row += 1;
257 }
258 }
259 RangeBacking::Arrow(av) => {
260 for r in 0..self.rows {
261 for c in 0..self.cols {
262 let tmp = av.get_cell(r, c);
263 f(&tmp)?;
264 }
265 }
266 }
267 RangeBacking::Hybrid { .. } => {
268 for r in 0..self.rows {
269 for c in 0..self.cols {
270 let v = self.get_cell(r, c);
271 f(&v)?;
272 }
273 }
274 }
275 }
276 Ok(())
277 }
278
279 pub fn for_each_row(
281 &self,
282 f: &mut dyn FnMut(&[LiteralValue]) -> Result<(), ExcelError>,
283 ) -> Result<(), ExcelError> {
284 match &self.backing {
285 RangeBacking::Borrowed2D(rows) => {
286 for r in rows.iter() {
287 f(&r[..])?;
288 }
289 }
290 RangeBacking::GraphSlice {
291 graph,
292 sheet_id,
293 sr,
294 sc,
295 er,
296 ec,
297 } => {
298 if *er < *sr || *ec < *sc {
299 return Ok(());
300 }
301 let cols = (*ec - *sc + 1) as usize;
302 let mut buf: Vec<LiteralValue> = Vec::with_capacity(cols);
303 let mut row = *sr;
304 while row <= *er {
305 buf.clear();
306 let mut col = *sc;
307 while col <= *ec {
308 let coord = crate::reference::Coord::new(row, col, true, true);
309 let addr = crate::reference::CellRef::new(*sheet_id, coord);
310 let v = graph
311 .get_vertex_id_for_address(&addr)
312 .and_then(|id| graph.get_value(*id))
313 .unwrap_or(LiteralValue::Empty);
314 buf.push(v);
315 col += 1;
316 }
317 f(&buf[..])?;
318 row += 1;
319 }
320 }
321 RangeBacking::Arrow(av) => {
322 let mut buf: Vec<LiteralValue> = Vec::with_capacity(self.cols);
323 for r in 0..self.rows {
324 buf.clear();
325 for c in 0..self.cols {
326 buf.push(av.get_cell(r, c));
327 }
328 f(&buf[..])?;
329 }
330 }
331 RangeBacking::Hybrid { .. } => {
332 let mut buf: Vec<LiteralValue> = Vec::with_capacity(self.cols);
333 for r in 0..self.rows {
334 buf.clear();
335 for c in 0..self.cols {
336 buf.push(self.get_cell(r, c));
337 }
338 f(&buf[..])?;
339 }
340 }
341 }
342 Ok(())
343 }
344
345 pub fn for_each_col(
347 &self,
348 f: &mut dyn FnMut(&[LiteralValue]) -> Result<(), ExcelError>,
349 ) -> Result<(), ExcelError> {
350 match &self.backing {
351 RangeBacking::Borrowed2D(rows) => {
352 if self.cols == 0 {
353 return Ok(());
354 }
355 let mut col_buf: Vec<LiteralValue> = Vec::with_capacity(self.rows);
356 for c in 0..self.cols {
357 col_buf.clear();
358 for r in 0..self.rows {
359 col_buf.push(rows[r][c].clone());
360 }
361 f(&col_buf[..])?;
362 }
363 }
364 RangeBacking::GraphSlice {
365 graph,
366 sheet_id,
367 sr,
368 sc,
369 er,
370 ec,
371 } => {
372 if *er < *sr || *ec < *sc {
373 return Ok(());
374 }
375 let rows = (*er - *sr + 1) as usize;
376 let mut col_buf: Vec<LiteralValue> = Vec::with_capacity(rows);
377 let mut col = *sc;
378 while col <= *ec {
379 col_buf.clear();
380 let mut row = *sr;
381 while row <= *er {
382 let coord = crate::reference::Coord::new(row, col, true, true);
383 let addr = crate::reference::CellRef::new(*sheet_id, coord);
384 let v = graph
385 .get_vertex_id_for_address(&addr)
386 .and_then(|id| graph.get_value(*id))
387 .unwrap_or(LiteralValue::Empty);
388 col_buf.push(v);
389 row += 1;
390 }
391 f(&col_buf[..])?;
392 col += 1;
393 }
394 }
395 RangeBacking::Arrow(av) => {
396 let mut col_buf: Vec<LiteralValue> = Vec::with_capacity(self.rows);
397 for c in 0..self.cols {
398 col_buf.clear();
399 for r in 0..self.rows {
400 col_buf.push(av.get_cell(r, c));
401 }
402 f(&col_buf[..])?;
403 }
404 }
405 RangeBacking::Hybrid { .. } => {
406 let mut col_buf: Vec<LiteralValue> = Vec::with_capacity(self.rows);
407 for c in 0..self.cols {
408 col_buf.clear();
409 for r in 0..self.rows {
410 col_buf.push(self.get_cell(r, c));
411 }
412 f(&col_buf[..])?;
413 }
414 }
415 }
416 Ok(())
417 }
418
419 pub fn as_arrow(&self) -> Option<&arrow_store::ArrowRangeView<'a>> {
421 match &self.backing {
422 RangeBacking::Arrow(av) => Some(av),
423 RangeBacking::Hybrid { arrow, .. } => Some(arrow),
424 _ => None,
425 }
426 }
427
428 pub fn get_cell_numeric(&self, row: usize, col: usize, policy: CoercionPolicy) -> Option<f64> {
431 if row >= self.rows || col >= self.cols {
432 return None;
433 }
434
435 let val = self.get_cell(row, col);
436 pack_numeric(&val, policy).ok().flatten()
437 }
438
439 pub fn numbers_chunked(
441 &self,
442 policy: CoercionPolicy,
443 min_chunk: usize,
444 f: &mut dyn FnMut(NumericChunk) -> Result<(), ExcelError>,
445 ) -> Result<(), ExcelError> {
446 let min_chunk = min_chunk.max(1);
447 let mut buf: Vec<f64> = Vec::with_capacity(min_chunk);
448 let mut flush = |buf: &mut Vec<f64>| -> Result<(), ExcelError> {
449 if buf.is_empty() {
450 return Ok(());
451 }
452 let ptr = buf.as_ptr();
454 let len = buf.len();
455 let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
456 let chunk = NumericChunk {
457 data: slice,
458 validity: None,
459 };
460 f(chunk)?;
461 buf.clear();
462 Ok(())
463 };
464
465 match self.backing {
466 RangeBacking::Borrowed2D(rows) => {
467 for r in rows.iter() {
468 for v in r.iter() {
469 if let Some(n) = pack_numeric(v, policy)? {
470 buf.push(n);
471 if buf.len() >= min_chunk {
472 flush(&mut buf)?;
473 }
474 }
475 }
476 }
477 flush(&mut buf)?;
478 }
479 RangeBacking::GraphSlice { .. } => {
480 self.for_each_cell(&mut |v| {
481 if let Some(n) = pack_numeric(v, policy)? {
482 buf.push(n);
483 if buf.len() >= min_chunk {
484 flush(&mut buf)?;
485 }
486 }
487 Ok(())
488 })?;
489 flush(&mut buf)?;
490 }
491 RangeBacking::Arrow(_) => {
492 self.for_each_cell(&mut |v| {
493 if let Some(n) = pack_numeric(v, policy)? {
494 buf.push(n);
495 if buf.len() >= min_chunk {
496 flush(&mut buf)?;
497 }
498 }
499 Ok(())
500 })?;
501 flush(&mut buf)?;
502 }
503 RangeBacking::Hybrid { .. } => {
504 self.for_each_cell(&mut |v| {
505 if let Some(n) = pack_numeric(v, policy)? {
506 buf.push(n);
507 if buf.len() >= min_chunk {
508 flush(&mut buf)?;
509 }
510 }
511 Ok(())
512 })?;
513 flush(&mut buf)?;
514 }
515 }
516
517 Ok(())
518 }
519}
520
521impl<'a> RangeView<'a> {
522 pub fn from_hybrid(
523 graph: &'a DependencyGraph,
524 sheet_id: EngineSheetId,
525 sr: u32,
526 sc: u32,
527 arrow: arrow_store::ArrowRangeView<'a>,
528 ) -> Self {
529 let (rows, cols) = arrow.dims();
530 Self {
531 backing: RangeBacking::Hybrid {
532 graph,
533 sheet_id,
534 sr,
535 sc,
536 arrow,
537 },
538 rows,
539 cols,
540 }
541 }
542}
543
544#[inline]
545fn pack_numeric(v: &LiteralValue, policy: CoercionPolicy) -> Result<Option<f64>, ExcelError> {
546 match policy {
547 CoercionPolicy::NumberLenientText => match v {
548 LiteralValue::Error(e) => Err(e.clone()),
549 LiteralValue::Empty => Ok(None),
550 other => Ok(crate::coercion::to_number_lenient(other).ok()),
551 },
552 CoercionPolicy::NumberStrict => match v {
553 LiteralValue::Error(e) => Err(e.clone()),
554 LiteralValue::Empty => Ok(None),
555 other => Ok(crate::coercion::to_number_strict(other).ok()),
556 },
557 _ => match v {
558 LiteralValue::Error(e) => Err(e.clone()),
559 _ => Ok(None),
560 },
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567
568 #[test]
569 fn borrowed2d_rows_are_borrowed() {
570 let row0 = vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)];
571 let row1 = vec![LiteralValue::Number(3.0), LiteralValue::Number(4.0)];
572 let data: Vec<Vec<LiteralValue>> = vec![row0, row1];
573 let rows_ptrs: Vec<*const LiteralValue> = data.iter().map(|r| r.as_ptr()).collect();
574 let view = RangeView::from_borrowed(&data);
575 let mut seen_ptrs: Vec<*const LiteralValue> = Vec::new();
576 view.for_each_row(&mut |row| {
577 seen_ptrs.push(row.as_ptr());
578 Ok(())
579 })
580 .unwrap();
581 assert_eq!(rows_ptrs.len(), seen_ptrs.len());
582 for (a, b) in rows_ptrs.iter().zip(seen_ptrs.iter()) {
583 assert_eq!(*a, *b, "row slices should be borrowed, not cloned");
584 }
585 }
586
587 #[test]
588 fn borrowed2d_numeric_chunking() {
589 let data: Vec<Vec<LiteralValue>> = vec![
590 vec![
591 LiteralValue::Number(1.0),
592 LiteralValue::Text("x".into()),
593 LiteralValue::Int(3),
594 ],
595 vec![
596 LiteralValue::Boolean(true),
597 LiteralValue::Empty,
598 LiteralValue::Number(2.5),
599 ],
600 ];
601 let view = RangeView::from_borrowed(&data);
602 let mut sum = 0.0f64;
603 view.numbers_chunked(CoercionPolicy::NumberLenientText, 2, &mut |chunk| {
604 for &n in chunk.data {
605 sum += n;
606 }
607 Ok(())
608 })
609 .unwrap();
610 assert!((sum - 7.5).abs() < 1e-9);
611 }
612
613 #[test]
614 fn flat_numeric_numbers_chunked_removed() {
615 let data: Vec<Vec<LiteralValue>> = vec![
617 vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)],
618 vec![LiteralValue::Number(3.0), LiteralValue::Number(4.0)],
619 ];
620 let view = RangeView::from_borrowed(&data);
621 assert_eq!(view.dims(), (2, 2));
622 let mut collected = Vec::new();
623 view.numbers_chunked(CoercionPolicy::NumberLenientText, 3, &mut |chunk| {
624 collected.extend_from_slice(chunk.data);
625 Ok(())
626 })
627 .unwrap();
628 assert_eq!(collected, vec![1.0, 2.0, 3.0, 4.0]);
629 }
630
631 #[test]
632 fn graph_slice_row_iteration_and_sum() {
633 use crate::engine::{Engine, EvalConfig};
634 use crate::test_workbook::TestWorkbook;
635 let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
636 let sheet = engine.default_sheet_name().to_string();
637 engine
638 .set_cell_value(&sheet, 1, 1, LiteralValue::Int(1))
639 .unwrap();
640 engine
641 .set_cell_value(&sheet, 1, 2, LiteralValue::Int(2))
642 .unwrap();
643 engine
644 .set_cell_value(&sheet, 2, 1, LiteralValue::Int(3))
645 .unwrap();
646 engine
647 .set_cell_value(&sheet, 2, 2, LiteralValue::Int(4))
648 .unwrap();
649
650 let sheet_id = engine.default_sheet_id();
651 let view = RangeView::from_graph(&engine.graph, sheet_id, 1, 1, 2, 2);
652 assert_eq!(view.dims(), (2, 2));
653 let mut rows_sum = Vec::new();
654 view.for_each_row(&mut |row| {
655 let mut s = 0.0;
656 for v in row {
657 if let LiteralValue::Int(i) = v {
658 s += *i as f64
659 } else if let LiteralValue::Number(n) = v {
660 s += *n
661 }
662 }
663 rows_sum.push(s);
664 Ok(())
665 })
666 .unwrap();
667 assert_eq!(rows_sum, vec![3.0, 7.0]);
668
669 let mut sum = 0.0;
670 RangeView::from_graph(&engine.graph, sheet_id, 1, 1, 2, 2)
671 .numbers_chunked(CoercionPolicy::NumberLenientText, 2, &mut |chunk| {
672 for &n in chunk.data {
673 sum += n;
674 }
675 Ok(())
676 })
677 .unwrap();
678 assert_eq!(sum, 10.0);
679 }
680}