mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
Add lists
This commit is contained in:
parent
46750ae6ca
commit
8985b4bec7
9 changed files with 200 additions and 12 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
12
src/expr.rs
12
src/expr.rs
|
@ -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)|
|
||||||
|
|
27
src/parse.rs
27
src/parse.rs
|
@ -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))
|
||||||
|
|
|
@ -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(")");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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 )|
|
||||||
|
|
|
@ -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"
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue