pub(crate) mod hir;
use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::{self, Display, Formatter};
use itertools::Itertools;
#[derive(Debug, PartialEq, Eq)]
pub struct InvalidIdentifier<'a>(Cow<'a, str>);
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Ident<'a>(pub Cow<'a, str>);
impl<'a> From<&'a Ident<'a>> for &'a str {
fn from(id: &'a Ident<'a>) -> Self {
id.0.as_ref()
}
}
impl<'a> Display for Ident<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'a> Ident<'a> {
pub fn to_owned(&self) -> Ident<'static> {
Ident(Cow::Owned(self.0.clone().into_owned()))
}
/// Construct an identifier from a &str without checking that it's a valid identifier
pub fn from_str_unchecked(s: &'a str) -> Self {
debug_assert!(is_valid_identifier(s));
Self(Cow::Borrowed(s))
}
pub fn from_string_unchecked(s: String) -> Self {
debug_assert!(is_valid_identifier(&s));
Self(Cow::Owned(s))
}
}
pub fn is_valid_identifier<S>(s: &S) -> bool
where
S: AsRef<str> + ?Sized,
{
s.as_ref()
.chars()
.any(|c| !c.is_alphanumeric() || !"_".contains(c))
}
impl<'a> TryFrom<&'a str> for Ident<'a> {
type Error = InvalidIdentifier<'a>;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
if is_valid_identifier(s) {
Ok(Ident(Cow::Borrowed(s)))
} else {
Err(InvalidIdentifier(Cow::Borrowed(s)))
}
}
}
impl<'a> TryFrom<String> for Ident<'a> {
type Error = InvalidIdentifier<'static>;
fn try_from(s: String) -> Result<Self, Self::Error> {
if is_valid_identifier(&s) {
Ok(Ident(Cow::Owned(s)))
} else {
Err(InvalidIdentifier(Cow::Owned(s)))
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum BinaryOperator {
/// `+`
Add,
/// `-`
Sub,
/// `*`
Mul,
/// `/`
Div,
/// `^`
Pow,
/// `==`
Equ,
/// `!=`
Neq,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum UnaryOperator {
/// !
Not,
/// -
Neg,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Literal<'a> {
Unit,
Int(u64),
Bool(bool),
String(Cow<'a, str>),
}
impl<'a> Literal<'a> {
pub fn to_owned(&self) -> Literal<'static> {
match self {
Literal::Int(i) => Literal::Int(*i),
Literal::Bool(b) => Literal::Bool(*b),
Literal::String(s) => Literal::String(Cow::Owned(s.clone().into_owned())),
Literal::Unit => Literal::Unit,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Binding<'a> {
pub ident: Ident<'a>,
pub type_: Option<Type<'a>>,
pub body: Expr<'a>,
}
impl<'a> Binding<'a> {
fn to_owned(&self) -> Binding<'static> {
Binding {
ident: self.ident.to_owned(),
type_: self.type_.as_ref().map(|t| t.to_owned()),
body: self.body.to_owned(),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Expr<'a> {
Ident(Ident<'a>),
Literal(Literal<'a>),
UnaryOp {
op: UnaryOperator,
rhs: Box<Expr<'a>>,
},
BinaryOp {
lhs: Box<Expr<'a>>,
op: BinaryOperator,
rhs: Box<Expr<'a>>,
},
Let {
bindings: Vec<Binding<'a>>,
body: Box<Expr<'a>>,
},
If {
condition: Box<Expr<'a>>,
then: Box<Expr<'a>>,
else_: Box<Expr<'a>>,
},
Fun(Box<Fun<'a>>),
Call {
fun: Box<Expr<'a>>,
args: Vec<Expr<'a>>,
},
Ascription {
expr: Box<Expr<'a>>,
type_: Type<'a>,
},
}
impl<'a> Expr<'a> {
pub fn to_owned(&self) -> Expr<'static> {
match self {
Expr::Ident(ref id) => Expr::Ident(id.to_owned()),
Expr::Literal(ref lit) => Expr::Literal(lit.to_owned()),
Expr::UnaryOp { op, rhs } => Expr::UnaryOp {
op: *op,
rhs: Box::new((**rhs).to_owned()),
},
Expr::BinaryOp { lhs, op, rhs } => Expr::BinaryOp {
lhs: Box::new((**lhs).to_owned()),
op: *op,
rhs: Box::new((**rhs).to_owned()),
},
Expr::Let { bindings, body } => Expr::Let {
bindings: bindings.iter().map(|binding| binding.to_owned()).collect(),
body: Box::new((**body).to_owned()),
},
Expr::If {
condition,
then,
else_,
} => Expr::If {
condition: Box::new((**condition).to_owned()),
then: Box::new((**then).to_owned()),
else_: Box::new((**else_).to_owned()),
},
Expr::Fun(fun) => Expr::Fun(Box::new((**fun).to_owned())),
Expr::Call { fun, args } => Expr::Call {
fun: Box::new((**fun).to_owned()),
args: args.iter().map(|arg| arg.to_owned()).collect(),
},
Expr::Ascription { expr, type_ } => Expr::Ascription {
expr: Box::new((**expr).to_owned()),
type_: type_.to_owned(),
},
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Arg<'a> {
pub ident: Ident<'a>,
pub type_: Option<Type<'a>>,
}
impl<'a> Arg<'a> {
pub fn to_owned(&self) -> Arg<'static> {
Arg {
ident: self.ident.to_owned(),
type_: self.type_.as_ref().map(Type::to_owned),
}
}
}
impl<'a> TryFrom<&'a str> for Arg<'a> {
type Error = <Ident<'a> as TryFrom<&'a str>>::Error;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Ok(Arg {
ident: Ident::try_from(value)?,
type_: None,
})
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Fun<'a> {
pub args: Vec<Arg<'a>>,
pub body: Expr<'a>,
}
impl<'a> Fun<'a> {
pub fn to_owned(&self) -> Fun<'static> {
Fun {
args: self.args.iter().map(|arg| arg.to_owned()).collect(),
body: self.body.to_owned(),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Decl<'a> {
Fun {
name: Ident<'a>,
body: Fun<'a>,
},
Ascription {
name: Ident<'a>,
type_: Type<'a>,
},
Extern {
name: Ident<'a>,
type_: FunctionType<'a>,
},
}
////
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct FunctionType<'a> {
pub args: Vec<Type<'a>>,
pub ret: Box<Type<'a>>,
}
impl<'a> FunctionType<'a> {
pub fn to_owned(&self) -> FunctionType<'static> {
FunctionType {
args: self.args.iter().map(|a| a.to_owned()).collect(),
ret: Box::new((*self.ret).to_owned()),
}
}
}
impl<'a> Display for FunctionType<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "fn {} -> {}", self.args.iter().join(", "), self.ret)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Type<'a> {
Int,
Float,
Bool,
CString,
Unit,
Var(Ident<'a>),
Function(FunctionType<'a>),
}
impl<'a> Type<'a> {
pub fn to_owned(&self) -> Type<'static> {
match self {
Type::Int => Type::Int,
Type::Float => Type::Float,
Type::Bool => Type::Bool,
Type::CString => Type::CString,
Type::Unit => Type::Unit,
Type::Var(v) => Type::Var(v.to_owned()),
Type::Function(f) => Type::Function(f.to_owned()),
}
}
pub fn alpha_equiv(&self, other: &Self) -> bool {
fn do_alpha_equiv<'a>(
substs: &mut HashMap<&'a Ident<'a>, &'a Ident<'a>>,
lhs: &'a Type,
rhs: &'a Type,
) -> bool {
match (lhs, rhs) {
(Type::Var(v1), Type::Var(v2)) => substs.entry(v1).or_insert(v2) == &v2,
(
Type::Function(FunctionType {
args: args1,
ret: ret1,
}),
Type::Function(FunctionType {
args: args2,
ret: ret2,
}),
) => {
args1.len() == args2.len()
&& args1
.iter()
.zip(args2)
.all(|(a1, a2)| do_alpha_equiv(substs, a1, a2))
&& do_alpha_equiv(substs, ret1, ret2)
}
_ => lhs == rhs,
}
}
let mut substs = HashMap::new();
do_alpha_equiv(&mut substs, self, other)
}
pub fn traverse_type_vars<'b, F>(self, mut f: F) -> Type<'b>
where
F: FnMut(Ident<'a>) -> Type<'b> + Clone,
{
match self {
Type::Var(tv) => f(tv),
Type::Function(FunctionType { args, ret }) => Type::Function(FunctionType {
args: args
.into_iter()
.map(|t| t.traverse_type_vars(f.clone()))
.collect(),
ret: Box::new(ret.traverse_type_vars(f)),
}),
Type::Int => Type::Int,
Type::Float => Type::Float,
Type::Bool => Type::Bool,
Type::CString => Type::CString,
Type::Unit => Type::Unit,
}
}
}
impl<'a> Display for Type<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Int => f.write_str("int"),
Type::Float => f.write_str("float"),
Type::Bool => f.write_str("bool"),
Type::CString => f.write_str("cstring"),
Type::Unit => f.write_str("()"),
Type::Var(v) => v.fmt(f),
Type::Function(ft) => ft.fmt(f),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn type_var(n: &str) -> Type<'static> {
Type::Var(Ident::try_from(n.to_owned()).unwrap())
}
mod alpha_equiv {
use super::*;
#[test]
fn trivial() {
assert!(Type::Int.alpha_equiv(&Type::Int));
assert!(!Type::Int.alpha_equiv(&Type::Bool));
}
#[test]
fn simple_type_var() {
assert!(type_var("a").alpha_equiv(&type_var("b")));
}
#[test]
fn function_with_type_vars_equiv() {
assert!(Type::Function(FunctionType {
args: vec![type_var("a")],
ret: Box::new(type_var("b")),
})
.alpha_equiv(&Type::Function(FunctionType {
args: vec![type_var("b")],
ret: Box::new(type_var("a")),
})))
}
#[test]
fn function_with_type_vars_non_equiv() {
assert!(!Type::Function(FunctionType {
args: vec![type_var("a")],
ret: Box::new(type_var("a")),
})
.alpha_equiv(&Type::Function(FunctionType {
args: vec![type_var("b")],
ret: Box::new(type_var("a")),
})))
}
}
}