Skip to main content

expr/
value.rs

1use crate::Rule;
2use indexmap::IndexMap;
3use log::trace;
4use pest::iterators::{Pair, Pairs};
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::fmt::{Display, Formatter};
9use std::ops::{Add, Deref, Sub};
10
11/// A time value together with the named timezone, when one is known.
12#[derive(Debug, Clone)]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14#[cfg_attr(feature = "serde", serde(transparent))]
15pub struct DateTimeValue {
16    value: chrono::DateTime<chrono::FixedOffset>,
17    #[cfg_attr(feature = "serde", serde(skip))]
18    timezone: Option<TimezoneValue>,
19    #[cfg_attr(feature = "serde", serde(skip))]
20    zone_name: Option<String>,
21}
22
23impl DateTimeValue {
24    pub fn fixed(value: chrono::DateTime<chrono::FixedOffset>) -> Self {
25        Self { value, timezone: None, zone_name: None }
26    }
27
28    pub fn zoned(
29        value: chrono::DateTime<chrono_tz::Tz>,
30        timezone: impl Into<TimezoneValue>,
31    ) -> Self {
32        Self {
33            value: value.fixed_offset(),
34            timezone: Some(timezone.into()),
35            zone_name: None,
36        }
37    }
38
39    pub(crate) fn with_timezone(&self, timezone: TimezoneValue) -> Self {
40        Self::zoned(self.value.with_timezone(&timezone.timezone()), timezone)
41    }
42
43    pub(crate) fn timezone(&self) -> Option<TimezoneValue> {
44        self.timezone
45    }
46
47    pub(crate) fn with_zone_name(mut self, zone_name: String) -> Self {
48        self.zone_name = Some(zone_name);
49        self
50    }
51
52    pub fn checked_add_signed(mut self, duration: chrono::Duration) -> Option<Self> {
53        self.value = self.value.checked_add_signed(duration)?;
54        self.refresh_timezone();
55        Some(self)
56    }
57
58    pub fn checked_sub_signed(mut self, duration: chrono::Duration) -> Option<Self> {
59        self.value = self.value.checked_sub_signed(duration)?;
60        self.refresh_timezone();
61        Some(self)
62    }
63
64    fn refresh_timezone(&mut self) {
65        if let Some(timezone) = self.timezone {
66            self.value = self.value.with_timezone(&timezone.timezone()).fixed_offset();
67        }
68    }
69
70    pub(crate) fn zone_name(&self) -> String {
71        if let Some(zone_name) = &self.zone_name {
72            return zone_name.clone();
73        }
74        if let Some(timezone) = self.timezone {
75            self.value
76                .with_timezone(&timezone.timezone())
77                .format("%Z")
78                .to_string()
79        } else if self.value.offset().local_minus_utc() == 0 {
80            "UTC".to_string()
81        } else {
82            self.value.format("%z").to_string()
83        }
84    }
85}
86
87impl From<chrono::DateTime<chrono::FixedOffset>> for DateTimeValue {
88    fn from(value: chrono::DateTime<chrono::FixedOffset>) -> Self {
89        Self::fixed(value)
90    }
91}
92
93impl Deref for DateTimeValue {
94    type Target = chrono::DateTime<chrono::FixedOffset>;
95
96    fn deref(&self) -> &Self::Target {
97        &self.value
98    }
99}
100
101impl PartialEq for DateTimeValue {
102    fn eq(&self, other: &Self) -> bool {
103        self.value == other.value
104    }
105}
106
107impl PartialOrd for DateTimeValue {
108    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
109        self.value.partial_cmp(&other.value)
110    }
111}
112
113impl Add<chrono::Duration> for DateTimeValue {
114    type Output = Self;
115
116    fn add(mut self, duration: chrono::Duration) -> Self::Output {
117        self.value += duration;
118        self.refresh_timezone();
119        self
120    }
121}
122
123impl Sub<chrono::Duration> for DateTimeValue {
124    type Output = Self;
125
126    fn sub(mut self, duration: chrono::Duration) -> Self::Output {
127        self.value -= duration;
128        self.refresh_timezone();
129        self
130    }
131}
132
133/// A timezone used by expr time values.
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
136#[cfg_attr(feature = "serde", serde(transparent))]
137pub struct TimezoneValue {
138    timezone: chrono_tz::Tz,
139    #[cfg_attr(feature = "serde", serde(skip))]
140    local: bool,
141}
142
143impl TimezoneValue {
144    /// Creates a named IANA timezone.
145    pub fn named(timezone: chrono_tz::Tz) -> Self {
146        Self { timezone, local: false }
147    }
148
149    /// Resolves the process-local timezone.
150    pub fn local() -> Result<Self, String> {
151        let timezone = match std::env::var_os("TZ") {
152            Some(timezone) => timezone_from_env(&timezone),
153            None => {
154                iana_time_zone::get_timezone()
155                    .map_err(|error| error.to_string())?
156                    .parse::<chrono_tz::Tz>()
157                    .map_err(|error| error.to_string())
158            }?,
159        };
160        Ok(Self { timezone, local: true })
161    }
162
163    #[cfg(test)]
164    pub(crate) fn local_with_timezone(timezone: chrono_tz::Tz) -> Self {
165        Self { timezone, local: true }
166    }
167
168    /// Returns the IANA timezone that supplies this location's offset rules.
169    pub fn timezone(self) -> chrono_tz::Tz {
170        self.timezone
171    }
172
173    /// Returns the Go-compatible location name.
174    pub fn name(self) -> &'static str {
175        if self.local {
176            "Local"
177        } else {
178            self.timezone.name()
179        }
180    }
181
182    /// Returns whether this is the process-local location.
183    pub fn is_local(self) -> bool {
184        self.local
185    }
186}
187
188fn timezone_from_env(value: &std::ffi::OsStr) -> chrono_tz::Tz {
189    value
190        .to_str()
191        .and_then(|timezone| {
192            timezone
193                .strip_prefix(':')
194                .unwrap_or(timezone)
195                .parse::<chrono_tz::Tz>()
196                .ok()
197        })
198        .unwrap_or(chrono_tz::UTC)
199}
200
201impl From<chrono_tz::Tz> for TimezoneValue {
202    fn from(timezone: chrono_tz::Tz) -> Self {
203        Self::named(timezone)
204    }
205}
206
207impl Display for TimezoneValue {
208    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
209        f.write_str(self.name())
210    }
211}
212
213#[cfg(test)]
214mod timezone_tests {
215    use super::*;
216
217    #[test]
218    fn empty_and_invalid_tz_environment_values_use_utc() {
219        assert_eq!(timezone_from_env(std::ffi::OsStr::new("")), chrono_tz::UTC);
220        assert_eq!(
221            timezone_from_env(std::ffi::OsStr::new("not-a-timezone")),
222            chrono_tz::UTC
223        );
224    }
225
226    #[test]
227    fn tz_environment_value_accepts_go_colon_prefix() {
228        assert_eq!(
229            timezone_from_env(std::ffi::OsStr::new(":America/New_York")),
230            chrono_tz::America::New_York
231        );
232    }
233}
234
235impl Sub for DateTimeValue {
236    type Output = chrono::Duration;
237
238    fn sub(self, other: Self) -> Self::Output {
239        self.value - other.value
240    }
241}
242
243/// Represents a data value as input or output to an expr program
244#[derive(Debug, Default, Clone, PartialEq)]
245#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
246#[cfg_attr(feature = "serde", serde(untagged))]
247pub enum Value {
248    Integer(i64),
249    Bool(bool),
250    Float(f64),
251    #[default]
252    Nil,
253    String(String),
254    DateTime(DateTimeValue),
255    Duration(i64),
256    Timezone(TimezoneValue),
257    Month(u32),
258    Weekday(u32),
259    Array(Vec<Value>),
260    // Keep Bytes after Array so untagged serde treats JSON integer arrays as arrays.
261    Bytes(Vec<u8>),
262    Map(IndexMap<String, Value>),
263    /// A map whose keys are arbitrary expr values.
264    KeyedMap(Vec<(Value, Value)>),
265}
266
267impl Value {
268    pub(crate) fn parse_integer(value: &str) -> std::result::Result<i64, std::num::ParseIntError> {
269        let value = value.replace('_', "");
270        let (digits, radix) = match value.as_bytes() {
271            [b'0', b'x' | b'X', ..] => (&value[2..], 16),
272            [b'0', b'o' | b'O', ..] => (&value[2..], 8),
273            [b'0', b'b' | b'B', ..] => (&value[2..], 2),
274            _ => (value.as_str(), 10),
275        };
276        i64::from_str_radix(digits, radix)
277    }
278
279    pub(crate) fn parse_float(value: &str) -> std::result::Result<f64, std::num::ParseFloatError> {
280        value.replace('_', "").parse()
281    }
282
283    pub fn as_bool(&self) -> Option<bool> {
284        match self {
285            Value::Bool(b) => Some(*b),
286            _ => None,
287        }
288    }
289
290    pub fn as_integer(&self) -> Option<i64> {
291        match self {
292            Value::Integer(n) => Some(*n),
293            _ => None,
294        }
295    }
296
297    pub fn as_float(&self) -> Option<f64> {
298        match self {
299            Value::Float(f) => Some(*f),
300            _ => None,
301        }
302    }
303
304    pub fn as_string(&self) -> Option<&str> {
305        match self {
306            Value::String(s) => Some(s),
307            _ => None,
308        }
309    }
310
311    pub fn as_bytes(&self) -> Option<&[u8]> {
312        match self {
313            Value::Bytes(bytes) => Some(bytes),
314            _ => None,
315        }
316    }
317
318    pub fn as_datetime(&self) -> Option<&chrono::DateTime<chrono::FixedOffset>> {
319        match self {
320            Value::DateTime(value) => Some(&value.value),
321            _ => None,
322        }
323    }
324
325    pub fn as_duration(&self) -> Option<i64> {
326        match self {
327            Value::Duration(value) => Some(*value),
328            _ => None,
329        }
330    }
331
332    pub fn as_array(&self) -> Option<&[Value]> {
333        match self {
334            Value::Array(a) => Some(a),
335            _ => None,
336        }
337    }
338
339    pub fn as_map(&self) -> Option<&IndexMap<String, Value>> {
340        match self {
341            Value::Map(m) => Some(m),
342            _ => None,
343        }
344    }
345
346    pub fn as_keyed_map(&self) -> Option<&[(Value, Value)]> {
347        match self {
348            Value::KeyedMap(m) => Some(m),
349            _ => None,
350        }
351    }
352
353    pub fn is_nil(&self) -> bool {
354        matches!(self, Value::Nil)
355    }
356}
357
358impl<K, V> FromIterator<(K, V)> for Value
359where
360    K: Into<String>, 
361    V: Into<Value>,
362{
363    fn from_iter<I>(iter: I) -> Self
364    where I: IntoIterator<Item = (K, V)> {
365        Value::Map(iter.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
366    }
367}
368
369impl AsRef<Value> for Value {
370    fn as_ref(&self) -> &Value {
371        self
372    }
373}
374
375impl From<i64> for Value {
376    fn from(n: i64) -> Self {
377        Value::Integer(n)
378    }
379}
380
381impl From<i32> for Value {
382    fn from(n: i32) -> Self {
383        Value::Integer(n as i64)
384    }
385}
386
387impl From<usize> for Value {
388    fn from(n: usize) -> Self {
389        Value::Integer(n as i64)
390    }
391}
392
393impl From<f64> for Value {
394    fn from(f: f64) -> Self {
395        Value::Float(f)
396    }
397}
398
399impl From<bool> for Value {
400    fn from(b: bool) -> Self {
401        Value::Bool(b)
402    }
403}
404
405impl From<String> for Value {
406    fn from(s: String) -> Self {
407        Value::String(s)
408    }
409}
410
411impl From<&String> for Value {
412    fn from(s: &String) -> Self {
413        s.to_string().into()
414    }
415}
416
417impl From<&str> for Value {
418    fn from(s: &str) -> Self {
419        s.to_string().into()
420    }
421}
422
423impl<V: Into<Value>> From<Vec<V>> for Value {
424    fn from(a: Vec<V>) -> Self {
425        Value::Array(a.into_iter().map(|v| v.into()).collect())
426    }
427}
428
429impl From<IndexMap<String, Value>> for Value {
430    fn from(m: IndexMap<String, Value>) -> Self {
431        Value::Map(m)
432    }
433}
434
435impl Display for Value {
436    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
437        match self {
438            Value::Integer(n) => write!(f, "{n}"),
439            Value::Float(n) => write!(f, "{n}"),
440            Value::Bool(b) => write!(f, "{b}"),
441            Value::Nil => write!(f, "nil"),
442            Value::String(s) => write!(
443                f,
444                r#""{}""#,
445                s.replace("\\", "\\\\")
446                    .replace("\n", "\\n")
447                    .replace("\r", "\\r")
448                    .replace("\t", "\\t")
449                    .replace("\"", "\\\"")
450            ),
451            Value::Bytes(bytes) => write!(
452                f,
453                "[{}]",
454                bytes
455                    .iter()
456                    .map(u8::to_string)
457                    .collect::<Vec<String>>()
458                    .join(" ")
459            ),
460            Value::DateTime(value) => write!(f, "{}", value.to_rfc3339()),
461            Value::Duration(value) => write!(f, "{value}ns"),
462            Value::Timezone(value) => write!(f, "{value}"),
463            Value::Month(value) | Value::Weekday(value) => write!(f, "{value}"),
464            Value::Array(a) => write!(
465                f,
466                "[{}]",
467                a.iter()
468                    .map(|v| v.to_string())
469                    .collect::<Vec<String>>()
470                    .join(", ")
471            ),
472            Value::Map(m) => write!(
473                f,
474                "{{{}}}",
475                m.iter()
476                    .map(|(k, v)| format!("{}: {}", k, v))
477                    .collect::<Vec<String>>()
478                    .join(", ")
479            ),
480            Value::KeyedMap(m) => write!(
481                f,
482                "{{{}}}",
483                m.iter()
484                    .map(|(k, v)| format!("{}: {}", k, v))
485                    .collect::<Vec<String>>()
486                    .join(", ")
487            ),
488        }
489    }
490}
491
492impl From<Pairs<'_, Rule>> for Value {
493    fn from(mut pairs: Pairs<Rule>) -> Self {
494        pairs.next().unwrap().into()
495    }
496}
497
498impl From<Pair<'_, Rule>> for Value {
499    fn from(pair: Pair<Rule>) -> Self {
500        trace!("{:?} = {}", pair.as_rule(), pair.as_str());
501        match pair.as_rule() {
502            Rule::value => pair.into_inner().into(),
503            Rule::nil => Value::Nil,
504            Rule::bool => Value::Bool(pair.as_str().parse().unwrap()),
505            Rule::int => {
506                Value::Integer(Value::parse_integer(pair.as_str()).expect("literal validated"))
507            }
508            Rule::decimal => {
509                Value::Float(Value::parse_float(pair.as_str()).expect("literal validated"))
510            }
511            Rule::bytes => Value::Bytes(parse_bytes_literal(pair.as_str())),
512            Rule::string_multiline => pair.into_inner().as_str().into(),
513            Rule::string => pair
514                .into_inner()
515                .as_str()
516                .replace("\\\\", "\\")
517                .replace("\\n", "\n")
518                .replace("\\r", "\r")
519                .replace("\\t", "\t")
520                .replace("\\\"", "\"")
521                .into(),
522            // Rule::operation => {
523            //     let mut pairs = pair.into_inner();
524            //     let operator = pairs.next().unwrap().into();
525            //     let left = Box::new(pairs.next().unwrap().into());
526            //     let right = Box::new(pairs.next().unwrap().into());
527            //     Node::Operation {
528            //         operator,
529            //         left,
530            //         right,
531            //     }
532            // }
533            rule => unreachable!("Unexpected rule: {rule:?} {}", pair.as_str()),
534        }
535    }
536}
537
538fn parse_bytes_literal(literal: &str) -> Vec<u8> {
539    let mut chars = literal[2..literal.len() - 1].chars();
540    let mut bytes = Vec::new();
541    while let Some(character) = chars.next() {
542        if character != '\\' {
543            let mut encoded = [0; 4];
544            bytes.extend_from_slice(character.encode_utf8(&mut encoded).as_bytes());
545            continue;
546        }
547
548        let escape = chars.next().expect("byte escape validated by grammar");
549        match escape {
550            'a' => bytes.push(7),
551            'b' => bytes.push(8),
552            'f' => bytes.push(12),
553            'n' => bytes.push(b'\n'),
554            'r' => bytes.push(b'\r'),
555            't' => bytes.push(b'\t'),
556            'v' => bytes.push(11),
557            '\\' | '\'' | '"' => bytes.push(escape as u8),
558            'x' => {
559                let digits = [
560                    chars.next().expect("hex escape validated by grammar"),
561                    chars.next().expect("hex escape validated by grammar"),
562                ];
563                bytes.push(
564                    u8::from_str_radix(&digits.iter().collect::<String>(), 16)
565                        .expect("hex escape validated by grammar"),
566                );
567            }
568            digit @ '0'..='7' => {
569                let digits = [
570                    digit,
571                    chars.next().expect("octal escape validated by grammar"),
572                    chars.next().expect("octal escape validated by grammar"),
573                ];
574                bytes.push(
575                    u8::from_str_radix(&digits.iter().collect::<String>(), 8)
576                        .expect("octal escape validated by grammar"),
577                );
578            }
579            _ => unreachable!("byte escape validated by grammar"),
580        }
581    }
582    bytes
583}