Skip to main content

qql_core/ast/
value.rs

1#[cfg(feature = "json")]
2use crate::error::QqlError;
3use alloc::string::String;
4use alloc::vec::Vec;
5
6/// QQL literal value: string, number, bool, `null`, object, or list.
7#[derive(Clone, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub enum Value {
10    /// String literal (`'…'`).
11    Str(String),
12    /// Integer literal.
13    Int(i64),
14    /// Unsigned integer literal: bare digit literals that overflow `i64`
15    /// (up to `u64::MAX`) parse here instead of failing or becoming strings.
16    UInt(u64),
17    /// Floating-point literal.
18    Float(f64),
19    /// Boolean literal (`true` / `false`).
20    Bool(bool),
21    /// `null` literal.
22    Null,
23    /// `{key: value, …}` object; entries keep their written order.
24    Dict(Vec<(String, Value)>),
25    /// `[v1, v2, …]` list literal (also dense vector input).
26    List(Vec<Value>),
27    /// Flat array of `f32` (dense vector). Bypasses per-element float boxing.
28    F32Array(Vec<f32>),
29    /// Named parameter placeholder (`:name`).
30    Param(
31        String,
32        #[cfg_attr(
33            feature = "serde",
34            serde(default, skip_serializing_if = "Option::is_none")
35        )]
36        Option<alloc::boxed::Box<crate::error::Span>>,
37    ),
38    /// Positional parameter placeholder (`?`).
39    PositionalParam(
40        usize,
41        #[cfg_attr(
42            feature = "serde",
43            serde(default, skip_serializing_if = "Option::is_none")
44        )]
45        Option<alloc::boxed::Box<crate::error::Span>>,
46    ),
47}
48
49impl core::fmt::Debug for Value {
50    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51        match self {
52            Self::Str(value) => f.debug_tuple("Str").field(value).finish(),
53            Self::Int(value) => f.debug_tuple("Int").field(value).finish(),
54            Self::UInt(value) => f.debug_tuple("UInt").field(value).finish(),
55            Self::Float(value) => f.debug_tuple("Float").field(value).finish(),
56            Self::Bool(value) => f.debug_tuple("Bool").field(value).finish(),
57            Self::Null => f.write_str("Null"),
58            Self::Dict(value) => f.debug_tuple("Dict").field(value).finish(),
59            Self::List(value) => f.debug_tuple("List").field(value).finish(),
60            Self::F32Array(value) => f.debug_tuple("F32Array").field(value).finish(),
61            Self::Param(value, span) => f.debug_tuple("Param").field(value).field(span).finish(),
62            Self::PositionalParam(idx, span) => f
63                .debug_tuple("PositionalParam")
64                .field(idx)
65                .field(span)
66                .finish(),
67        }
68    }
69}
70
71impl Value {
72    /// Case-insensitive lookup of a `Dict` entry; `None` for non-dict values.
73    pub fn dict_get(&self, key: &str) -> Option<&Value> {
74        match self {
75            Self::Dict(items) => items
76                .iter()
77                .find(|(candidate, _)| candidate.eq_ignore_ascii_case(key))
78                .map(|(_, value)| value),
79            _ => None,
80        }
81    }
82
83    /// Set a key in a `Dict` (case-insensitive replace) or append it.
84    ///
85    /// Returns `true` when this value is a dict and the key was written;
86    /// `false` when called on a non-dict (nothing is mutated).
87    pub fn dict_set(&mut self, key: String, value: Value) -> bool {
88        let Self::Dict(items) = self else {
89            return false;
90        };
91        if let Some((_, current)) = items
92            .iter_mut()
93            .find(|(candidate, _)| candidate.eq_ignore_ascii_case(&key))
94        {
95            *current = value;
96        } else {
97            items.push((key, value));
98        }
99        true
100    }
101
102    /// Borrow the string contents, if this value is a `Str`.
103    pub fn as_str(&self) -> Option<&str> {
104        match self {
105            Self::Str(value) => Some(value),
106            _ => None,
107        }
108    }
109
110    /// Convert a JSON value into a QQL `Value`.
111    ///
112    /// Integer numbers stay `Int`, unsigned integers above `i64::MAX` become
113    /// `UInt`, and any other number becomes `Float`.
114    #[cfg(feature = "json")]
115    pub fn from_json(value: serde_json::Value) -> Result<Self, QqlError> {
116        match value {
117            serde_json::Value::String(value) => Ok(Self::Str(value)),
118            serde_json::Value::Number(value) => match value.as_i64() {
119                Some(int) => Ok(Self::Int(int)),
120                None => match value.as_u64() {
121                    Some(uint) => Ok(Self::UInt(uint)),
122                    None => value.as_f64().map(Self::Float).ok_or_else(|| {
123                        QqlError::validation(
124                            "QQL-JSON-NUMBER",
125                            "JSON number cannot be represented by QQL",
126                            None,
127                        )
128                    }),
129                },
130            },
131            serde_json::Value::Bool(value) => Ok(Self::Bool(value)),
132            serde_json::Value::Null => Ok(Self::Null),
133            serde_json::Value::Array(items) => items
134                .into_iter()
135                .map(Self::from_json)
136                .collect::<Result<Vec<_>, _>>()
137                .map(Self::List),
138            serde_json::Value::Object(items) => items
139                .into_iter()
140                .map(|(key, value)| Self::from_json(value).map(|value| (key, value)))
141                .collect::<Result<Vec<_>, _>>()
142                .map(Self::Dict),
143        }
144    }
145
146    /// Convert the value into JSON, failing on non-finite floats.
147    ///
148    /// Note: Parameter placeholders (`Value::Param` and `Value::PositionalParam`)
149    /// emit diagnostic sentinel objects (`{"$param": ...}`) with optional `$span`.
150    /// This manual JSON representation is emit-only; `Value::from_json` decodes
151    /// all JSON objects as standard `Value::Dict` to prevent payload sentinel
152    /// hijacking. For full two-way AST serialization, use serde.
153    #[cfg(feature = "json")]
154    pub fn to_json(&self) -> Result<serde_json::Value, QqlError> {
155        match self {
156            Self::Str(value) => Ok(serde_json::Value::String(value.clone())),
157            Self::Int(value) => Ok(serde_json::Value::Number((*value).into())),
158            Self::UInt(value) => Ok(serde_json::Value::Number((*value).into())),
159            Self::Float(value) => serde_json::Number::from_f64(*value)
160                .map(serde_json::Value::Number)
161                .ok_or_else(|| {
162                    QqlError::validation(
163                        "QQL-JSON-NONFINITE",
164                        "non-finite floats cannot be converted to JSON",
165                        None,
166                    )
167                }),
168            Self::Bool(value) => Ok(serde_json::Value::Bool(*value)),
169            Self::Null => Ok(serde_json::Value::Null),
170            Self::Dict(items) => {
171                let mut object = serde_json::Map::new();
172                for (key, value) in items {
173                    object.insert(key.clone(), value.to_json()?);
174                }
175                Ok(serde_json::Value::Object(object))
176            }
177            Self::List(items) => items
178                .iter()
179                .map(Self::to_json)
180                .collect::<Result<Vec<_>, _>>()
181                .map(serde_json::Value::Array),
182            Self::F32Array(values) => {
183                let items = values
184                    .iter()
185                    .map(|&f| {
186                        serde_json::Number::from_f64(f as f64)
187                            .map(serde_json::Value::Number)
188                            .ok_or_else(|| {
189                                QqlError::validation(
190                                    "QQL-JSON-NONFINITE",
191                                    "non-finite floats cannot be converted to JSON",
192                                    None,
193                                )
194                            })
195                    })
196                    .collect::<Result<Vec<_>, _>>()?;
197                Ok(serde_json::Value::Array(items))
198            }
199            Self::Param(name, span) => {
200                if let Some(sp) = span {
201                    Ok(serde_json::json!({ "$param": name, "$span": [sp.start, sp.end] }))
202                } else {
203                    Ok(serde_json::json!({ "$param": name }))
204                }
205            }
206            Self::PositionalParam(idx, span) => {
207                if let Some(sp) = span {
208                    Ok(serde_json::json!({ "$param_idx": idx, "$span": [sp.start, sp.end] }))
209                } else {
210                    Ok(serde_json::json!({ "$param_idx": idx }))
211                }
212            }
213        }
214    }
215
216    /// Construct an unlocated named parameter placeholder.
217    pub fn param(name: impl Into<String>) -> Self {
218        Self::Param(name.into(), None)
219    }
220
221    /// Construct a located named parameter placeholder.
222    pub fn param_with_span(name: impl Into<String>, span: crate::error::Span) -> Self {
223        Self::Param(name.into(), Some(alloc::boxed::Box::new(span)))
224    }
225
226    /// Construct an unlocated positional parameter placeholder.
227    pub fn positional_param(idx: usize) -> Self {
228        Self::PositionalParam(idx, None)
229    }
230
231    /// Construct a located positional parameter placeholder.
232    pub fn positional_param_with_span(idx: usize, span: crate::error::Span) -> Self {
233        Self::PositionalParam(idx, Some(alloc::boxed::Box::new(span)))
234    }
235
236    /// Extract the parameter name if this is a named parameter.
237    pub fn param_name(&self) -> Option<&str> {
238        match self {
239            Self::Param(name, _) => Some(name.as_str()),
240            _ => None,
241        }
242    }
243
244    /// Extract the parameter source span if present.
245    pub fn param_span(&self) -> Option<crate::error::Span> {
246        match self {
247            Self::Param(_, span) | Self::PositionalParam(_, span) => span.as_deref().copied(),
248            _ => None,
249        }
250    }
251}
252
253impl From<Vec<f32>> for Value {
254    fn from(values: Vec<f32>) -> Self {
255        Self::F32Array(values)
256    }
257}