Skip to main content

panproto_expr/
expr.rs

1//! Expression AST, pattern, and builtin operation types.
2//!
3//! The expression language is a pure functional language — lambda calculus
4//! with pattern matching, algebraic data types, and built-in operations on
5//! strings, numbers, records, and lists. Comparable to a pure subset of ML.
6
7use std::sync::Arc;
8
9use crate::Literal;
10
11/// An expression in the pure functional language.
12///
13/// All variants are serializable, content-addressable, and evaluate
14/// deterministically on any platform (including WASM).
15#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
16pub enum Expr {
17    /// Variable reference.
18    Var(Arc<str>),
19    /// Lambda abstraction: `λparam. body`.
20    Lam(Arc<str>, Box<Self>),
21    /// Function application: `func(arg)`.
22    App(Box<Self>, Box<Self>),
23    /// Literal value.
24    Lit(Literal),
25    /// Record construction: `{ name: expr, ... }`.
26    Record(Vec<(Arc<str>, Self)>),
27    /// List construction: `[expr, ...]`.
28    List(Vec<Self>),
29    /// Field access: `expr.field`.
30    Field(Box<Self>, Arc<str>),
31    /// Index access: `expr[index]`.
32    Index(Box<Self>, Box<Self>),
33    /// Pattern matching: `match scrutinee { pat => body, ... }`.
34    Match {
35        /// The value being matched against.
36        scrutinee: Box<Self>,
37        /// Arms: (pattern, body) pairs tried in order.
38        arms: Vec<(Pattern, Self)>,
39    },
40    /// Let binding: `let name = value in body`.
41    Let {
42        /// The bound variable name.
43        name: Arc<str>,
44        /// The value to bind.
45        value: Box<Self>,
46        /// The body where the binding is visible.
47        body: Box<Self>,
48    },
49    /// Built-in operation applied to arguments.
50    Builtin(BuiltinOp, Vec<Self>),
51}
52
53/// A destructuring pattern for match expressions.
54#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
55pub enum Pattern {
56    /// Matches anything, binds nothing.
57    Wildcard,
58    /// Matches anything, binds the value to a name.
59    Var(Arc<str>),
60    /// Matches a specific literal value.
61    Lit(Literal),
62    /// Matches a record with specific field patterns.
63    Record(Vec<(Arc<str>, Self)>),
64    /// Matches a list with element patterns.
65    List(Vec<Self>),
66    /// Matches a tagged constructor with argument patterns.
67    Constructor(Arc<str>, Vec<Self>),
68}
69
70/// Built-in operations, grouped by domain.
71///
72/// Each operation has a fixed arity enforced at evaluation time.
73/// All operations are pure — no IO, no mutation, deterministic.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
75pub enum BuiltinOp {
76    // --- Arithmetic (7) ---
77    /// `add(a: int|float, b: int|float) → int|float`
78    Add,
79    /// `sub(a: int|float, b: int|float) → int|float`
80    Sub,
81    /// `mul(a: int|float, b: int|float) → int|float`
82    Mul,
83    /// `div(a: int|float, b: int|float) → int|float` (truncating for ints)
84    Div,
85    /// `mod_(a: int, b: int) → int`
86    Mod,
87    /// `neg(a: int|float) → int|float`
88    Neg,
89    /// `abs(a: int|float) → int|float`
90    Abs,
91
92    // --- Rounding (2) ---
93    /// `floor(a: float) → int`
94    Floor,
95    /// `ceil(a: float) → int`
96    Ceil,
97
98    // --- Comparison (6) ---
99    /// `eq(a, b) → bool`
100    Eq,
101    /// `neq(a, b) → bool`
102    Neq,
103    /// `lt(a, b) → bool`
104    Lt,
105    /// `lte(a, b) → bool`
106    Lte,
107    /// `gt(a, b) → bool`
108    Gt,
109    /// `gte(a, b) → bool`
110    Gte,
111
112    // --- Boolean (3) ---
113    /// `and(a: bool, b: bool) → bool`
114    And,
115    /// `or(a: bool, b: bool) → bool`
116    Or,
117    /// `not(a: bool) → bool`
118    Not,
119
120    // --- String (10) ---
121    /// `concat(a: string, b: string) → string`
122    Concat,
123    /// `len(s: string) → int` (byte length)
124    Len,
125    /// `slice(s: string, start: int, end: int) → string`
126    Slice,
127    /// `upper(s: string) → string`
128    Upper,
129    /// `lower(s: string) → string`
130    Lower,
131    /// `trim(s: string) → string`
132    Trim,
133    /// `split(s: string, delim: string) → [string]`
134    Split,
135    /// `join(parts: [string], delim: string) → string`
136    Join,
137    /// `replace(s: string, from: string, to: string) → string`
138    Replace,
139    /// `contains(s: string, substr: string) → bool`
140    Contains,
141
142    // --- List (9) ---
143    /// `map(list: [a], f: a → b) → [b]`
144    Map,
145    /// `filter(list: [a], pred: a → bool) → [a]`
146    Filter,
147    /// `fold(list: [a], init: b, f: (b, a) → b) → b`
148    Fold,
149    /// `append(list: [a], item: a) → [a]`
150    Append,
151    /// `head(list: [a]) → a`
152    Head,
153    /// `tail(list: [a]) → [a]`
154    Tail,
155    /// `reverse(list: [a]) → [a]`
156    Reverse,
157    /// `flat_map(list: [a], f: a → [b]) → [b]`
158    FlatMap,
159    /// `length(list: [a]) → int` (list length, distinct from string Len)
160    Length,
161
162    // --- Record (4) ---
163    /// `merge(a: record, b: record) → record` (b fields override a)
164    MergeRecords,
165    /// `keys(r: record) → [string]`
166    Keys,
167    /// `values(r: record) → [any]`
168    Values,
169    /// `has_field(r: record, name: string) → bool`
170    HasField,
171
172    // --- Type coercions (6) ---
173    /// `int_to_float(n: int) → float`
174    IntToFloat,
175    /// `float_to_int(f: float) → int` (truncates)
176    FloatToInt,
177    /// `int_to_str(n: int) → string`
178    IntToStr,
179    /// `float_to_str(f: float) → string`
180    FloatToStr,
181    /// `str_to_int(s: string) → int` (fails on non-numeric)
182    StrToInt,
183    /// `str_to_float(s: string) → float` (fails on non-numeric)
184    StrToFloat,
185
186    // --- Type inspection (3) ---
187    /// `type_of(v) → string` (returns type name)
188    TypeOf,
189    /// `is_null(v) → bool`
190    IsNull,
191    /// `is_list(v) → bool`
192    IsList,
193}
194
195impl BuiltinOp {
196    /// Returns the expected number of arguments for this builtin.
197    #[must_use]
198    pub const fn arity(self) -> usize {
199        match self {
200            // Unary
201            Self::Neg
202            | Self::Abs
203            | Self::Floor
204            | Self::Ceil
205            | Self::Not
206            | Self::Upper
207            | Self::Lower
208            | Self::Trim
209            | Self::Head
210            | Self::Tail
211            | Self::Reverse
212            | Self::Keys
213            | Self::Values
214            | Self::IntToFloat
215            | Self::FloatToInt
216            | Self::IntToStr
217            | Self::FloatToStr
218            | Self::StrToInt
219            | Self::StrToFloat
220            | Self::TypeOf
221            | Self::IsNull
222            | Self::IsList
223            | Self::Len
224            | Self::Length => 1,
225            // Binary
226            Self::Add
227            | Self::Sub
228            | Self::Mul
229            | Self::Div
230            | Self::Mod
231            | Self::Eq
232            | Self::Neq
233            | Self::Lt
234            | Self::Lte
235            | Self::Gt
236            | Self::Gte
237            | Self::And
238            | Self::Or
239            | Self::Concat
240            | Self::Split
241            | Self::Join
242            | Self::Append
243            | Self::Map
244            | Self::Filter
245            | Self::HasField
246            | Self::MergeRecords
247            | Self::Contains
248            | Self::FlatMap => 2,
249            // Ternary
250            Self::Slice | Self::Replace | Self::Fold => 3,
251        }
252    }
253}
254
255impl Expr {
256    /// Create a variable expression.
257    #[must_use]
258    pub fn var(name: impl Into<Arc<str>>) -> Self {
259        Self::Var(name.into())
260    }
261
262    /// Create a lambda expression.
263    #[must_use]
264    pub fn lam(param: impl Into<Arc<str>>, body: Self) -> Self {
265        Self::Lam(param.into(), Box::new(body))
266    }
267
268    /// Create an application expression.
269    #[must_use]
270    pub fn app(func: Self, arg: Self) -> Self {
271        Self::App(Box::new(func), Box::new(arg))
272    }
273
274    /// Create a let-binding expression.
275    #[must_use]
276    pub fn let_in(name: impl Into<Arc<str>>, value: Self, body: Self) -> Self {
277        Self::Let {
278            name: name.into(),
279            value: Box::new(value),
280            body: Box::new(body),
281        }
282    }
283
284    /// Create a field access expression.
285    #[must_use]
286    pub fn field(expr: Self, name: impl Into<Arc<str>>) -> Self {
287        Self::Field(Box::new(expr), name.into())
288    }
289
290    /// Create a builtin operation applied to arguments.
291    #[must_use]
292    pub const fn builtin(op: BuiltinOp, args: Vec<Self>) -> Self {
293        Self::Builtin(op, args)
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn builtin_arities() {
303        assert_eq!(BuiltinOp::Add.arity(), 2);
304        assert_eq!(BuiltinOp::Not.arity(), 1);
305        assert_eq!(BuiltinOp::Fold.arity(), 3);
306        assert_eq!(BuiltinOp::Slice.arity(), 3);
307    }
308
309    #[test]
310    fn expr_constructors() {
311        let e = Expr::let_in(
312            "x",
313            Expr::Lit(Literal::Int(42)),
314            Expr::builtin(
315                BuiltinOp::Add,
316                vec![Expr::var("x"), Expr::Lit(Literal::Int(1))],
317            ),
318        );
319        assert!(matches!(e, Expr::Let { .. }));
320    }
321}