Expand support for stuff

This commit is contained in:
Richard Feldman 2019-01-28 00:35:31 -05:00
parent 89b6399b20
commit 2e1c9c1c51
8 changed files with 117 additions and 74 deletions

7
Cargo.lock generated
View File

@ -11,6 +11,11 @@ name = "difference"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "maplit"
version = "1.0.1"
@ -29,6 +34,7 @@ dependencies = [
name = "roc"
version = "0.1.0"
dependencies = [
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -55,6 +61,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
"checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43"
"checksum pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a029430f0d744bc3d15dd474d591bed2402b645d024583082b9f63bb936dac6"
"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"

View File

@ -4,6 +4,7 @@ version = "0.1.0"
authors = ["Richard Feldman <richard.t.feldman@gmail.com>"]
[dependencies]
lazy_static = "1.2.0"
[dev-dependencies]
pretty_assertions="0.5.1"

View File

@ -4,13 +4,24 @@ use unify::Literal;
pub fn eval<'a>(expr: &'a Expr<'a>) -> &'a Literal<'a> {
match expr {
Expr::Literal(literal) => literal,
Expr::Assignment(_, subexpr) => eval(subexpr)
Expr::Assignment(_, subexpr) => eval(subexpr),
Expr::If(cond, if_true, if_false) => {
match eval(cond) {
Literal::Symbol("True") => eval(if_true),
Literal::Symbol("False") => eval(if_false),
_ => {
panic!("somehow an if-conditional did not evaluate to True or False!")
}
}
}
}
}
pub fn literal_to_string<'a>(literal: &'a Literal<'a>) -> String {
match literal {
Literal::String(str) => format!("\"{}\"", str),
Literal::Symbol(str) => str.to_string(),
Literal::Number(str) => str.to_string(),
Literal::Record(fields) => {
let mut field_strings = Vec::new();

View File

@ -1,3 +1,5 @@
#![feature(box_patterns)]
pub mod unify;
pub mod interpret;
pub mod repl;

View File

@ -1,35 +1,42 @@
use interpret::{eval, literal_to_string};
use unify::infer;
use unify::Expr;
use unify::Field;
use unify::Type;
pub fn eval_and_print<'a>(expr: &Expr<'a>) -> String {
let lit = eval(expr);
let typ = infer(&Expr::Literal(lit));
match infer(&expr) {
Ok(typ) => {
let lit = eval(expr);
format!("{}\n: {}", literal_to_string(lit), type_to_string(typ))
format!("{}\n: {}", literal_to_string(lit), type_to_string(&typ))
},
Err(_) =>
"[TYPE MISMATCH!]".to_string()
}
}
pub fn type_to_string<'a>(typ: Type<'a>) -> String {
pub fn type_to_string<'a>(typ: &'a Type<'a>) -> String {
match typ {
Type::String => "String".to_string(),
Type::Int => "Int".to_string(),
Type::Float => "Float".to_string(),
Type::Number => "Int | Float".to_string(),
Type::Symbol(sym) => format!(":{}", sym),
Type::Record(fields) => {
let mut field_strings = Vec::new();
let mut field_pairs = fields.into_iter().collect::<Vec<(Field<'a>, Type<'a>)>>();
// Sort record fields alphabetically in the type
field_pairs.sort_unstable_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap());
for (field, subtyp) in field_pairs {
let field_strings = fields.into_iter().map(|(field, subtyp)| {
let typ_str = type_to_string(subtyp);
field_strings.push(format!("{} : {}", field, typ_str));
}
format!("{} : {}", field, typ_str)
});
format!("{{ {} }}", field_strings.join(", "))
format!("{{ {} }}", field_strings.collect::<Vec<String>>().join(", "))
}
Type::Assignment(_, assigned_typ) => type_to_string(assigned_typ),
Type::Union(set) => {
set.into_iter().collect::<Vec<&'a Type<'a>>>().into_iter().map(|typ_in_set| {
type_to_string(typ_in_set)
}).collect::<Vec<String>>().join(" | |")
}
Type::Assignment(_, typ) => type_to_string(*typ),
}
}

View File

