Skip to main content

zerodds_ast/node/
constant.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Constants and expression trees. Spec §3.5.
4
5use crate::facet::Facets;
6use crate::node::member::EnumeratorNode;
7use crate::node::module::ModuleNode;
8use crate::node::type_::TypeNode;
9use alloc::string::String;
10use alloc::vec::Vec;
11use core::fmt;
12use zerodds_ast_core::{Id, NodeId, Ref};
13
14/// A named constant. Spec §3.5.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ConstNode {
17    /// Stable identity.
18    pub node_id: NodeId,
19    /// Simple name.
20    pub name: String,
21    /// Enclosing module.
22    pub module: Id<ModuleNode>,
23    /// The interface or value type this is declared in, when it is not
24    /// declared directly in a module (OMG IDL 4.2 §7.4.3.4: an interface
25    /// body holds type, constant and exception declarations). Its name is
26    /// a segment of the scoped name and of the repository id:
27    /// `CosNaming::NamingContext::NotFound`.
28    pub scope: Option<String>,
29    /// Declared type.
30    pub type_: Ref<TypeNode>,
31    /// The defining expression, retained as a tree.
32    pub expr: Id<ConstExprNode>,
33    /// Per-source information. Spec §4.
34    pub facets: Facets,
35}
36
37/// A literal value.
38///
39/// Floating-point and fixed-point literals retain their written form:
40/// `1.` and `1.0` are the same number but not the same source text, and
41/// the formatter has to render back what was written. Spec §3.5.
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum Literal {
44    /// `TRUE` / `FALSE`.
45    Boolean(bool),
46    /// Integer literal, in its evaluated form; the written radix lives
47    /// in the source facet.
48    Integer(i128),
49    /// Floating-point literal, as written.
50    Float {
51        /// Mantissa exactly as written, including any trailing point.
52        mantissa: String,
53        /// Exponent as written, without the `e`.
54        exponent: Option<String>,
55    },
56    /// Fixed-point literal, as written.
57    Fixed {
58        /// Digits as written, without the point.
59        digits: String,
60        /// Digits after the point.
61        scale: u8,
62    },
63    /// Narrow character literal.
64    Char(char),
65    /// Wide character literal, as a code point.
66    WChar(u32),
67    /// Narrow string literal, with escapes resolved.
68    String(String),
69    /// Wide string literal, as code points.
70    WString(Vec<u32>),
71    /// An enumerator used as a value.
72    Enumerator(Ref<EnumeratorNode>),
73}
74
75/// Unary operator. Spec §3.5, IDL 4.2 §7.4.1.4.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
77pub enum UnaryOp {
78    /// `+`
79    Plus,
80    /// `-`
81    Minus,
82    /// `~`
83    Not,
84}
85
86impl UnaryOp {
87    /// Every unary operator IDL 4.2 defines.
88    pub const ALL: [Self; 3] = [Self::Plus, Self::Minus, Self::Not];
89
90    /// Source spelling.
91    #[must_use]
92    pub const fn as_str(self) -> &'static str {
93        match self {
94            Self::Plus => "+",
95            Self::Minus => "-",
96            Self::Not => "~",
97        }
98    }
99}
100
101/// Binary operator. Spec §3.5, IDL 4.2 §7.4.1.4.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103pub enum BinaryOp {
104    /// `|`
105    Or,
106    /// `^`
107    Xor,
108    /// `&`
109    And,
110    /// `<<`
111    Shl,
112    /// `>>`
113    Shr,
114    /// `+`
115    Add,
116    /// `-`
117    Sub,
118    /// `*`
119    Mul,
120    /// `/`
121    Div,
122    /// `%`
123    Mod,
124}
125
126impl BinaryOp {
127    /// Every binary operator IDL 4.2 defines, in precedence order from
128    /// loosest to tightest.
129    pub const ALL: [Self; 10] = [
130        Self::Or,
131        Self::Xor,
132        Self::And,
133        Self::Shl,
134        Self::Shr,
135        Self::Add,
136        Self::Sub,
137        Self::Mul,
138        Self::Div,
139        Self::Mod,
140    ];
141
142    /// Source spelling.
143    #[must_use]
144    pub const fn as_str(self) -> &'static str {
145        match self {
146            Self::Or => "|",
147            Self::Xor => "^",
148            Self::And => "&",
149            Self::Shl => "<<",
150            Self::Shr => ">>",
151            Self::Add => "+",
152            Self::Sub => "-",
153            Self::Mul => "*",
154            Self::Div => "/",
155            Self::Mod => "%",
156        }
157    }
158}
159
160/// One node of a constant expression tree.
161#[derive(Debug, Clone, PartialEq, Eq)]
162pub enum ConstExpr {
163    /// A literal value.
164    Literal(Literal),
165    /// A reference to another constant by name.
166    NameRef(Ref<ConstNode>),
167    /// Unary application.
168    Unary {
169        /// The operator.
170        op: UnaryOp,
171        /// Its operand.
172        operand: Id<ConstExprNode>,
173    },
174    /// Binary application.
175    Binary {
176        /// The operator.
177        op: BinaryOp,
178        /// Left operand.
179        lhs: Id<ConstExprNode>,
180        /// Right operand.
181        rhs: Id<ConstExprNode>,
182    },
183}
184
185/// An expression node.
186///
187/// Expressions live in their own arena, so an expression referenced from
188/// a bound, an array dimension, a case label, a `@default` or an
189/// enumerator value is a first-class node with its own identity and its
190/// own source facet. Spec §3.5.
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct ConstExprNode {
193    /// Stable identity.
194    pub node_id: NodeId,
195    /// The expression at this node.
196    pub expr: ConstExpr,
197    /// Per-source information. Spec §4.
198    pub facets: Facets,
199}
200
201/// Why an expression could not be evaluated.
202///
203/// Evaluation of a partially discovered model fails loudly; it never
204/// substitutes a default. Spec §3.5.
205#[derive(Debug, Clone, PartialEq, Eq)]
206#[non_exhaustive]
207pub enum EvalError {
208    /// The tree reached a reference that is not resolved.
209    Unresolved {
210        /// The expression node that could not be followed.
211        at: NodeId,
212    },
213    /// A handle in the tree did not address a live expression node.
214    DanglingExpr,
215    /// Division or remainder by zero.
216    DivisionByZero {
217        /// The expression node performing the division.
218        at: NodeId,
219    },
220    /// The computation left the range of `i128`.
221    Overflow {
222        /// The expression node that overflowed.
223        at: NodeId,
224    },
225    /// A shift distance was negative or wider than the value.
226    InvalidShift {
227        /// The expression node performing the shift.
228        at: NodeId,
229    },
230    /// The literal is not integral, so integral evaluation cannot apply.
231    NotIntegral {
232        /// The expression node holding the literal.
233        at: NodeId,
234    },
235    /// The expression nests deeper than evaluation will follow. Bounds
236    /// the work a hostile snapshot can demand.
237    TooDeep,
238    /// An infix operator combines a floating-point operand with one that
239    /// is not. IDL 4.2 §7.4.1.4.2: "An infix operator may combine two
240    /// integer types, floating point types or fixed point types, but not
241    /// mixtures of these."
242    MixedOperands {
243        /// The expression node with the mixed operands.
244        at: NodeId,
245    },
246    /// An operator other than unary `+ -` and binary `* / + -` over
247    /// floating-point operands. IDL 4.2 §7.4.1.4.2 admits only those.
248    NotFloatingOperator {
249        /// The expression node performing the operation.
250        at: NodeId,
251    },
252    /// An operator other than unary `+ -` and binary `* / + -` over
253    /// fixed-point operands. IDL 4.2 §7.4.1.4.2 admits only those.
254    NotFixedOperator {
255        /// The expression node performing the operation.
256        at: NodeId,
257    },
258    /// The arithmetic would have to run in `long double`: the constant
259    /// is one, an operand is one, or a literal is too large for `double`
260    /// and so is one (IDL 4.2 §7.4.1.4.2). This build has no IEEE
261    /// double-extended or binary128 arithmetic to run it in, and doing it
262    /// in `double` instead would round where the clause says not to.
263    LongDouble {
264        /// The expression node that needs it.
265        at: NodeId,
266    },
267}
268
269impl fmt::Display for EvalError {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        match self {
272            Self::Unresolved { at } => write!(f, "expression {at:?} reaches an unresolved name"),
273            Self::DanglingExpr => write!(f, "expression tree contains a dangling handle"),
274            Self::DivisionByZero { at } => write!(f, "division by zero in expression {at:?}"),
275            Self::Overflow { at } => write!(f, "arithmetic overflow in expression {at:?}"),
276            Self::InvalidShift { at } => write!(f, "invalid shift distance in expression {at:?}"),
277            Self::NotIntegral { at } => write!(f, "expression {at:?} is not integral"),
278            Self::TooDeep => write!(f, "expression nests too deeply to evaluate"),
279            Self::MixedOperands { at } => write!(
280                f,
281                "expression {at:?} combines a floating-point operand with one that is not"
282            ),
283            Self::NotFloatingOperator { at } => write!(
284                f,
285                "expression {at:?} applies an operator floating-point values do not take"
286            ),
287            Self::NotFixedOperator { at } => write!(
288                f,
289                "expression {at:?} applies an operator fixed-point values do not take"
290            ),
291            Self::LongDouble { at } => write!(
292                f,
293                "expression {at:?} needs long double arithmetic, which this build does not have"
294            ),
295        }
296    }
297}
298
299impl core::error::Error for EvalError {}
300
301/// How deep integral evaluation will follow an expression tree.
302///
303/// The limit exists because a snapshot can be hostile; without it, a
304/// crafted tree would make evaluation run for an unbounded time.
305pub const MAX_EXPR_DEPTH: usize = 256;
306
307/// A constant expression's value, in the form the expression had.
308///
309/// [`Ast::eval_integral`] answers for the constants that are numbers a
310/// wire format cares about, and returns [`EvalError::NotIntegral`] for the
311/// rest. That is the right answer where an integer is required — an array
312/// bound, a `@bit_bound`, a case label — and the wrong one where the
313/// question is only "what does this constant say": `const string
314/// MODULE_ID = "spatial.sensing.lidar/1.5";` is a perfectly evaluated
315/// constant that no integer represents, and a backend that asked the
316/// integral question could not emit it at all.
317///
318/// [`Ast::eval_integral`]: crate::Ast::eval_integral
319/// [`EvalError::NotIntegral`]: crate::EvalError::NotIntegral
320#[derive(Debug, Clone, PartialEq)]
321#[non_exhaustive]
322pub enum ConstValue {
323    /// `TRUE` / `FALSE`.
324    Boolean(bool),
325    /// An integer, including a character or an enumerator evaluated as
326    /// one, and every arithmetic expression over them.
327    Integer(i128),
328    /// A floating-point literal, in the two halves it was written in.
329    ///
330    /// Kept as text: the decimal a source wrote and the nearest `f64` are
331    /// two different values, and which one a backend needs is the
332    /// backend's business.
333    Float {
334        /// Mantissa exactly as written, including any trailing point.
335        mantissa: String,
336        /// Exponent as written, without the `e`.
337        exponent: Option<String>,
338    },
339    /// A fixed-point literal — a decimal, and never a binary float.
340    Fixed {
341        /// Digits as written, without the point.
342        digits: String,
343        /// Digits after the point.
344        scale: u8,
345    },
346    /// A narrow character.
347    Char(char),
348    /// A wide character, as a code point.
349    WChar(u32),
350    /// A narrow string, with escapes resolved.
351    String(String),
352    /// A wide string, as code points.
353    WString(Vec<u32>),
354}