diff --git a/cli/test.roc b/cli/test.roc index 8c156658f1..994bac9195 100644 --- a/cli/test.roc +++ b/cli/test.roc @@ -1,64 +1,69 @@ -succeed = (val) -> Success val +succeed = (val) -> + Success val -fail = (val) -> Failure val +fail = (val) -> + Failure val -echo = (str) -> Echo str, succeed, fail +echo = (str) -> + Echo str, succeed, fail -read = Read succeed, fail +readInput = + Read succeed, fail map = (convert, task) -> - await task, (output) -> + after task, (output) -> succeed (convert output) mapErr = (convert, task) -> - fallback task, (err) -> + fallback task, (err) -> fail (convert err) -await = (task, cont) -> - match task - when Success val then cont val - when Failure val then Failure val +after = (task, cont) -> + case task + when Success val then cont val + when Failure val then Failure val - when Echo str, prevCont, onFailure then - Echo str, - (({}) -> await (prevCont {}), cont) - ((ioErr) -> await (onFailure ioErr), cont) - - when Read prevCont, onFailure then - Read - ((str) -> await (prevCont str), cont) - ((ioErr) -> await (onFailure ioErr), cont) + when Echo str, prevCont, onFailure then + Echo str, + ({} -> after (prevCont {}), cont), + (ioErr -> after (onFailure ioErr), cont) + when Read prevCont, onFailure then + Read + (str -> after (prevCont str), cont), + (ioErr -> after (onFailure ioErr), cont) fallback = (task, onFailure) -> - match task - when Success val then Success val - when Failure val then onFailure val + case task + when Success val then Success val + when Failure val then onFailure val - when Echo str, cont, prevOnFailure then - Echo str - (({}) -> fallback (cont {}), onFailure) - ((ioErr) -> fallback (prevOnFailure ioErr), onFailure) + when Echo str, cont, prevOnFailure then + Echo str + ({} -> fallback (cont {}), onFailure), + (ioErr -> fallback (prevOnFailure ioErr), onFailure) - when Read cont, prevOnFailure then - Read - ((str) -> fallback (cont str), onFailure) - ((ioErr) -> fallback (prevOnFailure ioErr), onFailure) + when Read cont, prevOnFailure then + Read + (str -> fallback (cont str), onFailure), + (ioErr -> fallback (prevOnFailure ioErr), onFailure) demo = - await (echo "Enter first name"), ({}) -> - await read, (firstName) -> - await (echo "Enter last name"), ({}) -> - await read, (lastName) -> - echo "Your name is: \(firstName) \(lastName)" + after (echo "Enter first name"), ({}) -> + after readInput, (firstName) -> + after (echo "Enter last name"), ({}) -> + after readInput, (lastName) -> + fullName = "\(firstName) \(lastName)" + + echo "Your name is: \(fullName)" demo diff --git a/src/eval.rs b/src/eval.rs index 8b6bd716cd..e956dcdaaf 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -31,7 +31,7 @@ fn problem(prob: Problem) -> Evaluated { pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated { match expr { // Primitives need no further evaluation - Error(_) | Int(_) | Str(_) | Frac(_, _) | Char(_) | Bool(_) | Closure(_, _) => Evaluated(expr), + Error(_) | Int(_) | Str(_) | Frac(_, _) | Char(_) | Bool(_) | Closure(_, _) | Expr::EmptyRecord => Evaluated(expr), // Resolve variable names Var(name) => match vars.get(&name) { @@ -72,6 +72,14 @@ pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated { scoped_eval(*in_expr, vars) }, + Let(Pattern::EmptyRecord, definition, in_expr) => { + // Faithfully eval this, but discard its result. + scoped_eval(*definition, &vars); + + // Actually use this part. + scoped_eval(*in_expr, vars) + }, + Func(name, args) => { let func_expr = match vars.get(&name) { Some(resolved) => { @@ -93,8 +101,8 @@ pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated { eval_apply(scoped_eval(*func_expr, vars), args, vars) }, - Match(condition, branches) => { - eval_match(scoped_eval(*condition, vars), branches, vars) + Case(condition, branches) => { + eval_case(scoped_eval(*condition, vars), branches, vars) }, Operator(left_arg, op, right_arg) => { @@ -225,7 +233,7 @@ fn eval_operator(Evaluated(left_expr): &Evaluated, op: Operator, Evaluated(right } #[inline(always)] -fn eval_match (condition: Evaluated, branches: SmallVec<[(Pattern, Box); 4]>, vars: &Scope) -> Evaluated { +fn eval_case (condition: Evaluated, branches: SmallVec<[(Pattern, Box); 4]>, vars: &Scope) -> Evaluated { let Evaluated(ref evaluated_expr) = condition; for (pattern, definition) in branches { @@ -254,6 +262,14 @@ fn pattern_match(evaluated: Evaluated, pattern: Pattern, vars: &mut Scope) -> Re // Underscore matches anything, and records no new vars. Ok(()) }, + Pattern::EmptyRecord => { + match evaluated { + Evaluated(Expr::EmptyRecord) => Ok(()), + Evaluated(expr) => Err(TypeMismatch( + format!("Wanted a `{}`, but was given `{}`.", "{}", expr) + )) + } + }, Variant(pattern_variant_name, opt_pattern_contents) => { match evaluated { Evaluated(ApplyVariant(applied_variant_name, opt_applied_contents)) => { diff --git a/src/expr.rs b/src/expr.rs index d2ca57a00c..5c976ecbb5 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -23,9 +23,12 @@ pub enum Expr { // Sum Types ApplyVariant(String, Option>), + // Product Types + EmptyRecord, + // Conditionals If(Box, Box, Box), - Match(Box, SmallVec<[(Pattern, Box); 4]>), + Case(Box, SmallVec<[(Pattern, Box); 4]>), // Error Error(Problem), @@ -104,6 +107,7 @@ pub enum Problem { pub enum Pattern { Identifier(String), Variant(String, Option>), + EmptyRecord, Underscore } diff --git a/src/parse.rs b/src/parse.rs index 9acb336698..c2ff621c4d 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -123,7 +123,9 @@ parser! { let min_indent = *min_indent_ref; choice(( + closure(min_indent), parenthetical_expr(min_indent), + string("{}").with(value(Expr::EmptyRecord)), string("True").with(value(Expr::Bool(true))), string("False").with(value(Expr::Bool(false))), string_literal(), @@ -131,7 +133,6 @@ parser! { char_literal(), if_expr(min_indent), match_expr(min_indent), - closure(min_indent), let_expr(min_indent), apply_variant(min_indent), func_or_var(min_indent), @@ -204,7 +205,7 @@ pub fn match_expr(min_indent: i32) -> impl Parser where I: Stream, I::Error: ParseError { - string("match").skip(indented_whitespaces1(min_indent)) + string("case").skip(indented_whitespaces1(min_indent)) .with(expr_body(min_indent)).skip(indented_whitespaces1(min_indent)) .and( many::, _>( @@ -219,7 +220,7 @@ where I: Stream, // TODO handle this more gracefully panic!("encountered match-expression with no branches!") } else { - Expr::Match(Box::new(conditional), branches) + Expr::Case(Box::new(conditional), branches) } ) } @@ -336,15 +337,20 @@ where I: Stream, I::Error: ParseError { // TODO patterns must be separated by commas! - between(char('|'), char('|'), + attempt( + between(char('('), char(')'), sep_by1( pattern(min_indent), char(',').skip(indented_whitespaces(min_indent)) )) - .and(whitespace1().with(expr_body(min_indent))) - .map(|(patterns, closure_body)| { - Expr::Closure(patterns, Box::new(closure_body)) - }) + .skip(indented_whitespaces1(min_indent)) + .skip(string("->")) + .skip(indented_whitespaces1(min_indent)) + ) + .and(expr_body(min_indent)) + .map(|(patterns, closure_body)| { + Expr::Closure(patterns, Box::new(closure_body)) + }) } parser! { @@ -356,6 +362,7 @@ parser! { choice(( char('_').map(|_| Pattern::Underscore), + string("{}").map(|_| Pattern::EmptyRecord), ident().map(|name| Pattern::Identifier(name)), match_variant(min_indent) )) @@ -420,7 +427,7 @@ where I: Stream, "if" => unexpected_any("Reserved keyword `if`").left(), "then" => unexpected_any("Reserved keyword `then`").left(), "else" => unexpected_any("Reserved keyword `else`").left(), - "match" => unexpected_any("Reserved keyword `match`").left(), + "case" => unexpected_any("Reserved keyword `case`").left(), "when" => unexpected_any("Reserved keyword `when`").left(), _ => value(ident_str).right() }