1use std::cmp::Ordering;
10
11use corium_core::{TotalF64, Value};
12
13use crate::QueryError;
14
15#[derive(Clone, Debug, PartialEq)]
17pub enum CallResult {
18 Test(bool),
20 Scalar(Value),
22 Coll(Vec<Value>),
24 Rel(Vec<Vec<Value>>),
26}
27
28fn type_error(name: &str, args: &[Value]) -> QueryError {
29 QueryError::Type(format!("{name} cannot be applied to {args:?}"))
30}
31
32fn arity_error(name: &str) -> QueryError {
33 QueryError::Arity(format!("wrong number of arguments to {name}"))
34}
35
36enum Num {
38 Long(i64),
39 Double(f64),
40}
41
42fn as_num(value: &Value) -> Option<Num> {
43 match value {
44 Value::Long(v) | Value::Instant(v) => Some(Num::Long(*v)),
45 Value::Double(v) => Some(Num::Double(v.0)),
46 Value::Ref(e) => i64::try_from(e.raw()).ok().map(Num::Long),
48 _ => None,
49 }
50}
51
52pub fn compare(left: &Value, right: &Value) -> Result<Ordering, QueryError> {
58 if let (Some(l), Some(r)) = (as_num(left), as_num(right)) {
59 return Ok(match (l, r) {
60 (Num::Long(l), Num::Long(r)) => l.cmp(&r),
61 (Num::Long(l), Num::Double(r)) => TotalF64(to_f64(l)).cmp(&TotalF64(r)),
62 (Num::Double(l), Num::Long(r)) => TotalF64(l).cmp(&TotalF64(to_f64(r))),
63 (Num::Double(l), Num::Double(r)) => TotalF64(l).cmp(&TotalF64(r)),
64 });
65 }
66 match (left, right) {
67 (Value::Str(l), Value::Str(r)) => Ok(l.cmp(r)),
68 (Value::Bool(l), Value::Bool(r)) => Ok(l.cmp(r)),
69 (Value::Keyword(l), Value::Keyword(r)) => Ok(l.cmp(r)),
70 (Value::Uuid(l), Value::Uuid(r)) => Ok(l.cmp(r)),
71 (Value::Bytes(l), Value::Bytes(r)) => Ok(l.cmp(r)),
72 _ => Err(QueryError::Type(format!(
73 "cannot compare {left:?} with {right:?}"
74 ))),
75 }
76}
77
78#[must_use]
81pub fn loose_eq(left: &Value, right: &Value) -> bool {
82 compare(left, right).is_ok_and(Ordering::is_eq)
83}
84
85#[allow(clippy::cast_precision_loss)]
86fn to_f64(v: i64) -> f64 {
87 v as f64
88}
89
90fn fold_numeric(
91 name: &str,
92 args: &[Value],
93 long_op: impl Fn(i64, i64) -> Option<i64>,
94 double_op: impl Fn(f64, f64) -> f64,
95) -> Result<Value, QueryError> {
96 let mut iter = args.iter();
97 let first = iter.next().ok_or_else(|| arity_error(name))?;
98 let mut acc = match first {
99 Value::Long(v) => Num::Long(*v),
100 Value::Double(v) => Num::Double(v.0),
101 _ => return Err(type_error(name, args)),
102 };
103 for arg in iter {
104 let rhs = match arg {
105 Value::Long(v) => Num::Long(*v),
106 Value::Double(v) => Num::Double(v.0),
107 _ => return Err(type_error(name, args)),
108 };
109 acc = match (acc, rhs) {
110 (Num::Long(l), Num::Long(r)) => match long_op(l, r) {
111 Some(v) => Num::Long(v),
112 None => return Err(QueryError::Type(format!("{name} overflowed"))),
113 },
114 (l, r) => {
115 let l = match l {
116 Num::Long(v) => to_f64(v),
117 Num::Double(v) => v,
118 };
119 let r = match r {
120 Num::Long(v) => to_f64(v),
121 Num::Double(v) => v,
122 };
123 Num::Double(double_op(l, r))
124 }
125 };
126 }
127 Ok(match acc {
128 Num::Long(v) => Value::Long(v),
129 Num::Double(v) => Value::Double(TotalF64(v)),
130 })
131}
132
133fn string_arg<'a>(name: &str, args: &'a [Value], index: usize) -> Result<&'a str, QueryError> {
134 match args.get(index) {
135 Some(Value::Str(s)) => Ok(s),
136 _ => Err(type_error(name, args)),
137 }
138}
139
140fn long_arg(name: &str, args: &[Value], index: usize) -> Result<i64, QueryError> {
141 match args.get(index) {
142 Some(Value::Long(v)) => Ok(*v),
143 _ => Err(type_error(name, args)),
144 }
145}
146
147fn one_long(name: &str, args: &[Value]) -> Result<i64, QueryError> {
148 if args.len() != 1 {
149 return Err(arity_error(name));
150 }
151 long_arg(name, args, 0)
152}
153
154fn two_longs(name: &str, args: &[Value]) -> Result<(i64, i64), QueryError> {
155 if args.len() != 2 {
156 return Err(arity_error(name));
157 }
158 Ok((long_arg(name, args, 0)?, long_arg(name, args, 1)?))
159}
160
161fn chain_compare(
162 args: &[Value],
163 accept: impl Fn(Ordering) -> bool,
164) -> Result<CallResult, QueryError> {
165 for window in args.windows(2) {
166 if !accept(compare(&window[0], &window[1])?) {
167 return Ok(CallResult::Test(false));
168 }
169 }
170 Ok(CallResult::Test(true))
171}
172
173#[must_use]
175pub fn value_to_display(value: &Value) -> String {
176 match value {
177 Value::Bool(v) => v.to_string(),
178 Value::Long(v) | Value::Instant(v) => v.to_string(),
179 Value::Double(v) => v.0.to_string(),
180 Value::Str(s) => s.to_string(),
181 Value::Uuid(v) => format!("{v:032x}"),
182 Value::Keyword(id) => format!(":kw{id}"),
183 Value::Ref(e) => e.raw().to_string(),
184 Value::Bytes(b) => format!("{b:02x?}"),
185 }
186}
187
188#[must_use]
192pub fn is_native(name: &str) -> bool {
193 matches!(
194 name,
195 "<" | "<="
196 | ">"
197 | ">="
198 | "="
199 | "=="
200 | "!="
201 | "not="
202 | "even?"
203 | "odd?"
204 | "zero?"
205 | "pos?"
206 | "neg?"
207 | "true?"
208 | "false?"
209 | "starts-with?"
210 | "ends-with?"
211 | "includes?"
212 | "+"
213 | "-"
214 | "*"
215 | "/"
216 | "quot"
217 | "rem"
218 | "mod"
219 | "inc"
220 | "dec"
221 | "abs"
222 | "min"
223 | "max"
224 | "str"
225 | "count"
226 | "subs"
227 | "upper-case"
228 | "lower-case"
229 | "identity"
230 )
231}
232
233#[allow(clippy::too_many_lines)]
239pub fn call(name: &str, args: &[Value]) -> Result<CallResult, QueryError> {
240 match name {
241 "<" => chain_compare(args, Ordering::is_lt),
242 "<=" => chain_compare(args, Ordering::is_le),
243 ">" => chain_compare(args, Ordering::is_gt),
244 ">=" => chain_compare(args, Ordering::is_ge),
245 "=" | "==" => Ok(CallResult::Test(
246 args.windows(2).all(|w| loose_eq(&w[0], &w[1])),
247 )),
248 "!=" | "not=" => Ok(CallResult::Test(
249 args.windows(2).any(|w| !loose_eq(&w[0], &w[1])),
250 )),
251 "even?" => Ok(CallResult::Test(one_long(name, args)? % 2 == 0)),
252 "odd?" => Ok(CallResult::Test(one_long(name, args)? % 2 != 0)),
253 "zero?" => Ok(CallResult::Test(one_long(name, args)? == 0)),
254 "pos?" => Ok(CallResult::Test(one_long(name, args)? > 0)),
255 "neg?" => Ok(CallResult::Test(one_long(name, args)? < 0)),
256 "true?" => Ok(CallResult::Test(args == [Value::Bool(true)])),
257 "false?" => Ok(CallResult::Test(args == [Value::Bool(false)])),
258 "starts-with?" => Ok(CallResult::Test(
259 string_arg(name, args, 0)?.starts_with(string_arg(name, args, 1)?),
260 )),
261 "ends-with?" => Ok(CallResult::Test(
262 string_arg(name, args, 0)?.ends_with(string_arg(name, args, 1)?),
263 )),
264 "includes?" => Ok(CallResult::Test(
265 string_arg(name, args, 0)?.contains(string_arg(name, args, 1)?),
266 )),
267 "+" => Ok(CallResult::Scalar(fold_numeric(
268 name,
269 args,
270 i64::checked_add,
271 |l, r| l + r,
272 )?)),
273 "-" => {
274 if args.len() == 1 {
275 return call("-", &[Value::Long(0), args[0].clone()]);
276 }
277 Ok(CallResult::Scalar(fold_numeric(
278 name,
279 args,
280 i64::checked_sub,
281 |l, r| l - r,
282 )?))
283 }
284 "*" => Ok(CallResult::Scalar(fold_numeric(
285 name,
286 args,
287 i64::checked_mul,
288 |l, r| l * r,
289 )?)),
290 "/" => {
291 if args.len() != 2 {
294 return Err(arity_error(name));
295 }
296 match (&args[0], &args[1]) {
297 (Value::Long(_), Value::Long(0)) => {
298 Err(QueryError::Type("division by zero".into()))
299 }
300 (Value::Long(l), Value::Long(r)) if l % r == 0 => {
301 Ok(CallResult::Scalar(Value::Long(l / r)))
302 }
303 (left, right) => {
304 let (Some(l), Some(r)) = (as_num(left), as_num(right)) else {
305 return Err(type_error(name, args));
306 };
307 let l = match l {
308 Num::Long(v) => to_f64(v),
309 Num::Double(v) => v,
310 };
311 let r = match r {
312 Num::Long(v) => to_f64(v),
313 Num::Double(v) => v,
314 };
315 Ok(CallResult::Scalar(Value::Double(TotalF64(l / r))))
316 }
317 }
318 }
319 "quot" => {
320 let (l, r) = two_longs(name, args)?;
321 if r == 0 {
322 return Err(QueryError::Type("division by zero".into()));
323 }
324 Ok(CallResult::Scalar(Value::Long(l / r)))
325 }
326 "rem" => {
327 let (l, r) = two_longs(name, args)?;
328 if r == 0 {
329 return Err(QueryError::Type("division by zero".into()));
330 }
331 Ok(CallResult::Scalar(Value::Long(l % r)))
332 }
333 "mod" => {
334 let (l, r) = two_longs(name, args)?;
335 if r == 0 {
336 return Err(QueryError::Type("division by zero".into()));
337 }
338 Ok(CallResult::Scalar(Value::Long(l.rem_euclid(r))))
339 }
340 "inc" => Ok(CallResult::Scalar(Value::Long(
341 one_long(name, args)?
342 .checked_add(1)
343 .ok_or_else(|| QueryError::Type("inc overflowed".into()))?,
344 ))),
345 "dec" => Ok(CallResult::Scalar(Value::Long(
346 one_long(name, args)?
347 .checked_sub(1)
348 .ok_or_else(|| QueryError::Type("dec overflowed".into()))?,
349 ))),
350 "abs" => match args {
351 [Value::Long(v)] => Ok(CallResult::Scalar(Value::Long(v.abs()))),
352 [Value::Double(v)] => Ok(CallResult::Scalar(Value::Double(TotalF64(v.0.abs())))),
353 _ => Err(type_error(name, args)),
354 },
355 "min" | "max" => {
356 if args.is_empty() {
357 return Err(arity_error(name));
358 }
359 let mut best = args[0].clone();
360 for arg in &args[1..] {
361 let ordering = compare(arg, &best)?;
362 let better = if name == "min" {
363 ordering.is_lt()
364 } else {
365 ordering.is_gt()
366 };
367 if better {
368 best = arg.clone();
369 }
370 }
371 Ok(CallResult::Scalar(best))
372 }
373 "str" => Ok(CallResult::Scalar(Value::Str(
374 args.iter().map(value_to_display).collect::<String>().into(),
375 ))),
376 "count" => Ok(CallResult::Scalar(Value::Long(
377 i64::try_from(string_arg(name, args, 0)?.chars().count())
378 .map_err(|_| QueryError::Type("count overflowed".into()))?,
379 ))),
380 "subs" => {
381 let text = string_arg(name, args, 0)?;
382 let start =
383 usize::try_from(long_arg(name, args, 1)?).map_err(|_| type_error(name, args))?;
384 let end = match args.get(2) {
385 Some(Value::Long(v)) => usize::try_from(*v).map_err(|_| type_error(name, args))?,
386 None => text.chars().count(),
387 Some(_) => return Err(type_error(name, args)),
388 };
389 let out: String = text
390 .chars()
391 .skip(start)
392 .take(end.saturating_sub(start))
393 .collect();
394 Ok(CallResult::Scalar(Value::Str(out.into())))
395 }
396 "upper-case" => Ok(CallResult::Scalar(Value::Str(
397 string_arg(name, args, 0)?.to_uppercase().into(),
398 ))),
399 "lower-case" => Ok(CallResult::Scalar(Value::Str(
400 string_arg(name, args, 0)?.to_lowercase().into(),
401 ))),
402 "identity" => match args {
403 [value] => Ok(CallResult::Scalar(value.clone())),
404 _ => Err(arity_error(name)),
405 },
406 _ => Err(QueryError::Unsupported(format!(
407 "unknown function or predicate {name}"
408 ))),
409 }
410}