1mod edit;
2mod functions;
3
4pub use edit::get_word_boundaries_from_str;
5
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet, VecDeque};
8use web_time::Instant;
9
10use super::cell::{
11 CellRef, CellType, Dependency, EngineError, EvalError, TextCellRef, generate_unique_id,
12};
13use super::column::DataColumn;
14use super::result_data::ResultData;
15#[derive(Default)]
17pub struct Context<'a> {
18 pub sheets: HashMap<String, &'a Sheet>,
20 pub pivot_tables: &'a [crate::core::pivot::PivotTable],
23 pub sheet_order: Vec<String>,
26}
27
28impl<'a> Context<'a> {
29 pub fn new() -> Self {
31 Self {
32 sheets: HashMap::new(),
33 pivot_tables: &[],
34 sheet_order: Vec::new(),
35 }
36 }
37
38 pub fn add_table(&mut self, name: String, sheet: &'a Sheet) {
40 self.sheets.insert(name, sheet);
41 }
42}
43
44enum LetScope<'a> {
45 Empty,
46 Bound {
47 name: &'a str,
48 value: &'a ResultData,
49 parent: &'a LetScope<'a>,
50 },
51}
52
53struct EvalReference {
54 sheet: String,
55 start_row: usize,
56 start_col: usize,
57 end_row: usize,
58 end_col: usize,
59}
60
61impl<'a> LetScope<'a> {
62 fn get(&self, name: &str) -> Option<&ResultData> {
63 match self {
64 LetScope::Empty => None,
65 LetScope::Bound {
66 name: n,
67 value,
68 parent,
69 } => {
70 if n.eq_ignore_ascii_case(name) {
71 Some(value)
72 } else {
73 parent.get(name)
74 }
75 }
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq)]
82pub enum Direction {
83 None,
85 Up,
87 Down,
89 Left,
91 Right,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct Sheet {
98 #[serde(default = "generate_unique_id")]
100 pub id: u64,
101 pub name: String,
103 pub(crate) columns: Vec<DataColumn>,
108 #[serde(default)]
110 pub(crate) row_heights: Vec<Option<f64>>,
111 #[serde(default)]
113 pub tables: Vec<crate::core::table::ExcelTable>,
114 #[serde(skip, default)]
117 pub dependencies: HashMap<Dependency, HashSet<CellRef>>,
118 #[serde(skip, default)]
121 pub dependencies_rev: HashMap<CellRef, HashSet<Dependency>>,
122 #[serde(skip)]
125 pub uncommitted_actions: Vec<crate::core::SheetAction>,
126 #[serde(default)]
128 pub locale: crate::core::locale::Locale,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct SheetInit {
134 #[serde(default)]
136 pub id: Option<u64>,
137 pub name: Option<String>,
139 pub rows: usize,
141 pub cols: usize,
143}
144
145impl Default for SheetInit {
146 fn default() -> Self {
147 Self {
148 id: None,
149 name: None,
150 rows: 10,
151 cols: 5,
152 }
153 }
154}
155
156#[derive(Clone, Copy, PartialEq, Eq)]
157enum BlankPolicy {
158 Zero,
160 Skip,
162 Reject,
164}
165
166impl Sheet {
167 pub fn new(args: SheetInit) -> Sheet {
170 let SheetInit {
171 id,
172 name,
173 rows,
174 cols,
175 } = args;
176 let sheet_id = id.unwrap_or_else(generate_unique_id);
177 let sheet_name = name.unwrap_or_else(|| "table_1".to_string());
178
179 let mut columns = Vec::with_capacity(cols);
180 for _ in 0..cols {
181 columns.push(DataColumn::new(rows));
182 }
183
184 let mut uncommitted_actions = Vec::new();
185 for c in 0..cols {
186 for r in 0..rows {
187 uncommitted_actions.push(crate::core::SheetAction::SetCellSrc {
188 sheet_name: sheet_name.clone(),
189 col: c,
190 row: r,
191 src: String::new(),
192 });
193 }
194 }
195
196 Self {
197 id: sheet_id,
198 name: sheet_name,
199 columns,
200 row_heights: vec![None; rows],
201 tables: Vec::new(),
202 dependencies: HashMap::new(),
203 dependencies_rev: HashMap::new(),
204 uncommitted_actions,
205 locale: crate::core::locale::Locale::default(),
206 }
207 }
208
209 pub fn setup_after_deserialization(&mut self) {
211 for col in &mut self.columns {
212 col.rebuild_after_load();
213 }
214 let row_count = self.row_count();
215 self.row_heights.resize(row_count, None);
216 self.mark_all_dirty();
217 }
218
219 pub(crate) fn get_all_sheets_for_compilation(&self, context: Option<&Context>) -> Vec<Sheet> {
220 let mut list = vec![self.clone()];
221 let mut seen = std::collections::HashSet::new();
222 seen.insert(self.id);
223 if let Some(ctx) = context {
224 for sheet in ctx.sheets.values() {
225 if !seen.contains(&sheet.id) {
226 seen.insert(sheet.id);
227 list.push((*sheet).clone());
228 }
229 }
230 }
231 list
232 }
233
234 pub fn mark_all_dirty(&mut self) {
236 for col in &mut self.columns {
237 col.dirty_indices.clear();
238 col.dirty_indices.extend(0..col.src.len());
239 }
240 }
241
242 pub fn commit(&mut self, context: Option<&Context>) -> Result<HashSet<CellRef>, EngineError> {
244 let mut queue: VecDeque<CellRef> = VecDeque::new();
245 let mut queue_set: HashSet<CellRef> = HashSet::new();
246 let mut updated_cells: HashSet<CellRef> = HashSet::new();
247
248 for (col_idx, col_data) in self.columns.iter_mut().enumerate() {
249 for row_idx in &col_data.dirty_indices {
250 let cell = CellRef::new(*row_idx, col_idx);
251 queue.push_back(cell);
252 queue_set.insert(cell);
253 updated_cells.insert(cell);
254 }
255 col_data.dirty_indices.clear();
256 }
257
258 let initial_queue_len = queue.len();
259 if initial_queue_len == 0 {
260 return Ok(updated_cells);
261 }
262
263 let start_commit = Instant::now();
264 log::info!(
265 "Sheet '{}' commit starting for {} dirty cells",
266 self.name,
267 initial_queue_len
268 );
269 let max_ops = 10000.max(initial_queue_len * 3);
270 let mut ops = 0;
271
272 let mut sheets_for_compilation = self.get_all_sheets_for_compilation(context);
273 let mut last_log_time = Instant::now();
274
275 while let Some(cell_ref) = queue.pop_front() {
276 queue_set.remove(&cell_ref);
277 ops += 1;
278 if ops > max_ops {
279 println!("Circular dependency or too many updates detected");
280 break;
281 }
282
283 if ops % 50000 == 0 {
284 log::info!(
285 "Sheet '{}' commit progress: {}/{} cells processed ({:.2?})",
286 self.name,
287 ops,
288 initial_queue_len,
289 last_log_time.elapsed()
290 );
291 last_log_time = Instant::now();
292 }
293
294 let cell_type_hint = self
295 .columns
296 .get(cell_ref.col)
297 .and_then(|c| c.cell_types.get(cell_ref.row).copied())
298 .unwrap_or(CellType::Empty);
299
300 let mut detected_num_format: Option<String> = None;
301 let (result, new_deps, compiled_to_cache, mut final_cell_type) = {
302 let src = self.get_src_str_ref(&cell_ref).unwrap_or("");
303 if !src.starts_with('=') && cell_type_hint == CellType::String {
304 let val = if src.starts_with('"') && src.ends_with('"') && src.len() >= 2 {
305 src[1..src.len() - 1].to_string()
306 } else {
307 src.to_string()
308 };
309 (ResultData::String(val), vec![], None, CellType::String)
310 } else if !src.starts_with('=') {
311 let (res, c_type) = if let Some(stripped) = src.strip_prefix('\'') {
312 (ResultData::String(stripped.to_string()), CellType::String)
313 } else if matches!(
314 cell_type_hint,
315 CellType::DateTimeIso | CellType::DurationIso
316 ) {
317 (ResultData::String(src.to_string()), cell_type_hint)
318 } else if cell_type_hint == CellType::DateTime {
319 if let Ok(f) = src.trim().parse::<f64>() {
320 (ResultData::Float(f), CellType::DateTime)
321 } else if let Some((date, format)) =
322 crate::core::date::parse_date_with_locale(
323 src.trim_matches(' '),
324 &self.locale,
325 )
326 {
327 detected_num_format = Some(format.to_format_code());
328 (
329 ResultData::Float(crate::core::date::date_to_excel_serial(date)),
330 CellType::DateTime,
331 )
332 } else if let Some(f) = crate::core::date_fn::parse_time_fraction(src) {
333 (ResultData::Float(f), CellType::DateTime)
334 } else {
335 (ResultData::String(src.to_string()), CellType::String)
336 }
337 } else if cell_type_hint == CellType::Float {
338 if let Ok(f) = src.trim().parse::<f64>()
339 && f.is_finite()
340 {
341 (ResultData::Float(f), CellType::Float)
342 } else {
343 (ResultData::String(src.to_string()), CellType::String)
344 }
345 } else if cell_type_hint == CellType::Int {
346 if let Ok(i) = src.trim().parse::<i64>() {
347 (ResultData::Integer(i), CellType::Int)
348 } else {
349 (ResultData::String(src.to_string()), CellType::String)
350 }
351 } else if cell_type_hint == CellType::Bool {
352 if src == "1" || src.eq_ignore_ascii_case("true") {
353 (ResultData::Boolean(true), CellType::Bool)
354 } else if src == "0" || src.eq_ignore_ascii_case("false") {
355 (ResultData::Boolean(false), CellType::Bool)
356 } else {
357 (ResultData::String(src.to_string()), CellType::String)
358 }
359 } else if cell_type_hint == CellType::Error {
360 (ResultData::Error(src.to_uppercase()), CellType::Error)
361 } else if src.is_empty() {
362 (ResultData::None, CellType::Empty)
363 } else if src.starts_with('"') && src.ends_with('"') && src.len() >= 2 {
364 (
365 ResultData::String(src[1..src.len() - 1].to_string()),
366 CellType::String,
367 )
368 } else if let Ok(i) = src.trim().parse::<i64>() {
369 (ResultData::Integer(i), CellType::Int)
370 } else if let Ok(f) = src.trim().parse::<f64>()
371 && f.is_finite()
372 {
373 (ResultData::Float(f), CellType::Float)
374 } else if crate::core::engine::result_data::is_excel_error_code(src) {
375 (ResultData::Error(src.to_uppercase()), CellType::Error)
376 } else if src.eq_ignore_ascii_case("true") {
377 (ResultData::Boolean(true), CellType::Bool)
378 } else if src.eq_ignore_ascii_case("false") {
379 (ResultData::Boolean(false), CellType::Bool)
380 } else if let Some((date, format)) = crate::core::date::parse_date_with_locale(
381 src.trim_matches(' '),
382 &self.locale,
383 ) {
384 detected_num_format = Some(format.to_format_code());
385 (
386 ResultData::Float(crate::core::date::date_to_excel_serial(date)),
387 CellType::DateTime,
388 )
389 } else if let Some(f) = crate::core::date_fn::parse_time_fraction(src) {
390 (ResultData::Float(f), CellType::DateTime)
391 } else {
392 (ResultData::String(src.to_string()), CellType::String)
393 };
394 (res, vec![], None, c_type)
395 } else {
396 let compiled =
397 crate::core::parser::compile_formula(src, &sheets_for_compilation);
398 let eval_src =
399 crate::core::parser::serialize_formula(&compiled, &sheets_for_compilation);
400 let (res, deps) = match self.eval_with_row(
401 &eval_src,
402 context,
403 Some(cell_ref.row),
404 Some(cell_ref.col),
405 ) {
406 Ok(r) => r,
407 Err(e) => (ResultData::Error(e.to_string()), vec![]),
408 };
409 let final_res = if let ResultData::None = res {
410 ResultData::Float(0.0)
411 } else {
412 res
413 };
414 let final_cell_type = match &final_res {
415 ResultData::None => CellType::Empty,
416 ResultData::Integer(_) => CellType::Int,
417 ResultData::Float(_) => CellType::Float,
418 ResultData::String(_) => CellType::String,
419 ResultData::Boolean(_) => CellType::Bool,
420 ResultData::Error(_) => CellType::Error,
421 ResultData::List(_) | ResultData::Dict(_) => CellType::String,
422 };
423 (final_res, deps, Some(compiled), final_cell_type)
424 }
425 };
426
427 if let Some(src_str) = self.get_src_str_ref(&cell_ref)
428 && let Some(stripped) = src_str.strip_prefix('\'')
429 {
430 let stripped_str = stripped.to_string();
431 if let Some(col) = self.columns.get_mut(cell_ref.col)
432 && cell_ref.row < col.src.len()
433 {
434 col.src[cell_ref.row] = stripped_str;
435 }
436 }
437
438 if let Some(col) = self.columns.get_mut(cell_ref.col)
439 && cell_ref.row < col.compiled_src.len()
440 {
441 col.compiled_src[cell_ref.row] = compiled_to_cache.unwrap_or_default();
442 }
443
444 if let Some(old_deps) = self.dependencies_rev.remove(&cell_ref) {
445 for provider in old_deps {
446 if let Some(dependents) = self.dependencies.get_mut(&provider) {
447 dependents.remove(&cell_ref);
448 }
449 }
450 }
451
452 if !new_deps.is_empty() {
453 let mut new_deps_set = HashSet::new();
454 for provider in new_deps {
455 new_deps_set.insert(provider.clone());
456 self.dependencies
457 .entry(provider)
458 .or_default()
459 .insert(cell_ref);
460 }
461 self.dependencies_rev.insert(cell_ref, new_deps_set);
462 }
463
464 let inherited = if detected_num_format.is_some()
465 || !matches!(&result, ResultData::Float(_) | ResultData::Integer(_))
466 {
467 None
468 } else {
469 self.get_src_str_ref(&cell_ref)
470 .and_then(|src| src.strip_prefix('='))
471 .and_then(|body| crate::core::parser::parse_excel_formula(body).ok())
472 .and_then(|ast| self.inherited_date_format(&ast))
473 };
474 if let Some(code) = detected_num_format.or(inherited) {
475 if matches!(&result, ResultData::Float(_) | ResultData::Integer(_)) {
476 final_cell_type = CellType::DateTime;
477 }
478 let existing = self
479 .get_cell_style(cell_ref.row, cell_ref.col)
480 .and_then(|s| s.num_format.clone());
481 if existing.is_none() {
482 self.update_cell_style(cell_ref.row, cell_ref.col, |style| {
483 style.num_format = Some(code);
484 });
485 }
486 }
487
488 if let Some(col) = self.columns.get_mut(cell_ref.col)
489 && cell_ref.row < col.data.len()
490 {
491 col.cell_types[cell_ref.row] = final_cell_type;
492 col.data.set(cell_ref.row, result.clone());
493 updated_cells.insert(cell_ref);
494 }
495 if let Some(comp_sheet) = sheets_for_compilation
496 .iter_mut()
497 .find(|s| s.name == self.name)
498 && let Some(col) = comp_sheet.columns.get_mut(cell_ref.col)
499 && cell_ref.row < col.data.len()
500 {
501 col.cell_types[cell_ref.row] = final_cell_type;
502 col.data.set(cell_ref.row, result);
503 }
504
505 let local_dep_key = Dependency::Local(cell_ref);
506 if let Some(dependents) = self.dependencies.get(&local_dep_key) {
507 for dependent in dependents {
508 if !queue_set.contains(dependent) {
509 queue.push_back(*dependent);
510 queue_set.insert(*dependent);
511 }
512 }
513 }
514
515 let local_col_dep_key = Dependency::LocalColumn(cell_ref.col);
516 if let Some(dependents) = self.dependencies.get(&local_col_dep_key) {
517 for dependent in dependents {
518 if !queue_set.contains(dependent) {
519 queue.push_back(*dependent);
520 queue_set.insert(*dependent);
521 }
522 }
523 }
524 }
525 if initial_queue_len > 0 {
526 log::info!(
527 "Sheet '{}' commit finished. Processed {} cell updates. Total time: {:.2?}",
528 self.name,
529 ops,
530 start_commit.elapsed()
531 );
532 }
533 Ok(updated_cells)
534 }
535
536 pub fn eval_with_row(
539 &self,
540 input: &str,
541 context: Option<&Context>,
542 row: Option<usize>,
543 col: Option<usize>,
544 ) -> Result<(ResultData, Vec<Dependency>), EngineError> {
545 if input.is_empty() {
546 return Ok((ResultData::None, vec![]));
547 }
548 if let Some(formula) = input.strip_prefix('=') {
549 self.eval_excel(formula, context, row, col)
550 } else {
551 if let Ok(i) = input.parse::<i64>() {
552 Ok((ResultData::Integer(i), vec![]))
553 } else if let Ok(f) = input.parse::<f64>() {
554 Ok((ResultData::Float(f), vec![]))
555 } else if let Ok(b) = input.parse::<bool>() {
556 Ok((ResultData::Boolean(b), vec![]))
557 } else {
558 Ok((ResultData::String(input.to_string()), vec![]))
559 }
560 }
561 }
562
563 pub fn eval(
566 &self,
567 input: &str,
568 context: Option<&Context>,
569 ) -> Result<(ResultData, Vec<Dependency>), EngineError> {
570 self.eval_with_row(input, context, None, None)
571 }
572
573 fn eval_excel(
574 &self,
575 code: &str,
576 context: Option<&Context>,
577 row: Option<usize>,
578 col: Option<usize>,
579 ) -> Result<(ResultData, Vec<Dependency>), EngineError> {
580 let ast = crate::core::parser::parse_excel_formula(code)
581 .map_err(|e| EngineError::EvalError(EvalError::UnknownFunction(e)))?;
582
583 let mut deps = Vec::new();
584 let result = match self.evaluate_ast(&ast, context, row, col, &mut deps, &LetScope::Empty) {
585 Ok(r) => r,
586 Err(EngineError::EvalError(EvalError::UnknownFunction(err_str)))
587 if err_str.starts_with('#') =>
588 {
589 ResultData::Error(err_str)
590 }
591 Err(e) => return Err(e),
592 };
593 Ok((result, deps))
594 }
595
596 fn reference_sheet_dims(
597 &self,
598 sheet_name: &str,
599 context: Option<&Context>,
600 ) -> Result<(usize, usize), EngineError> {
601 if sheet_name == self.name {
602 Ok((self.row_count(), self.col_count()))
603 } else if let Some(ctx) = context {
604 ctx.sheets
605 .get(sheet_name)
606 .map(|sheet| (sheet.row_count(), sheet.col_count()))
607 .ok_or_else(|| {
608 EngineError::EvalError(EvalError::UnknownFunction(format!(
609 "Sheet not found: {}",
610 sheet_name
611 )))
612 })
613 } else {
614 Err(EngineError::EvalError(EvalError::UnknownFunction(
615 "No context to resolve sheet reference".to_string(),
616 )))
617 }
618 }
619
620 fn areas_from_expr(
621 &self,
622 expr: &crate::core::parser::Expr,
623 context: Option<&Context>,
624 ) -> Result<Option<Vec<EvalReference>>, EngineError> {
625 use crate::core::parser::Expr;
626 use crate::core::parser::Op;
627
628 match expr {
629 Expr::CellRef {
630 sheet, row, col, ..
631 } => Ok(Some(vec![EvalReference {
632 sheet: sheet.clone().unwrap_or_else(|| self.name.clone()),
633 start_row: *row,
634 start_col: *col,
635 end_row: *row,
636 end_col: *col,
637 }])),
638 Expr::RangeRef {
639 sheet,
640 start_row,
641 start_col,
642 end_row,
643 end_col,
644 ..
645 } => {
646 let sheet_name = sheet.clone().unwrap_or_else(|| self.name.clone());
647 let (row_count, col_count) = self.reference_sheet_dims(&sheet_name, context)?;
648 let actual_end_row = if *end_row == usize::MAX {
649 row_count.saturating_sub(1)
650 } else {
651 *end_row
652 };
653 let actual_end_col = if *end_col == usize::MAX {
654 col_count.saturating_sub(1)
655 } else {
656 *end_col
657 };
658 Ok(Some(vec![EvalReference {
659 sheet: sheet_name,
660 start_row: *start_row,
661 start_col: *start_col,
662 end_row: actual_end_row,
663 end_col: actual_end_col,
664 }]))
665 }
666 Expr::BinaryOp {
667 op: Op::Union,
668 left,
669 right,
670 } => {
671 let Some(mut left_areas) = self.areas_from_expr(left, context)? else {
672 return Ok(None);
673 };
674 let Some(right_areas) = self.areas_from_expr(right, context)? else {
675 return Ok(None);
676 };
677 left_areas.extend(right_areas);
678 Ok(Some(left_areas))
679 }
680 Expr::BinaryOp {
681 op: Op::Intersect,
682 left,
683 right,
684 } => {
685 let Some(left_areas) = self.areas_from_expr(left, context)? else {
686 return Ok(None);
687 };
688 let Some(right_areas) = self.areas_from_expr(right, context)? else {
689 return Ok(None);
690 };
691 let intersections = Self::intersect_areas(&left_areas, &right_areas);
692 if intersections.is_empty() {
693 return Err(EngineError::EvalError(EvalError::UnknownFunction(
694 "#NULL!".to_string(),
695 )));
696 }
697 Ok(Some(intersections))
698 }
699 _ => Ok(None),
700 }
701 }
702
703 fn intersect_areas(left: &[EvalReference], right: &[EvalReference]) -> Vec<EvalReference> {
704 let mut out = Vec::new();
705 for l in left {
706 for r in right {
707 if l.sheet != r.sheet {
708 continue;
709 }
710 let start_row = l.start_row.max(r.start_row);
711 let start_col = l.start_col.max(r.start_col);
712 let end_row = l.end_row.min(r.end_row);
713 let end_col = l.end_col.min(r.end_col);
714 if start_row <= end_row && start_col <= end_col {
715 out.push(EvalReference {
716 sheet: l.sheet.clone(),
717 start_row,
718 start_col,
719 end_row,
720 end_col,
721 });
722 }
723 }
724 }
725 out
726 }
727
728 fn eval_area(
729 &self,
730 area: &EvalReference,
731 context: Option<&Context>,
732 row: Option<usize>,
733 col: Option<usize>,
734 deps: &mut Vec<Dependency>,
735 ) -> Result<ResultData, EngineError> {
736 let sheet = if area.sheet == self.name {
737 None
738 } else {
739 Some(area.sheet.clone())
740 };
741 let expr = if area.start_row == area.end_row && area.start_col == area.end_col {
742 crate::core::parser::Expr::CellRef {
743 sheet,
744 row: area.start_row,
745 col: area.start_col,
746 row_abs: false,
747 col_abs: false,
748 }
749 } else {
750 crate::core::parser::Expr::RangeRef {
751 sheet,
752 start_row: area.start_row,
753 start_col: area.start_col,
754 end_row: area.end_row,
755 end_col: area.end_col,
756 start_row_abs: false,
757 start_col_abs: false,
758 end_row_abs: false,
759 end_col_abs: false,
760 }
761 };
762 self.evaluate_ast(&expr, context, row, col, deps, &LetScope::Empty)
763 }
764
765 fn combine_union_values(left: ResultData, right: ResultData) -> ResultData {
766 match (left, right) {
767 (ResultData::List(mut l), ResultData::List(r)) => {
768 l.extend(r);
769 ResultData::List(l)
770 }
771 (ResultData::List(mut l), r) => {
772 l.push(r);
773 ResultData::List(l)
774 }
775 (l, ResultData::List(mut r)) => {
776 r.insert(0, l);
777 ResultData::List(r)
778 }
779 (l, r) => ResultData::List(vec![l, r]),
780 }
781 }
782
783 fn evaluate_reference_intersection(
784 &self,
785 left: &crate::core::parser::Expr,
786 right: &crate::core::parser::Expr,
787 context: Option<&Context>,
788 row: Option<usize>,
789 col: Option<usize>,
790 deps: &mut Vec<Dependency>,
791 ) -> Result<ResultData, EngineError> {
792 let Some(left_areas) = self.areas_from_expr(left, context)? else {
793 return Ok(ResultData::Error("#VALUE!".to_string()));
794 };
795 let Some(right_areas) = self.areas_from_expr(right, context)? else {
796 return Ok(ResultData::Error("#VALUE!".to_string()));
797 };
798 let intersections = Self::intersect_areas(&left_areas, &right_areas);
799 if intersections.is_empty() {
800 return Ok(ResultData::Error("#NULL!".to_string()));
801 }
802 let mut out = Vec::new();
803 for area in intersections {
804 match self.eval_area(&area, context, row, col, deps)? {
805 ResultData::Error(e) => return Ok(ResultData::Error(e)),
806 ResultData::List(items) => out.extend(items),
807 value => out.push(value),
808 }
809 }
810 if out.len() == 1 {
811 Ok(out.pop().unwrap())
812 } else {
813 Ok(ResultData::List(out))
814 }
815 }
816
817 fn evaluate_implicit_intersection(
818 &self,
819 expr: &crate::core::parser::Expr,
820 context: Option<&Context>,
821 row: Option<usize>,
822 col: Option<usize>,
823 deps: &mut Vec<Dependency>,
824 scope: &LetScope<'_>,
825 ) -> Result<ResultData, EngineError> {
826 if let crate::core::parser::Expr::StructuredRef {
827 sheet,
828 column,
829 is_this_row: false,
830 section,
831 } = expr
832 && matches!(
833 section,
834 crate::core::SheetSection::Data | crate::core::SheetSection::All
835 )
836 {
837 let intersected = crate::core::parser::Expr::StructuredRef {
838 sheet: sheet.clone(),
839 column: column.clone(),
840 is_this_row: true,
841 section: *section,
842 };
843 return self.evaluate_ast(&intersected, context, row, col, deps, scope);
844 }
845
846 if let Some(areas) = self.areas_from_expr(expr, context)? {
847 if areas.len() != 1 {
848 return Ok(ResultData::Error("#VALUE!".to_string()));
849 }
850 let area = &areas[0];
851 let target = if area.start_row == area.end_row && area.start_col == area.end_col {
852 Some((area.start_row, area.start_col))
853 } else if area.start_col == area.end_col {
854 let Some(r) = row else {
855 return Ok(ResultData::Error("#VALUE!".to_string()));
856 };
857 if r >= area.start_row && r <= area.end_row {
858 Some((r, area.start_col))
859 } else {
860 None
861 }
862 } else if area.start_row == area.end_row {
863 let Some(c) = col else {
864 return Ok(ResultData::Error("#VALUE!".to_string()));
865 };
866 if c >= area.start_col && c <= area.end_col {
867 Some((area.start_row, c))
868 } else {
869 None
870 }
871 } else {
872 None
873 };
874 let Some((target_row, target_col)) = target else {
875 return Ok(ResultData::Error("#VALUE!".to_string()));
876 };
877 return self.eval_area(
878 &EvalReference {
879 sheet: area.sheet.clone(),
880 start_row: target_row,
881 start_col: target_col,
882 end_row: target_row,
883 end_col: target_col,
884 },
885 context,
886 row,
887 col,
888 deps,
889 );
890 }
891
892 let value = self.evaluate_ast(expr, context, row, col, deps, scope)?;
893 match value {
894 ResultData::List(items) => {
895 let (mut flat, _) = Self::flatten_row_major(items);
896 if flat.is_empty() {
897 Ok(ResultData::Error("#VALUE!".to_string()))
898 } else {
899 Ok(flat.remove(0))
900 }
901 }
902 other => Ok(other),
903 }
904 }
905
906 fn evaluate_ast(
907 &self,
908 ast: &crate::core::parser::Expr,
909 context: Option<&Context>,
910 row: Option<usize>,
911 col: Option<usize>,
912 deps: &mut Vec<Dependency>,
913 scope: &LetScope<'_>,
914 ) -> Result<ResultData, EngineError> {
915 use crate::core::SheetSection;
916 use crate::core::parser::Expr;
917 use crate::core::parser::Op;
918
919 match ast {
920 Expr::Number(n) => Ok(ResultData::Float(*n)),
921 Expr::String(s) => Ok(ResultData::String(s.clone())),
922 Expr::Boolean(b) => Ok(ResultData::Boolean(*b)),
923 Expr::Error(code) => Ok(ResultData::Error(code.to_string())),
924 Expr::Identifier(name) => match scope.get(name) {
925 Some(val) => Ok(val.clone()),
926 None => Ok(ResultData::Error("#NAME?".to_string())),
927 },
928 Expr::StructuredRef {
929 sheet,
930 column,
931 is_this_row,
932 section,
933 } => {
934 let ref_name = match sheet {
935 Some(name) => name.clone(),
936 None => self.name.clone(),
937 };
938
939 let mut found: Option<(&Sheet, &crate::core::table::ExcelTable)> =
940 self.find_table(&ref_name).map(|t| (self, t));
941 if found.is_none()
942 && let Some(ctx) = context
943 {
944 for s in ctx.sheets.values() {
945 if let Some(t) = s.find_table(&ref_name) {
946 found = Some((s, t));
947 break;
948 }
949 }
950 }
951
952 if let Some((table_sheet, excel_table)) = found {
953 let is_self = table_sheet.name == self.name;
954 let sheet_name = table_sheet.name.clone();
955
956 let col_indices: Vec<(usize, usize)> = if let Some(col_name) = column {
957 let local = excel_table.local_column_index(col_name).ok_or_else(|| {
958 EngineError::EvalError(EvalError::UnknownFunction(format!(
959 "Column not found: {}",
960 col_name
961 )))
962 })?;
963 vec![(local, excel_table.start_col + local)]
964 } else {
965 (0..excel_table.columns.len())
966 .map(|local| (local, excel_table.start_col + local))
967 .collect()
968 };
969 let is_whole_table = column.is_none();
970
971 match section {
972 SheetSection::Headers => {
973 let names: Vec<ResultData> = col_indices
974 .iter()
975 .map(|&(local, _)| {
976 ResultData::String(
977 excel_table.columns.get(local).cloned().unwrap_or_default(),
978 )
979 })
980 .collect();
981 if is_whole_table {
982 Ok(ResultData::List(names))
983 } else {
984 Ok(names.into_iter().next().unwrap_or(ResultData::None))
985 }
986 }
987 SheetSection::Totals => {
988 if let Some(totals_row) = excel_table.totals_row() {
989 let mut results = Vec::new();
990 for &(_, col_idx) in &col_indices {
991 let cell_ref = CellRef::new(totals_row, col_idx);
992 if is_self {
993 deps.push(Dependency::Local(cell_ref));
994 } else {
995 deps.push(Dependency::Remote {
996 sheet: sheet_name.clone(),
997 cell: cell_ref,
998 });
999 }
1000 results.push(table_sheet.get_result_data(&cell_ref));
1001 }
1002 if is_whole_table {
1003 Ok(ResultData::List(results))
1004 } else {
1005 Ok(results.into_iter().next().unwrap_or(ResultData::None))
1006 }
1007 } else {
1008 Ok(ResultData::None)
1009 }
1010 }
1011 SheetSection::Data | SheetSection::All => {
1012 if *is_this_row {
1013 let r = row.ok_or_else(|| {
1014 EngineError::EvalError(EvalError::UnknownFunction(
1015 "This row reference cannot be evaluated without row context"
1016 .to_string(),
1017 ))
1018 })?;
1019 let mut results = Vec::new();
1020 for &(_, col_idx) in &col_indices {
1021 let cell_ref = CellRef::new(r, col_idx);
1022 if is_self {
1023 deps.push(Dependency::Local(cell_ref));
1024 } else {
1025 deps.push(Dependency::Remote {
1026 sheet: sheet_name.clone(),
1027 cell: cell_ref,
1028 });
1029 }
1030 results.push(table_sheet.get_result_data(&cell_ref));
1031 }
1032 if is_whole_table {
1033 Ok(ResultData::List(results))
1034 } else {
1035 Ok(results.into_iter().next().unwrap_or(ResultData::None))
1036 }
1037 } else {
1038 let mut results = Vec::new();
1039 for &(_, col_idx) in &col_indices {
1040 for r in
1041 excel_table.data_start_row()..=excel_table.data_end_row()
1042 {
1043 let cell_ref = CellRef::new(r, col_idx);
1044 if is_self {
1045 deps.push(Dependency::Local(cell_ref));
1046 } else {
1047 deps.push(Dependency::Remote {
1048 sheet: sheet_name.clone(),
1049 cell: cell_ref,
1050 });
1051 }
1052 results.push(table_sheet.get_result_data(&cell_ref));
1053 }
1054 }
1055 Ok(ResultData::List(results))
1056 }
1057 }
1058 }
1059 } else {
1060 let sheet_name = ref_name;
1061 let is_self = sheet_name == self.name;
1062
1063 let target_sheet = if is_self {
1064 self
1065 } else if let Some(ctx) = context {
1066 if let Some(sheet) = ctx.sheets.get(&sheet_name) {
1067 sheet
1068 } else {
1069 return Err(EngineError::EvalError(EvalError::UnknownFunction(
1070 format!("Sheet not found: {}", sheet_name),
1071 )));
1072 }
1073 } else {
1074 return Err(EngineError::EvalError(EvalError::UnknownFunction(format!(
1075 "No context to resolve sheet reference: {}",
1076 sheet_name
1077 ))));
1078 };
1079
1080 let col_indices: Vec<usize> = if let Some(col_name) = column {
1081 let pos = target_sheet
1082 .columns
1083 .iter()
1084 .position(|c| c.name == *col_name)
1085 .ok_or_else(|| {
1086 EngineError::EvalError(EvalError::UnknownFunction(format!(
1087 "Column not found: {}",
1088 col_name
1089 )))
1090 })?;
1091 vec![pos]
1092 } else {
1093 (0..target_sheet.columns.len()).collect()
1094 };
1095 let is_whole_table = column.is_none();
1096
1097 match section {
1098 SheetSection::Headers => {
1099 let names: Vec<ResultData> = col_indices
1100 .iter()
1101 .map(|&idx| {
1102 ResultData::String(
1103 target_sheet
1104 .columns
1105 .get(idx)
1106 .map(|c| c.name.clone())
1107 .unwrap_or_default(),
1108 )
1109 })
1110 .collect();
1111 if is_whole_table {
1112 Ok(ResultData::List(names))
1113 } else {
1114 Ok(names.into_iter().next().unwrap_or(ResultData::None))
1115 }
1116 }
1117 SheetSection::Totals => Ok(ResultData::None),
1118 SheetSection::Data | SheetSection::All => {
1119 if *is_this_row {
1120 let r = row.ok_or_else(|| {
1121 EngineError::EvalError(EvalError::UnknownFunction(
1122 "This row reference cannot be evaluated without row context"
1123 .to_string(),
1124 ))
1125 })?;
1126 let mut results = Vec::new();
1127 for &col_idx in &col_indices {
1128 let cell_ref = CellRef::new(r, col_idx);
1129 if is_self {
1130 deps.push(Dependency::Local(cell_ref));
1131 } else {
1132 deps.push(Dependency::Remote {
1133 sheet: sheet_name.clone(),
1134 cell: cell_ref,
1135 });
1136 }
1137 results.push(target_sheet.get_result_data(&cell_ref));
1138 }
1139 if is_whole_table {
1140 Ok(ResultData::List(results))
1141 } else {
1142 Ok(results.into_iter().next().unwrap_or(ResultData::None))
1143 }
1144 } else {
1145 let mut results = Vec::new();
1146 for &col_idx in &col_indices {
1147 if is_self {
1148 deps.push(Dependency::LocalColumn(col_idx));
1149 } else {
1150 deps.push(Dependency::RemoteColumn {
1151 sheet: sheet_name.clone(),
1152 col: col_idx,
1153 });
1154 }
1155 for r in 0..target_sheet.row_count() {
1156 let cell_ref = CellRef::new(r, col_idx);
1157 results.push(target_sheet.get_result_data(&cell_ref));
1158 }
1159 }
1160 Ok(ResultData::List(results))
1161 }
1162 }
1163 }
1164 }
1165 }
1166 Expr::CellRef {
1167 sheet,
1168 row: r_val,
1169 col,
1170 ..
1171 } => {
1172 let cell_ref = CellRef::new(*r_val, *col);
1173 let is_self = match sheet {
1174 Some(name) => name == &self.name,
1175 None => true,
1176 };
1177
1178 if is_self {
1179 deps.push(Dependency::Local(cell_ref));
1180 Ok(self.get_result_data(&cell_ref))
1181 } else {
1182 let name = sheet.as_ref().unwrap().clone();
1183 deps.push(Dependency::Remote {
1184 sheet: name.clone(),
1185 cell: cell_ref,
1186 });
1187
1188 if let Some(ctx) = context {
1189 if let Some(t) = ctx.sheets.get(&name) {
1190 Ok(t.get_result_data(&cell_ref))
1191 } else {
1192 Err(EngineError::EvalError(EvalError::UnknownFunction(format!(
1193 "Sheet not found: {}",
1194 name
1195 ))))
1196 }
1197 } else {
1198 Err(EngineError::EvalError(EvalError::UnknownFunction(
1199 "No context to resolve sheet reference".to_string(),
1200 )))
1201 }
1202 }
1203 }
1204 Expr::RangeRef {
1205 sheet,
1206 start_row,
1207 start_col,
1208 end_row,
1209 end_col,
1210 ..
1211 } => {
1212 let is_self = match sheet {
1213 Some(name) => name == &self.name,
1214 None => true,
1215 };
1216
1217 let target_sheet = if is_self {
1218 Some(self)
1219 } else {
1220 context.and_then(|ctx| ctx.sheets.get(sheet.as_ref().unwrap()).copied())
1221 };
1222
1223 let actual_end_row = if *end_row == usize::MAX {
1224 target_sheet
1225 .map(|t| t.row_count().saturating_sub(1))
1226 .unwrap_or(0)
1227 } else {
1228 *end_row
1229 };
1230 let actual_end_col = if *end_col == usize::MAX {
1231 target_sheet
1232 .map(|t| t.col_count().saturating_sub(1))
1233 .unwrap_or(0)
1234 } else {
1235 *end_col
1236 };
1237
1238 let is_col_range = *end_row == usize::MAX;
1239
1240 let mut seen_col_deps: HashSet<usize> = HashSet::new();
1241
1242 let mut results = Vec::new();
1243 for r in *start_row..=actual_end_row {
1244 for c in *start_col..=actual_end_col {
1245 let cell_ref = CellRef::new(r, c);
1246 if is_self {
1247 if is_col_range {
1248 if seen_col_deps.insert(c) {
1249 let col_dep = Dependency::LocalColumn(c);
1250 if !deps.contains(&col_dep) {
1251 deps.push(col_dep);
1252 }
1253 }
1254 } else {
1255 deps.push(Dependency::Local(cell_ref));
1256 }
1257 if row == Some(r) && col == Some(c) {
1258 results.push(ResultData::None);
1259 } else {
1260 results.push(self.get_result_data(&cell_ref));
1261 }
1262 } else {
1263 let name = sheet.as_ref().unwrap().clone();
1264 if is_col_range {
1265 if seen_col_deps.insert(c) {
1266 let col_dep = Dependency::RemoteColumn {
1267 sheet: name.clone(),
1268 col: c,
1269 };
1270 if !deps.contains(&col_dep) {
1271 deps.push(col_dep);
1272 }
1273 }
1274 } else {
1275 deps.push(Dependency::Remote {
1276 sheet: name.clone(),
1277 cell: cell_ref,
1278 });
1279 }
1280 if let Some(ctx) = context {
1281 if let Some(t) = ctx.sheets.get(&name) {
1282 results.push(t.get_result_data(&cell_ref));
1283 } else {
1284 return Err(EngineError::EvalError(
1285 EvalError::UnknownFunction(format!(
1286 "Sheet not found: {}",
1287 name
1288 )),
1289 ));
1290 }
1291 } else {
1292 return Err(EngineError::EvalError(EvalError::UnknownFunction(
1293 "No context to resolve sheet reference".to_string(),
1294 )));
1295 }
1296 }
1297 }
1298 }
1299 Ok(ResultData::List(results))
1300 }
1301 Expr::List(list) => {
1302 let mut results = Vec::new();
1303 for item in list {
1304 results.push(self.evaluate_ast(item, context, row, col, deps, scope)?);
1305 }
1306 Ok(ResultData::List(results))
1307 }
1308 Expr::Slice { expr, start, end } => {
1309 let target_val = self.evaluate_ast(expr, context, row, col, deps, scope)?;
1310 if let ResultData::Error(_) = &target_val {
1311 return Ok(target_val);
1312 }
1313 if let ResultData::List(list) = target_val {
1314 let len = list.len() as isize;
1315 let start_idx = if let Some(start_expr) = start {
1316 let s_val =
1317 self.evaluate_ast(start_expr, context, row, col, deps, scope)?;
1318 if let ResultData::Error(_) = &s_val {
1319 return Ok(s_val);
1320 }
1321 let s = self.to_f64(&s_val).unwrap_or(0.0) as isize;
1322 if s < 0 {
1323 (len + s).max(0) as usize
1324 } else {
1325 s.min(len) as usize
1326 }
1327 } else {
1328 0
1329 };
1330
1331 let end_idx = if let Some(end_expr) = end {
1332 let e_val = self.evaluate_ast(end_expr, context, row, col, deps, scope)?;
1333 if let ResultData::Error(_) = &e_val {
1334 return Ok(e_val);
1335 }
1336 let e = self.to_f64(&e_val).unwrap_or(len as f64) as isize;
1337 if e < 0 {
1338 (len + e).max(0) as usize
1339 } else {
1340 e.min(len) as usize
1341 }
1342 } else {
1343 len as usize
1344 };
1345
1346 let sliced = if start_idx < end_idx && start_idx < list.len() {
1347 list[start_idx..end_idx.min(list.len())].to_vec()
1348 } else {
1349 Vec::new()
1350 };
1351 Ok(ResultData::List(sliced))
1352 } else {
1353 Ok(ResultData::None)
1354 }
1355 }
1356 Expr::UnaryOp { op, expr } => {
1357 if matches!(op, Op::ImplicitIntersection) {
1358 return self
1359 .evaluate_implicit_intersection(expr, context, row, col, deps, scope);
1360 }
1361 let val = self.evaluate_ast(expr, context, row, col, deps, scope)?;
1362 match op {
1363 Op::Sub => match val {
1364 ResultData::Error(_) => Ok(val),
1365 ResultData::Integer(i) if i != i64::MIN => Ok(ResultData::Integer(-i)),
1366 _ => match self.to_f64(&val) {
1367 Some(f) => Ok(ResultData::Float(-f)),
1368 None => Ok(ResultData::Error("#VALUE!".to_string())),
1369 },
1370 },
1371 Op::Percent => {
1372 if let ResultData::Error(_) = &val {
1373 return Ok(val);
1374 }
1375 match self.to_f64(&val) {
1376 Some(f) => Ok(ResultData::Float(f / 100.0)),
1377 None => Ok(ResultData::Error("#VALUE!".to_string())),
1378 }
1379 }
1380 Op::Spill => match val {
1381 ResultData::Error(_) => Ok(val),
1382 ResultData::List(_) => Ok(val),
1383 _ => {
1384 if let Expr::CellRef {
1385 sheet,
1386 row: r_val,
1387 col: c_val,
1388 ..
1389 } = &**expr
1390 {
1391 let is_self = match sheet {
1392 Some(name) => name == &self.name,
1393 None => true,
1394 };
1395 let has_formula = if is_self {
1396 self.get_src_str_ref(&CellRef::new(*r_val, *c_val))
1397 .is_some_and(|s| s.starts_with('='))
1398 } else if let Some(ctx) = context {
1399 ctx.sheets
1400 .get(sheet.as_ref().unwrap())
1401 .and_then(|s| {
1402 s.get_src_str_ref(&CellRef::new(*r_val, *c_val))
1403 })
1404 .is_some_and(|s| s.starts_with('='))
1405 } else {
1406 false
1407 };
1408 if has_formula {
1409 Ok(val)
1410 } else {
1411 Ok(ResultData::Error("#REF!".to_string()))
1412 }
1413 } else {
1414 Ok(val)
1415 }
1416 }
1417 },
1418 _ => Ok(val),
1419 }
1420 }
1421 Expr::BinaryOp { op, left, right } => {
1422 if matches!(op, Op::Intersect) {
1423 return self
1424 .evaluate_reference_intersection(left, right, context, row, col, deps);
1425 }
1426
1427 let l_val = self.evaluate_ast(left, context, row, col, deps, scope)?;
1428
1429 match op {
1430 Op::Union => {
1431 if let ResultData::Error(_) = &l_val {
1432 return Ok(l_val);
1433 }
1434 let r_val = self.evaluate_ast(right, context, row, col, deps, scope)?;
1435 if let ResultData::Error(_) = &r_val {
1436 return Ok(r_val);
1437 }
1438 Ok(Self::combine_union_values(l_val, r_val))
1439 }
1440 Op::Eq | Op::Ne | Op::Lt | Op::Gt | Op::Le | Op::Ge => {
1441 if let ResultData::Error(_) = &l_val {
1442 return Ok(l_val);
1443 }
1444 let r_val = self.evaluate_ast(right, context, row, col, deps, scope)?;
1445 if let ResultData::Error(_) = &r_val {
1446 return Ok(r_val);
1447 }
1448 let ord = Self::compare_excel_values(&l_val, &r_val);
1449 let b = match op {
1450 Op::Eq => ord.is_eq(),
1451 Op::Ne => !ord.is_eq(),
1452 Op::Lt => ord.is_lt(),
1453 Op::Gt => ord.is_gt(),
1454 Op::Le => ord.is_le(),
1455 Op::Ge => ord.is_ge(),
1456 _ => unreachable!(),
1457 };
1458 Ok(ResultData::Boolean(b))
1459 }
1460 Op::Concat => {
1461 if let ResultData::Error(_) = &l_val {
1462 return Ok(l_val);
1463 }
1464 let r_val = self.evaluate_ast(right, context, row, col, deps, scope)?;
1465 if let ResultData::Error(_) = &r_val {
1466 return Ok(r_val);
1467 }
1468 let mut out = match Self::concat_text(&l_val) {
1469 Ok(s) => s,
1470 Err(e) => return Ok(ResultData::Error(e)),
1471 };
1472 let rhs = match Self::concat_text(&r_val) {
1473 Ok(s) => s,
1474 Err(e) => return Ok(ResultData::Error(e)),
1475 };
1476 out.push_str(&rhs);
1477 Ok(ResultData::String(out))
1478 }
1479 _ => {
1480 if let ResultData::Error(_) = &l_val {
1481 return Ok(l_val);
1482 }
1483 let lf = match self.to_f64(&l_val) {
1484 Some(f) => f,
1485 None => return Ok(ResultData::Error("#VALUE!".to_string())),
1486 };
1487 let r_val = self.evaluate_ast(right, context, row, col, deps, scope)?;
1488 if let ResultData::Error(_) = &r_val {
1489 return Ok(r_val);
1490 }
1491 let rf = match self.to_f64(&r_val) {
1492 Some(f) => f,
1493 None => return Ok(ResultData::Error("#VALUE!".to_string())),
1494 };
1495 match op {
1496 Op::Add => Ok(ResultData::Float(lf + rf)),
1497 Op::Sub => Ok(ResultData::Float(lf - rf)),
1498 Op::Mul => Ok(ResultData::Float(lf * rf)),
1499 Op::Div => {
1500 if rf == 0.0 {
1501 return Ok(ResultData::Error("#DIV/0!".to_string()));
1502 }
1503 Ok(ResultData::Float(lf / rf))
1504 }
1505 Op::Exp => {
1506 if lf == 0.0 && rf == 0.0 {
1507 return Ok(ResultData::Error("#NUM!".to_string()));
1508 }
1509 if lf == 0.0 && rf < 0.0 {
1510 return Ok(ResultData::Error("#DIV/0!".to_string()));
1511 }
1512 if lf < 0.0 {
1513 if rf.fract() != 0.0 || rf.abs() > 1e6 {
1514 return Ok(ResultData::Error("#NUM!".to_string()));
1515 }
1516 let res = lf.powi(rf as i32);
1517 if res.is_nan() || res.is_infinite() {
1518 return Ok(ResultData::Error("#NUM!".to_string()));
1519 }
1520 return Ok(ResultData::Float(res));
1521 }
1522 let res = lf.powf(rf);
1523 if res.is_nan() || res.is_infinite() {
1524 return Ok(ResultData::Error("#NUM!".to_string()));
1525 }
1526 Ok(ResultData::Float(res))
1527 }
1528 _ => unreachable!(),
1529 }
1530 }
1531 }
1532 }
1533 Expr::FunctionCall { name, args } => {
1534 self.evaluate_function(name, args, context, row, col, deps, scope)
1535 }
1536 }
1537 }
1538
1539 fn excel_type_rank(val: &ResultData) -> u8 {
1540 match val {
1541 ResultData::None => 0,
1542 ResultData::Integer(_) | ResultData::Float(_) => 1,
1543 ResultData::String(_) => 2,
1544 ResultData::Boolean(_) => 3,
1545 _ => 4,
1546 }
1547 }
1548
1549 fn compare_excel_values(l: &ResultData, r: &ResultData) -> std::cmp::Ordering {
1550 match (l, r) {
1551 (ResultData::None, ResultData::None) => return std::cmp::Ordering::Equal,
1552 (ResultData::None, ResultData::Integer(b)) => {
1553 return 0.0
1554 .partial_cmp(&(*b as f64))
1555 .unwrap_or(std::cmp::Ordering::Equal);
1556 }
1557 (ResultData::None, ResultData::Float(b)) => {
1558 return 0.0.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal);
1559 }
1560 (ResultData::Integer(a), ResultData::None) => {
1561 return (*a as f64)
1562 .partial_cmp(&0.0)
1563 .unwrap_or(std::cmp::Ordering::Equal);
1564 }
1565 (ResultData::Float(a), ResultData::None) => {
1566 return a.partial_cmp(&0.0).unwrap_or(std::cmp::Ordering::Equal);
1567 }
1568 (ResultData::None, ResultData::String(b)) => {
1569 return "".cmp(b.to_lowercase().as_str());
1570 }
1571 (ResultData::String(a), ResultData::None) => {
1572 return a.to_lowercase().as_str().cmp("");
1573 }
1574 (ResultData::None, ResultData::Boolean(b)) => {
1575 return false.cmp(b);
1576 }
1577 (ResultData::Boolean(a), ResultData::None) => {
1578 return a.cmp(&false);
1579 }
1580 _ => {}
1581 }
1582
1583 let rank_l = Self::excel_type_rank(l);
1584 let rank_r = Self::excel_type_rank(r);
1585 if rank_l != rank_r {
1586 return rank_l.cmp(&rank_r);
1587 }
1588 match (l, r) {
1589 (ResultData::Integer(a), ResultData::Integer(b)) => a.cmp(b),
1590 (ResultData::Float(a), ResultData::Float(b)) => {
1591 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
1592 }
1593 (ResultData::Integer(a), ResultData::Float(b)) => (*a as f64)
1594 .partial_cmp(b)
1595 .unwrap_or(std::cmp::Ordering::Equal),
1596 (ResultData::Float(a), ResultData::Integer(b)) => a
1597 .partial_cmp(&(*b as f64))
1598 .unwrap_or(std::cmp::Ordering::Equal),
1599 (ResultData::Boolean(a), ResultData::Boolean(b)) => a.cmp(b),
1600 (ResultData::String(a), ResultData::String(b)) => Self::compare_excel_strings(a, b),
1601 _ => std::cmp::Ordering::Equal,
1602 }
1603 }
1604
1605 fn sort_compare_blanks_last(
1606 l: &ResultData,
1607 r: &ResultData,
1608 sort_order: f64,
1609 ) -> std::cmp::Ordering {
1610 match (matches!(l, ResultData::None), matches!(r, ResultData::None)) {
1611 (true, true) => std::cmp::Ordering::Equal,
1612 (true, false) => std::cmp::Ordering::Greater,
1613 (false, true) => std::cmp::Ordering::Less,
1614 (false, false) => {
1615 let ord = Self::compare_excel_values(l, r);
1616 if sort_order < 0.0 { ord.reverse() } else { ord }
1617 }
1618 }
1619 }
1620
1621 fn is_excel_number_str(s: &str) -> bool {
1622 let s = s.trim();
1623 if s.is_empty() {
1624 return false;
1625 }
1626 let bytes = s.as_bytes();
1627 let first = bytes[0];
1628 if first == b'e' || first == b'E' {
1629 return false;
1630 }
1631 if (first == b'+' || first == b'-') && bytes.len() > 1 {
1632 let second = bytes[1];
1633 if second == b'e' || second == b'E' {
1634 return false;
1635 }
1636 }
1637 true
1638 }
1639
1640 fn compare_excel_strings(a: &str, b: &str) -> std::cmp::Ordering {
1641 let char_weight = |ch: char| -> u32 {
1642 match ch {
1643 ' ' => 0,
1644 '_' => 1,
1645 '-' => 2,
1646 ',' => 3,
1647 ';' => 4,
1648 ':' => 5,
1649 '!' => 6,
1650 '?' => 7,
1651 '.' => 8,
1652 '\'' => 9,
1653 '"' => 10,
1654 '(' => 11,
1655 ')' => 12,
1656 '[' => 13,
1657 ']' => 14,
1658 '{' => 15,
1659 '}' => 16,
1660 '@' => 17,
1661 '*' => 18,
1662 '/' => 19,
1663 '\\' => 20,
1664 '&' => 21,
1665 '#' => 22,
1666 '%' => 23,
1667 '`' => 24,
1668 '^' => 25,
1669 '+' => 26,
1670 '<' => 27,
1671 '=' => 28,
1672 '>' => 29,
1673 '|' => 30,
1674 '~' => 31,
1675 '$' => 32,
1676 '0'..='9' => 33 + (ch as u32 - '0' as u32),
1677 'A'..='Z' => 43 + (ch as u32 - 'A' as u32),
1678 'a'..='z' => 43 + (ch as u32 - 'a' as u32),
1679 _ => ch
1680 .to_lowercase()
1681 .next()
1682 .map(|c| c as u32 + 200)
1683 .unwrap_or(ch as u32 + 200),
1684 }
1685 };
1686
1687 let mut a_chars = a.chars().filter(|&c| c != '-');
1688 let mut b_chars = b.chars().filter(|&c| c != '-');
1689 loop {
1690 match (a_chars.next(), b_chars.next()) {
1691 (Some(ca), Some(cb)) => {
1692 let wa = char_weight(ca);
1693 let wb = char_weight(cb);
1694 if wa != wb {
1695 return wa.cmp(&wb);
1696 }
1697 }
1698 (Some(_), None) => return std::cmp::Ordering::Greater,
1699 (None, Some(_)) => return std::cmp::Ordering::Less,
1700 (None, None) => return a.len().cmp(&b.len()),
1701 }
1702 }
1703 }
1704
1705 pub(crate) fn clean_float(val: f64) -> f64 {
1706 if val == 0.0 || !val.is_finite() {
1707 return val;
1708 }
1709 let abs_val = val.abs();
1710 let exp = abs_val.log10().floor() as i32;
1711 let factor = 10.0f64.powi(15 - 1 - exp);
1712 if factor.is_finite() && factor != 0.0 {
1713 let rounded = (val * factor).round() / factor;
1714 if (val - rounded).abs() <= 1e-14 * abs_val {
1715 return rounded;
1716 }
1717 }
1718 val
1719 }
1720
1721 pub(crate) fn to_f64(&self, val: &ResultData) -> Option<f64> {
1722 match val {
1723 ResultData::None => Some(0.0),
1724 ResultData::Float(f) => Some(*f),
1725 ResultData::Integer(i) => Some(*i as f64),
1726 ResultData::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
1727 ResultData::String(s) => {
1728 let s_trim = s.trim();
1729 if Self::is_excel_number_str(s_trim) {
1730 if let Ok(f) = s_trim.parse::<f64>() {
1731 return Some(f);
1732 }
1733 if let Some((date, _)) =
1734 crate::core::date::parse_date_with_locale(s_trim, &self.locale)
1735 {
1736 return (date.year >= 1900)
1737 .then(|| crate::core::date::date_to_excel_serial(date));
1738 }
1739 None
1740 } else if let Some((date, _)) =
1741 crate::core::date::parse_date_with_locale(s_trim, &self.locale)
1742 {
1743 (date.year >= 1900).then(|| crate::core::date::date_to_excel_serial(date))
1744 } else {
1745 None
1746 }
1747 }
1748 _ => None,
1749 }
1750 }
1751
1752 fn to_f64_arg(&self, arg_opt: Option<&ResultData>, fn_name: &str) -> Result<f64, EngineError> {
1753 let val = arg_opt.ok_or_else(|| {
1754 EngineError::EvalError(EvalError::UnknownFunction(format!(
1755 "{} requires argument",
1756 fn_name
1757 )))
1758 })?;
1759 if let ResultData::Error(e) = val {
1760 return Err(EngineError::EvalError(EvalError::UnknownFunction(
1761 e.clone(),
1762 )));
1763 }
1764 self.to_f64(val).ok_or_else(|| {
1765 EngineError::EvalError(EvalError::UnknownFunction("#VALUE!".to_string()))
1766 })
1767 }
1768
1769 fn find_error_in_args(args: &[ResultData]) -> Option<ResultData> {
1770 for arg in args {
1771 match arg {
1772 ResultData::Error(_) => return Some(arg.clone()),
1773 ResultData::List(list) => {
1774 if let Some(err) = Self::find_error_in_args(list) {
1775 return Some(err);
1776 }
1777 }
1778 _ => {}
1779 }
1780 }
1781 None
1782 }
1783
1784 fn check_arg_errors(&self, args: &[ResultData], is_direct: &[bool]) -> Option<ResultData> {
1785 for (i, arg) in args.iter().enumerate() {
1786 match arg {
1787 ResultData::Error(_) => return Some(arg.clone()),
1788 ResultData::List(list) => {
1789 if let Some(err) = self.check_arg_errors(list, &[]) {
1790 return Some(err);
1791 }
1792 }
1793 ResultData::String(_)
1794 if is_direct.get(i).copied().unwrap_or(false) && self.to_f64(arg).is_none() =>
1795 {
1796 return Some(ResultData::Error("#VALUE!".to_string()));
1797 }
1798 _ => {}
1799 }
1800 }
1801 None
1802 }
1803
1804 fn sum_helper(&self, arg: &ResultData, is_direct: bool) -> f64 {
1805 match arg {
1806 ResultData::Float(f) => *f,
1807 ResultData::Integer(i) => *i as f64,
1808 ResultData::Boolean(b) => {
1809 if is_direct {
1810 if *b { 1.0 } else { 0.0 }
1811 } else {
1812 0.0
1813 }
1814 }
1815 ResultData::String(_) => {
1816 if is_direct {
1817 self.to_f64(arg).unwrap_or(0.0)
1818 } else {
1819 0.0
1820 }
1821 }
1822 ResultData::List(list) => {
1823 let mut sum = 0.0;
1824 for item in list {
1825 sum += self.sum_helper(item, false);
1826 }
1827 sum
1828 }
1829 _ => 0.0,
1830 }
1831 }
1832
1833 fn flatten_finance_numbers(&self, arg: &ResultData, is_direct: bool) -> Vec<f64> {
1834 match arg {
1835 ResultData::Float(f) => vec![*f],
1836 ResultData::Integer(i) => vec![*i as f64],
1837 ResultData::Boolean(b) => {
1838 if is_direct {
1839 vec![if *b { 1.0 } else { 0.0 }]
1840 } else {
1841 vec![]
1842 }
1843 }
1844 ResultData::String(_) => {
1845 if is_direct {
1846 self.to_f64(arg).into_iter().collect()
1847 } else {
1848 vec![]
1849 }
1850 }
1851 ResultData::List(list) => list
1852 .iter()
1853 .flat_map(|v| self.flatten_finance_numbers(v, false))
1854 .collect(),
1855 _ => vec![],
1856 }
1857 }
1858
1859 fn flatten_stat_numbers(&self, arg: &ResultData, is_direct: bool) -> Vec<f64> {
1860 match arg {
1861 ResultData::Float(f) => vec![*f],
1862 ResultData::Integer(i) => vec![*i as f64],
1863 ResultData::Boolean(b) => {
1864 if is_direct {
1865 vec![if *b { 1.0 } else { 0.0 }]
1866 } else {
1867 vec![]
1868 }
1869 }
1870 ResultData::String(_) => {
1871 if is_direct {
1872 self.to_f64(arg).into_iter().collect()
1873 } else {
1874 vec![]
1875 }
1876 }
1877 ResultData::List(list) => list
1878 .iter()
1879 .flat_map(|v| self.flatten_stat_numbers(v, false))
1880 .collect(),
1881 _ => vec![],
1882 }
1883 }
1884
1885 fn flatten_positional(
1886 &self,
1887 arg: &ResultData,
1888 out: &mut Vec<Option<f64>>,
1889 first_err: &mut Option<String>,
1890 ) {
1891 match arg {
1892 ResultData::List(items) => {
1893 for item in items {
1894 self.flatten_positional(item, out, first_err);
1895 }
1896 }
1897 ResultData::Float(f) => out.push(Some(*f)),
1898 ResultData::Integer(i) => out.push(Some(*i as f64)),
1899 ResultData::Error(e) => {
1900 if first_err.is_none() {
1901 *first_err = Some(e.clone());
1902 }
1903 out.push(None);
1904 }
1905 _ => out.push(None),
1906 }
1907 }
1908
1909 fn positional_numbers(
1910 &self,
1911 arg: Option<&ResultData>,
1912 first_err: &mut Option<String>,
1913 ) -> Vec<Option<f64>> {
1914 let mut out = Vec::new();
1915 if let Some(a) = arg {
1916 self.flatten_positional(a, &mut out, first_err);
1917 }
1918 out
1919 }
1920
1921 fn flatten_positional_with_errors(
1922 &self,
1923 arg: &ResultData,
1924 out: &mut Vec<(Option<f64>, Option<String>)>,
1925 ) {
1926 match arg {
1927 ResultData::List(items) => {
1928 for item in items {
1929 self.flatten_positional_with_errors(item, out);
1930 }
1931 }
1932 ResultData::Float(f) => out.push((Some(*f), None)),
1933 ResultData::Integer(i) => out.push((Some(*i as f64), None)),
1934 ResultData::Error(e) => out.push((None, Some(e.clone()))),
1935 _ => out.push((None, None)),
1936 }
1937 }
1938
1939 fn positional_numbers_with_errors(
1940 &self,
1941 arg: Option<&ResultData>,
1942 ) -> Vec<(Option<f64>, Option<String>)> {
1943 let mut out = Vec::new();
1944 if let Some(a) = arg {
1945 self.flatten_positional_with_errors(a, &mut out);
1946 }
1947 out
1948 }
1949
1950 fn pair_and_filter(
1951 xs_raw: Vec<Option<f64>>,
1952 ys_raw: Vec<Option<f64>>,
1953 ) -> Result<(Vec<f64>, Vec<f64>), String> {
1954 if xs_raw.len() != ys_raw.len() {
1955 return Err("#N/A".to_string());
1956 }
1957 let mut xs = Vec::with_capacity(xs_raw.len());
1958 let mut ys = Vec::with_capacity(ys_raw.len());
1959 for (x, y) in xs_raw.into_iter().zip(ys_raw) {
1960 if let (Some(x), Some(y)) = (x, y) {
1961 xs.push(x);
1962 ys.push(y);
1963 }
1964 }
1965 Ok((xs, ys))
1966 }
1967
1968 fn paired_args(
1969 &self,
1970 x_arg: Option<&ResultData>,
1971 y_arg: Option<&ResultData>,
1972 ) -> Result<(Vec<f64>, Vec<f64>), String> {
1973 for arg in [x_arg, y_arg].into_iter().flatten() {
1974 let scalar = match arg {
1975 ResultData::List(items) if items.len() == 1 => &items[0],
1976 other => other,
1977 };
1978 if let ResultData::Error(e) = scalar {
1979 return Err(e.clone());
1980 }
1981 if Self::is_empty_scalar_operand(arg) {
1982 return Err("#VALUE!".to_string());
1983 }
1984 }
1985 let mut first_err = None;
1986 let xs_raw = self.positional_numbers(x_arg, &mut first_err);
1987 let ys_raw = self.positional_numbers(y_arg, &mut first_err);
1988 if xs_raw.len() != ys_raw.len() {
1989 return Err("#N/A".to_string());
1990 }
1991 if let Some(e) = first_err {
1992 return Err(e);
1993 }
1994 Self::pair_and_filter(xs_raw, ys_raw)
1995 }
1996
1997 fn paired_args_pairwise_errors(
1998 &self,
1999 x_arg: Option<&ResultData>,
2000 y_arg: Option<&ResultData>,
2001 ) -> Result<(Vec<f64>, Vec<f64>), String> {
2002 for arg in [x_arg, y_arg].into_iter().flatten() {
2003 let scalar = match arg {
2004 ResultData::List(items) if items.len() == 1 => &items[0],
2005 other => other,
2006 };
2007 if let ResultData::Error(e) = scalar {
2008 return Err(e.clone());
2009 }
2010 if Self::is_empty_scalar_operand(arg) {
2011 return Err("#VALUE!".to_string());
2012 }
2013 }
2014 let xs_raw = self.positional_numbers_with_errors(x_arg);
2015 let ys_raw = self.positional_numbers_with_errors(y_arg);
2016 if xs_raw.len() != ys_raw.len() {
2017 return Err("#N/A".to_string());
2018 }
2019 let mut xs = Vec::with_capacity(xs_raw.len());
2020 let mut ys = Vec::with_capacity(ys_raw.len());
2021 for ((x, x_err), (y, y_err)) in xs_raw.into_iter().zip(ys_raw) {
2022 if let Some(e) = x_err {
2023 return Err(if e == "#NULL!" {
2024 "#NUM!".to_string()
2025 } else {
2026 e
2027 });
2028 }
2029 if let Some(e) = y_err {
2030 return Err(if e == "#NULL!" {
2031 "#NUM!".to_string()
2032 } else {
2033 e
2034 });
2035 }
2036 if let (Some(x), Some(y)) = (x, y) {
2037 xs.push(x);
2038 ys.push(y);
2039 }
2040 }
2041 Ok((xs, ys))
2042 }
2043
2044 fn flatten_strict_inner(
2045 &self,
2046 arg: &ResultData,
2047 blanks: BlankPolicy,
2048 coerce_text: bool,
2049 out: &mut Vec<f64>,
2050 ) -> Result<(), String> {
2051 match arg {
2052 ResultData::List(items) => {
2053 for item in items {
2054 self.flatten_strict_inner(item, blanks, coerce_text, out)?;
2055 }
2056 Ok(())
2057 }
2058 ResultData::Error(e) => Err(e.clone()),
2059 ResultData::Float(f) => {
2060 out.push(*f);
2061 Ok(())
2062 }
2063 ResultData::Integer(i) => {
2064 out.push(*i as f64);
2065 Ok(())
2066 }
2067 ResultData::None => match blanks {
2068 BlankPolicy::Zero => {
2069 out.push(0.0);
2070 Ok(())
2071 }
2072 BlankPolicy::Skip => Ok(()),
2073 BlankPolicy::Reject => Err("#VALUE!".to_string()),
2074 },
2075 ResultData::String(_) if coerce_text => match self.to_f64(arg) {
2076 Some(f) => {
2077 out.push(f);
2078 Ok(())
2079 }
2080 None => Err("#VALUE!".to_string()),
2081 },
2082 _ => Err("#VALUE!".to_string()),
2083 }
2084 }
2085
2086 fn flatten_strict_numbers(&self, arg: &ResultData) -> Result<Vec<f64>, String> {
2087 let mut out = Vec::new();
2088 self.flatten_strict_inner(arg, BlankPolicy::Zero, true, &mut out)?;
2089 Ok(out)
2090 }
2091
2092 fn flatten_skipping_blanks(&self, arg: Option<&ResultData>) -> Result<Vec<f64>, String> {
2093 let mut out = Vec::new();
2094 if let Some(a) = arg {
2095 self.flatten_strict_inner(a, BlankPolicy::Skip, true, &mut out)?;
2096 }
2097 Ok(out)
2098 }
2099
2100 fn flatten_skipping_blanks_no_text_coercion(
2101 &self,
2102 arg: Option<&ResultData>,
2103 ) -> Result<Vec<f64>, String> {
2104 let mut out = Vec::new();
2105 if let Some(a) = arg {
2106 self.flatten_strict_inner(a, BlankPolicy::Skip, false, &mut out)?;
2107 }
2108 Ok(out)
2109 }
2110
2111 fn flatten_numbers_only(&self, arg: &ResultData) -> Result<Vec<f64>, String> {
2112 let mut out = Vec::new();
2113 self.flatten_strict_inner(arg, BlankPolicy::Reject, false, &mut out)?;
2114 Ok(out)
2115 }
2116
2117 fn aggregate_range_number(val: &ResultData) -> Option<f64> {
2118 match val {
2119 ResultData::Float(f) => Some(*f),
2120 ResultData::Integer(i) => Some(*i as f64),
2121 _ => None,
2122 }
2123 }
2124
2125 fn flatten_numbers_only_arg(&self, arg: Option<&ResultData>) -> Result<Vec<f64>, String> {
2126 match arg {
2127 Some(a) => self.flatten_numbers_only(a),
2128 None => Ok(vec![]),
2129 }
2130 }
2131
2132 fn flatten_args_stat_numbers(
2133 &self,
2134 args: &[ResultData],
2135 is_direct: &[bool],
2136 ) -> Result<Vec<f64>, String> {
2137 let mut out = Vec::new();
2138 for (i, arg) in args.iter().enumerate() {
2139 let direct = is_direct.get(i).copied().unwrap_or(false);
2140 if direct && matches!(arg, ResultData::String(_)) && self.to_f64(arg).is_none() {
2141 return Err("#VALUE!".to_string());
2142 }
2143 out.extend(self.flatten_stat_numbers(arg, direct));
2144 }
2145 Ok(out)
2146 }
2147
2148 fn flatten_stat_numbers_a(
2149 &self,
2150 arg: &ResultData,
2151 is_direct: bool,
2152 ) -> Result<Vec<f64>, String> {
2153 Ok(match arg {
2154 ResultData::Float(f) => vec![*f],
2155 ResultData::Integer(i) => vec![*i as f64],
2156 ResultData::Boolean(b) => vec![if *b { 1.0 } else { 0.0 }],
2157 ResultData::String(_) => {
2158 if is_direct {
2159 match self.to_f64(arg) {
2160 Some(f) => vec![f],
2161 None => return Err("#VALUE!".to_string()),
2162 }
2163 } else {
2164 vec![0.0]
2165 }
2166 }
2167 ResultData::Error(e) => return Err(e.clone()),
2168 ResultData::List(list) => {
2169 let mut out = Vec::new();
2170 for v in list {
2171 out.extend(self.flatten_stat_numbers_a(v, false)?);
2172 }
2173 out
2174 }
2175 ResultData::None => vec![],
2176 _ => vec![0.0],
2177 })
2178 }
2179
2180 fn flatten_args_stat_numbers_a(
2181 &self,
2182 args: &[ResultData],
2183 is_direct: &[bool],
2184 ) -> Result<Vec<f64>, String> {
2185 let mut out = Vec::new();
2186 for (i, arg) in args.iter().enumerate() {
2187 out.extend(
2188 self.flatten_stat_numbers_a(arg, is_direct.get(i).copied().unwrap_or(false))?,
2189 );
2190 }
2191 Ok(out)
2192 }
2193
2194 fn extract_matrix(&self, arg: &ResultData) -> Vec<Vec<f64>> {
2195 match arg {
2196 ResultData::List(list) => {
2197 let mut rows = Vec::new();
2198 for item in list {
2199 match item {
2200 ResultData::List(sub_list) => {
2201 let row: Vec<f64> =
2202 sub_list.iter().flat_map(|v| self.to_f64(v)).collect();
2203 if !row.is_empty() {
2204 rows.push(row);
2205 }
2206 }
2207 _ => {
2208 if let Some(f) = self.to_f64(item) {
2209 rows.push(vec![f]);
2210 }
2211 }
2212 }
2213 }
2214 rows
2215 }
2216 _ => vec![],
2217 }
2218 }
2219
2220 fn matrix_from_arg(
2221 &self,
2222 expr: &crate::core::parser::Expr,
2223 value: &ResultData,
2224 ) -> Vec<Vec<f64>> {
2225 if let ResultData::List(items) = value
2226 && items.iter().any(|i| matches!(i, ResultData::List(_)))
2227 {
2228 return self.extract_matrix(value);
2229 }
2230 fn plain(v: &ResultData) -> Option<f64> {
2231 match v {
2232 ResultData::Float(f) => Some(*f),
2233 ResultData::Integer(i) => Some(*i as f64),
2234 _ => None,
2235 }
2236 }
2237 let items: Vec<&ResultData> = match value {
2238 ResultData::List(items) => items.iter().collect(),
2239 other => vec![other],
2240 };
2241 if items.iter().any(|v| plain(v).is_none()) {
2242 return Vec::new();
2243 }
2244 let flat: Vec<f64> = items.iter().filter_map(|v| plain(v)).collect();
2245 let cols = match Self::range_bounds(expr) {
2246 Some((_, _, start_col, _, end_col)) => end_col.saturating_sub(start_col) + 1,
2247 None => flat.len().max(1),
2248 };
2249 if cols == 0 || !flat.len().is_multiple_of(cols) {
2250 return self.extract_matrix(value);
2251 }
2252 flat.chunks(cols).map(|c| c.to_vec()).collect()
2253 }
2254
2255 fn paired_sum_has_no_numbers(&self, arg: Option<&ResultData>) -> bool {
2256 let mut ignored = None;
2257 let slots = self.positional_numbers(arg, &mut ignored);
2258 slots.iter().all(|v| v.is_none())
2259 }
2260
2261 fn is_empty_scalar_operand(arg: &ResultData) -> bool {
2262 let scalar = match arg {
2263 ResultData::List(items) if items.len() == 1 => &items[0],
2264 other => other,
2265 };
2266 matches!(scalar, ResultData::None)
2267 }
2268
2269 fn first_arg_is_boolean(args: &[ResultData]) -> bool {
2270 matches!(args.first(), Some(ResultData::Boolean(_)))
2271 }
2272
2273 fn opt_f64_arg(&self, args: &[ResultData], i: usize, default: f64) -> Result<f64, EngineError> {
2274 match args.get(i) {
2275 None => Ok(default),
2276 Some(ResultData::None) => Ok(0.0),
2277 Some(ResultData::Error(e)) => Err(EngineError::EvalError(EvalError::UnknownFunction(
2278 e.clone(),
2279 ))),
2280 Some(v) => self.to_f64(v).ok_or_else(|| {
2281 EngineError::EvalError(EvalError::UnknownFunction("#VALUE!".to_string()))
2282 }),
2283 }
2284 }
2285
2286 fn opt_f64(&self, args: &[ResultData], i: usize, default: f64) -> f64 {
2287 args.get(i).and_then(|v| self.to_f64(v)).unwrap_or(default)
2288 }
2289
2290 fn average_helper(&self, arg: &ResultData, is_direct: bool) -> (f64, usize) {
2291 match arg {
2292 ResultData::Float(f) => (*f, 1),
2293 ResultData::Integer(i) => (*i as f64, 1),
2294 ResultData::Boolean(b) => {
2295 if is_direct {
2296 (if *b { 1.0 } else { 0.0 }, 1)
2297 } else {
2298 (0.0, 0)
2299 }
2300 }
2301 ResultData::String(_) => {
2302 if is_direct {
2303 if let Some(f) = self.to_f64(arg) {
2304 (f, 1)
2305 } else {
2306 (0.0, 0)
2307 }
2308 } else {
2309 (0.0, 0)
2310 }
2311 }
2312 ResultData::List(list) => {
2313 let mut sum = 0.0;
2314 let mut count = 0;
2315 for item in list {
2316 let (s, c) = self.average_helper(item, false);
2317 sum += s;
2318 count += c;
2319 }
2320 (sum, count)
2321 }
2322 _ => (0.0, 0),
2323 }
2324 }
2325
2326 fn count_helper(&self, arg: &ResultData) -> usize {
2327 match arg {
2328 ResultData::Float(_) | ResultData::Integer(_) => 1,
2329 ResultData::List(list) => {
2330 let mut count = 0;
2331 for item in list {
2332 count += self.count_helper(item);
2333 }
2334 count
2335 }
2336 _ => 0,
2337 }
2338 }
2339
2340 fn min_helper(&self, arg: &ResultData, is_direct: bool) -> f64 {
2341 match arg {
2342 ResultData::Float(f) => *f,
2343 ResultData::Integer(i) => *i as f64,
2344 ResultData::Boolean(b) => {
2345 if is_direct {
2346 if *b { 1.0 } else { 0.0 }
2347 } else {
2348 f64::INFINITY
2349 }
2350 }
2351 ResultData::String(_) => {
2352 if is_direct {
2353 self.to_f64(arg).unwrap_or(f64::INFINITY)
2354 } else {
2355 f64::INFINITY
2356 }
2357 }
2358 ResultData::List(list) => {
2359 let mut min_val = f64::INFINITY;
2360 for item in list {
2361 min_val = min_val.min(self.min_helper(item, false));
2362 }
2363 min_val
2364 }
2365 _ => f64::INFINITY,
2366 }
2367 }
2368
2369 fn max_helper(&self, arg: &ResultData, is_direct: bool) -> f64 {
2370 match arg {
2371 ResultData::Float(f) => *f,
2372 ResultData::Integer(i) => *i as f64,
2373 ResultData::Boolean(b) => {
2374 if is_direct {
2375 if *b { 1.0 } else { 0.0 }
2376 } else {
2377 f64::NEG_INFINITY
2378 }
2379 }
2380 ResultData::String(_) => {
2381 if is_direct {
2382 self.to_f64(arg).unwrap_or(f64::NEG_INFINITY)
2383 } else {
2384 f64::NEG_INFINITY
2385 }
2386 }
2387 ResultData::List(list) => {
2388 let mut max_val = f64::NEG_INFINITY;
2389 for item in list {
2390 max_val = max_val.max(self.max_helper(item, false));
2391 }
2392 max_val
2393 }
2394 _ => f64::NEG_INFINITY,
2395 }
2396 }
2397
2398 fn concat_helper(&self, arg: &ResultData, out: &mut String) {
2399 match arg {
2400 ResultData::List(list) => {
2401 for item in list {
2402 self.concat_helper(item, out);
2403 }
2404 }
2405 other => {
2406 out.push_str(&other.to_string());
2407 }
2408 }
2409 }
2410
2411 fn concat_text(arg: &ResultData) -> Result<String, String> {
2412 match arg {
2413 ResultData::Error(e) => Err(e.clone()),
2414 ResultData::List(list) => {
2415 let mut out = String::new();
2416 for item in list {
2417 out.push_str(&Self::concat_text(item)?);
2418 }
2419 Ok(out)
2420 }
2421 other => Ok(other.to_string()),
2422 }
2423 }
2424
2425 fn counta_helper(&self, arg: &ResultData) -> usize {
2426 match arg {
2427 ResultData::None => 0,
2428 ResultData::List(list) => {
2429 let mut count = 0;
2430 for item in list {
2431 count += self.counta_helper(item);
2432 }
2433 count
2434 }
2435 _ => 1,
2436 }
2437 }
2438
2439 fn product_helper(&self, arg: &ResultData, is_direct: bool) -> (f64, bool) {
2440 match arg {
2441 ResultData::Float(f) => (*f, true),
2442 ResultData::Integer(i) => (*i as f64, true),
2443 ResultData::Boolean(b) => {
2444 if is_direct {
2445 (if *b { 1.0 } else { 0.0 }, true)
2446 } else {
2447 (1.0, false)
2448 }
2449 }
2450 ResultData::String(_) => {
2451 if is_direct {
2452 if let Some(f) = self.to_f64(arg) {
2453 (f, true)
2454 } else {
2455 (1.0, false)
2456 }
2457 } else {
2458 (1.0, false)
2459 }
2460 }
2461 ResultData::List(list) => {
2462 let mut prod = 1.0;
2463 let mut has_nums = false;
2464 for item in list {
2465 let (p, h) = self.product_helper(item, false);
2466 if h {
2467 prod *= p;
2468 has_nums = true;
2469 }
2470 }
2471 (prod, has_nums)
2472 }
2473 _ => (1.0, false),
2474 }
2475 }
2476
2477 fn to_bool_opt(&self, val: &ResultData) -> Option<bool> {
2478 match val {
2479 ResultData::Boolean(b) => Some(*b),
2480 ResultData::Integer(i) => Some(*i != 0),
2481 ResultData::Float(f) => Some(*f != 0.0),
2482 ResultData::String(s) => {
2483 let s_trim = s.trim();
2484 if s_trim.eq_ignore_ascii_case("true") {
2485 Some(true)
2486 } else if s_trim.eq_ignore_ascii_case("false") {
2487 Some(false)
2488 } else if let Ok(f) = s_trim.parse::<f64>() {
2489 Some(f != 0.0)
2490 } else {
2491 None
2492 }
2493 }
2494 ResultData::None => Some(false),
2495 _ => None,
2496 }
2497 }
2498
2499 fn to_bool(&self, val: &ResultData) -> bool {
2500 self.to_bool_opt(val).unwrap_or(false)
2501 }
2502
2503 fn range_numeric(val: &ResultData) -> Option<f64> {
2504 match val {
2505 ResultData::Integer(i) => Some(*i as f64),
2506 ResultData::Float(f) => Some(*f),
2507 _ => None,
2508 }
2509 }
2510
2511 fn exact_lookup_matches(lookup: &ResultData, candidate: &ResultData) -> bool {
2512 if matches!(candidate, ResultData::None) {
2513 return false;
2514 }
2515 let lookup_key = match lookup {
2516 ResultData::None => "0".to_string(),
2517 other => other.to_string(),
2518 };
2519 candidate.to_string() == lookup_key
2520 }
2521
2522 fn wildcard_criteria_matches(pattern: &str, text: &str) -> bool {
2523 fn rec(pat: &[char], txt: &[char]) -> bool {
2524 if pat.is_empty() {
2525 return txt.is_empty();
2526 }
2527 match pat[0] {
2528 '*' => rec(&pat[1..], txt) || (!txt.is_empty() && rec(pat, &txt[1..])),
2529 '?' => !txt.is_empty() && rec(&pat[1..], &txt[1..]),
2530 '~' if pat.len() > 1 && matches!(pat[1], '*' | '?' | '~') => {
2531 !txt.is_empty() && pat[1] == txt[0] && rec(&pat[2..], &txt[1..])
2532 }
2533 ch => !txt.is_empty() && ch == txt[0] && rec(&pat[1..], &txt[1..]),
2534 }
2535 }
2536
2537 let pat = pattern.to_lowercase().chars().collect::<Vec<_>>();
2538 let txt = text.to_lowercase().chars().collect::<Vec<_>>();
2539 rec(&pat, &txt)
2540 }
2541
2542 fn criteria_text_eq(val: &ResultData, pattern: &str) -> bool {
2543 let text = val.to_string();
2544 if pattern.contains('*') || pattern.contains('?') {
2545 matches!(val, ResultData::String(_)) && Self::wildcard_criteria_matches(pattern, &text)
2546 } else {
2547 text.to_lowercase() == pattern.to_lowercase()
2548 }
2549 }
2550
2551 fn match_criteria(&self, val: &ResultData, criteria: &ResultData) -> bool {
2552 let crit_str = criteria.to_string();
2553 if let Some(rest) = crit_str.strip_prefix(">=") {
2554 let val_f = match Self::range_numeric(val) {
2555 Some(f) => f,
2556 None => return false,
2557 };
2558 let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2559 val_f >= crit_f
2560 } else if let Some(rest) = crit_str.strip_prefix('>') {
2561 let val_f = match Self::range_numeric(val) {
2562 Some(f) => f,
2563 None => return false,
2564 };
2565 let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2566 val_f > crit_f
2567 } else if let Some(rest) = crit_str.strip_prefix("<>") {
2568 let remainder = rest.trim();
2569 !Self::criteria_text_eq(val, remainder)
2570 } else if let Some(rest) = crit_str.strip_prefix("<=") {
2571 let val_f = match Self::range_numeric(val) {
2572 Some(f) => f,
2573 None => return false,
2574 };
2575 let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2576 val_f <= crit_f
2577 } else if let Some(rest) = crit_str.strip_prefix('<') {
2578 let val_f = match Self::range_numeric(val) {
2579 Some(f) => f,
2580 None => return false,
2581 };
2582 let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2583 val_f < crit_f
2584 } else if let Some(rest) = crit_str.strip_prefix('=') {
2585 let remainder = rest.trim();
2586 Self::criteria_text_eq(val, remainder)
2587 } else {
2588 Self::criteria_text_eq(val, &crit_str)
2589 }
2590 }
2591
2592 fn range_bounds(
2593 expr: &crate::core::parser::Expr,
2594 ) -> Option<(Option<String>, usize, usize, usize, usize)> {
2595 use crate::core::parser::Expr;
2596 match expr {
2597 Expr::RangeRef {
2598 sheet,
2599 start_row,
2600 start_col,
2601 end_row,
2602 end_col,
2603 ..
2604 } => Some((sheet.clone(), *start_row, *start_col, *end_row, *end_col)),
2605 Expr::CellRef {
2606 sheet, row, col, ..
2607 } => Some((sheet.clone(), *row, *col, *row, *col)),
2608 _ => None,
2609 }
2610 }
2611
2612 fn materialize_range(
2613 &self,
2614 sheet_opt: &Option<String>,
2615 start_row: usize,
2616 start_col: usize,
2617 end_row: usize,
2618 end_col: usize,
2619 context: Option<&Context>,
2620 ) -> Option<Vec<Vec<ResultData>>> {
2621 let is_self = match sheet_opt {
2622 Some(name) => name == &self.name,
2623 None => true,
2624 };
2625 let source: &Sheet = if is_self {
2626 self
2627 } else {
2628 context?.sheets.get(sheet_opt.as_ref()?)?
2629 };
2630 let actual_end_row = if end_row == usize::MAX {
2631 source.row_count().saturating_sub(1)
2632 } else {
2633 end_row
2634 };
2635 let actual_end_col = if end_col == usize::MAX {
2636 source.col_count().saturating_sub(1)
2637 } else {
2638 end_col
2639 };
2640 if actual_end_row < start_row || actual_end_col < start_col {
2641 return Some(Vec::new());
2642 }
2643 let mut grid = Vec::with_capacity(actual_end_row - start_row + 1);
2644 for r in start_row..=actual_end_row {
2645 let mut row = Vec::with_capacity(actual_end_col - start_col + 1);
2646 for c in start_col..=actual_end_col {
2647 row.push(source.get_result_data(&CellRef::new(r, c)));
2648 }
2649 grid.push(row);
2650 }
2651 Some(grid)
2652 }
2653
2654 fn evaluate_database_function(
2655 &self,
2656 func_name: &str,
2657 args: &[crate::core::parser::Expr],
2658 evaluated_args: &[ResultData],
2659 context: Option<&Context>,
2660 ) -> Result<ResultData, EngineError> {
2661 if args.len() < 3 || evaluated_args.len() < 3 {
2662 return Ok(ResultData::Error("#VALUE!".to_string()));
2663 }
2664 let (db_sheet, db_sr, db_sc, db_er, db_ec) = match Self::range_bounds(&args[0]) {
2665 Some(v) => v,
2666 None => return Ok(ResultData::Error("#VALUE!".to_string())),
2667 };
2668 let (crit_sheet, crit_sr, crit_sc, crit_er, crit_ec) = match Self::range_bounds(&args[2]) {
2669 Some(v) => v,
2670 None => return Ok(ResultData::Error("#VALUE!".to_string())),
2671 };
2672 let db = match self.materialize_range(&db_sheet, db_sr, db_sc, db_er, db_ec, context) {
2673 Some(g) => g,
2674 None => return Ok(ResultData::Error("#REF!".to_string())),
2675 };
2676 let crit = match self.materialize_range(
2677 &crit_sheet,
2678 crit_sr,
2679 crit_sc,
2680 crit_er,
2681 crit_ec,
2682 context,
2683 ) {
2684 Some(g) => g,
2685 None => return Ok(ResultData::Error("#REF!".to_string())),
2686 };
2687 if db.len() < 2 || crit.len() < 2 {
2688 return Ok(ResultData::Error("#VALUE!".to_string()));
2689 }
2690
2691 let db_headers: Vec<String> = db[0].iter().map(|v| v.to_string()).collect();
2692 let field_idx: usize = match &evaluated_args[1] {
2693 ResultData::String(s) => {
2694 match db_headers.iter().position(|h| h.eq_ignore_ascii_case(s)) {
2695 Some(idx) => idx,
2696 None => return Ok(ResultData::Error("#VALUE!".to_string())),
2697 }
2698 }
2699 other => match self.to_f64(other) {
2700 Some(n) if n >= 1.0 && (n as usize) <= db_headers.len() => n as usize - 1,
2701 _ => return Ok(ResultData::Error("#VALUE!".to_string())),
2702 },
2703 };
2704
2705 let crit_headers: Vec<String> = crit[0].iter().map(|v| v.to_string()).collect();
2706 let crit_to_db: Vec<Option<usize>> = crit_headers
2707 .iter()
2708 .map(|h| db_headers.iter().position(|dh| dh.eq_ignore_ascii_case(h)))
2709 .collect();
2710
2711 let mut matched: Vec<ResultData> = Vec::new();
2712 for row in db.iter().skip(1) {
2713 let row_matches_any_criteria_row = crit.iter().skip(1).any(|crit_row| {
2714 crit_row.iter().enumerate().all(|(ci, cell)| {
2715 if matches!(cell, ResultData::None) {
2716 return true;
2717 }
2718 match crit_to_db.get(ci).copied().flatten() {
2719 Some(db_col) => self.match_criteria(&row[db_col], cell),
2720 None => false,
2721 }
2722 })
2723 });
2724 if row_matches_any_criteria_row {
2725 matched.push(row[field_idx].clone());
2726 }
2727 }
2728
2729 match func_name {
2730 "DGET" => match matched.len() {
2731 0 => Ok(ResultData::Error("#VALUE!".to_string())),
2732 1 => Ok(matched.into_iter().next().unwrap()),
2733 _ => Ok(ResultData::Error("#NUM!".to_string())),
2734 },
2735 "DCOUNT" => Ok(ResultData::Float(
2736 matched
2737 .iter()
2738 .filter(|v| Self::range_numeric(v).is_some())
2739 .count() as f64,
2740 )),
2741 "DCOUNTA" => Ok(ResultData::Float(
2742 matched.iter().map(|v| self.counta_helper(v)).sum::<usize>() as f64,
2743 )),
2744 _ => {
2745 let nums: Vec<f64> = matched.iter().filter_map(Self::range_numeric).collect();
2746 match func_name {
2747 "DSUM" => Ok(ResultData::Float(nums.iter().sum())),
2748 "DPRODUCT" => Ok(ResultData::Float(if nums.is_empty() {
2749 0.0
2750 } else {
2751 nums.iter().product()
2752 })),
2753 "DMAX" => {
2754 let m = nums.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
2755 Ok(ResultData::Float(if m.is_finite() { m } else { 0.0 }))
2756 }
2757 "DMIN" => {
2758 let m = nums.iter().cloned().fold(f64::INFINITY, f64::min);
2759 Ok(ResultData::Float(if m.is_finite() { m } else { 0.0 }))
2760 }
2761 "DAVERAGE" => {
2762 if nums.is_empty() {
2763 Ok(ResultData::Error("#DIV/0!".to_string()))
2764 } else {
2765 Ok(ResultData::Float(
2766 nums.iter().sum::<f64>() / nums.len() as f64,
2767 ))
2768 }
2769 }
2770 "DSTDEV" => match crate::core::stats::stdev_s(&nums) {
2771 Ok(v) => Ok(ResultData::Float(v)),
2772 Err(e) => Ok(ResultData::Error(e)),
2773 },
2774 "DSTDEVP" => match crate::core::stats::stdev_p(&nums) {
2775 Ok(v) => Ok(ResultData::Float(v)),
2776 Err(e) => Ok(ResultData::Error(e)),
2777 },
2778 "DVAR" => match crate::core::stats::var_s(&nums) {
2779 Ok(v) => Ok(ResultData::Float(v)),
2780 Err(e) => Ok(ResultData::Error(e)),
2781 },
2782 "DVARP" => match crate::core::stats::var_p(&nums) {
2783 Ok(v) => Ok(ResultData::Float(v)),
2784 Err(e) => Ok(ResultData::Error(e)),
2785 },
2786 _ => unreachable!(),
2787 }
2788 }
2789 }
2790 }
2791
2792 fn proper(&self, s: &str) -> String {
2793 let mut c_chars = Vec::new();
2794 let mut capitalize_next = true;
2795 for c in s.chars() {
2796 if c.is_alphabetic() {
2797 if capitalize_next {
2798 c_chars.extend(c.to_uppercase());
2799 } else {
2800 c_chars.extend(c.to_lowercase());
2801 }
2802 capitalize_next = false;
2803 } else {
2804 c_chars.push(c);
2805 capitalize_next = true;
2806 }
2807 }
2808 c_chars.into_iter().collect()
2809 }
2810
2811 fn get_ymd_hms(&self) -> ((i32, u32, u32), (u32, u32, u32)) {
2812 let now = web_time::SystemTime::now()
2813 .duration_since(web_time::SystemTime::UNIX_EPOCH)
2814 .unwrap_or_default()
2815 .as_secs();
2816 let secs_in_day = 86400;
2817 let days_since_epoch = (now / secs_in_day) as i32;
2818 let seconds_of_day = (now % secs_in_day) as u32;
2819
2820 let hour = seconds_of_day / 3600;
2821 let minute = (seconds_of_day % 3600) / 60;
2822 let second = seconds_of_day % 60;
2823
2824 let era = (if days_since_epoch >= -719468 {
2825 days_since_epoch + 719468
2826 } else {
2827 days_since_epoch + 719468 - 146096
2828 }) / 146097;
2829 let doe = (days_since_epoch + 719468 - era * 146097) as u32;
2830 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
2831 let y = (yoe as i32) + era * 400;
2832 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
2833 let mp = (5 * doy + 2) / 153;
2834 let d = doy - (153 * mp + 2) / 5 + 1;
2835 let m = if mp < 10 { mp + 3 } else { mp - 9 };
2836 let year = if m <= 2 { y + 1 } else { y };
2837
2838 ((year, m, d), (hour, minute, second))
2839 }
2840
2841 fn evaluate_let(
2842 &self,
2843 args: &[crate::core::parser::Expr],
2844 context: Option<&Context>,
2845 row: Option<usize>,
2846 col: Option<usize>,
2847 deps: &mut Vec<Dependency>,
2848 scope: &LetScope<'_>,
2849 ) -> Result<ResultData, EngineError> {
2850 use crate::core::parser::Expr;
2851
2852 if args.is_empty() || args.len().is_multiple_of(2) {
2853 return Ok(ResultData::Error("#VALUE!".to_string()));
2854 }
2855 if args.len() == 1 {
2856 return self.evaluate_ast(&args[0], context, row, col, deps, scope);
2857 }
2858
2859 let name = match &args[0] {
2860 Expr::Identifier(n) => n.as_str(),
2861 _ => return Ok(ResultData::Error("#VALUE!".to_string())),
2862 };
2863 let remaining_pairs = args.len() / 2 - 1;
2864 let is_duplicate = args[2..]
2865 .iter()
2866 .step_by(2)
2867 .take(remaining_pairs)
2868 .any(|a| matches!(a, Expr::Identifier(n2) if n2.eq_ignore_ascii_case(name)));
2869 if is_duplicate {
2870 return Ok(ResultData::Error("#VALUE!".to_string()));
2871 }
2872
2873 let value = self.evaluate_ast(&args[1], context, row, col, deps, scope)?;
2874 let inner_scope = LetScope::Bound {
2875 name,
2876 value: &value,
2877 parent: scope,
2878 };
2879 self.evaluate_let(&args[2..], context, row, col, deps, &inner_scope)
2880 }
2881
2882 fn extract_lambda(
2883 expr: &crate::core::parser::Expr,
2884 ) -> Option<(Vec<&str>, &crate::core::parser::Expr)> {
2885 use crate::core::parser::Expr;
2886 let Expr::FunctionCall { name, args } = expr else {
2887 return None;
2888 };
2889 if !name.eq_ignore_ascii_case("LAMBDA") || args.is_empty() {
2890 return None;
2891 }
2892 let (body, params) = args.split_last().unwrap();
2893 let param_names: Vec<&str> = params
2894 .iter()
2895 .filter_map(|p| match p {
2896 Expr::Identifier(n) => Some(n.as_str()),
2897 _ => None,
2898 })
2899 .collect();
2900 if param_names.len() != params.len() {
2901 return None;
2902 }
2903 Some((param_names, body))
2904 }
2905
2906 #[allow(clippy::too_many_arguments)]
2907 fn invoke_lambda<'v>(
2908 &self,
2909 params: &[&str],
2910 values: &'v [ResultData],
2911 body: &crate::core::parser::Expr,
2912 context: Option<&Context>,
2913 row: Option<usize>,
2914 col: Option<usize>,
2915 deps: &mut Vec<Dependency>,
2916 scope: &LetScope<'v>,
2917 ) -> Result<ResultData, EngineError> {
2918 match (params.split_first(), values.split_first()) {
2919 (Some((&pname, prest)), Some((vfirst, vrest))) => {
2920 let inner_scope = LetScope::Bound {
2921 name: pname,
2922 value: vfirst,
2923 parent: scope,
2924 };
2925 self.invoke_lambda(prest, vrest, body, context, row, col, deps, &inner_scope)
2926 }
2927 _ => self.evaluate_ast(body, context, row, col, deps, scope),
2928 }
2929 }
2930
2931 fn eval_as_array(
2932 &self,
2933 expr: &crate::core::parser::Expr,
2934 context: Option<&Context>,
2935 row: Option<usize>,
2936 col: Option<usize>,
2937 deps: &mut Vec<Dependency>,
2938 scope: &LetScope<'_>,
2939 ) -> Result<Vec<ResultData>, EngineError> {
2940 Ok(
2941 match self.evaluate_ast(expr, context, row, col, deps, scope)? {
2942 ResultData::List(items) => Self::flatten_row_major(items).0,
2943 other => vec![other],
2944 },
2945 )
2946 }
2947
2948 fn flatten_row_major(items: Vec<ResultData>) -> (Vec<ResultData>, Option<usize>) {
2949 if !items.is_empty() && items.iter().all(|v| matches!(v, ResultData::List(_))) {
2950 let cols = match &items[0] {
2951 ResultData::List(inner) => inner.len().max(1),
2952 _ => 1,
2953 };
2954 let flat = items
2955 .into_iter()
2956 .flat_map(|v| match v {
2957 ResultData::List(inner) => inner,
2958 other => vec![other],
2959 })
2960 .collect();
2961 (flat, Some(cols))
2962 } else {
2963 (items, None)
2964 }
2965 }
2966
2967 fn array_shape(
2968 &self,
2969 expr: &crate::core::parser::Expr,
2970 context: Option<&Context>,
2971 row: Option<usize>,
2972 col: Option<usize>,
2973 deps: &mut Vec<Dependency>,
2974 scope: &LetScope<'_>,
2975 ) -> Result<(Vec<ResultData>, usize), EngineError> {
2976 use crate::core::parser::Expr;
2977 let items = match self.evaluate_ast(expr, context, row, col, deps, scope)? {
2978 ResultData::List(items) => items,
2979 other => vec![other],
2980 };
2981 let (flat, nested_cols) = Self::flatten_row_major(items);
2982 if let Some(cols) = nested_cols {
2983 return Ok((flat, cols));
2984 }
2985 let num_cols = match expr {
2986 Expr::RangeRef {
2987 start_col, end_col, ..
2988 } => (end_col - start_col + 1).max(1),
2989 Expr::CellRef { .. } => 1,
2990 Expr::FunctionCall { name, args } => self
2991 .function_call_cols(name, args, context, row, col, deps, scope)
2992 .unwrap_or_else(|| flat.len().max(1)),
2993 _ => flat.len().max(1),
2994 };
2995 Ok((flat, num_cols))
2996 }
2997
2998 #[allow(clippy::too_many_arguments)]
2999 fn function_call_cols(
3000 &self,
3001 name: &str,
3002 args: &[crate::core::parser::Expr],
3003 context: Option<&Context>,
3004 row: Option<usize>,
3005 col: Option<usize>,
3006 deps: &mut Vec<Dependency>,
3007 scope: &LetScope<'_>,
3008 ) -> Option<usize> {
3009 let mut upper = name.to_ascii_uppercase();
3010 if let Some(rest) = upper.strip_prefix("_XLFN.") {
3011 upper = rest.to_string();
3012 }
3013 if let Some(rest) = upper.strip_prefix("_XLWS.") {
3014 upper = rest.to_string();
3015 }
3016 match upper.as_str() {
3017 "TRANSPOSE" => {
3018 let (flat, cols) = self
3019 .array_shape(args.first()?, context, row, col, deps, scope)
3020 .ok()?;
3021 Some((flat.len().checked_div(cols).unwrap_or(0)).max(1))
3022 }
3023 "HSTACK" => {
3024 let mut total = 0usize;
3025 for a in args {
3026 total += self.array_shape(a, context, row, col, deps, scope).ok()?.1;
3027 }
3028 Some(total)
3029 }
3030 "VSTACK" => {
3031 let mut max_cols = 0usize;
3032 for a in args {
3033 max_cols =
3034 max_cols.max(self.array_shape(a, context, row, col, deps, scope).ok()?.1);
3035 }
3036 Some(max_cols)
3037 }
3038 "CHOOSEROWS" => Some(
3039 self.array_shape(args.first()?, context, row, col, deps, scope)
3040 .ok()?
3041 .1,
3042 ),
3043 "CHOOSECOLS" => Some(args.len().saturating_sub(1).max(1)),
3044 "DROP" | "TAKE" => {
3045 let (_, cols) = self
3046 .array_shape(args.first()?, context, row, col, deps, scope)
3047 .ok()?;
3048 let is_take = upper == "TAKE";
3049 match args.get(2) {
3050 Some(e) => {
3051 let n = self
3052 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
3053 .unwrap_or(0.0) as isize;
3054 let (s, e2) = Self::drop_take_bounds(cols as isize, n, is_take);
3055 Some((e2 - s).max(0) as usize)
3056 }
3057 None => Some(if is_take { cols } else { 0 }),
3058 }
3059 }
3060 "EXPAND" => {
3061 let (_, cols) = self
3062 .array_shape(args.first()?, context, row, col, deps, scope)
3063 .ok()?;
3064 match args.get(2) {
3065 Some(e) => Some(
3066 self.to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
3067 .unwrap_or(cols as f64) as usize,
3068 ),
3069 None => Some(cols),
3070 }
3071 }
3072 "TOCOL" => Some(1),
3073 "WRAPROWS" => {
3074 let n = self
3075 .to_f64(
3076 &self
3077 .evaluate_ast(args.get(1)?, context, row, col, deps, scope)
3078 .ok()?,
3079 )
3080 .unwrap_or(1.0)
3081 .max(1.0) as usize;
3082 Some(n)
3083 }
3084 "WRAPCOLS" => {
3085 let (flat, _) = self
3086 .array_shape(args.first()?, context, row, col, deps, scope)
3087 .ok()?;
3088 let wrap = self
3089 .to_f64(
3090 &self
3091 .evaluate_ast(args.get(1)?, context, row, col, deps, scope)
3092 .ok()?,
3093 )
3094 .unwrap_or(1.0)
3095 .max(1.0) as usize;
3096 Some(flat.len().div_ceil(wrap).max(1))
3097 }
3098 "UNIQUE" | "SORT" | "SORTBY" | "FILTER" | "TRIMRANGE" => Some(
3099 self.array_shape(args.first()?, context, row, col, deps, scope)
3100 .ok()?
3101 .1,
3102 ),
3103 "SEQUENCE" => match args.get(1) {
3104 Some(e) => Some(
3105 self.to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
3106 .unwrap_or(1.0)
3107 .max(1.0) as usize,
3108 ),
3109 None => Some(1),
3110 },
3111 "MUNIT" => {
3112 let n = self
3113 .to_f64(
3114 &self
3115 .evaluate_ast(args.first()?, context, row, col, deps, scope)
3116 .ok()?,
3117 )
3118 .unwrap_or(1.0)
3119 .max(1.0) as usize;
3120 Some(n)
3121 }
3122 "MAKEARRAY" => match args.get(1) {
3123 Some(e) => Some(
3124 self.to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
3125 .unwrap_or(1.0)
3126 .max(1.0) as usize,
3127 ),
3128 None => Some(1),
3129 },
3130 _ => None,
3131 }
3132 }
3133
3134 fn drop_take_bounds(total: isize, n: isize, is_take: bool) -> (isize, isize) {
3135 let n = n.clamp(-total, total);
3136 if is_take {
3137 if n >= 0 { (0, n) } else { (total + n, total) }
3138 } else if n >= 0 {
3139 (n, total)
3140 } else {
3141 (0, total + n)
3142 }
3143 }
3144
3145 #[allow(clippy::too_many_arguments)]
3146 fn evaluate_lambda_function(
3147 &self,
3148 func_name: &str,
3149 args: &[crate::core::parser::Expr],
3150 context: Option<&Context>,
3151 row: Option<usize>,
3152 col: Option<usize>,
3153 deps: &mut Vec<Dependency>,
3154 scope: &LetScope<'_>,
3155 ) -> Result<ResultData, EngineError> {
3156 use crate::core::parser::Expr;
3157
3158 match func_name {
3159 "MAP" => {
3160 if args.len() < 2 {
3161 return Ok(ResultData::Error("#VALUE!".to_string()));
3162 }
3163 let (lambda_expr, array_exprs) = args.split_last().unwrap();
3164 let Some((params, body)) = Self::extract_lambda(lambda_expr) else {
3165 return Ok(ResultData::Error("#VALUE!".to_string()));
3166 };
3167 if params.len() != array_exprs.len() {
3168 return Ok(ResultData::Error("#VALUE!".to_string()));
3169 }
3170 let arrays: Vec<Vec<ResultData>> = array_exprs
3171 .iter()
3172 .map(|e| self.eval_as_array(e, context, row, col, deps, scope))
3173 .collect::<Result<_, _>>()?;
3174 let len = arrays.iter().map(|a| a.len()).max().unwrap_or(0);
3175 let mut results = Vec::with_capacity(len);
3176 for i in 0..len {
3177 let values: Vec<ResultData> = arrays
3178 .iter()
3179 .map(|a| a.get(i).cloned().unwrap_or(ResultData::None))
3180 .collect();
3181 results.push(
3182 self.invoke_lambda(¶ms, &values, body, context, row, col, deps, scope)?,
3183 );
3184 }
3185 Ok(ResultData::List(results))
3186 }
3187 "BYROW" | "BYCOL" => {
3188 if args.len() != 2 {
3189 return Ok(ResultData::Error("#VALUE!".to_string()));
3190 }
3191 let Some((params, body)) = Self::extract_lambda(&args[1]) else {
3192 return Ok(ResultData::Error("#VALUE!".to_string()));
3193 };
3194 if params.len() != 1 {
3195 return Ok(ResultData::Error("#VALUE!".to_string()));
3196 }
3197 let num_cols = match &args[0] {
3198 Expr::RangeRef {
3199 start_col, end_col, ..
3200 } => (end_col - start_col + 1).max(1),
3201 _ => 1,
3202 };
3203 let flat = self.eval_as_array(&args[0], context, row, col, deps, scope)?;
3204 let num_rows = if num_cols == 0 {
3205 0
3206 } else {
3207 flat.len().div_ceil(num_cols)
3208 };
3209 let mut results = Vec::new();
3210 if func_name == "BYROW" {
3211 for r in 0..num_rows {
3212 let row_vals: Vec<ResultData> = (0..num_cols)
3213 .filter_map(|c| flat.get(r * num_cols + c).cloned())
3214 .collect();
3215 let arg = vec![ResultData::List(row_vals)];
3216 results.push(
3217 self.invoke_lambda(
3218 ¶ms, &arg, body, context, row, col, deps, scope,
3219 )?,
3220 );
3221 }
3222 } else {
3223 for c in 0..num_cols {
3224 let col_vals: Vec<ResultData> = (0..num_rows)
3225 .filter_map(|r| flat.get(r * num_cols + c).cloned())
3226 .collect();
3227 let arg = vec![ResultData::List(col_vals)];
3228 results.push(
3229 self.invoke_lambda(
3230 ¶ms, &arg, body, context, row, col, deps, scope,
3231 )?,
3232 );
3233 }
3234 }
3235 Ok(ResultData::List(results))
3236 }
3237 "REDUCE" | "SCAN" => {
3238 if args.len() != 2 && args.len() != 3 {
3239 return Ok(ResultData::Error("#VALUE!".to_string()));
3240 }
3241 let lambda_idx = args.len() - 1;
3242 let array_idx = args.len() - 2;
3243 let Some((params, body)) = Self::extract_lambda(&args[lambda_idx]) else {
3244 return Ok(ResultData::Error("#VALUE!".to_string()));
3245 };
3246 if params.len() != 2 {
3247 return Ok(ResultData::Error("#VALUE!".to_string()));
3248 }
3249 let array = self.eval_as_array(&args[array_idx], context, row, col, deps, scope)?;
3250 let (mut acc, rest, mut history): (ResultData, &[ResultData], Vec<ResultData>) =
3251 if args.len() == 3 {
3252 let init = self.evaluate_ast(&args[0], context, row, col, deps, scope)?;
3253 (init, &array[..], Vec::new())
3254 } else {
3255 match array.split_first() {
3256 Some((first, rest)) => (first.clone(), rest, vec![first.clone()]),
3257 None => return Ok(ResultData::Error("#VALUE!".to_string())),
3258 }
3259 };
3260 for item in rest {
3261 let call_args = [acc.clone(), item.clone()];
3262 acc = self
3263 .invoke_lambda(¶ms, &call_args, body, context, row, col, deps, scope)?;
3264 history.push(acc.clone());
3265 }
3266 if func_name == "REDUCE" {
3267 Ok(acc)
3268 } else {
3269 Ok(ResultData::List(history))
3270 }
3271 }
3272 "MAKEARRAY" => {
3273 if args.len() != 3 {
3274 return Ok(ResultData::Error("#VALUE!".to_string()));
3275 }
3276 let Some((params, body)) = Self::extract_lambda(&args[2]) else {
3277 return Ok(ResultData::Error("#VALUE!".to_string()));
3278 };
3279 if params.len() != 2 {
3280 return Ok(ResultData::Error("#VALUE!".to_string()));
3281 }
3282 let rows_val = self.evaluate_ast(&args[0], context, row, col, deps, scope)?;
3283 let cols_val = self.evaluate_ast(&args[1], context, row, col, deps, scope)?;
3284 let num_rows = self.to_f64(&rows_val).unwrap_or(0.0).max(0.0) as usize;
3285 let num_cols = self.to_f64(&cols_val).unwrap_or(0.0).max(0.0) as usize;
3286 let mut results = Vec::with_capacity(num_rows * num_cols);
3287 for r in 1..=num_rows {
3288 for c in 1..=num_cols {
3289 let call_args = [ResultData::Float(r as f64), ResultData::Float(c as f64)];
3290 results.push(self.invoke_lambda(
3291 ¶ms, &call_args, body, context, row, col, deps, scope,
3292 )?);
3293 }
3294 }
3295 Ok(ResultData::List(results))
3296 }
3297 _ => unreachable!(),
3298 }
3299 }
3300
3301 fn parse_a1_reference(text: &str) -> Option<(Option<String>, usize, usize, usize, usize)> {
3302 let text = text.trim();
3303 let (sheet_part, ref_part) = match text.rfind('!') {
3304 Some(idx) => (Some(&text[..idx]), &text[idx + 1..]),
3305 None => (None, text),
3306 };
3307 let sheet = sheet_part.map(|s| s.trim().trim_matches('\'').to_string());
3308
3309 fn parse_cell(s: &str) -> Option<(usize, usize)> {
3310 let s = s.replace('$', "");
3311 let col_end = s.find(|c: char| c.is_ascii_digit())?;
3312 let (col_str, row_str) = s.split_at(col_end);
3313 if col_str.is_empty() || row_str.is_empty() {
3314 return None;
3315 }
3316 let mut col = 0usize;
3317 for ch in col_str.chars() {
3318 if !ch.is_ascii_alphabetic() {
3319 return None;
3320 }
3321 col = col * 26 + (ch.to_ascii_uppercase() as usize - 'A' as usize + 1);
3322 }
3323 let row: usize = row_str.parse().ok()?;
3324 if row == 0 || col == 0 {
3325 return None;
3326 }
3327 Some((row - 1, col - 1))
3328 }
3329
3330 if let Some((start, end)) = ref_part.split_once(':') {
3331 let (r1, c1) = parse_cell(start)?;
3332 let (r2, c2) = parse_cell(end)?;
3333 Some((sheet, r1.min(r2), c1.min(c2), r1.max(r2), c1.max(c2)))
3334 } else {
3335 let (r, c) = parse_cell(ref_part)?;
3336 Some((sheet, r, c, r, c))
3337 }
3338 }
3339
3340 fn read_cell_with_deps(
3341 &self,
3342 sheet_opt: &Option<String>,
3343 r: usize,
3344 c: usize,
3345 context: Option<&Context>,
3346 deps: &mut Vec<Dependency>,
3347 ) -> ResultData {
3348 let is_self = sheet_opt.as_deref().is_none_or(|n| n == self.name);
3349 if is_self {
3350 deps.push(Dependency::Local(CellRef::new(r, c)));
3351 self.get_result_data(&CellRef::new(r, c))
3352 } else if let Some(ctx) = context {
3353 let name = sheet_opt.clone().unwrap();
3354 deps.push(Dependency::Remote {
3355 sheet: name.clone(),
3356 cell: CellRef::new(r, c),
3357 });
3358 ctx.sheets
3359 .get(&name)
3360 .map(|s| s.get_result_data(&CellRef::new(r, c)))
3361 .unwrap_or(ResultData::None)
3362 } else {
3363 ResultData::None
3364 }
3365 }
3366
3367 #[allow(clippy::too_many_arguments)]
3368 fn evaluate_range_info_function(
3369 &self,
3370 func_name: &str,
3371 args: &[crate::core::parser::Expr],
3372 context: Option<&Context>,
3373 row: Option<usize>,
3374 col: Option<usize>,
3375 deps: &mut Vec<Dependency>,
3376 scope: &LetScope<'_>,
3377 ) -> Result<ResultData, EngineError> {
3378 use crate::core::parser::Expr;
3379
3380 match func_name {
3381 "ROW" => match args.first() {
3382 Some(arg) => match Self::range_bounds(arg) {
3383 Some((_, start_row, _, end_row, _)) if end_row > start_row => {
3384 Ok(ResultData::List(
3385 (start_row..=end_row)
3386 .map(|r| ResultData::Float((r + 1) as f64))
3387 .collect(),
3388 ))
3389 }
3390 Some((_, start_row, _, _, _)) => Ok(ResultData::Float((start_row + 1) as f64)),
3391 None => Ok(ResultData::Error("#VALUE!".to_string())),
3392 },
3393 None => match row {
3394 Some(r) => Ok(ResultData::Float((r + 1) as f64)),
3395 None => Ok(ResultData::Error("#VALUE!".to_string())),
3396 },
3397 },
3398 "COLUMN" => match args.first() {
3399 Some(arg) => match Self::range_bounds(arg) {
3400 Some((_, _, start_col, _, end_col)) if end_col > start_col => {
3401 Ok(ResultData::List(
3402 (start_col..=end_col)
3403 .map(|c| ResultData::Float((c + 1) as f64))
3404 .collect(),
3405 ))
3406 }
3407 Some((_, _, start_col, _, _)) => Ok(ResultData::Float((start_col + 1) as f64)),
3408 None => Ok(ResultData::Error("#VALUE!".to_string())),
3409 },
3410 None => match col {
3411 Some(c) => Ok(ResultData::Float((c + 1) as f64)),
3412 None => Ok(ResultData::Error("#VALUE!".to_string())),
3413 },
3414 },
3415 "ROWS" => {
3416 let Some(arg) = args.first() else {
3417 return Ok(ResultData::Error("#VALUE!".to_string()));
3418 };
3419 let Some((sheet_opt, start_row, _, end_row, _)) = Self::range_bounds(arg) else {
3420 return Ok(ResultData::Error("#VALUE!".to_string()));
3421 };
3422 let is_self = sheet_opt.as_deref().is_none_or(|n| n == self.name);
3423 let actual_end_row = if end_row == usize::MAX {
3424 if is_self {
3425 self.row_count().saturating_sub(1)
3426 } else {
3427 context
3428 .and_then(|ctx| sheet_opt.as_ref().and_then(|n| ctx.sheets.get(n)))
3429 .map(|s| s.row_count().saturating_sub(1))
3430 .unwrap_or(0)
3431 }
3432 } else {
3433 end_row
3434 };
3435 Ok(ResultData::Float(
3436 (actual_end_row.saturating_sub(start_row) + 1) as f64,
3437 ))
3438 }
3439 "COLUMNS" => {
3440 let Some(arg) = args.first() else {
3441 return Ok(ResultData::Error("#VALUE!".to_string()));
3442 };
3443 match Self::range_bounds(arg) {
3444 Some((_, _, start_col, _, end_col)) => Ok(ResultData::Float(
3445 (end_col.saturating_sub(start_col) + 1) as f64,
3446 )),
3447 None => Ok(ResultData::Error("#VALUE!".to_string())),
3448 }
3449 }
3450 "AREAS" => {
3451 if args.is_empty() {
3452 Ok(ResultData::Error("#VALUE!".to_string()))
3453 } else {
3454 Ok(ResultData::Float(1.0))
3455 }
3456 }
3457 "ISREF" => Ok(ResultData::Boolean(matches!(
3458 args.first(),
3459 Some(Expr::CellRef { .. } | Expr::RangeRef { .. } | Expr::StructuredRef { .. })
3460 ))),
3461 "FORMULATEXT" | "ISFORMULA" => {
3462 let Some(arg) = args.first() else {
3463 return Ok(ResultData::Error("#VALUE!".to_string()));
3464 };
3465 let Some((sheet_opt, r, c, _, _)) = Self::range_bounds(arg) else {
3466 return Ok(ResultData::Error("#VALUE!".to_string()));
3467 };
3468 let is_self = sheet_opt.as_deref().is_none_or(|n| n == self.name);
3469 let src = if is_self {
3470 deps.push(Dependency::Local(CellRef::new(r, c)));
3471 self.get_src_str(&CellRef::new(r, c))
3472 } else if let Some(ctx) = context {
3473 let name = sheet_opt.unwrap();
3474 deps.push(Dependency::Remote {
3475 sheet: name.clone(),
3476 cell: CellRef::new(r, c),
3477 });
3478 ctx.sheets
3479 .get(&name)
3480 .map(|s| s.get_src_str(&CellRef::new(r, c)))
3481 .unwrap_or_default()
3482 } else {
3483 String::new()
3484 };
3485 let is_formula = src.starts_with('=');
3486 if func_name == "ISFORMULA" {
3487 Ok(ResultData::Boolean(is_formula))
3488 } else if is_formula {
3489 Ok(ResultData::String(src))
3490 } else {
3491 Ok(ResultData::Error("#N/A".to_string()))
3492 }
3493 }
3494 "SHEETS" => Ok(ResultData::Float(
3495 context.map(|c| c.sheets.len() + 1).unwrap_or(1) as f64,
3496 )),
3497 "SHEET" => {
3498 let sheet_name = match args.first() {
3499 None => Some(self.name.clone()),
3500 Some(arg) => match Self::range_bounds(arg) {
3501 Some((sheet_opt, ..)) => {
3502 Some(sheet_opt.unwrap_or_else(|| self.name.clone()))
3503 }
3504 None => self
3505 .evaluate_ast(arg, context, row, col, deps, scope)
3506 .ok()
3507 .map(|v| v.to_string()),
3508 },
3509 };
3510
3511 match sheet_name {
3512 Some(name) => {
3513 let ordinal = context
3514 .and_then(|c| {
3515 c.sheet_order
3516 .iter()
3517 .position(|n| n.eq_ignore_ascii_case(&name))
3518 })
3519 .map(|i| i + 1)
3520 .unwrap_or(1);
3521 Ok(ResultData::Float(ordinal as f64))
3522 }
3523 None => Ok(ResultData::Error("#N/A".to_string())),
3524 }
3525 }
3526 "CELL" => {
3527 if args.is_empty() {
3528 return Ok(ResultData::Error("#VALUE!".to_string()));
3529 }
3530 let info_type = self
3531 .evaluate_ast(&args[0], context, row, col, deps, scope)?
3532 .to_string()
3533 .to_lowercase();
3534 let bounds = args.get(1).and_then(Self::range_bounds);
3535 match info_type.as_str() {
3536 "row" => match bounds.map(|b| b.1).or(row) {
3537 Some(r) => Ok(ResultData::Float((r + 1) as f64)),
3538 None => Ok(ResultData::Error("#VALUE!".to_string())),
3539 },
3540 "col" => match bounds {
3541 Some((_, _, c, _, _)) => Ok(ResultData::Float((c + 1) as f64)),
3542 None => Ok(ResultData::Error("#VALUE!".to_string())),
3543 },
3544 "address" => match bounds {
3545 Some((_, r, c, _, _)) => Ok(ResultData::String(format!(
3546 "${}${}",
3547 crate::core::parser::col_idx_to_letters(c),
3548 r + 1
3549 ))),
3550 None => Ok(ResultData::Error("#VALUE!".to_string())),
3551 },
3552 "contents" => match bounds {
3553 Some((sheet_opt, r, c, _, _)) => {
3554 Ok(self.read_cell_with_deps(&sheet_opt, r, c, context, deps))
3555 }
3556 None => Ok(ResultData::Error("#VALUE!".to_string())),
3557 },
3558 _ => Ok(ResultData::Error("#VALUE!".to_string())),
3559 }
3560 }
3561 "INFO" => {
3562 if args.is_empty() {
3563 return Ok(ResultData::Error("#VALUE!".to_string()));
3564 }
3565 let info_type = self
3566 .evaluate_ast(&args[0], context, row, col, deps, scope)?
3567 .to_string()
3568 .to_lowercase();
3569 match info_type.as_str() {
3570 "numfile" => Ok(ResultData::Float(
3571 context.map(|c| c.sheets.len() + 1).unwrap_or(1) as f64,
3572 )),
3573 "release" => Ok(ResultData::String("16.0".to_string())),
3574 "system" => Ok(ResultData::String(
3575 if cfg!(target_os = "macos") {
3576 "mac"
3577 } else {
3578 "pcdos"
3579 }
3580 .to_string(),
3581 )),
3582 _ => Ok(ResultData::Error("#VALUE!".to_string())),
3583 }
3584 }
3585 "INDIRECT" => {
3586 if args.is_empty() {
3587 return Ok(ResultData::Error("#VALUE!".to_string()));
3588 }
3589 let text = self
3590 .evaluate_ast(&args[0], context, row, col, deps, scope)?
3591 .to_string();
3592 let a1_style = match args.get(1) {
3593 Some(a) => self.to_bool(&self.evaluate_ast(a, context, row, col, deps, scope)?),
3594 None => true,
3595 };
3596 if !a1_style {
3597 return Ok(ResultData::Error("#VALUE!".to_string()));
3598 }
3599 match Self::parse_a1_reference(&text) {
3600 Some((sheet_opt, start_row, start_col, end_row, end_col)) => {
3601 if start_row == end_row && start_col == end_col {
3602 Ok(self.read_cell_with_deps(
3603 &sheet_opt, start_row, start_col, context, deps,
3604 ))
3605 } else {
3606 match self.materialize_range(
3607 &sheet_opt, start_row, start_col, end_row, end_col, context,
3608 ) {
3609 Some(grid) => {
3610 Ok(ResultData::List(grid.into_iter().flatten().collect()))
3611 }
3612 None => Ok(ResultData::Error("#REF!".to_string())),
3613 }
3614 }
3615 }
3616 None => Ok(ResultData::Error("#REF!".to_string())),
3617 }
3618 }
3619 "OFFSET" => {
3620 if args.len() < 3 {
3621 return Ok(ResultData::Error("#VALUE!".to_string()));
3622 }
3623 let Some((sheet_opt, base_row, base_col, base_end_row, base_end_col)) =
3624 Self::range_bounds(&args[0])
3625 else {
3626 return Ok(ResultData::Error("#VALUE!".to_string()));
3627 };
3628 let row_offset = self
3629 .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3630 .unwrap_or(0.0) as isize;
3631 let col_offset = self
3632 .to_f64(&self.evaluate_ast(&args[2], context, row, col, deps, scope)?)
3633 .unwrap_or(0.0) as isize;
3634 let base_height = (base_end_row.saturating_sub(base_row) + 1) as isize;
3635 let base_width = (base_end_col.saturating_sub(base_col) + 1) as isize;
3636 let height = match args.get(3) {
3637 Some(a) => self
3638 .to_f64(&self.evaluate_ast(a, context, row, col, deps, scope)?)
3639 .unwrap_or(base_height as f64) as isize,
3640 None => base_height,
3641 };
3642 let width = match args.get(4) {
3643 Some(a) => self
3644 .to_f64(&self.evaluate_ast(a, context, row, col, deps, scope)?)
3645 .unwrap_or(base_width as f64) as isize,
3646 None => base_width,
3647 };
3648 let new_row = base_row as isize + row_offset;
3649 let new_col = base_col as isize + col_offset;
3650 if new_row < 0 || new_col < 0 || height <= 0 || width <= 0 {
3651 return Ok(ResultData::Error("#REF!".to_string()));
3652 }
3653 let (start_row, start_col) = (new_row as usize, new_col as usize);
3654 let (end_row, end_col) = (
3655 start_row + (height - 1) as usize,
3656 start_col + (width - 1) as usize,
3657 );
3658 if start_row == end_row && start_col == end_col {
3659 Ok(self.read_cell_with_deps(&sheet_opt, start_row, start_col, context, deps))
3660 } else {
3661 match self.materialize_range(
3662 &sheet_opt, start_row, start_col, end_row, end_col, context,
3663 ) {
3664 Some(grid) => Ok(ResultData::List(grid.into_iter().flatten().collect())),
3665 None => Ok(ResultData::Error("#REF!".to_string())),
3666 }
3667 }
3668 }
3669 _ => unreachable!(),
3670 }
3671 }
3672
3673 fn evaluate_getpivotdata(
3674 &self,
3675 args: &[crate::core::parser::Expr],
3676 context: Option<&Context>,
3677 row: Option<usize>,
3678 col: Option<usize>,
3679 deps: &mut Vec<Dependency>,
3680 scope: &LetScope<'_>,
3681 ) -> Result<ResultData, EngineError> {
3682 if args.len() < 2 || !(args.len() - 2).is_multiple_of(2) {
3683 return Ok(ResultData::Error("#VALUE!".to_string()));
3684 }
3685
3686 let data_field = self
3687 .evaluate_ast(&args[0], context, row, col, deps, scope)?
3688 .to_string();
3689
3690 let (sheet_opt, target_row, target_col, _, _) = match Self::range_bounds(&args[1]) {
3691 Some(bounds) => bounds,
3692 None => return Ok(ResultData::Error("#REF!".to_string())),
3693 };
3694 self.read_cell_with_deps(&sheet_opt, target_row, target_col, context, deps);
3695
3696 let sheet_id = match &sheet_opt {
3697 None => self.id,
3698 Some(name) if name == &self.name => self.id,
3699 Some(name) => match context.and_then(|c| c.sheets.get(name)) {
3700 Some(s) => s.id,
3701 None => return Ok(ResultData::Error("#REF!".to_string())),
3702 },
3703 };
3704
3705 let pivot_tables = context.map(|c| c.pivot_tables).unwrap_or(&[]);
3706 let pivot = match pivot_tables.iter().find(|p| {
3707 p.dest_sheet_id == sheet_id
3708 && p.last_output_end_row
3709 .is_some_and(|end| target_row >= p.dest_row && target_row <= end)
3710 && p.last_output_end_col
3711 .is_some_and(|end| target_col >= p.dest_col && target_col <= end)
3712 }) {
3713 Some(p) => p,
3714 None => return Ok(ResultData::Error("#REF!".to_string())),
3715 };
3716
3717 let mut criteria: Vec<(String, String)> = Vec::new();
3718 let mut i = 2;
3719 while i < args.len() {
3720 let field = self
3721 .evaluate_ast(&args[i], context, row, col, deps, scope)?
3722 .to_string();
3723 let item = self
3724 .evaluate_ast(&args[i + 1], context, row, col, deps, scope)?
3725 .to_string();
3726 criteria.push((field, item));
3727 i += 2;
3728 }
3729
3730 let mut sheet_refs: Vec<&Sheet> = context
3731 .map(|c| c.sheets.values().copied().collect())
3732 .unwrap_or_default();
3733 sheet_refs.push(self);
3734
3735 match crate::core::pivot::getpivotdata(&sheet_refs, pivot, &data_field, &criteria) {
3736 Ok(v) => Ok(v),
3737 Err(e) => Ok(ResultData::Error(e)),
3738 }
3739 }
3740
3741 #[allow(clippy::too_many_arguments)]
3742 fn evaluate_array_reshape_function(
3743 &self,
3744 func_name: &str,
3745 args: &[crate::core::parser::Expr],
3746 context: Option<&Context>,
3747 row: Option<usize>,
3748 col: Option<usize>,
3749 deps: &mut Vec<Dependency>,
3750 scope: &LetScope<'_>,
3751 ) -> Result<ResultData, EngineError> {
3752 match func_name {
3753 "TRANSPOSE" => {
3754 let Some(arg) = args.first() else {
3755 return Ok(ResultData::Error("#VALUE!".to_string()));
3756 };
3757 let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3758 let rows = flat.len().checked_div(cols).unwrap_or(0);
3759 let mut result = Vec::with_capacity(flat.len());
3760 for c in 0..cols {
3761 for r in 0..rows {
3762 result.push(flat[r * cols + c].clone());
3763 }
3764 }
3765 Ok(ResultData::List(result))
3766 }
3767 "HSTACK" | "VSTACK" => {
3768 if args.is_empty() {
3769 return Ok(ResultData::Error("#VALUE!".to_string()));
3770 }
3771 let mut shapes = Vec::with_capacity(args.len());
3772 for a in args {
3773 shapes.push(self.array_shape(a, context, row, col, deps, scope)?);
3774 }
3775 let mut result = Vec::new();
3776 if func_name == "HSTACK" {
3777 let max_rows = shapes
3778 .iter()
3779 .map(|(f, c)| if *c == 0 { 0 } else { f.len() / c })
3780 .max()
3781 .unwrap_or(0);
3782 for r in 0..max_rows {
3783 for (flat, cols) in &shapes {
3784 let rows = if *cols == 0 { 0 } else { flat.len() / cols };
3785 for c in 0..*cols {
3786 result.push(if r < rows {
3787 flat[r * cols + c].clone()
3788 } else {
3789 ResultData::Error("#N/A".to_string())
3790 });
3791 }
3792 }
3793 }
3794 } else {
3795 let max_cols = shapes.iter().map(|(_, c)| *c).max().unwrap_or(0);
3796 for (flat, cols) in &shapes {
3797 let rows = if *cols == 0 { 0 } else { flat.len() / cols };
3798 for r in 0..rows {
3799 for c in 0..max_cols {
3800 result.push(if c < *cols {
3801 flat[r * cols + c].clone()
3802 } else {
3803 ResultData::Error("#N/A".to_string())
3804 });
3805 }
3806 }
3807 }
3808 }
3809 Ok(ResultData::List(result))
3810 }
3811 "CHOOSEROWS" | "CHOOSECOLS" => {
3812 if args.len() < 2 {
3813 return Ok(ResultData::Error("#VALUE!".to_string()));
3814 }
3815 let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3816 let rows = flat.len().checked_div(cols).unwrap_or(0);
3817 let total = if func_name == "CHOOSEROWS" {
3818 rows
3819 } else {
3820 cols
3821 } as isize;
3822 let mut indices = Vec::with_capacity(args.len() - 1);
3823 for idx_expr in &args[1..] {
3824 let n = self
3825 .to_f64(&self.evaluate_ast(idx_expr, context, row, col, deps, scope)?)
3826 .unwrap_or(0.0) as isize;
3827 let real_idx = if n < 0 { total + n } else { n - 1 };
3828 if real_idx < 0 || real_idx >= total {
3829 return Ok(ResultData::Error("#VALUE!".to_string()));
3830 }
3831 indices.push(real_idx as usize);
3832 }
3833 let mut result = Vec::new();
3834 if func_name == "CHOOSEROWS" {
3835 for r in indices {
3836 for c in 0..cols {
3837 result.push(flat[r * cols + c].clone());
3838 }
3839 }
3840 } else {
3841 for r in 0..rows {
3842 for &c in &indices {
3843 result.push(flat[r * cols + c].clone());
3844 }
3845 }
3846 }
3847 Ok(ResultData::List(result))
3848 }
3849 "DROP" | "TAKE" => {
3850 if args.len() < 2 {
3851 return Ok(ResultData::Error("#VALUE!".to_string()));
3852 }
3853 let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3854 let num_rows = flat.len().checked_div(cols).unwrap_or(0) as isize;
3855 let is_take = func_name == "TAKE";
3856 let rows_n = self
3857 .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3858 .unwrap_or(0.0) as isize;
3859 let cols_n = match args.get(2) {
3860 Some(e) => self
3861 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3862 .unwrap_or(0.0) as isize,
3863 None => {
3864 if is_take {
3865 cols as isize
3866 } else {
3867 0
3868 }
3869 }
3870 };
3871 let (row_start, row_end) = Self::drop_take_bounds(num_rows, rows_n, is_take);
3872 let (col_start, col_end) = Self::drop_take_bounds(cols as isize, cols_n, is_take);
3873 if row_start >= row_end || col_start >= col_end {
3874 return Ok(ResultData::Error("#CALC!".to_string()));
3875 }
3876 let mut result = Vec::new();
3877 for r in row_start..row_end {
3878 for c in col_start..col_end {
3879 result.push(flat[(r as usize) * cols + (c as usize)].clone());
3880 }
3881 }
3882 Ok(ResultData::List(result))
3883 }
3884 "EXPAND" => {
3885 if args.len() < 2 {
3886 return Ok(ResultData::Error("#VALUE!".to_string()));
3887 }
3888 let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3889 let orig_rows = flat.len().checked_div(cols).unwrap_or(0);
3890 let new_rows = self
3891 .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3892 .unwrap_or(orig_rows as f64) as usize;
3893 let new_cols = match args.get(2) {
3894 Some(e) => self
3895 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3896 .unwrap_or(cols as f64) as usize,
3897 None => cols,
3898 };
3899 let pad = match args.get(3) {
3900 Some(e) => self.evaluate_ast(e, context, row, col, deps, scope)?,
3901 None => ResultData::Error("#N/A".to_string()),
3902 };
3903 if new_rows < orig_rows || new_cols < cols {
3904 return Ok(ResultData::Error("#VALUE!".to_string()));
3905 }
3906 let mut result = Vec::with_capacity(new_rows * new_cols);
3907 for r in 0..new_rows {
3908 for c in 0..new_cols {
3909 result.push(if r < orig_rows && c < cols {
3910 flat[r * cols + c].clone()
3911 } else {
3912 pad.clone()
3913 });
3914 }
3915 }
3916 Ok(ResultData::List(result))
3917 }
3918 "TOCOL" | "TOROW" => {
3919 let Some(arg) = args.first() else {
3920 return Ok(ResultData::Error("#VALUE!".to_string()));
3921 };
3922 let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3923 let rows = flat.len().checked_div(cols).unwrap_or(0);
3924 let ignore = match args.get(1) {
3925 Some(e) => self
3926 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3927 .unwrap_or(0.0) as i64,
3928 None => 0,
3929 };
3930 let scan_by_col = match args.get(2) {
3931 Some(e) => self.to_bool(&self.evaluate_ast(e, context, row, col, deps, scope)?),
3932 None => false,
3933 };
3934 let ordered: Vec<ResultData> = if scan_by_col {
3935 let mut v = Vec::with_capacity(flat.len());
3936 for c in 0..cols {
3937 for r in 0..rows {
3938 v.push(flat[r * cols + c].clone());
3939 }
3940 }
3941 v
3942 } else {
3943 flat
3944 };
3945 let filtered: Vec<ResultData> = ordered
3946 .into_iter()
3947 .filter(|v| match ignore {
3948 1 => !matches!(v, ResultData::None),
3949 2 => !matches!(v, ResultData::Error(_)),
3950 3 => !matches!(v, ResultData::None | ResultData::Error(_)),
3951 _ => true,
3952 })
3953 .collect();
3954 Ok(ResultData::List(filtered))
3955 }
3956 "WRAPROWS" | "WRAPCOLS" => {
3957 if args.len() < 2 {
3958 return Ok(ResultData::Error("#VALUE!".to_string()));
3959 }
3960 let (flat, _cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3961 let wrap = self
3962 .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3963 .unwrap_or(1.0)
3964 .max(1.0) as usize;
3965 let pad = match args.get(2) {
3966 Some(e) => self.evaluate_ast(e, context, row, col, deps, scope)?,
3967 None => ResultData::Error("#N/A".to_string()),
3968 };
3969 if func_name == "WRAPROWS" {
3970 let mut result = flat;
3971 let rem = result.len() % wrap;
3972 if rem != 0 {
3973 result.extend(std::iter::repeat_n(pad, wrap - rem));
3974 }
3975 Ok(ResultData::List(result))
3976 } else {
3977 let num_result_cols = flat.len().div_ceil(wrap).max(1);
3978 let total = wrap * num_result_cols;
3979 let mut result = Vec::with_capacity(total);
3980 for i in 0..total {
3981 let col = i / wrap;
3982 let r = i % wrap;
3983 let target = r * num_result_cols + col;
3984 while result.len() <= target {
3985 result.push(pad.clone());
3986 }
3987 if i < flat.len() {
3988 result[target] = flat[i].clone();
3989 }
3990 }
3991 Ok(ResultData::List(result))
3992 }
3993 }
3994 "UNIQUE" => {
3995 let Some(arg) = args.first() else {
3996 return Ok(ResultData::Error("#VALUE!".to_string()));
3997 };
3998 let (flat, _cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3999 let exactly_once = match args.get(2) {
4000 Some(e) => self.to_bool(&self.evaluate_ast(e, context, row, col, deps, scope)?),
4001 None => false,
4002 };
4003 let mut seen: Vec<(String, ResultData, usize)> = Vec::new();
4004 for v in &flat {
4005 let key = match v {
4006 ResultData::None => "blank:".to_string(),
4007 ResultData::Boolean(b) => format!("bool:{b}"),
4008 ResultData::Integer(i) => format!("num:{}", *i as f64),
4009 ResultData::Float(f) => format!("num:{f}"),
4010 ResultData::String(s) => format!("str:{s}"),
4011 ResultData::Error(e) => format!("err:{e}"),
4012 ResultData::List(_) | ResultData::Dict(_) => format!("other:{v}"),
4013 };
4014 match seen.iter_mut().find(|(k, ..)| k == &key) {
4015 Some(entry) => entry.2 += 1,
4016 None => seen.push((key, v.clone(), 1)),
4017 }
4018 }
4019 let result: Vec<ResultData> = seen
4020 .into_iter()
4021 .filter(|(_, _, count)| !exactly_once || *count == 1)
4022 .map(|(_, v, _)| v)
4023 .collect();
4024 Ok(ResultData::List(result))
4025 }
4026 "SORT" => {
4027 let Some(arg) = args.first() else {
4028 return Ok(ResultData::Error("#VALUE!".to_string()));
4029 };
4030 let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
4031 let rows = flat.len().checked_div(cols).unwrap_or(0);
4032 let sort_index = match args.get(1) {
4033 Some(e) => self
4034 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
4035 .unwrap_or(1.0) as usize,
4036 None => 1,
4037 };
4038 let sort_order = match args.get(2) {
4039 Some(e) => self
4040 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
4041 .unwrap_or(1.0),
4042 None => 1.0,
4043 };
4044 let col_idx = sort_index.saturating_sub(1).min(cols.saturating_sub(1));
4045 let mut row_indices: Vec<usize> = (0..rows).collect();
4046 row_indices.sort_by(|&a, &b| {
4047 Self::sort_compare_blanks_last(
4048 &flat[a * cols + col_idx],
4049 &flat[b * cols + col_idx],
4050 sort_order,
4051 )
4052 });
4053 let mut result = Vec::with_capacity(flat.len());
4054 for r in row_indices {
4055 for c in 0..cols {
4056 result.push(flat[r * cols + c].clone());
4057 }
4058 }
4059 Ok(ResultData::List(result))
4060 }
4061 "SORTBY" => {
4062 if args.len() < 2 {
4063 return Ok(ResultData::Error("#VALUE!".to_string()));
4064 }
4065 let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
4066 let rows = flat.len().checked_div(cols).unwrap_or(0);
4067 let by = self.eval_as_array(&args[1], context, row, col, deps, scope)?;
4068 let order = match args.get(2) {
4069 Some(e) => self
4070 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
4071 .unwrap_or(1.0),
4072 None => 1.0,
4073 };
4074 let mut row_indices: Vec<usize> = (0..rows).collect();
4075 row_indices.sort_by(|&a, &b| {
4076 let va = by.get(a).cloned().unwrap_or(ResultData::None);
4077 let vb = by.get(b).cloned().unwrap_or(ResultData::None);
4078 Self::sort_compare_blanks_last(&va, &vb, order)
4079 });
4080 let mut result = Vec::with_capacity(flat.len());
4081 for r in row_indices {
4082 for c in 0..cols {
4083 result.push(flat[r * cols + c].clone());
4084 }
4085 }
4086 Ok(ResultData::List(result))
4087 }
4088 "FILTER" => {
4089 if args.len() < 2 {
4090 return Ok(ResultData::Error("#VALUE!".to_string()));
4091 }
4092 let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
4093 let rows = flat.len().checked_div(cols).unwrap_or(0);
4094 let include = self.eval_as_array(&args[1], context, row, col, deps, scope)?;
4095 let mut result = Vec::new();
4096 for r in 0..rows {
4097 let keep = include.get(r).map(|v| self.to_bool(v)).unwrap_or(false);
4098 if keep {
4099 for c in 0..cols {
4100 result.push(flat[r * cols + c].clone());
4101 }
4102 }
4103 }
4104 if result.is_empty() {
4105 match args.get(2) {
4106 Some(e) => Ok(self.evaluate_ast(e, context, row, col, deps, scope)?),
4107 None => Ok(ResultData::Error("#CALC!".to_string())),
4108 }
4109 } else {
4110 Ok(ResultData::List(result))
4111 }
4112 }
4113 "TRIMRANGE" => {
4114 let Some(arg) = args.first() else {
4115 return Ok(ResultData::Error("#VALUE!".to_string()));
4116 };
4117 let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
4118 let rows = flat.len().checked_div(cols).unwrap_or(0);
4119 let is_blank = |v: &ResultData| {
4120 matches!(v, ResultData::None)
4121 || matches!(v, ResultData::String(s) if s.is_empty())
4122 };
4123 let row_blank = |r: usize| (0..cols).all(|c| is_blank(&flat[r * cols + c]));
4124 let col_blank = |c: usize| (0..rows).all(|r| is_blank(&flat[r * cols + c]));
4125 let mut r_start = 0;
4126 while r_start < rows && row_blank(r_start) {
4127 r_start += 1;
4128 }
4129 let mut r_end = rows;
4130 while r_end > r_start && row_blank(r_end - 1) {
4131 r_end -= 1;
4132 }
4133 let mut c_start = 0;
4134 while c_start < cols && col_blank(c_start) {
4135 c_start += 1;
4136 }
4137 let mut c_end = cols;
4138 while c_end > c_start && col_blank(c_end - 1) {
4139 c_end -= 1;
4140 }
4141 let mut result = Vec::new();
4142 for r in r_start..r_end {
4143 for c in c_start..c_end {
4144 result.push(flat[r * cols + c].clone());
4145 }
4146 }
4147 Ok(ResultData::List(result))
4148 }
4149 _ => unreachable!(),
4150 }
4151 }
4152
4153 pub fn get_src(&self, cell: &CellRef) -> Option<&String> {
4155 let col = self.columns.get(cell.col);
4156 if let Some(col) = col {
4157 col.src.get(cell.row)
4158 } else {
4159 None
4160 }
4161 }
4162
4163 pub fn get_src_str(&self, cell: &CellRef) -> String {
4165 let col = self.columns.get(cell.col);
4166 if let Some(col) = col {
4167 col.src.get(cell.row).cloned().unwrap_or("".to_string())
4168 } else {
4169 "".to_string()
4170 }
4171 }
4172
4173 pub fn get_src_str_ref(&self, cell: &CellRef) -> Option<&str> {
4175 let col = self.columns.get(cell.col)?;
4176 col.src.get(cell.row).map(|s| s.as_str())
4177 }
4178
4179 pub fn get_word_boundaries(&self, cell: &CellRef, char_offset: usize) -> (usize, usize) {
4181 let text = self.get_src_str(cell);
4182 get_word_boundaries_from_str(&text, char_offset)
4183 }
4184}