use std::sync::Arc;
use crate::Literal;
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum Expr {
Var(Arc<str>),
Lam(Arc<str>, Box<Self>),
App(Box<Self>, Box<Self>),
Lit(Literal),
Record(Vec<(Arc<str>, Self)>),
List(Vec<Self>),
Field(Box<Self>, Arc<str>),
Index(Box<Self>, Box<Self>),
Match {
scrutinee: Box<Self>,
arms: Vec<(Pattern, Self)>,
},
Let {
name: Arc<str>,
value: Box<Self>,
body: Box<Self>,
},
Builtin(BuiltinOp, Vec<Self>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum Pattern {
Wildcard,
Var(Arc<str>),
Lit(Literal),
Record(Vec<(Arc<str>, Self)>),
List(Vec<Self>),
Constructor(Arc<str>, Vec<Self>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum BuiltinOp {
Add,
Sub,
Mul,
Div,
Mod,
Neg,
Abs,
Floor,
Ceil,
Eq,
Neq,
Lt,
Lte,
Gt,
Gte,
And,
Or,
Not,
Concat,
Len,
Slice,
Upper,
Lower,
Trim,
Split,
Join,
Replace,
Contains,
Map,
Filter,
Fold,
Append,
Head,
Tail,
Reverse,
FlatMap,
Length,
MergeRecords,
Keys,
Values,
HasField,
IntToFloat,
FloatToInt,
IntToStr,
FloatToStr,
StrToInt,
StrToFloat,
TypeOf,
IsNull,
IsList,
Edge,
Children,
HasEdge,
EdgeCount,
Anchor,
}
impl BuiltinOp {
#[must_use]
pub const fn arity(self) -> usize {
match self {
Self::Neg
| Self::Abs
| Self::Floor
| Self::Ceil
| Self::Not
| Self::Upper
| Self::Lower
| Self::Trim
| Self::Head
| Self::Tail
| Self::Reverse
| Self::Keys
| Self::Values
| Self::IntToFloat
| Self::FloatToInt
| Self::IntToStr
| Self::FloatToStr
| Self::StrToInt
| Self::StrToFloat
| Self::TypeOf
| Self::IsNull
| Self::IsList
| Self::Len
| Self::Length
| Self::Children
| Self::EdgeCount
| Self::Anchor => 1,
Self::Add
| Self::Sub
| Self::Mul
| Self::Div
| Self::Mod
| Self::Eq
| Self::Neq
| Self::Lt
| Self::Lte
| Self::Gt
| Self::Gte
| Self::And
| Self::Or
| Self::Concat
| Self::Split
| Self::Join
| Self::Append
| Self::Map
| Self::Filter
| Self::HasField
| Self::MergeRecords
| Self::Contains
| Self::FlatMap
| Self::Edge
| Self::HasEdge => 2,
Self::Slice | Self::Replace | Self::Fold => 3,
}
}
}
impl Expr {
#[must_use]
pub fn var(name: impl Into<Arc<str>>) -> Self {
Self::Var(name.into())
}
#[must_use]
pub fn lam(param: impl Into<Arc<str>>, body: Self) -> Self {
Self::Lam(param.into(), Box::new(body))
}
#[must_use]
pub fn app(func: Self, arg: Self) -> Self {
Self::App(Box::new(func), Box::new(arg))
}
#[must_use]
pub fn let_in(name: impl Into<Arc<str>>, value: Self, body: Self) -> Self {
Self::Let {
name: name.into(),
value: Box::new(value),
body: Box::new(body),
}
}
#[must_use]
pub fn field(expr: Self, name: impl Into<Arc<str>>) -> Self {
Self::Field(Box::new(expr), name.into())
}
#[must_use]
pub const fn builtin(op: BuiltinOp, args: Vec<Self>) -> Self {
Self::Builtin(op, args)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtin_arities() {
assert_eq!(BuiltinOp::Add.arity(), 2);
assert_eq!(BuiltinOp::Not.arity(), 1);
assert_eq!(BuiltinOp::Fold.arity(), 3);
assert_eq!(BuiltinOp::Slice.arity(), 3);
}
#[test]
fn expr_constructors() {
let e = Expr::let_in(
"x",
Expr::Lit(Literal::Int(42)),
Expr::builtin(
BuiltinOp::Add,
vec![Expr::var("x"), Expr::Lit(Literal::Int(1))],
),
);
assert!(matches!(e, Expr::Let { .. }));
}
}