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