@ -1,55 +1,19 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::cmp::Ordering;
use std::collections::BTreeSet;
pub type Ident<'a> = &'a str;
pub type Field<'a> = &'a str;
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Type<'a> {
String,
Int,
Float,
Number,
Symbol(&'a str),
Record(Vec<(Field<'a>, Type<'a>)>),
Assignment(Ident<'a>, Box<Type<'a>>),
Union(TypeSet<'a>),
}
#[derive(Debug)]
struct TypeSet<'a>(HashSet<Type<'a>>);
impl<'a> PartialOrd for TypeSet<'a> {
fn partial_cmp(&self, other: &TypeSet<'a>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'a> Ord for TypeSet<'a> {
fn cmp(&self, other: &TypeSet<'a>) -> Ordering {
panic!("TypeSet does not support ordering or equality. Do not use them!");
}
}
impl<'a> PartialEq for TypeSet<'a> {
fn eq(&self, other: &TypeSet) -> bool {
match (self, other) {
(TypeSet(my_set), TypeSet(other_set)) => {
// Delegate to HashSet's eq
my_set == other_set
}
}
}
}
impl<'a> Eq for TypeSet<'a> {}
impl<'a> Hash for TypeSet<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
panic!("at the dissco");
// self.id.hash(state);
// self.phone.hash(state);
}
Union(BTreeSet<Type<'a>>),
}
// CANONICAL IR - we have already done stuff like giving errors for
@ -68,31 +32,83 @@ pub enum Expr<'a> {
#[derive(Debug, PartialEq)]
pub enum Literal<'a> {
String(&'a str),
Number(&'a str),
Symbol(&'a str),
Record(Vec<(Field<'a>, &'a Expr<'a>)>)
}
pub fn infer<'a>(expr: &Expr<'a>) -> Result<Type<'a>, ()> {
pub fn infer<'a>(expr: &Expr<'a>) -> Result<Type<'a>, UnificationProblem> {
match expr {
Expr::Literal(Literal::String(_)) => Ok(Type::String),
Expr::Literal(Literal::Number(_)) => Ok(Type::Number),
Expr::Literal(Literal::Symbol(sym)) => Ok(Type::Symbol(sym)),
Expr::Literal(Literal::Record(fields)) => {
let mut rec_type: HashMap<&'a str, Type<'a>> = HashMap::new();
let mut rec_type: Vec<(&'a str, Type<'a>)> = Vec::new();
for (field, subexpr) in fields {
let field_type = infer(subexpr)?;
rec_type.insert(&field, field_type);
rec_type.push((&field, field_type));
}
Ok(Type::Record(rec_type))
},
Expr::If(cond, expr_if_true, expr_if_false) => {
panic!("TODO union the types of expr_if_true and expr_if_false");
Expr::If(box cond, expr_if_true, expr_if_false) => {
let cond_type = infer(&cond)?;
// if-conditionals must be of type Bool
if !matches_bool_type(&cond_type) {
return Err(UnificationProblem::IfConditionNotBool);
}
// unify the true and false branches
let true_type = infer(&expr_if_true)?;
let false_type = infer(&expr_if_false)?;
let mut unified_type = BTreeSet::new();
unified_type.insert(true_type);
unified_type.insert(false_type);
if unified_type.len() == 1 {
// No point in storing a union of 1.
//
// We can't reuse true_type because it's been moved into the set
// but we can pull it back out of the set
Ok(unified_type.into_iter().next().unwrap())
} else {
Ok(Type::Union(unified_type))
}
},
Expr::Assignment(ident, subexpr) => {
Ok(Type::Assignment(ident, Box::new(infer(subexpr)?)))
}
}
}
const TRUE_SYMBOL_STR: &'static str = "True";
const FALSE_SYMBOL_STR: &'static str = "False";
pub fn matches_bool_type<'a>(candidate: &Type<'a>) -> bool {
match candidate {
Type::Symbol(str) => {
str == &TRUE_SYMBOL_STR || str == &FALSE_SYMBOL_STR
}
Type::Union(types) => {
types.len() <= 2 && types.iter().all(|typ| matches_bool_type(typ))
}
_ => {
false
}
}
}
#[derive(Debug)]
pub enum UnificationProblem {
CannotUnifyAssignments,
NotMemberOfUnion,
IfConditionNotBool,
SymbolMismatch
}

View File

@ -19,7 +19,7 @@ mod repl_tests {
#[test]
fn test_record_literal() {
let expected = "{ string = \"abc\", record = { x = \"one\", y = \"two\" } }\n: { record : { x : String, y : String }, string : String }";
let expected = "{ string = \"abc\", record = { x = \"one\", y = \"two\" } }\n: { string : String, record : { x : String, y : String } }";
let str0 = &String("abc");
let str1 = &String("one");

View File

@ -1,4 +1,3 @@
#[macro_use] extern crate maplit;
#[macro_use] extern crate pretty_assertions;
extern crate roc;
@ -27,14 +26,14 @@ mod tests {
let expr = Literal(literal);
let expected_type = Type::Record(hashmap!{
"string" => Type::String,
"record" => Type::Record(hashmap!{
"x" => Type::String,
"y" => Type::String,
})
});
let expected_type = Type::Record(vec![
("string", Type::String),
("record", Type::Record(vec![
("x", Type::String),
("y", Type::String)
]))
]);
assert_eq!(expected_type, infer(&expr));
assert_eq!(expected_type, infer(&expr).unwrap());
}
}