aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Kronqvist <joel.kronqvist@iki.fi>2025-08-19 13:55:05 +0300
committerJoel Kronqvist <joel.kronqvist@iki.fi>2025-08-19 13:55:05 +0300
commita70dcaa949f41c585f9aea5e79f2550053d8e857 (patch)
tree9366f0b1e026c009708ebc243d006a79cbf452c9
parentd4d6e972650370d529363f7db3946e58bdbd7bca (diff)
downloadmyslip-a70dcaa949f41c585f9aea5e79f2550053d8e857.tar.gz
myslip-a70dcaa949f41c585f9aea5e79f2550053d8e857.zip
test: added boilerplate and tests for coproduct parsing, type checking and evaluation
-rw-r--r--src/parse/parsetree.rs4
-rw-r--r--src/sexp/case.rs12
-rw-r--r--src/sexp/display.rs3
-rw-r--r--src/sexp/mod.rs3
-rw-r--r--src/sexp/step.rs8
-rw-r--r--src/type/case.rs18
-rw-r--r--src/type/check.rs43
-rw-r--r--src/type/display.rs1
-rw-r--r--src/type/mod.rs5
-rw-r--r--src/type/subst.rs2
-rw-r--r--src/type/util.rs4
11 files changed, 102 insertions, 1 deletions
diff --git a/src/parse/parsetree.rs b/src/parse/parsetree.rs
index c773f3b..081489d 100644
--- a/src/parse/parsetree.rs
+++ b/src/parse/parsetree.rs
@@ -119,6 +119,9 @@ fn tokens_to_ast_inner(
Some(Sym(s)) if s == "false" => Ok(Atom(False)),
Some(Sym(s)) if s == "quote" => Ok(Atom(Quote)),
Some(Sym(s)) if s == "vector" => Ok(Atom(Vector)),
+ Some(Sym(s)) if s == "coprod" => Ok(Atom(Coprod)),
+ Some(Sym(s)) if s == "inl" => Ok(Atom(Inl)),
+ Some(Sym(s)) if s == "inr" => Ok(Atom(Inr)),
Some(Sym(s)) if s == "<>" => Ok(Atom(Concat)),
Some(Sym(s)) if s == "print" => Ok(Atom(Print)),
Some(Sym(s)) if s == "let" => Ok(Atom(Let)),
@@ -248,6 +251,7 @@ mod private_parsing_tests {
Ok(("", List(vec![Integer, Boolean, arr(Boolean, Integer), Integer])))
);
assert_eq!(parse_type("T"), Ok(("", VarType("T".to_string()))));
+ assert_eq!(parse_type("(Sum (Int -> Bool) Int)"), Ok(("", sumtype(arr(Integer, Boolean), Integer))));
}
#[test]
diff --git a/src/sexp/case.rs b/src/sexp/case.rs
index 4c2c7ba..cc643f3 100644
--- a/src/sexp/case.rs
+++ b/src/sexp/case.rs
@@ -52,6 +52,18 @@ impl SExp {
* ])
* );
* ```
+ *
+ * Coproducts must be handled individually:
+ * ```rust
+ * use myslip::r#type::Type::*;
+ * use myslip::sexp::{SExp::*, SLeaf::*, util::*};
+ *
+ * assert_eq!(
+ * scons(Coprod, scons(Ty(Boolean), scons(2, Nil)))
+ * .matches_pat(&scons(Inr, scons(var("x"), Nil))),
+ * Some(vec![(Var("x".to_string()), Atom(Int(2)))])
+ * );
+ * ```
*/
pub fn matches_pat(&self, pat: &SExp) -> Option<Vec<(SLeaf, SExp)>> {
match (self, pat) {
diff --git a/src/sexp/display.rs b/src/sexp/display.rs
index ec1b421..703bda3 100644
--- a/src/sexp/display.rs
+++ b/src/sexp/display.rs
@@ -28,6 +28,9 @@ impl fmt::Display for SLeaf {
RestPat(s) => format!("..{s}"),
Quote => "quote".to_string(),
Vector => "vector".to_string(),
+ Coprod => "coprod".to_string(),
+ Inl => "inl".to_string(),
+ Inr => "inr".to_string(),
Concat => "<>".to_string(),
Print => "print".to_string(),
Let => "let".to_string(),
diff --git a/src/sexp/mod.rs b/src/sexp/mod.rs
index 1a90f52..14731dd 100644
--- a/src/sexp/mod.rs
+++ b/src/sexp/mod.rs
@@ -34,6 +34,9 @@ pub enum SLeaf {
Quote,
Vector,
+ Coprod,
+ Inl,
+ Inr,
Let,
diff --git a/src/sexp/step.rs b/src/sexp/step.rs
index 59b201c..3168c5b 100644
--- a/src/sexp/step.rs
+++ b/src/sexp/step.rs
@@ -297,6 +297,14 @@ impl SExp {
/// assert_eq!(exp, expshould);
/// let exp = exp.and_then(|e| e.step());
/// assert_eq!(exp, Ok(Atom(True)));
+ ///
+ /// let exp = "case (coprod Bool (+ 1 2)) ((inl b) (if b 1 0)) ((inr x) (- x 1))";
+ /// let exp = parse_to_ast(exp);
+ /// let exp = exp.and_then(|e| e.step());
+ /// let expshould = parse_to_ast("case (coprod Bool 3) ((inl b) (if b 1 0)) ((inr x) (- x 1))");
+ /// assert_eq!(exp, expshould);
+ /// let exp = exp.and_then(|e| e.step());
+ /// assert_eq!(exp, Ok(scons(Sub, scons(3, scons(1, Nil)))));
/// ```
///
/// Shadowing:
diff --git a/src/type/case.rs b/src/type/case.rs
index c53bbd0..d8871af 100644
--- a/src/type/case.rs
+++ b/src/type/case.rs
@@ -324,4 +324,22 @@ mod tests {
Ok(vec![])
);
}
+
+ #[test]
+ fn test_sumtype_match() {
+ assert_eq!(
+ scons(Inl, scons(var("x"), Nil)).matches_type(&sumtype(Integer, Boolean)),
+ Ok(vec![("x".to_string(), Integer)])
+ );
+ assert_eq!(
+ scons(Inr, scons(var("x"), Nil)).matches_type(&sumtype(Integer, Boolean)),
+ Ok(vec![("x".to_string(), Boolean)])
+ );
+ assert!(
+ scons(Inl, scons(True, Nil)).matches_type(&sumtype(Integer, Boolean)).is_err()
+ );
+ assert!(
+ scons(Inr, scons(1, Nil)).matches_type(&sumtype(Integer, Boolean)).is_err()
+ );
+ }
}
diff --git a/src/type/check.rs b/src/type/check.rs
index c0f4b1f..96a4408 100644
--- a/src/type/check.rs
+++ b/src/type/check.rs
@@ -60,6 +60,25 @@ impl SExp {
/// ```
/// ...so please don't ask what their type is.
///
+ /// **Sum types**
+ ///
+ /// Instantiation types:
+ /// ```rust
+ /// use myslip::{
+ /// r#type::{*, Type::*, TypeError::*, util::*},
+ /// sexp::{SExp::*, SLeaf::*, util::*},
+ /// };
+ /// assert_eq!(
+ /// scons(Coprod, scons(7, scons(Ty(Boolean), Nil))).type_check(),
+ /// Ok(sumtype(Integer, Boolean))
+ /// );
+ /// assert_eq!(
+ /// scons(Coprod, scons(Ty(Boolean), scons(False, Nil))).type_check(),
+ /// Ok(sumtype(Boolean, Boolean))
+ /// );
+ /// ```
+ /// Destructuring is tested in the tests of case.
+ ///
/// Some common operators get arrow types:
/// ```rust
/// use myslip::{
@@ -161,7 +180,28 @@ impl SExp {
/// Ok(Integer)
/// );
/// ```
+ ///
+ /// Here's a test on pattern matching on sum types:
+ /// ```rust
+ /// use myslip::{
+ /// r#type::{*, Type::*, TypeError::*, util::*},
+ /// sexp::{SExp::*, SLeaf::*, util::*},
+ /// parse::parsetree::parse_to_ast
+ /// };
///
+ /// fn main() {
+ /// let exp = parse_to_ast(
+ /// "(let mysum (coprod Bool 7)) \
+ /// (case mysum \
+ /// ((inl b) (if b 0 1)) \
+ /// ((inr x) x))"
+ /// ).unwrap();
+ /// assert_eq!(
+ /// exp.type_check(),
+ /// Ok(Integer)
+ /// );
+ /// }
+ /// ```
///
/// Though perhaps the most important task of the type system
/// is to increase safety by being able to warn about errors
@@ -260,6 +300,9 @@ impl SExp {
vecof(vt("T")),
List(vec![VecType, vecof(vt("T"))])
)),
+ Atom(Coprod) => todo!(),
+ Atom(Inl) => todo!(),
+ Atom(Inr) => todo!(),
Atom(Let) => Ok(LetType),
Atom(Print) => Ok(arr(vt("_"), NilType)),
Atom(Ty(_)) => Ok(TypeLit),
diff --git a/src/type/display.rs b/src/type/display.rs
index a2f4b58..99394df 100644
--- a/src/type/display.rs
+++ b/src/type/display.rs
@@ -35,6 +35,7 @@ impl fmt::Display for Type {
TypeLit => write!(f, "Type"),
VecOf(ty) => write!(f, "({} ...)", *ty),
Arrow(a, b) => write!(f, "({} -> {})", a, b),
+ SumType(a,b)=> write!(f, "(Sum {} {})", a, b),
List(types) => write!(
f,
"({})",
diff --git a/src/type/mod.rs b/src/type/mod.rs
index bfe0055..c726001 100644
--- a/src/type/mod.rs
+++ b/src/type/mod.rs
@@ -31,6 +31,8 @@ pub enum Type {
VecOf(Box<Type>),
VecType, // constructor
+ SumType(Box<Type>, Box<Type>),
+
LetType,
/// Type for generics
@@ -156,7 +158,8 @@ impl Type {
LetType => Ok(()),
NilType => Ok(()),
TypeLit => Ok(()),
- Arrow(a, b) => b.is_concrete().and_then(|_ok| a.is_concrete()),
+ Arrow(a, b) => b.is_concrete().and_then(|_ok| a.is_concrete()),
+ SumType(a, b) => b.is_concrete().and_then(|_ok| a.is_concrete()),
List(v) => {
let mut res = Ok(());
for t in v {
diff --git a/src/type/subst.rs b/src/type/subst.rs
index 18c9926..fd45005 100644
--- a/src/type/subst.rs
+++ b/src/type/subst.rs
@@ -27,6 +27,8 @@ impl Type {
Arrow(a, b) => arr((*a).subst(name, value), (*b).subst(name, value)),
+ SumType(a, b) => sumtype((*a).subst(name, value), (*b).subst(name, value)),
+
List(v) => List(
v.into_iter()
.map(|t| t.subst(name, value))
diff --git a/src/type/util.rs b/src/type/util.rs
index 04b4ee6..0ea1e4d 100644
--- a/src/type/util.rs
+++ b/src/type/util.rs
@@ -6,6 +6,10 @@ pub fn arr(a: impl Into<Box<Type>>, b: impl Into<Box<Type>>) -> Type {
Arrow(a.into(), b.into())
}
+pub fn sumtype(a: impl Into<Box<Type>>, b: impl Into<Box<Type>>) -> Type {
+ SumType(a.into(), b.into())
+}
+
pub fn vt(name: &str) -> Type {
VarType(name.to_string())
}