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 ExprType {
Int,
Float,
Str,
Bool,
List,
Record,
Any,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum BuiltinOp {
Add,
Sub,
Mul,
Div,
Mod,
Neg,
Abs,
Floor,
Ceil,
Round,
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,
DefaultVal,
Clamp,
TruncateStr,
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::Round
| 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
| Self::DefaultVal
| Self::TruncateStr => 2,
Self::Slice | Self::Replace | Self::Fold | Self::Clamp => 3,
}
}
#[must_use]
pub const fn signature(self) -> Option<(&'static [ExprType], ExprType)> {
match self {
Self::IntToFloat => Some((&[ExprType::Int], ExprType::Float)),
Self::FloatToInt | Self::Floor | Self::Ceil | Self::Round => {
Some((&[ExprType::Float], ExprType::Int))
}
Self::IntToStr => Some((&[ExprType::Int], ExprType::Str)),
Self::FloatToStr => Some((&[ExprType::Float], ExprType::Str)),
Self::StrToInt | Self::Len => Some((&[ExprType::Str], ExprType::Int)),
Self::StrToFloat => Some((&[ExprType::Str], ExprType::Float)),
Self::And | Self::Or => Some((&[ExprType::Bool, ExprType::Bool], ExprType::Bool)),
Self::Not => Some((&[ExprType::Bool], ExprType::Bool)),
Self::Eq | Self::Neq | Self::Lt | Self::Lte | Self::Gt | Self::Gte => {
Some((&[ExprType::Any, ExprType::Any], ExprType::Bool))
}
Self::Concat => Some((&[ExprType::Str, ExprType::Str], ExprType::Str)),
Self::Slice => Some((
&[ExprType::Str, ExprType::Int, ExprType::Int],
ExprType::Str,
)),
Self::Upper | Self::Lower | Self::Trim => Some((&[ExprType::Str], ExprType::Str)),
Self::Split => Some((&[ExprType::Str, ExprType::Str], ExprType::List)),
Self::Join => Some((&[ExprType::List, ExprType::Str], ExprType::Str)),
Self::Replace => Some((
&[ExprType::Str, ExprType::Str, ExprType::Str],
ExprType::Str,
)),
Self::Contains => Some((&[ExprType::Str, ExprType::Str], ExprType::Bool)),
Self::TruncateStr => Some((&[ExprType::Str, ExprType::Int], ExprType::Str)),
Self::Length => Some((&[ExprType::List], ExprType::Int)),
Self::Reverse => Some((&[ExprType::List], ExprType::List)),
Self::MergeRecords => Some((&[ExprType::Record, ExprType::Record], ExprType::Record)),
Self::Keys | Self::Values => Some((&[ExprType::Record], ExprType::List)),
Self::HasField => Some((&[ExprType::Record, ExprType::Str], ExprType::Bool)),
Self::TypeOf => Some((&[ExprType::Any], ExprType::Str)),
Self::IsNull | Self::IsList => Some((&[ExprType::Any], ExprType::Bool)),
Self::Add
| Self::Sub
| Self::Mul
| Self::Div
| Self::Mod
| Self::Neg
| Self::Abs
| Self::Map
| Self::Filter
| Self::Fold
| Self::FlatMap
| Self::Append
| Self::Head
| Self::Tail
| Self::DefaultVal
| Self::Clamp
| Self::Edge
| Self::Children
| Self::HasEdge
| Self::EdgeCount
| Self::Anchor => None,
}
}
}
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)
}
#[must_use]
pub fn int_to_float(arg: Self) -> Self {
Self::Builtin(BuiltinOp::IntToFloat, vec![arg])
}
#[must_use]
pub fn float_to_int(arg: Self) -> Self {
Self::Builtin(BuiltinOp::FloatToInt, vec![arg])
}
#[must_use]
pub fn int_to_str(arg: Self) -> Self {
Self::Builtin(BuiltinOp::IntToStr, vec![arg])
}
#[must_use]
pub fn float_to_str(arg: Self) -> Self {
Self::Builtin(BuiltinOp::FloatToStr, vec![arg])
}
#[must_use]
pub fn str_to_int(arg: Self) -> Self {
Self::Builtin(BuiltinOp::StrToInt, vec![arg])
}
#[must_use]
pub fn str_to_float(arg: Self) -> Self {
Self::Builtin(BuiltinOp::StrToFloat, vec![arg])
}
}
#[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 { .. }));
}
}