rib/interpreter/
literal.rs

1// Copyright 2024-2025 Golem Cloud
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use golem_wasm_ast::analysis::AnalysedType;
16use golem_wasm_rpc::{IntoValueAndType, Value, ValueAndType};
17use std::cmp::Ordering;
18use std::fmt::Display;
19use std::ops::{Add, Div, Mul, Sub};
20
21pub trait GetLiteralValue {
22    fn get_literal(&self) -> Option<LiteralValue>;
23}
24
25impl GetLiteralValue for ValueAndType {
26    fn get_literal(&self) -> Option<LiteralValue> {
27        match self {
28            ValueAndType {
29                value: Value::String(value),
30                ..
31            } => Some(LiteralValue::String(value.clone())),
32            ValueAndType {
33                value: Value::Char(code_point),
34                ..
35            } => char::from_u32(*code_point as u32)
36                .map(|c| c.to_string())
37                .map(LiteralValue::String),
38            ValueAndType {
39                value: Value::Bool(value),
40                ..
41            } => Some(LiteralValue::Bool(*value)),
42            ValueAndType {
43                value: Value::Enum(idx),
44                typ: AnalysedType::Enum(typ),
45            } => {
46                // An enum can be turned into a simple literal and can be part of string concatenations
47                Some(LiteralValue::String(typ.cases[*idx as usize].clone()))
48            }
49            ValueAndType {
50                value:
51                    Value::Variant {
52                        case_idx,
53                        case_value,
54                    },
55                typ: AnalysedType::Variant(typ),
56            } => {
57                // A no arg variant can be turned into a simple literal and can be part of string concatenations
58                if case_value.is_none() {
59                    Some(LiteralValue::String(
60                        typ.cases[*case_idx as usize].name.clone(),
61                    ))
62                } else {
63                    None
64                }
65            }
66            other => internal::get_numeric_value(other).map(LiteralValue::Num),
67        }
68    }
69}
70
71#[derive(Clone, Debug, PartialEq, PartialOrd)]
72pub enum LiteralValue {
73    Num(CoercedNumericValue),
74    String(String),
75    Bool(bool),
76}
77
78impl LiteralValue {
79    pub fn get_bool(&self) -> Option<bool> {
80        match self {
81            LiteralValue::Bool(value) => Some(*value),
82            _ => None,
83        }
84    }
85
86    pub fn get_number(&self) -> Option<CoercedNumericValue> {
87        match self {
88            LiteralValue::Num(num) => Some(num.clone()),
89            _ => None,
90        }
91    }
92
93    pub fn as_string(&self) -> String {
94        match self {
95            LiteralValue::Num(number) => number.to_string(),
96            LiteralValue::String(value) => value.clone(),
97            LiteralValue::Bool(value) => value.to_string(),
98        }
99    }
100}
101
102impl From<String> for LiteralValue {
103    fn from(value: String) -> Self {
104        if let Ok(u64) = value.parse::<u64>() {
105            LiteralValue::Num(CoercedNumericValue::PosInt(u64))
106        } else if let Ok(i64_value) = value.parse::<i64>() {
107            LiteralValue::Num(CoercedNumericValue::NegInt(i64_value))
108        } else if let Ok(f64_value) = value.parse::<f64>() {
109            LiteralValue::Num(CoercedNumericValue::Float(f64_value))
110        } else if let Ok(bool) = value.parse::<bool>() {
111            LiteralValue::Bool(bool)
112        } else {
113            LiteralValue::String(value.to_string())
114        }
115    }
116}
117
118// A coerced representation of numeric wasm types, simplifying finer-grained TypeAnnotatedValue types into u64, i64, and f64.
119#[derive(Clone, Debug)]
120pub enum CoercedNumericValue {
121    PosInt(u64),
122    NegInt(i64),
123    Float(f64),
124}
125
126impl CoercedNumericValue {
127    pub fn cast_to(&self, analysed_type: &AnalysedType) -> Option<ValueAndType> {
128        match (self, analysed_type) {
129            (CoercedNumericValue::PosInt(val), AnalysedType::U8(_)) if *val <= u8::MAX as u64 => {
130                Some((*val as u8).into_value_and_type())
131            }
132            (CoercedNumericValue::PosInt(val), AnalysedType::U16(_)) if *val <= u16::MAX as u64 => {
133                Some((*val as u16).into_value_and_type())
134            }
135            (CoercedNumericValue::PosInt(val), AnalysedType::U32(_)) if *val <= u32::MAX as u64 => {
136                Some((*val as u32).into_value_and_type())
137            }
138            (CoercedNumericValue::PosInt(val), AnalysedType::U64(_)) => {
139                Some((*val).into_value_and_type())
140            }
141
142            (CoercedNumericValue::NegInt(val), AnalysedType::S8(_))
143                if *val >= i8::MIN as i64 && *val <= i8::MAX as i64 =>
144            {
145                Some((*val as i8).into_value_and_type())
146            }
147            (CoercedNumericValue::NegInt(val), AnalysedType::S16(_))
148                if *val >= i16::MIN as i64 && *val <= i16::MAX as i64 =>
149            {
150                Some((*val as i16).into_value_and_type())
151            }
152            (CoercedNumericValue::NegInt(val), AnalysedType::S32(_))
153                if *val >= i32::MIN as i64 && *val <= i32::MAX as i64 =>
154            {
155                Some((*val as i32).into_value_and_type())
156            }
157            (CoercedNumericValue::NegInt(val), AnalysedType::S64(_)) => {
158                Some((*val).into_value_and_type())
159            }
160
161            (CoercedNumericValue::Float(val), AnalysedType::F64(_)) => {
162                Some((*val).into_value_and_type())
163            }
164            (CoercedNumericValue::Float(val), AnalysedType::F32(_))
165                if *val >= f32::MIN as f64 && *val <= f32::MAX as f64 =>
166            {
167                Some((*val as f32).into_value_and_type())
168            }
169
170            _ => None,
171        }
172    }
173}
174
175macro_rules! impl_ops {
176    ($trait:ident, $method:ident) => {
177        impl $trait for CoercedNumericValue {
178            type Output = Self;
179
180            fn $method(self, rhs: Self) -> Self::Output {
181                match (self, rhs) {
182                    (CoercedNumericValue::Float(a), CoercedNumericValue::Float(b)) => {
183                        CoercedNumericValue::Float(a.$method(b))
184                    }
185                    (CoercedNumericValue::Float(a), CoercedNumericValue::PosInt(b)) => {
186                        CoercedNumericValue::Float(a.$method(b as f64))
187                    }
188                    (CoercedNumericValue::Float(a), CoercedNumericValue::NegInt(b)) => {
189                        CoercedNumericValue::Float(a.$method(b as f64))
190                    }
191                    (CoercedNumericValue::PosInt(a), CoercedNumericValue::Float(b)) => {
192                        CoercedNumericValue::Float((a as f64).$method(b))
193                    }
194                    (CoercedNumericValue::NegInt(a), CoercedNumericValue::Float(b)) => {
195                        CoercedNumericValue::Float((a as f64).$method(b))
196                    }
197                    (CoercedNumericValue::PosInt(a), CoercedNumericValue::PosInt(b)) => {
198                        CoercedNumericValue::PosInt(a.$method(b))
199                    }
200                    (CoercedNumericValue::NegInt(a), CoercedNumericValue::NegInt(b)) => {
201                        CoercedNumericValue::NegInt(a.$method(b))
202                    }
203                    (CoercedNumericValue::PosInt(a), CoercedNumericValue::NegInt(b)) => {
204                        CoercedNumericValue::NegInt((a as i64).$method(b))
205                    }
206                    (CoercedNumericValue::NegInt(a), CoercedNumericValue::PosInt(b)) => {
207                        CoercedNumericValue::NegInt(a.$method(b as i64))
208                    }
209                }
210            }
211        }
212    };
213}
214
215impl_ops!(Add, add);
216impl_ops!(Sub, sub);
217impl_ops!(Mul, mul);
218impl_ops!(Div, div);
219
220// Auto-derived PartialOrd fails if types don't match
221// and therefore custom impl.
222impl PartialOrd for CoercedNumericValue {
223    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
224        use CoercedNumericValue::*;
225        match (self, other) {
226            (PosInt(a), PosInt(b)) => a.partial_cmp(b),
227            (NegInt(a), NegInt(b)) => a.partial_cmp(b),
228            (Float(a), Float(b)) => a.partial_cmp(b),
229
230            (PosInt(a), NegInt(b)) => {
231                if let Ok(b_as_u64) = u64::try_from(*b) {
232                    a.partial_cmp(&b_as_u64)
233                } else {
234                    Some(Ordering::Greater) // Positive numbers are greater than negative numbers
235                }
236            }
237
238            (NegInt(a), PosInt(b)) => {
239                if let Ok(a_as_u64) = u64::try_from(*a) {
240                    a_as_u64.partial_cmp(b)
241                } else {
242                    Some(Ordering::Less) // Negative numbers are less than positive numbers
243                }
244            }
245
246            (PosInt(a), Float(b)) => (*a as f64).partial_cmp(b),
247
248            (Float(a), PosInt(b)) => a.partial_cmp(&(*b as f64)),
249
250            (NegInt(a), Float(b)) => (*a as f64).partial_cmp(b),
251
252            (Float(a), NegInt(b)) => a.partial_cmp(&(*b as f64)),
253        }
254    }
255}
256
257// Similarly, auto-derived PartialEq fails if types don't match
258// and therefore custom impl
259// There is a high chance two variables can be inferred S32(1) and U32(1)
260impl PartialEq for CoercedNumericValue {
261    fn eq(&self, other: &Self) -> bool {
262        use CoercedNumericValue::*;
263        match (self, other) {
264            (PosInt(a), PosInt(b)) => a == b,
265            (NegInt(a), NegInt(b)) => a == b,
266            (Float(a), Float(b)) => a == b,
267
268            // Comparing PosInt with NegInt
269            (PosInt(a), NegInt(b)) => {
270                if let Ok(b_as_u64) = u64::try_from(*b) {
271                    a == &b_as_u64
272                } else {
273                    false
274                }
275            }
276
277            // Comparing NegInt with PosInt
278            (NegInt(a), PosInt(b)) => {
279                if let Ok(a_as_u64) = u64::try_from(*a) {
280                    &a_as_u64 == b
281                } else {
282                    false
283                }
284            }
285
286            // Comparing PosInt with Float
287            (PosInt(a), Float(b)) => (*a as f64) == *b,
288
289            // Comparing Float with PosInt
290            (Float(a), PosInt(b)) => *a == (*b as f64),
291
292            // Comparing NegInt with Float
293            (NegInt(a), Float(b)) => (*a as f64) == *b,
294
295            // Comparing Float with NegInt
296            (Float(a), NegInt(b)) => *a == (*b as f64),
297        }
298    }
299}
300
301impl Display for CoercedNumericValue {
302    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303        match self {
304            CoercedNumericValue::PosInt(value) => write!(f, "{}", value),
305            CoercedNumericValue::NegInt(value) => write!(f, "{}", value),
306            CoercedNumericValue::Float(value) => write!(f, "{}", value),
307        }
308    }
309}
310
311impl Display for LiteralValue {
312    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313        match self {
314            LiteralValue::Num(number) => write!(f, "{}", number),
315            LiteralValue::String(value) => write!(f, "{}", value),
316            LiteralValue::Bool(value) => write!(f, "{}", value),
317        }
318    }
319}
320
321mod internal {
322    use crate::interpreter::literal::CoercedNumericValue;
323    use golem_wasm_rpc::{Value, ValueAndType};
324
325    pub(crate) fn get_numeric_value(value_and_type: &ValueAndType) -> Option<CoercedNumericValue> {
326        match &value_and_type.value {
327            Value::S8(value) => Some(CoercedNumericValue::NegInt(*value as i64)),
328            Value::S16(value) => Some(CoercedNumericValue::NegInt(*value as i64)),
329            Value::S32(value) => Some(CoercedNumericValue::NegInt(*value as i64)),
330            Value::S64(value) => Some(CoercedNumericValue::NegInt(*value)),
331            Value::U8(value) => Some(CoercedNumericValue::PosInt(*value as u64)),
332            Value::U16(value) => Some(CoercedNumericValue::PosInt(*value as u64)),
333            Value::U32(value) => Some(CoercedNumericValue::PosInt(*value as u64)),
334            Value::U64(value) => Some(CoercedNumericValue::PosInt(*value)),
335            Value::F32(value) => Some(CoercedNumericValue::Float(*value as f64)),
336            Value::F64(value) => Some(CoercedNumericValue::Float(*value)),
337            _ => None,
338        }
339    }
340}