Skip to main content

plc_ast/
literals.rs

1use std::fmt::{Debug, Formatter};
2
3use chrono::NaiveDate;
4use serde::{Deserialize, Serialize};
5
6use crate::ast::AstNode;
7use derive_more::TryInto;
8
9macro_rules! impl_getters {
10    ($type:ty, [$($name:ident),+], [$($out:ty),+]) => {
11        $(impl $type {
12            pub fn $name(&self) -> $out {
13                self.$name
14            }
15        })*
16    }
17}
18
19#[derive(Clone, PartialEq, TryInto, Serialize, Deserialize)]
20#[serde(bound(deserialize = "'de: 'static"))]
21#[try_into(ref)]
22pub enum AstLiteral {
23    /// a null literal used to initialize pointers
24    Null,
25    /// a literal that represents a whole number (e.g. 7)
26    Integer(i128),
27    /// a literal that represents a date
28    Date(Date),
29    /// a literal that represents a date and time
30    DateAndTime(DateAndTime),
31    /// a literal that represents the time of day
32    TimeOfDay(TimeOfDay),
33    /// a literal that represents a time period
34    Time(Time),
35    /// a literal that represents a real number (e.g. 7.0)
36    Real(String),
37    /// a literal that represents a boolean value (true, false)
38    Bool(bool),
39    /// a literal that represents a string
40    String(StringValue),
41    /// a literal that represents an array
42    Array(Array),
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub struct Date {
47    year: i32,
48    month: u32,
49    day: u32,
50    is_long: bool,
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
54pub struct DateAndTime {
55    year: i32,
56    month: u32,
57    day: u32,
58    hour: u32,
59    min: u32,
60    sec: u32,
61    nano: u32,
62    is_long: bool,
63}
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub struct TimeOfDay {
67    hour: u32,
68    min: u32,
69    sec: u32,
70    nano: u32,
71    is_long: bool,
72}
73
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct Time {
76    pub day: f64,
77    pub hour: f64,
78    pub min: f64,
79    pub sec: f64,
80    pub milli: f64,
81    pub micro: f64,
82    pub nano: u32,
83    pub negative: bool,
84    pub is_long: bool,
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88pub struct StringValue {
89    pub value: String,
90    pub is_wide: bool,
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94#[serde(bound(deserialize = "'de: 'static"))]
95pub struct Array {
96    pub elements: Option<Box<AstNode>>, // expression-list
97}
98
99/// calculates the nanoseconds since 1970-01-01-00:00:00 for the given
100/// point in time
101fn calculate_date_time(
102    year: i32,
103    month: u32,
104    day: u32,
105    hour: u32,
106    min: u32,
107    sec: u32,
108    nano: u32,
109) -> Result<i64, String> {
110    NaiveDate::from_ymd_opt(year, month, day)
111        .and_then(|date| date.and_hms_nano_opt(hour, min, sec, nano))
112        .ok_or_else(|| format!("Invalid Date {year}-{month}-{day}-{hour}:{min}:{sec}.{nano}"))
113        .and_then(|date_time| {
114            date_time
115                .and_utc()
116                .timestamp_nanos_opt()
117                .ok_or_else(|| format!("Out of range Date {year}-{month}-{day}-{hour}:{min}:{sec}.{nano}"))
118        })
119}
120
121impl DateAndTime {
122    /// the value of the date and time in nanoseconds since 1970-01-01-00:00:00
123    pub fn value(&self) -> Result<i64, String> {
124        calculate_date_time(self.year, self.month, self.day, self.hour, self.min, self.sec, self.nano)
125    }
126}
127
128impl Time {
129    /// the nanos represented by the given time-period
130    pub fn value(&self) -> i64 {
131        let dhm_seconds = {
132            let hours = self.day * 24_f64 + self.hour;
133            let mins = hours * 60_f64 + self.min;
134            mins * 60_f64 + self.sec
135        };
136        let millis = dhm_seconds * 1000_f64 + self.milli;
137        let micro = millis * 1000_f64 + self.micro;
138        let nano = micro * 1000_f64 + self.nano as f64;
139        //go to full micro
140        let nanos = nano.round() as i64;
141
142        if self.negative {
143            -nanos
144        } else {
145            nanos
146        }
147    }
148}
149
150impl TimeOfDay {
151    /// the value of the time of day in nanoseconds since 1970-01-01-00:00:00
152    pub fn value(&self) -> Result<i64, String> {
153        calculate_date_time(1970, 1, 1, self.hour, self.min, self.sec, self.nano)
154    }
155}
156
157impl Date {
158    /// the value of the date in nanoseconds since 1970-01-01-00:00:00
159    /// the time-part of the returned value is set to 00:00:00
160    pub fn value(&self) -> Result<i64, String> {
161        calculate_date_time(self.year, self.month, self.day, 0, 0, 0, 0)
162    }
163}
164
165impl_getters! { Date, [year, month, day], [i32, u32, u32] }
166impl_getters! { DateAndTime, [year, month, day, hour, min, sec, nano], [i32, u32, u32, u32, u32, u32, u32]}
167impl_getters! { TimeOfDay, [hour, min, sec, nano], [u32, u32, u32, u32]}
168impl_getters! { Time, [day, hour, min, sec, milli, micro, nano], [f64, f64, f64, f64, f64, f64, u32]}
169
170impl StringValue {
171    pub fn is_wide(&self) -> bool {
172        self.is_wide
173    }
174
175    pub fn value(&self) -> &str {
176        self.value.as_str()
177    }
178}
179
180impl Time {
181    pub fn is_negative(&self) -> bool {
182        self.negative
183    }
184
185    pub fn is_long(&self) -> bool {
186        self.is_long
187    }
188}
189
190impl Date {
191    pub fn is_long(&self) -> bool {
192        self.is_long
193    }
194}
195
196impl DateAndTime {
197    pub fn is_long(&self) -> bool {
198        self.is_long
199    }
200}
201
202impl TimeOfDay {
203    pub fn is_long(&self) -> bool {
204        self.is_long
205    }
206}
207
208impl Array {
209    pub fn elements(&self) -> Option<&AstNode> {
210        self.elements.as_ref().map(|it| it.as_ref())
211    }
212}
213
214impl AstLiteral {
215    /// Creates a new literal array
216    pub fn new_array(elements: Option<Box<AstNode>>) -> Self {
217        AstLiteral::Array(Array { elements })
218    }
219    /// Creates a new literal integer
220    pub fn new_integer(value: i128) -> Self {
221        AstLiteral::Integer(value)
222    }
223    /// Creates a new literal real
224    pub fn new_real(value: String) -> Self {
225        AstLiteral::Real(value)
226    }
227    /// Creates a new literal bool
228    pub fn new_bool(value: bool) -> Self {
229        AstLiteral::Bool(value)
230    }
231    /// Creates a new literal string
232    pub fn new_string(value: String, is_wide: bool) -> Self {
233        AstLiteral::String(StringValue { value, is_wide })
234    }
235
236    /// Creates a new literal date
237    pub fn new_date(year: i32, month: u32, day: u32) -> Self {
238        AstLiteral::Date(Date { year, month, day, is_long: false })
239    }
240
241    /// Creates a new literal date with explicit width flavor
242    pub fn new_date_with_long_flag(year: i32, month: u32, day: u32, is_long: bool) -> Self {
243        AstLiteral::Date(Date { year, month, day, is_long })
244    }
245
246    /// Creates a new literal date and time
247    pub fn new_date_and_time(
248        year: i32,
249        month: u32,
250        day: u32,
251        hour: u32,
252        min: u32,
253        sec: u32,
254        nano: u32,
255    ) -> Self {
256        AstLiteral::DateAndTime(DateAndTime { year, month, day, hour, min, sec, nano, is_long: false })
257    }
258
259    /// Creates a new long literal date and time
260    pub fn new_long_date_and_time(
261        year: i32,
262        month: u32,
263        day: u32,
264        hour: u32,
265        min: u32,
266        sec: u32,
267        nano: u32,
268    ) -> Self {
269        AstLiteral::DateAndTime(DateAndTime { year, month, day, hour, min, sec, nano, is_long: true })
270    }
271
272    /// Creates a new literal time of day
273    pub fn new_time_of_day(hour: u32, min: u32, sec: u32, nano: u32) -> Self {
274        AstLiteral::TimeOfDay(TimeOfDay { hour, min, sec, nano, is_long: false })
275    }
276
277    /// Creates a new literal time of day with explicit width flavor
278    pub fn new_time_of_day_with_long_flag(hour: u32, min: u32, sec: u32, nano: u32, is_long: bool) -> Self {
279        AstLiteral::TimeOfDay(TimeOfDay { hour, min, sec, nano, is_long })
280    }
281
282    /// Creates a new literal null
283    pub fn new_null() -> Self {
284        AstLiteral::Null
285    }
286
287    pub fn get_literal_value(&self) -> String {
288        match self {
289            AstLiteral::String(StringValue { value, is_wide: true, .. }) => format!(r#""{value}""#),
290            AstLiteral::String(StringValue { value, is_wide: false, .. }) => format!(r#"'{value}'"#),
291            AstLiteral::Bool(value) => {
292                format!("{value}")
293            }
294            AstLiteral::Integer(value) => {
295                format!("{value}")
296            }
297            AstLiteral::Real(value) => value.clone(),
298            _ => format!("{self:#?}"),
299        }
300    }
301
302    pub fn is_cast_prefix_eligible(&self) -> bool {
303        // TODO: figure out a better name for this...
304        matches!(
305            self,
306            AstLiteral::Bool { .. }
307                | AstLiteral::Integer { .. }
308                | AstLiteral::Real { .. }
309                | AstLiteral::String { .. }
310                | AstLiteral::Time { .. }
311                | AstLiteral::Date { .. }
312                | AstLiteral::TimeOfDay { .. }
313                | AstLiteral::DateAndTime { .. }
314        )
315    }
316
317    pub fn is_numerical(&self) -> bool {
318        matches!(
319            self,
320            AstLiteral::Integer { .. }
321                | AstLiteral::Real { .. }
322                | AstLiteral::Time { .. }
323                | AstLiteral::Date { .. }
324                | AstLiteral::TimeOfDay { .. }
325                | AstLiteral::DateAndTime { .. }
326        )
327    }
328
329    pub fn is_zero(&self) -> bool {
330        match self {
331            AstLiteral::Integer(0) => true,
332            AstLiteral::Real(val) => val == "0" || val == "0.0",
333            _ => false,
334        }
335    }
336
337    pub fn get_literal_integer_value(&self) -> Option<i128> {
338        let Self::Integer(val) = self else { return None };
339        Some(*val)
340    }
341}
342
343impl Debug for AstLiteral {
344    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
345        match self {
346            AstLiteral::Null => f.debug_struct("LiteralNull").finish(),
347            AstLiteral::Integer(value) => f.debug_struct("LiteralInteger").field("value", value).finish(),
348            AstLiteral::Date(Date { year, month, day, .. }) => f
349                .debug_struct("LiteralDate")
350                .field("year", year)
351                .field("month", month)
352                .field("day", day)
353                .finish(),
354            AstLiteral::DateAndTime(DateAndTime { year, month, day, hour, min, sec, nano, .. }) => f
355                .debug_struct("LiteralDateAndTime")
356                .field("year", year)
357                .field("month", month)
358                .field("day", day)
359                .field("hour", hour)
360                .field("min", min)
361                .field("sec", sec)
362                .field("nano", nano)
363                .finish(),
364            AstLiteral::TimeOfDay(TimeOfDay { hour, min, sec, nano, .. }) => f
365                .debug_struct("LiteralTimeOfDay")
366                .field("hour", hour)
367                .field("min", min)
368                .field("sec", sec)
369                .field("nano", nano)
370                .finish(),
371            AstLiteral::Time(Time { day, hour, min, sec, milli, micro, nano, negative, .. }) => f
372                .debug_struct("LiteralTime")
373                .field("day", day)
374                .field("hour", hour)
375                .field("min", min)
376                .field("sec", sec)
377                .field("milli", milli)
378                .field("micro", micro)
379                .field("nano", nano)
380                .field("negative", negative)
381                .finish(),
382            AstLiteral::Real(value) => f.debug_struct("LiteralReal").field("value", value).finish(),
383            AstLiteral::Bool(value) => f.debug_struct("LiteralBool").field("value", value).finish(),
384            AstLiteral::String(StringValue { value, is_wide, .. }) => {
385                f.debug_struct("LiteralString").field("value", value).field("is_wide", is_wide).finish()
386            }
387            AstLiteral::Array(Array { elements, .. }) => {
388                f.debug_struct("LiteralArray").field("elements", elements).finish()
389            }
390        }
391    }
392}