mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-12 23:50:20 +03:00
Expand support for stuff
This commit is contained in:
parent
89b6399b20
commit
2e1c9c1c51
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![feature(box_patterns)]
|
||||
|
||||
pub mod unify;
|
||||
pub mod interpret;
|
||||
pub mod repl;
|
||||
|
39
src/repl.rs
39
src/repl.rs
@ -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),
|
||||
|
||||
}
|
||||
}
|
||||
|
110
src/unify.rs
110
src/unify.rs
@ -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
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user