1use sparrowdb_common::{Error, Result};
10
11use crate::types::Value;
12
13pub fn dispatch_function(name: &str, args: Vec<Value>) -> Result<Value> {
20 match name.to_lowercase().as_str() {
21 "toupper" => fn_to_upper(args),
23 "tolower" => fn_to_lower(args),
24 "trim" => fn_trim(args),
25 "ltrim" => fn_ltrim(args),
26 "rtrim" => fn_rtrim(args),
27 "split" => fn_split(args),
28 "substring" => fn_substring(args),
29 "size" => fn_size(args),
30 "startswith" => fn_starts_with(args),
31 "endswith" => fn_ends_with(args),
32 "contains" => fn_contains(args),
33 "replace" => fn_replace(args),
34
35 "abs" => fn_abs(args),
37 "ceil" => fn_ceil(args),
38 "floor" => fn_floor(args),
39 "round" => fn_round(args),
40 "sqrt" => fn_sqrt(args),
41 "log" => fn_log(args),
42 "log10" => fn_log10(args),
43 "exp" => fn_exp(args),
44 "sign" => fn_sign(args),
45 "rand" => fn_rand(args),
46
47 "range" => fn_range(args),
49 "head" => fn_head(args),
50 "tail" => fn_tail(args),
51 "last" => fn_last(args),
52 "reverse" => fn_reverse(args),
53 "sort" => fn_sort(args),
54 "distinct" => fn_distinct(args),
55 "reduce" => Err(Error::InvalidArgument(
56 "reduce() must be handled by the evaluator (it requires a lambda)".into(),
57 )),
58
59 "tostring" => fn_to_string(args),
61 "tointeger" => fn_to_integer(args),
62 "tofloat" => fn_to_float(args),
63 "toboolean" => fn_to_boolean(args),
64 "type" => fn_type(args),
65 "labels" => fn_labels(args),
66 "keys" => fn_keys(args),
67 "properties" => fn_properties(args),
68 "id" => fn_id(args),
69 "coalesce" => fn_coalesce(args),
70 "isnull" => fn_is_null(args),
71 "isnotnull" => fn_is_not_null(args),
72
73 "collect" | "count" | "sum" | "avg" | "min" | "max" => Err(Error::InvalidArgument(
75 format!("{name}() is an aggregate function and cannot be used as a scalar expression"),
76 )),
77
78 "datetime" => fn_datetime(args),
80 "timestamp" => fn_datetime(args), "date" => fn_date(args),
82 "duration" => fn_duration(args),
83
84 other => Err(Error::InvalidArgument(format!("unknown function: {other}"))),
85 }
86}
87
88fn expect_arity(name: &str, args: &[Value], expected: usize) -> Result<()> {
91 if args.len() != expected {
92 Err(Error::InvalidArgument(format!(
93 "{name}() expects {expected} argument(s), got {}",
94 args.len()
95 )))
96 } else {
97 Ok(())
98 }
99}
100
101fn expect_min_arity(name: &str, args: &[Value], min: usize) -> Result<()> {
102 if args.len() < min {
103 Err(Error::InvalidArgument(format!(
104 "{name}() expects at least {min} argument(s), got {}",
105 args.len()
106 )))
107 } else {
108 Ok(())
109 }
110}
111
112fn as_string<'a>(name: &str, v: &'a Value) -> Result<&'a str> {
113 match v {
114 Value::String(s) => Ok(s.as_str()),
115 Value::Null => Err(Error::InvalidArgument(format!(
116 "{name}(): argument is null"
117 ))),
118 other => Err(Error::InvalidArgument(format!(
119 "{name}(): expected string, got {other}"
120 ))),
121 }
122}
123
124fn as_int(name: &str, v: &Value) -> Result<i64> {
125 match v {
126 Value::Int64(n) => Ok(*n),
127 Value::Float64(f) => Ok(*f as i64),
128 Value::Null => Err(Error::InvalidArgument(format!(
129 "{name}(): argument is null"
130 ))),
131 other => Err(Error::InvalidArgument(format!(
132 "{name}(): expected integer, got {other}"
133 ))),
134 }
135}
136
137fn as_float(name: &str, v: &Value) -> Result<f64> {
138 match v {
139 Value::Float64(f) => Ok(*f),
140 Value::Int64(n) => Ok(*n as f64),
141 Value::Null => Err(Error::InvalidArgument(format!(
142 "{name}(): argument is null"
143 ))),
144 other => Err(Error::InvalidArgument(format!(
145 "{name}(): expected numeric, got {other}"
146 ))),
147 }
148}
149
150fn fn_to_upper(args: Vec<Value>) -> Result<Value> {
153 expect_arity("toUpper", &args, 1)?;
154 if matches!(args[0], Value::Null) {
155 return Ok(Value::Null);
156 }
157 let s = as_string("toUpper", &args[0])?;
158 Ok(Value::String(s.to_uppercase()))
159}
160
161fn fn_to_lower(args: Vec<Value>) -> Result<Value> {
162 expect_arity("toLower", &args, 1)?;
163 if matches!(args[0], Value::Null) {
164 return Ok(Value::Null);
165 }
166 let s = as_string("toLower", &args[0])?;
167 Ok(Value::String(s.to_lowercase()))
168}
169
170fn fn_trim(args: Vec<Value>) -> Result<Value> {
171 expect_arity("trim", &args, 1)?;
172 if matches!(args[0], Value::Null) {
173 return Ok(Value::Null);
174 }
175 let s = as_string("trim", &args[0])?;
176 Ok(Value::String(s.trim().to_string()))
177}
178
179fn fn_ltrim(args: Vec<Value>) -> Result<Value> {
180 expect_arity("ltrim", &args, 1)?;
181 if matches!(args[0], Value::Null) {
182 return Ok(Value::Null);
183 }
184 let s = as_string("ltrim", &args[0])?;
185 Ok(Value::String(s.trim_start().to_string()))
186}
187
188fn fn_rtrim(args: Vec<Value>) -> Result<Value> {
189 expect_arity("rtrim", &args, 1)?;
190 if matches!(args[0], Value::Null) {
191 return Ok(Value::Null);
192 }
193 let s = as_string("rtrim", &args[0])?;
194 Ok(Value::String(s.trim_end().to_string()))
195}
196
197fn fn_split(args: Vec<Value>) -> Result<Value> {
203 expect_arity("split", &args, 2)?;
204 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
205 return Ok(Value::Null);
206 }
207 let s = as_string("split", &args[0])?;
208 let delim = as_string("split", &args[1])?;
209 let count = s.split(delim).count() as i64;
212 Ok(Value::Int64(count))
213}
214
215fn fn_substring(args: Vec<Value>) -> Result<Value> {
217 expect_min_arity("substring", &args, 2)?;
218 if matches!(args[0], Value::Null) {
219 return Ok(Value::Null);
220 }
221 let s = as_string("substring", &args[0])?;
222 let start = as_int("substring", &args[1])?;
223 let chars: Vec<char> = s.chars().collect();
224 let len = chars.len() as i64;
225
226 let start = start.max(0).min(len) as usize;
227
228 let result: String = if args.len() >= 3 {
229 let take = as_int("substring", &args[2])?.max(0) as usize;
230 chars[start..].iter().take(take).collect()
231 } else {
232 chars[start..].iter().collect()
233 };
234
235 Ok(Value::String(result))
236}
237
238fn fn_size(args: Vec<Value>) -> Result<Value> {
240 expect_arity("size", &args, 1)?;
241 match &args[0] {
242 Value::Null => Ok(Value::Null),
243 Value::String(s) => Ok(Value::Int64(s.chars().count() as i64)),
244 other => Err(Error::InvalidArgument(format!(
245 "size(): expected string or null, got {other}"
246 ))),
247 }
248}
249
250fn fn_starts_with(args: Vec<Value>) -> Result<Value> {
251 expect_arity("startsWith", &args, 2)?;
252 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
253 return Ok(Value::Null);
254 }
255 let s = as_string("startsWith", &args[0])?;
256 let prefix = as_string("startsWith", &args[1])?;
257 Ok(Value::Bool(s.starts_with(prefix)))
258}
259
260fn fn_ends_with(args: Vec<Value>) -> Result<Value> {
261 expect_arity("endsWith", &args, 2)?;
262 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
263 return Ok(Value::Null);
264 }
265 let s = as_string("endsWith", &args[0])?;
266 let suffix = as_string("endsWith", &args[1])?;
267 Ok(Value::Bool(s.ends_with(suffix)))
268}
269
270fn fn_contains(args: Vec<Value>) -> Result<Value> {
271 expect_arity("contains", &args, 2)?;
272 if matches!(args[0], Value::Null) || matches!(args[1], Value::Null) {
273 return Ok(Value::Null);
274 }
275 let s = as_string("contains", &args[0])?;
276 let needle = as_string("contains", &args[1])?;
277 Ok(Value::Bool(s.contains(needle)))
278}
279
280fn fn_replace(args: Vec<Value>) -> Result<Value> {
281 expect_arity("replace", &args, 3)?;
282 if matches!(args[0], Value::Null) {
283 return Ok(Value::Null);
284 }
285 let s = as_string("replace", &args[0])?;
286 let from = as_string("replace", &args[1])?;
287 let to = as_string("replace", &args[2])?;
288 Ok(Value::String(s.replace(from, to)))
289}
290
291fn fn_abs(args: Vec<Value>) -> Result<Value> {
294 expect_arity("abs", &args, 1)?;
295 match &args[0] {
296 Value::Null => Ok(Value::Null),
297 Value::Int64(n) => Ok(Value::Int64(n.abs())),
298 Value::Float64(f) => Ok(Value::Float64(f.abs())),
299 other => Err(Error::InvalidArgument(format!(
300 "abs(): expected numeric, got {other}"
301 ))),
302 }
303}
304
305fn fn_ceil(args: Vec<Value>) -> Result<Value> {
306 expect_arity("ceil", &args, 1)?;
307 if matches!(args[0], Value::Null) {
308 return Ok(Value::Null);
309 }
310 let f = as_float("ceil", &args[0])?;
311 Ok(Value::Float64(f.ceil()))
312}
313
314fn fn_floor(args: Vec<Value>) -> Result<Value> {
315 expect_arity("floor", &args, 1)?;
316 if matches!(args[0], Value::Null) {
317 return Ok(Value::Null);
318 }
319 let f = as_float("floor", &args[0])?;
320 Ok(Value::Float64(f.floor()))
321}
322
323fn fn_round(args: Vec<Value>) -> Result<Value> {
324 expect_arity("round", &args, 1)?;
325 if matches!(args[0], Value::Null) {
326 return Ok(Value::Null);
327 }
328 let f = as_float("round", &args[0])?;
329 Ok(Value::Float64(f.round()))
330}
331
332fn fn_sqrt(args: Vec<Value>) -> Result<Value> {
333 expect_arity("sqrt", &args, 1)?;
334 if matches!(args[0], Value::Null) {
335 return Ok(Value::Null);
336 }
337 let f = as_float("sqrt", &args[0])?;
338 Ok(Value::Float64(f.sqrt()))
339}
340
341fn fn_log(args: Vec<Value>) -> Result<Value> {
343 expect_arity("log", &args, 1)?;
344 if matches!(args[0], Value::Null) {
345 return Ok(Value::Null);
346 }
347 let f = as_float("log", &args[0])?;
348 Ok(Value::Float64(f.ln()))
349}
350
351fn fn_log10(args: Vec<Value>) -> Result<Value> {
352 expect_arity("log10", &args, 1)?;
353 if matches!(args[0], Value::Null) {
354 return Ok(Value::Null);
355 }
356 let f = as_float("log10", &args[0])?;
357 Ok(Value::Float64(f.log10()))
358}
359
360fn fn_exp(args: Vec<Value>) -> Result<Value> {
361 expect_arity("exp", &args, 1)?;
362 if matches!(args[0], Value::Null) {
363 return Ok(Value::Null);
364 }
365 let f = as_float("exp", &args[0])?;
366 Ok(Value::Float64(f.exp()))
367}
368
369fn fn_sign(args: Vec<Value>) -> Result<Value> {
370 expect_arity("sign", &args, 1)?;
371 match &args[0] {
372 Value::Null => Ok(Value::Null),
373 Value::Int64(n) => Ok(Value::Int64(n.signum())),
374 Value::Float64(f) => {
375 let s = if *f > 0.0 {
376 1i64
377 } else if *f < 0.0 {
378 -1
379 } else {
380 0
381 };
382 Ok(Value::Int64(s))
383 }
384 other => Err(Error::InvalidArgument(format!(
385 "sign(): expected numeric, got {other}"
386 ))),
387 }
388}
389
390fn fn_rand(args: Vec<Value>) -> Result<Value> {
392 expect_arity("rand", &args, 0)?;
393 use std::time::{SystemTime, UNIX_EPOCH};
395 let seed = SystemTime::now()
396 .duration_since(UNIX_EPOCH)
397 .map(|d| d.subsec_nanos())
398 .unwrap_or(42);
399 let v = (seed as u64)
401 .wrapping_mul(6364136223846793005)
402 .wrapping_add(1442695040888963407);
403 let f = (v >> 11) as f64 / (1u64 << 53) as f64;
404 Ok(Value::Float64(f))
405}
406
407fn fn_range(args: Vec<Value>) -> Result<Value> {
413 expect_min_arity("range", &args, 2)?;
414 let start = as_int("range", &args[0])?;
415 let end = as_int("range", &args[1])?;
416 let step: i64 = if args.len() >= 3 {
417 as_int("range", &args[2])?
418 } else {
419 1
420 };
421 if step == 0 {
422 return Err(Error::InvalidArgument(
423 "range(): step must not be zero".into(),
424 ));
425 }
426 let mut values = Vec::new();
427 if step > 0 {
428 let mut i = start;
429 while i <= end {
430 values.push(Value::Int64(i));
431 i += step;
432 }
433 } else {
434 let mut i = start;
435 while i >= end {
436 values.push(Value::Int64(i));
437 i += step;
438 }
439 }
440 Ok(Value::List(values))
441}
442
443fn fn_head(args: Vec<Value>) -> Result<Value> {
450 expect_arity("head", &args, 1)?;
451 match &args[0] {
453 Value::Null => Ok(Value::Null),
454 v => Ok(v.clone()),
456 }
457}
458
459fn fn_tail(args: Vec<Value>) -> Result<Value> {
460 expect_arity("tail", &args, 1)?;
461 Ok(Value::Null)
463}
464
465fn fn_last(args: Vec<Value>) -> Result<Value> {
466 expect_arity("last", &args, 1)?;
467 match &args[0] {
468 Value::Null => Ok(Value::Null),
469 v => Ok(v.clone()),
470 }
471}
472
473fn fn_reverse(args: Vec<Value>) -> Result<Value> {
474 expect_arity("reverse", &args, 1)?;
475 match &args[0] {
476 Value::Null => Ok(Value::Null),
477 Value::String(s) => Ok(Value::String(s.chars().rev().collect())),
478 v => Ok(v.clone()),
480 }
481}
482
483fn fn_sort(args: Vec<Value>) -> Result<Value> {
484 expect_arity("sort", &args, 1)?;
485 Ok(args.into_iter().next().unwrap_or(Value::Null))
487}
488
489fn fn_distinct(args: Vec<Value>) -> Result<Value> {
490 expect_arity("distinct", &args, 1)?;
491 Ok(args.into_iter().next().unwrap_or(Value::Null))
492}
493
494fn fn_to_string(args: Vec<Value>) -> Result<Value> {
497 expect_arity("toString", &args, 1)?;
498 match &args[0] {
499 Value::Null => Ok(Value::Null),
500 Value::String(s) => Ok(Value::String(s.clone())),
501 Value::Int64(n) => Ok(Value::String(n.to_string())),
502 Value::Float64(f) => Ok(Value::String(f.to_string())),
503 Value::Bool(b) => Ok(Value::String(b.to_string())),
504 Value::NodeRef(id) => Ok(Value::String(format!("node({})", id.0))),
505 Value::EdgeRef(id) => Ok(Value::String(format!("edge({})", id.0))),
506 Value::List(items) => Ok(Value::String(format!(
507 "{}",
508 crate::types::Value::List(items.clone())
509 ))),
510 Value::Map(entries) => Ok(Value::String(format!(
511 "{}",
512 crate::types::Value::Map(entries.clone())
513 ))),
514 }
515}
516
517fn fn_to_integer(args: Vec<Value>) -> Result<Value> {
518 expect_arity("toInteger", &args, 1)?;
519 match &args[0] {
520 Value::Null => Ok(Value::Null),
521 Value::Int64(n) => Ok(Value::Int64(*n)),
522 Value::Float64(f) => Ok(Value::Int64(*f as i64)),
523 Value::Bool(b) => Ok(Value::Int64(if *b { 1 } else { 0 })),
524 Value::String(s) => {
525 if let Ok(n) = s.trim().parse::<i64>() {
527 Ok(Value::Int64(n))
528 } else if let Ok(f) = s.trim().parse::<f64>() {
529 Ok(Value::Int64(f as i64))
530 } else {
531 Ok(Value::Null) }
533 }
534 _ => Ok(Value::Null),
535 }
536}
537
538fn fn_to_float(args: Vec<Value>) -> Result<Value> {
539 expect_arity("toFloat", &args, 1)?;
540 match &args[0] {
541 Value::Null => Ok(Value::Null),
542 Value::Float64(f) => Ok(Value::Float64(*f)),
543 Value::Int64(n) => Ok(Value::Float64(*n as f64)),
544 Value::Bool(b) => Ok(Value::Float64(if *b { 1.0 } else { 0.0 })),
545 Value::String(s) => {
546 if let Ok(f) = s.trim().parse::<f64>() {
547 Ok(Value::Float64(f))
548 } else {
549 Ok(Value::Null)
550 }
551 }
552 _ => Ok(Value::Null),
553 }
554}
555
556fn fn_to_boolean(args: Vec<Value>) -> Result<Value> {
557 expect_arity("toBoolean", &args, 1)?;
558 match &args[0] {
559 Value::Null => Ok(Value::Null),
560 Value::Bool(b) => Ok(Value::Bool(*b)),
561 Value::Int64(n) => Ok(Value::Bool(*n != 0)),
562 Value::String(s) => match s.to_lowercase().as_str() {
563 "true" => Ok(Value::Bool(true)),
564 "false" => Ok(Value::Bool(false)),
565 _ => Ok(Value::Null),
566 },
567 _ => Ok(Value::Null),
568 }
569}
570
571fn fn_type(args: Vec<Value>) -> Result<Value> {
576 expect_arity("type", &args, 1)?;
577 match &args[0] {
578 Value::Null => Ok(Value::Null),
579 Value::EdgeRef(_) => {
580 Ok(Value::String("UNKNOWN".into()))
584 }
585 other => Err(Error::InvalidArgument(format!(
586 "type(): expected relationship, got {other}"
587 ))),
588 }
589}
590
591fn fn_labels(args: Vec<Value>) -> Result<Value> {
593 expect_arity("labels", &args, 1)?;
594 match &args[0] {
595 Value::Null => Ok(Value::Null),
596 Value::NodeRef(_) => {
597 Ok(Value::String("[]".into()))
599 }
600 other => Err(Error::InvalidArgument(format!(
601 "labels(): expected node, got {other}"
602 ))),
603 }
604}
605
606fn fn_keys(args: Vec<Value>) -> Result<Value> {
608 expect_arity("keys", &args, 1)?;
609 match &args[0] {
610 Value::Null => Ok(Value::Null),
611 Value::NodeRef(_) | Value::EdgeRef(_) => Ok(Value::String("[]".into())),
612 other => Err(Error::InvalidArgument(format!(
613 "keys(): expected node or relationship, got {other}"
614 ))),
615 }
616}
617
618fn fn_properties(args: Vec<Value>) -> Result<Value> {
620 expect_arity("properties", &args, 1)?;
621 match &args[0] {
622 Value::Null => Ok(Value::Null),
623 Value::NodeRef(_) | Value::EdgeRef(_) => Ok(Value::String("{}".into())),
624 other => Err(Error::InvalidArgument(format!(
625 "properties(): expected node or relationship, got {other}"
626 ))),
627 }
628}
629
630fn fn_id(args: Vec<Value>) -> Result<Value> {
632 expect_arity("id", &args, 1)?;
633 match &args[0] {
634 Value::Null => Ok(Value::Null),
635 Value::NodeRef(id) => Ok(Value::Int64(id.0 as i64)),
636 Value::EdgeRef(id) => Ok(Value::Int64(id.0 as i64)),
637 other => Err(Error::InvalidArgument(format!(
638 "id(): expected node or relationship, got {other}"
639 ))),
640 }
641}
642
643fn fn_coalesce(args: Vec<Value>) -> Result<Value> {
645 for v in args {
646 if !matches!(v, Value::Null) {
647 return Ok(v);
648 }
649 }
650 Ok(Value::Null)
651}
652
653fn fn_is_null(args: Vec<Value>) -> Result<Value> {
654 expect_arity("isNull", &args, 1)?;
655 Ok(Value::Bool(matches!(args[0], Value::Null)))
656}
657
658fn fn_is_not_null(args: Vec<Value>) -> Result<Value> {
659 expect_arity("isNotNull", &args, 1)?;
660 Ok(Value::Bool(!matches!(args[0], Value::Null)))
661}
662
663fn fn_datetime(args: Vec<Value>) -> Result<Value> {
670 expect_arity("datetime", &args, 0)?;
671 use std::time::{SystemTime, UNIX_EPOCH};
672 let millis = SystemTime::now()
673 .duration_since(UNIX_EPOCH)
674 .map(|d| d.as_millis() as i64)
675 .unwrap_or(0);
676 Ok(Value::Int64(millis))
677}
678
679fn fn_date(args: Vec<Value>) -> Result<Value> {
684 expect_arity("date", &args, 0)?;
685 use std::time::{SystemTime, UNIX_EPOCH};
686 let secs = SystemTime::now()
687 .duration_since(UNIX_EPOCH)
688 .map(|d| d.as_secs() as i64)
689 .unwrap_or(0);
690 Ok(Value::Int64(secs / 86_400))
691}
692
693fn fn_duration(args: Vec<Value>) -> Result<Value> {
703 expect_arity("duration", &args, 1)?;
704 if matches!(args[0], Value::Null) {
705 return Ok(Value::Null);
706 }
707 let s = as_string("duration", &args[0])?;
708 let millis = parse_iso_duration(s).ok_or_else(|| {
709 Error::InvalidArgument(format!("duration(): cannot parse ISO-8601 duration: {s}"))
710 })?;
711 Ok(Value::Int64(millis))
712}
713
714fn parse_iso_duration(s: &str) -> Option<i64> {
716 let s = s.trim();
717 let s = if s.starts_with(['P', 'p']) {
719 &s[1..]
720 } else {
721 return None;
722 };
723
724 const MS_PER_SEC: i64 = 1_000;
725 const MS_PER_MIN: i64 = 60 * MS_PER_SEC;
726 const MS_PER_HOUR: i64 = 60 * MS_PER_MIN;
727 const MS_PER_DAY: i64 = 24 * MS_PER_HOUR;
728 const MS_PER_WEEK: i64 = 7 * MS_PER_DAY;
729 const MS_PER_MONTH: i64 = 30 * MS_PER_DAY;
730 const MS_PER_YEAR: i64 = 365 * MS_PER_DAY;
731
732 let mut total: i64 = 0;
733 let mut in_time = false;
734 let mut buf = String::new();
735
736 for ch in s.chars() {
737 match ch {
738 'T' | 't' => {
739 in_time = true;
740 buf.clear();
741 }
742 '0'..='9' | '.' => buf.push(ch),
743 'Y' | 'y' if !in_time => {
744 let n: i64 = buf.parse().ok()?;
745 total += n * MS_PER_YEAR;
746 buf.clear();
747 }
748 'M' | 'm' if !in_time => {
749 let n: i64 = buf.parse().ok()?;
750 total += n * MS_PER_MONTH;
751 buf.clear();
752 }
753 'W' | 'w' if !in_time => {
754 let n: i64 = buf.parse().ok()?;
755 total += n * MS_PER_WEEK;
756 buf.clear();
757 }
758 'D' | 'd' if !in_time => {
759 let n: i64 = buf.parse().ok()?;
760 total += n * MS_PER_DAY;
761 buf.clear();
762 }
763 'H' | 'h' if in_time => {
764 let n: i64 = buf.parse().ok()?;
765 total += n * MS_PER_HOUR;
766 buf.clear();
767 }
768 'M' | 'm' if in_time => {
769 let n: i64 = buf.parse().ok()?;
770 total += n * MS_PER_MIN;
771 buf.clear();
772 }
773 'S' | 's' if in_time => {
774 let f: f64 = buf.parse().ok()?;
776 total += (f * MS_PER_SEC as f64) as i64;
777 buf.clear();
778 }
779 _ => return None,
780 }
781 }
782
783 Some(total)
784}