1use super::{Area, BinOp, Ev, Expr, Reference, SheetSel, Value, Workbook};
2use crate::base::{CellRef, MAX_COLS, MAX_ROWS, RangeRef};
3
4pub(super) fn resizes_sum_range(name: &str, args: usize) -> bool {
6 args == 3 && matches!(name, "SUMIF" | "AVERAGEIF")
7}
8
9pub(super) fn ev_shape(ev: &Ev) -> Option<(u32, u32)> {
11 match ev {
12 Ev::R(areas) => match areas.as_slice() {
13 [a] => Some((a.range.height(), a.range.width())),
14 _ => None,
15 },
16 Ev::V(Value::Array(a)) => Some((u32::try_from(a.rows).unwrap_or(u32::MAX), u32::try_from(a.cols).unwrap_or(u32::MAX))),
17 Ev::V(_) => Some((1, 1)),
18 Ev::L(_) => None,
19 }
20}
21
22pub fn resized_sum_range(sum: RangeRef, height: u32, width: u32) -> RangeRef {
25 let row = sum.start.row.saturating_add(height.saturating_sub(1)).min(MAX_ROWS - 1);
26 let col = sum.start.col.saturating_add(width.saturating_sub(1)).min(MAX_COLS - 1);
27 RangeRef::new(sum.start, CellRef::new(row, col))
28}
29
30pub fn precedents(wb: &Workbook, sheet: usize, e: &Expr) -> (Vec<Area>, bool) {
33 let mut out = Vec::new();
34 let mut dynamic = false;
35 let push = |out: &mut Vec<Area>, r: &Reference, range: RangeRef| match &r.sheet {
36 SheetSel::Current => out.push(Area { sheet, range }),
37 SheetSel::Named(n) => {
38 if let Some(i) = wb.sheet_index(n) {
39 out.push(Area { sheet: i, range });
40 }
41 }
42 SheetSel::Span(a, b) => {
43 if let (Some(i), Some(j)) = (wb.sheet_index(a), wb.sheet_index(b)) {
44 for s in i.min(j)..=i.max(j) {
45 out.push(Area { sheet: s, range });
46 }
47 }
48 }
49 };
50 e.walk(&mut |x| match x {
51 Expr::Ref(r) => push(&mut out, r, r.range()),
52 Expr::Call(n, args) if resizes_sum_range(n, args.len()) => match (args.first(), args.get(2)) {
55 (Some(Expr::Ref(c)), Some(Expr::Ref(s))) => {
56 let (crit, sum) = (c.range(), s.range());
57 push(&mut out, s, resized_sum_range(sum, crit.height(), crit.width()));
58 }
59 (_, Some(Expr::Number(_) | Expr::Text(_) | Expr::Bool(_) | Expr::Error(_) | Expr::Array(_) | Expr::Missing)) => {}
60 _ => dynamic = true,
61 },
62 Expr::Call(n, _)
63 if matches!(n.as_str(), "INDIRECT" | "OFFSET" | "NOW" | "TODAY" | "RAND" | "RANDBETWEEN" | "RANDARRAY" | "CELL" | "INFO") =>
64 {
65 dynamic = true
66 }
67 Expr::Name(_) | Expr::Struct(_) => dynamic = true,
68 Expr::Call(n, _) if crate::functions::lookup(n).is_none() && !is_special(n) => dynamic = true,
69 Expr::Binary(BinOp::Range, a, b) if !matches!((&**a, &**b), (Expr::Ref(_), Expr::Ref(_))) => {
72 let mut inner = precedents(wb, sheet, a).0;
73 inner.extend(precedents(wb, sheet, b).0);
74 let mut boxes: Vec<Area> = Vec::new();
75 for area in inner {
76 match boxes.iter_mut().find(|x| x.sheet == area.sheet) {
77 Some(x) => x.range = x.range.union(&area.range),
78 None => boxes.push(area),
79 }
80 }
81 out.extend(boxes);
82 }
83 _ => {}
84 });
85 (out, dynamic)
86}
87
88fn is_special(n: &str) -> bool {
89 matches!(
90 n,
91 "IF" | "IFS"
92 | "IFERROR"
93 | "IFNA"
94 | "CHOOSE"
95 | "SWITCH"
96 | "ROW"
97 | "COLUMN"
98 | "ROWS"
99 | "COLUMNS"
100 | "AREAS"
101 | "ISREF"
102 | "ISFORMULA"
103 | "FORMULATEXT"
104 | "SHEET"
105 | "SHEETS"
106 | "OFFSET"
107 | "INDIRECT"
108 | "INDEX"
109 | "CELL"
110 | "TRIMRANGE"
111 | "SUBTOTAL"
112 | "AGGREGATE"
113 | "TEXT"
114 | "LET"
115 | "LAMBDA"
116 | "ISOMITTED"
117 | "MAP"
118 | "REDUCE"
119 | "SCAN"
120 | "BYROW"
121 | "BYCOL"
122 | "MAKEARRAY"
123 )
124}
125
126pub const SPECIAL_FUNCTIONS: &[&str] = &[
128 "IF",
129 "IFS",
130 "IFERROR",
131 "IFNA",
132 "CHOOSE",
133 "SWITCH",
134 "ROW",
135 "COLUMN",
136 "ROWS",
137 "COLUMNS",
138 "AREAS",
139 "ISREF",
140 "ISFORMULA",
141 "FORMULATEXT",
142 "SHEET",
143 "SHEETS",
144 "OFFSET",
145 "INDIRECT",
146 "INDEX",
147 "CELL",
148 "TRIMRANGE",
149 "SUBTOTAL",
150 "AGGREGATE",
151 "TEXT",
152 "LET",
153 "LAMBDA",
154 "ISOMITTED",
155 "MAP",
156 "REDUCE",
157 "SCAN",
158 "BYROW",
159 "BYCOL",
160 "MAKEARRAY",
161];
162
163pub fn is_known_function(n: &str) -> bool {
164 is_special(&n.to_ascii_uppercase()) || crate::functions::lookup(n).is_some()
165}