Add lists

This commit is contained in:
Richard Feldman 2019-08-28 00:04:08 -04:00
parent 46750ae6ca
commit 8985b4bec7
9 changed files with 200 additions and 12 deletions

View file

@ -18,6 +18,8 @@ pub enum Expr {
EmptyStr, EmptyStr,
Str(String), Str(String),
Char(char), Char(char),
List(Vec<Located<Expr>>),
EmptyList,
// Lookups // Lookups
Var(Symbol), Var(Symbol),
@ -304,6 +306,24 @@ fn canonicalize(
expr::Expr::Str(string) => ( Str(string), Output::new()), expr::Expr::Str(string) => ( Str(string), Output::new()),
expr::Expr::Char(ch) => ( Char(ch), Output::new()), expr::Expr::Char(ch) => ( Char(ch), Output::new()),
expr::Expr::EmptyStr => ( EmptyStr, Output::new()), expr::Expr::EmptyStr => ( EmptyStr, Output::new()),
expr::Expr::EmptyList => ( EmptyList, Output::new()),
expr::Expr::List(elems) => {
let mut output = Output::new();
let mut can_elems = Vec::with_capacity(elems.len());
for loc_elem in elems {
let ( can_expr, elem_out ) = canonicalize(env, scope, loc_elem);
output.references = output.references.union(elem_out.references);
can_elems.push(can_expr);
}
// A list literal is never a tail call!
output.tail_call = None;
( List(can_elems), output )
},
expr::Expr::If(loc_cond, loc_true, loc_false) => { expr::Expr::If(loc_cond, loc_true, loc_false) => {
// Canonicalize the nested expressions // Canonicalize the nested expressions

View file

@ -34,10 +34,16 @@ pub fn constrain(
EmptyStr => { Eq(string(), expected, region) }, EmptyStr => { Eq(string(), expected, region) },
InterpolatedStr(_, _) => { Eq(string(), expected, region) }, InterpolatedStr(_, _) => { Eq(string(), expected, region) },
EmptyRecord => { Eq(EmptyRec, expected, region) }, EmptyRecord => { Eq(EmptyRec, expected, region) },
EmptyList => { Eq(empty_list(subs.mk_flex_var()), expected, region) },
List(elems) => { list(elems, bound_vars.clone(), subs, expected, region) },
_ => { panic!("TODO constraints") } _ => { panic!("TODO constraints") }
} }
} }
fn empty_list(var: Variable) -> Type {
builtin_type("List", "List", vec![Type::Variable(var)])
}
fn string() -> Type { fn string() -> Type {
builtin_type("String", "String", Vec::new()) builtin_type("String", "String", Vec::new())
} }
@ -46,6 +52,34 @@ fn num(var: Variable) -> Type {
builtin_type("Num", "Num", vec![Type::Variable(var)]) builtin_type("Num", "Num", vec![Type::Variable(var)])
} }
fn list(loc_elems: Vec<Located<Expr>>, bound_vars: BoundTypeVars, subs: &mut Subs, expected: Expected<Type>, region: Region) -> Constraint {
let list_var = subs.mk_flex_var(); // `v` in the type (List v)
let list_type = Type::Variable(list_var);
let mut constraints = Vec::with_capacity(1 + (loc_elems.len() * 2));
for loc_elem in loc_elems {
let elem_var = subs.mk_flex_var();
let elem_type = Variable(elem_var);
let elem_expected = NoExpectation(elem_type.clone());
let elem_constraint = constrain(bound_vars.clone(), subs, loc_elem, elem_expected);
let list_elem_constraint =
Eq(
list_type.clone(),
ForReason(Reason::ElemInList, elem_type, region.clone()),
region.clone()
);
constraints.push(elem_constraint);
constraints.push(list_elem_constraint);
}
constraints.push(
Eq(builtin_type("List", "List", vec![list_type]), expected, region)
);
And(constraints)
}
fn fractional(subs: &mut Subs, expected: Expected<Type>, region: Region) -> Constraint { fn fractional(subs: &mut Subs, expected: Expected<Type>, region: Region) -> Constraint {
// We'll make a Num var1 and a Fractional var2, // We'll make a Num var1 and a Fractional var2,
// and then add a constraint that var1 needs to equal Fractional var2 // and then add a constraint that var1 needs to equal Fractional var2

View file

@ -11,6 +11,8 @@ pub enum Expr {
EmptyStr, EmptyStr,
Str(String), Str(String),
Char(char), Char(char),
List(Vec<Located<Expr>>),
EmptyList,
// Lookups // Lookups
Var(Ident), Var(Ident),
@ -113,7 +115,15 @@ impl Expr {
let transformed = transform(self); let transformed = transform(self);
match transformed { match transformed {
Int(_) | Frac(_, _) | Approx(_) | EmptyStr | Str(_) | Char(_) | Var(_) | EmptyRecord | InterpolatedStr(_, _) => transformed, Int(_) | Frac(_, _) | Approx(_) | EmptyStr | Str(_) | Char(_) | Var(_) | EmptyRecord | InterpolatedStr(_, _) | EmptyList => transformed,
List(elems) => {
let new_elems =
elems.into_iter()
.map(|loc_elem| loc_elem.with_value(loc_elem.value.walk(transform)))
.collect();
List(new_elems)
}
Assign(assignments, loc_ret) => { Assign(assignments, loc_ret) => {
Assign( Assign(
assignments.into_iter().map(|(pattern, loc_expr)| assignments.into_iter().map(|(pattern, loc_expr)|

View file

@ -6,7 +6,7 @@ use std::char;
use parse_state::{IndentablePosition}; use parse_state::{IndentablePosition};
use combine::parser::char::{char, string, spaces, digit, hex_digit, HexDigit, alpha_num}; use combine::parser::char::{char, string, spaces, digit, hex_digit, HexDigit, alpha_num};
use combine::parser::repeat::{many, count_min_max, sep_by1, skip_many, skip_many1, skip_until}; use combine::parser::repeat::{many, count_min_max, sep_by, sep_by1, skip_many, skip_many1, skip_until};
use combine::parser::item::{any, satisfy_map, value, position, satisfy}; use combine::parser::item::{any, satisfy_map, value, position, satisfy};
use combine::parser::combinator::{look_ahead, not_followed_by}; use combine::parser::combinator::{look_ahead, not_followed_by};
use combine::{attempt, choice, eof, many1, parser, Parser, optional, between, unexpected_any, unexpected}; use combine::{attempt, choice, eof, many1, parser, Parser, optional, between, unexpected_any, unexpected};
@ -205,6 +205,7 @@ parser! {
choice(( choice((
closure(min_indent), closure(min_indent),
apply_with_parens(min_indent), apply_with_parens(min_indent),
list(min_indent),
string("{}").with(value(Expr::EmptyRecord)), string("{}").with(value(Expr::EmptyRecord)),
string_literal(), string_literal(),
int_or_frac_literal(), int_or_frac_literal(),
@ -237,7 +238,6 @@ parser! {
located(choice(( located(choice((
function_arg_expr(min_indent), function_arg_expr(min_indent),
apply_with_parens(min_indent),
assignment(min_indent), assignment(min_indent),
apply_variant(min_indent), apply_variant(min_indent),
func_or_var(min_indent), func_or_var(min_indent),
@ -312,6 +312,27 @@ where I: Stream<Item = char, Position = IndentablePosition>,
) )
} }
pub fn list<I>(min_indent: u32) -> impl Parser<Input = I, Output = Expr>
where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position>
{
between(char('['), char(']'),
sep_by(
indented_whitespaces(min_indent)
.with(located(expr_body(min_indent)))
.skip(indented_whitespaces(min_indent)),
char(',')
)
).map(|loc_elems: Vec<Located<Expr>>| {
if loc_elems.is_empty() {
Expr::EmptyList
} else {
Expr::List(loc_elems)
}
})
}
pub fn apply_with_parens<I>(min_indent: u32) -> impl Parser<Input = I, Output = Expr> pub fn apply_with_parens<I>(min_indent: u32) -> impl Parser<Input = I, Output = Expr>
where I: Stream<Item = char, Position = IndentablePosition>, where I: Stream<Item = char, Position = IndentablePosition>,
I::Error: ParseError<I::Item, I::Range, I::Position> I::Error: ParseError<I::Item, I::Range, I::Position>
@ -322,7 +343,7 @@ where I: Stream<Item = char, Position = IndentablePosition>,
.skip(indented_whitespaces(min_indent)) .skip(indented_whitespaces(min_indent))
).and( ).and(
// Parenthetical expressions can optionally be followed by // Parenthetical expressions can optionally be followed by
// whitespace and one or more comma-separated expressions, // whitespace and one or more whitespace-separated expressions,
// meaning this is function application! // meaning this is function application!
optional( optional(
attempt(apply_args(min_indent)) attempt(apply_args(min_indent))

View file

@ -29,7 +29,9 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, use_p
match flat_type { match flat_type {
Apply(module_name, type_name, vars) => { Apply(module_name, type_name, vars) => {
if use_parens { let write_parens = use_parens && !vars.is_empty();
if write_parens {
buf.push_str("("); buf.push_str("(");
} }
@ -40,7 +42,7 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, use_p
write_content(subs.get(var).content, subs, buf, true); write_content(subs.get(var).content, subs, buf, true);
} }
if use_parens { if write_parens {
buf.push_str(")"); buf.push_str(")");
} }
}, },

View file

@ -40,7 +40,8 @@ impl<T> Expected<T> {
pub enum Reason { pub enum Reason {
OperatorLeftArg(Operator), OperatorLeftArg(Operator),
OperatorRightArg(Operator), OperatorRightArg(Operator),
FractionalLiteral FractionalLiteral,
ElemInList,
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -38,8 +38,16 @@ pub fn zero_loc_expr(expr: Expr) -> Expr {
use roc::expr::Expr::*; use roc::expr::Expr::*;
match expr { match expr {
Int(_) | Frac(_, _) | Approx(_) | EmptyStr | Str(_) | Char(_) | Var(_) | EmptyRecord => expr, Int(_) | Frac(_, _) | Approx(_) | EmptyStr | Str(_) | Char(_) | Var(_) | EmptyRecord | EmptyList => expr,
InterpolatedStr(pairs, string) => InterpolatedStr(pairs.into_iter().map(|( prefix, ident )| ( prefix, zero_loc(ident))).collect(), string), InterpolatedStr(pairs, string) => InterpolatedStr(pairs.into_iter().map(|( prefix, ident )| ( prefix, zero_loc(ident))).collect(), string),
List(elems) => {
let zeroed_elems =
elems.into_iter().map(|loc_expr|
loc(zero_loc_expr(loc_expr.value))
).collect();
List(zeroed_elems)
},
Assign(assignments, loc_ret) => { Assign(assignments, loc_ret) => {
let zeroed_assignments = let zeroed_assignments =
assignments.into_iter().map(|( pattern, loc_expr )| assignments.into_iter().map(|( pattern, loc_expr )|

View file

@ -102,11 +102,70 @@ mod test_infer {
); );
} }
// LIST
#[test]
fn infer_empty_list() {
infer_eq(
indoc!(r#"
[]
"#),
"List.List *"
);
}
#[test]
fn infer_list_of_one_num() {
infer_eq(
indoc!(r#"
[42]
"#),
"List.List (Num.Num *)"
);
}
#[test]
fn infer_list_of_nums() {
infer_eq(
indoc!(r#"
[ 1, 2, 3 ]
"#),
"List.List (Num.Num *)"
);
}
#[test]
fn infer_list_of_one_string() {
infer_eq(
indoc!(r#"
[ "cowabunga" ]
"#),
"List.List String.String"
);
}
#[test]
fn infer_list_of_strings() {
infer_eq(
indoc!(r#"
[ "foo", "bar" ]
"#),
"List.List String.String"
);
}
// #[test] // #[test]
// fn infer_interpolated_string() { // fn infer_interpolated_string() {
// assert_eq!( // infer_eq(
// infer_expr(&Expr::InterpolatedStr(vec![], "type inference!".to_string()), MutMap::default()), // indoc!(r#"
// Builtin(Str) // whatItIs = "great"
// "type inference is \(whatItIs)!"
// "#),
// "String.String"
// ); // );
// } // }

View file

@ -44,6 +44,39 @@ mod test_parse {
) )
} }
// LIST LITERALS
#[test]
fn empty_list() {
assert_fully_parses(
indoc!(r#"
[]
"#),
EmptyList
);
}
#[test]
fn single_list() {
assert_fully_parses(
indoc!(r#"
[ 1 ]
"#),
List(vec![loc(Int(1))])
);
}
#[test]
fn multi_list() {
assert_fully_parses(
indoc!(r#"
[1 , 2,3]
"#),
List(vec![loc(Int(1)), loc(Int(2)), loc(Int(3))])
);
}
// STRING LITERALS // STRING LITERALS
fn expect_parsed_str<'a>(expected_str: &'a str, actual_str: &'a str) { fn expect_parsed_str<'a>(expected_str: &'a str, actual_str: &'a str) {