1use crate::traits::ArgumentHandle;
2use formualizer_common::{ArgKind, ExcelError, ExcelErrorKind, LiteralValue};
4use smallvec::{SmallVec, smallvec};
5use std::borrow::Cow;
6
7#[derive(Copy, Clone, Debug, Eq, PartialEq)]
8pub enum ShapeKind {
9 Scalar,
10 Range,
11 Array,
12}
13
14#[derive(Copy, Clone, Debug, Eq, PartialEq)]
15pub enum CoercionPolicy {
16 None,
17 NumberStrict,
18 NumberLenientText,
19 Logical,
20 Criteria,
21 DateTimeSerial,
22}
23
24#[derive(Clone, Debug)]
25pub struct ArgSchema {
26 pub kinds: SmallVec<[ArgKind; 2]>,
27 pub required: bool,
28 pub by_ref: bool,
29 pub shape: ShapeKind,
30 pub coercion: CoercionPolicy,
31 pub max: Option<usize>,
32 pub repeating: Option<usize>,
33 pub default: Option<LiteralValue>,
34}
35
36impl ArgSchema {
37 pub fn any() -> Self {
38 Self {
39 kinds: smallvec![ArgKind::Any],
40 required: true,
41 by_ref: false,
42 shape: ShapeKind::Scalar,
43 coercion: CoercionPolicy::None,
44 max: None,
45 repeating: None,
46 default: None,
47 }
48 }
49
50 pub fn number_lenient_scalar() -> Self {
51 Self {
52 kinds: smallvec![ArgKind::Number],
53 required: true,
54 by_ref: false,
55 shape: ShapeKind::Scalar,
56 coercion: CoercionPolicy::NumberLenientText,
57 max: None,
58 repeating: None,
59 default: None,
60 }
61 }
62}
63
64#[derive(Clone, Debug)]
65pub enum CriteriaPredicate {
66 Eq(LiteralValue),
67 Ne(LiteralValue),
68 Gt(f64),
69 Ge(f64),
70 Lt(f64),
71 Le(f64),
72 TextLike {
73 pattern: String,
74 case_insensitive: bool,
75 },
76 IsBlank,
77 IsNumber,
78 IsText,
79 IsLogical,
80}
81
82#[derive(Debug)]
83pub enum PreparedArg<'a> {
84 Value(Cow<'a, LiteralValue>),
85 Range(crate::engine::range_view::RangeView<'a>),
86 Reference(formualizer_parse::parser::ReferenceType),
87 Predicate(CriteriaPredicate),
88}
89
90pub struct PreparedArgs<'a> {
91 pub items: Vec<PreparedArg<'a>>,
92}
93
94#[derive(Default)]
95pub struct ValidationOptions {
96 pub warn_only: bool,
97}
98
99pub fn parse_criteria(v: &LiteralValue) -> Result<CriteriaPredicate, ExcelError> {
102 match v {
103 LiteralValue::Text(s) => {
104 let s_trim = s.trim();
105 let ops = [">=", "<=", "<>", ">", "<", "="];
107 for op in ops.iter() {
108 if let Some(rhs) = s_trim.strip_prefix(op) {
109 if let Ok(n) = rhs.trim().parse::<f64>() {
111 return Ok(match *op {
112 ">=" => CriteriaPredicate::Ge(n),
113 "<=" => CriteriaPredicate::Le(n),
114 ">" => CriteriaPredicate::Gt(n),
115 "<" => CriteriaPredicate::Lt(n),
116 "=" => CriteriaPredicate::Eq(LiteralValue::Number(n)),
117 "<>" => CriteriaPredicate::Ne(LiteralValue::Number(n)),
118 _ => unreachable!(),
119 });
120 }
121 let lit = LiteralValue::Text(rhs.to_string());
123 return Ok(match *op {
124 "=" => CriteriaPredicate::Eq(lit),
125 "<>" => CriteriaPredicate::Ne(lit),
126 ">=" | "<=" | ">" | "<" => {
127 CriteriaPredicate::Eq(LiteralValue::Text(s_trim.to_string()))
129 }
130 _ => unreachable!(),
131 });
132 }
133 }
134 if s_trim.contains('*') || s_trim.contains('?') {
136 return Ok(CriteriaPredicate::TextLike {
137 pattern: s_trim.to_string(),
138 case_insensitive: true,
139 });
140 }
141 let lower = s_trim.to_ascii_lowercase();
143 if lower == "true" {
144 return Ok(CriteriaPredicate::Eq(LiteralValue::Boolean(true)));
145 } else if lower == "false" {
146 return Ok(CriteriaPredicate::Eq(LiteralValue::Boolean(false)));
147 }
148 Ok(CriteriaPredicate::Eq(LiteralValue::Text(
150 s_trim.to_string(),
151 )))
152 }
153 LiteralValue::Empty => Ok(CriteriaPredicate::IsBlank),
154 LiteralValue::Number(n) => Ok(CriteriaPredicate::Eq(LiteralValue::Number(*n))),
155 LiteralValue::Int(i) => Ok(CriteriaPredicate::Eq(LiteralValue::Int(*i))),
156 LiteralValue::Boolean(b) => Ok(CriteriaPredicate::Eq(LiteralValue::Boolean(*b))),
157 LiteralValue::Error(e) => Err(e.clone()),
158 LiteralValue::Array(arr) => {
159 if arr.len() == 1 && arr.first().map(|r| r.len()).unwrap_or(0) == 1 {
161 parse_criteria(&arr[0][0])
162 } else {
163 Ok(CriteriaPredicate::Eq(LiteralValue::Array(arr.clone())))
164 }
165 }
166 other => Ok(CriteriaPredicate::Eq(other.clone())),
167 }
168}
169
170pub fn validate_and_prepare<'a, 'b>(
171 args: &'a [ArgumentHandle<'a, 'b>],
172 schema: &[ArgSchema],
173 options: ValidationOptions,
174) -> Result<PreparedArgs<'a>, ExcelError> {
175 if schema.is_empty() {
177 return Ok(PreparedArgs { items: Vec::new() });
178 }
179
180 let mut items: Vec<PreparedArg<'a>> = Vec::with_capacity(args.len());
181 for (idx, arg) in args.iter().enumerate() {
182 let spec = if schema.len() == 1 {
183 &schema[0]
184 } else if idx < schema.len() {
185 &schema[idx]
186 } else {
187 if let Some(rep_spec) = schema.iter().find(|s| s.repeating.is_some()) {
189 rep_spec
190 } else if options.warn_only {
191 continue;
192 } else {
193 return Err(
194 ExcelError::new(ExcelErrorKind::Value).with_message("Too many arguments")
195 );
196 }
197 };
198
199 if spec.by_ref {
201 match arg.as_reference_or_eval() {
202 Ok(r) => {
203 items.push(PreparedArg::Reference(r));
204 continue;
205 }
206 Err(e) => {
207 if options.warn_only {
208 continue;
209 } else {
210 return Err(e);
211 }
212 }
213 }
214 }
215
216 if matches!(spec.coercion, CoercionPolicy::Criteria) {
218 let v = arg.value()?.into_owned();
219 match parse_criteria(&v) {
220 Ok(pred) => {
221 items.push(PreparedArg::Predicate(pred));
222 continue;
223 }
224 Err(e) => {
225 if options.warn_only {
226 continue;
227 } else {
228 return Err(e);
229 }
230 }
231 }
232 }
233
234 match spec.shape {
236 ShapeKind::Scalar => {
237 match arg.value() {
239 Ok(v) => {
240 let v = match v.as_ref() {
241 LiteralValue::Array(arr) => {
242 let tl = arr
243 .first()
244 .and_then(|row| row.first())
245 .cloned()
246 .unwrap_or(LiteralValue::Empty);
247 Cow::Owned(tl)
248 }
249 _ => v,
250 };
251 let coerced = match spec.coercion {
253 CoercionPolicy::None => v,
254 CoercionPolicy::NumberStrict => {
255 match crate::coercion::to_number_strict(v.as_ref()) {
256 Ok(n) => Cow::Owned(LiteralValue::Number(n)),
257 Err(e) => {
258 if options.warn_only {
259 v
260 } else {
261 return Err(e);
262 }
263 }
264 }
265 }
266 CoercionPolicy::NumberLenientText => {
267 match crate::coercion::to_number_lenient(v.as_ref()) {
268 Ok(n) => Cow::Owned(LiteralValue::Number(n)),
269 Err(e) => {
270 if options.warn_only {
271 v
272 } else {
273 return Err(e);
274 }
275 }
276 }
277 }
278 CoercionPolicy::Logical => {
279 match crate::coercion::to_logical(v.as_ref()) {
280 Ok(b) => Cow::Owned(LiteralValue::Boolean(b)),
281 Err(e) => {
282 if options.warn_only {
283 v
284 } else {
285 return Err(e);
286 }
287 }
288 }
289 }
290 CoercionPolicy::Criteria => v, CoercionPolicy::DateTimeSerial => {
292 match crate::coercion::to_datetime_serial(v.as_ref()) {
293 Ok(n) => Cow::Owned(LiteralValue::Number(n)),
294 Err(e) => {
295 if options.warn_only {
296 v
297 } else {
298 return Err(e);
299 }
300 }
301 }
302 }
303 };
304 items.push(PreparedArg::Value(coerced))
305 }
306 Err(e) => items.push(PreparedArg::Value(Cow::Owned(LiteralValue::Error(e)))),
307 }
308 }
309 ShapeKind::Range | ShapeKind::Array => {
310 match arg.range_view() {
311 Ok(r) => items.push(PreparedArg::Range(r)),
312 Err(_e) => {
313 match arg.value() {
316 Ok(v) => items.push(PreparedArg::Value(v)),
317 Err(e2) => {
318 items.push(PreparedArg::Value(Cow::Owned(LiteralValue::Error(e2))))
319 }
320 }
321 }
322 }
323 }
324 }
325 }
326
327 Ok(PreparedArgs { items })
328}