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