Restore eval, add |> operator

This commit is contained in:
Richard Feldman 2019-07-08 23:35:10 -04:00
parent 7bcc471d4a
commit 07a05b90fc
9 changed files with 450 additions and 175 deletions

View file

@ -6,6 +6,7 @@ use roc::expr::Expr;
use roc::eval::{Evaluated, eval, call};
use roc::eval::Evaluated::*;
use roc::parse;
use roc::region::{Region, Located};
use std::io;
fn main() -> std::io::Result<()> {
@ -54,7 +55,7 @@ fn process_task(evaluated: Evaluated) -> std::io::Result<()> {
// Continue with the callback.
let callback = vals.pop().unwrap();
process_task(call(callback, vec![Expr::EmptyRecord]))
process_task(call(callback, vec![with_zero_loc(Expr::EmptyRecord)]))
},
"Read" => {
// Read a line from from stdin, since that's what Read does!
@ -65,7 +66,7 @@ fn process_task(evaluated: Evaluated) -> std::io::Result<()> {
// Continue with the callback.
let callback = vals.pop().unwrap();
process_task(call(callback, vec![Expr::Str(input.trim().to_string())]))
process_task(call(callback, vec![with_zero_loc(Expr::Str(input.trim().to_string()))]))
},
"Success" => {
// We finished all our tasks. Great! No need to print anything.
@ -88,6 +89,17 @@ fn process_task(evaluated: Evaluated) -> std::io::Result<()> {
}
}
fn with_zero_loc<T>(val: T) -> Located<T> {
Located::new(val, Region {
start_line: 0,
start_col: 0,
end_line: 0,
end_col: 0,
})
}
fn display_val(evaluated: Evaluated) {
println!("\n\u{001B}[4mroc out\u{001B}[24m\n\n{}\n", evaluated);
}

View file

@ -5,12 +5,17 @@ use operator::Operator;
use std::rc::Rc;
use std::fmt;
use im_rc::hashmap::HashMap;
use self::Evaluated::*;
use self::Problem::*;
use fraction::Fraction;
use region::{Located, Region};
pub fn eval(expr: Expr) -> Evaluated {
scoped_eval(expr, &HashMap::new())
pub fn eval(expr: Located<Expr>) -> Evaluated {
scoped_eval(prepare_for_eval(expr), &HashMap::new())
}
fn prepare_for_eval(expr: Located<Expr>) -> Located<Expr> {
// TODO apply operator precedence
expr.map(&apply_pizza)
}
#[derive(Clone, Debug, PartialEq)]
@ -22,7 +27,7 @@ pub enum Evaluated {
Str(String),
InterpolatedStr(Vec<(String, Ident)>, String),
Char(char),
Closure(Vec<Pattern>, Box<Expr>, Scope),
Closure(Vec<Pattern>, Box<Located<Expr>>, Scope),
// Sum Types
ApplyVariant(String, Option<Vec<Evaluated>>),
@ -36,9 +41,9 @@ pub enum Evaluated {
#[derive(Clone, Debug, PartialEq)]
pub enum Problem {
UnrecognizedVarName(String),
UnrecognizedVarName(Region, String),
TypeMismatch(String),
ReassignedVarName(String),
ReassignedVarName(Region, String),
WrongArity(u32 /* Expected */, u32 /* Provided */),
NotEqual, // Used when (for example) a string literal pattern match fails
NoBranchesMatched,
@ -46,20 +51,24 @@ pub enum Problem {
type Scope = HashMap<String, Rc<Evaluated>>;
pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated {
match expr {
pub fn scoped_eval(expr: Located<Expr>, vars: &Scope) -> Evaluated {
use self::Evaluated::*;
let region = expr.region;
match expr.value {
Expr::Int(num) => Int(num),
Expr::EmptyStr => EmptyStr,
Expr::Str(string) => Str(string),
Expr::Frac(numerator, denominator) => Frac(fraction_from_i64s(numerator, denominator)),
Expr::Char(ch) => Char(ch),
Expr::Closure(args, body) => Closure(args.into_iter().map(|e| e.value).collect(), Box::new(body.value), vars.clone()),
Expr::Closure(args, body) => Closure(args.into_iter().map(|e| e.value).collect(), body, vars.clone()),
Expr::EmptyRecord => EmptyRecord,
// Resolve variable names
Expr::Var(name) => match vars.get(&name) {
Some(resolved) => (**resolved).clone(),
None => EvalError(UnrecognizedVarName(name))
None => EvalError(UnrecognizedVarName(region, name))
},
Expr::InterpolatedStr(pairs, trailing_str) => {
@ -78,7 +87,7 @@ pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated {
}
}
},
None => { return EvalError(UnrecognizedVarName(var_name.value)); }
None => { return EvalError(UnrecognizedVarName(region, var_name.value)); }
}
}
@ -87,45 +96,14 @@ pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated {
Str(output)
},
Expr::Assign(Identifier(name), definition, in_expr) => {
if vars.contains_key(&name) {
EvalError(ReassignedVarName(name))
} else {
// Create a new scope containing the new declaration.
let mut new_vars = vars.clone();
let evaluated_defn = scoped_eval(*definition, vars);
new_vars.insert(name, Rc::new(evaluated_defn));
// Evaluate in_expr with that new scope's variables.
scoped_eval(*in_expr, &new_vars)
Expr::Assign(located_pattern, assigned_expr, returned_expr) => {
eval_assign(located_pattern, *assigned_expr, *returned_expr, vars)
}
},
Expr::Assign(Integer(_), _, _) => {
panic!("You cannot assign integers to other values!");
},
Expr::Assign(Fraction(_, _), _, _) => {
panic!("You cannot assign fractions to other values!");
},
Expr::Assign(Variant(_name, _patterns), _definition, _in_expr) => {
panic!("Pattern matching on variants is not yet supported!");
},
Expr::Assign(Underscore, _definition, _in_expr) => {
panic!("Cannot assign to the _ pattern!");
},
Expr::Assign(Pattern::EmptyRecordLiteral, _definition, _in_expr) => {
panic!("Cannot assign to the {} pattern!");
},
Expr::CallByName(name, args) => {
let func_expr = match vars.get(&name) {
Some(resolved) => (**resolved).clone(),
None => EvalError(UnrecognizedVarName(name))
None => EvalError(UnrecognizedVarName(region, name))
};
eval_apply(func_expr, args, vars)
@ -151,7 +129,7 @@ pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated {
Expr::Operator(left_arg, op, right_arg) => {
eval_operator(
&scoped_eval(*left_arg, vars),
op,
op.value,
&scoped_eval(*right_arg, vars)
)
},
@ -171,13 +149,58 @@ pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated {
}
}
fn eval_assign(pattern: Located<Pattern>, assigned_expr: Located<Expr>, returned_expr: Located<Expr>, vars: &Scope) -> Evaluated {
use self::Evaluated::*;
let pattern_region = pattern.region;
match pattern.value {
Identifier(name) => {
if vars.contains_key(&name) {
EvalError(ReassignedVarName(pattern_region, name))
} else {
// Create a new scope containing the new declaration.
let mut new_vars = vars.clone();
let evaluated_defn = scoped_eval(assigned_expr, vars);
new_vars.insert(name, Rc::new(evaluated_defn));
// Evaluate in_expr with that new scope's variables.
scoped_eval(returned_expr, &new_vars)
}
},
Integer(_) => {
panic!("You cannot assign integers to other values!");
},
Fraction(_, _) => {
panic!("You cannot assign fractions to other values!");
},
Variant(_name, _patterns) => {
panic!("Pattern matching on variants is not yet supported!");
},
Underscore => {
panic!("Cannot assign to the _ pattern!");
},
Pattern::EmptyRecordLiteral => {
panic!("Cannot assign to the {} pattern!");
},
}
}
#[inline(always)]
pub fn call(evaluated: Evaluated, args: Vec<Expr>) -> Evaluated {
pub fn call(evaluated: Evaluated, args: Vec<Located<Expr>>) -> Evaluated {
eval_apply(evaluated, args, &HashMap::new())
}
#[inline(always)]
fn eval_apply(evaluated: Evaluated, args: Vec<Expr>, vars: &Scope) -> Evaluated {
fn eval_apply(evaluated: Evaluated, args: Vec<Located<Expr>>, vars: &Scope) -> Evaluated {
use self::Evaluated::*;
match evaluated {
Closure(arg_patterns, body, closure_vars) => {
let combined_vars = vars.clone().union(closure_vars);
@ -191,8 +214,8 @@ fn eval_apply(evaluated: Evaluated, args: Vec<Expr>, vars: &Scope) -> Evaluated
Err(prob) => EvalError(prob)
}
},
expr => {
EvalError(TypeMismatch(format!("Tried to call a non-function: {}", expr)))
val => {
EvalError(TypeMismatch(format!("Tried to call a non-function: {}", val)))
}
}
}
@ -217,13 +240,15 @@ fn eval_closure(args: Vec<Evaluated>, arg_patterns: Vec<Pattern>, vars: &Scope)
fn bool_variant(is_true: bool) -> Evaluated {
if is_true {
ApplyVariant("True".to_string(), None)
Evaluated::ApplyVariant("True".to_string(), None)
} else {
ApplyVariant("False".to_string(), None)
Evaluated::ApplyVariant("False".to_string(), None)
}
}
fn eq(evaluated1: &Evaluated, evaluated2: &Evaluated) -> Evaluated {
use self::Evaluated::*;
match (evaluated1, evaluated2) {
// All functions are defined as equal
(Closure(_, _, _), Closure(_, _, _)) => bool_variant(true),
@ -253,13 +278,43 @@ fn eq(evaluated1: &Evaluated, evaluated2: &Evaluated) -> Evaluated {
}
}
fn bool_from_variant_name(name: &str) -> Option<bool> {
if name == "True" {
Some(true)
} else if name == "False" {
Some(false)
} else {
None
}
}
#[inline(always)]
fn eval_operator(left_expr: &Evaluated, op: Operator, right_expr: &Evaluated) -> Evaluated {
use self::Evaluated::*;
// TODO in the future, replace these with named function calls to stdlib
match (left_expr, op, right_expr) {
// Equals
(_, Equals, _) => eq(left_expr, right_expr),
// And
(ApplyVariant(left_name, None), And, ApplyVariant(right_name, None)) => {
match (bool_from_variant_name(left_name), bool_from_variant_name(right_name)) {
(Some(left_bool), Some(right_bool)) => bool_variant(left_bool && right_bool),
_ => EvalError(TypeMismatch("tried to use && on non-bools".to_string())),
}
}
(_, And, _) => EvalError(TypeMismatch("tried to use && on non-bools".to_string())),
// Or
(ApplyVariant(left_name, None), Or, ApplyVariant(right_name, None)) => {
match (bool_from_variant_name(left_name), bool_from_variant_name(right_name)) {
(Some(left_bool), Some(right_bool)) => bool_variant(left_bool || right_bool),
_ => EvalError(TypeMismatch("tried to use && on non-bools".to_string())),
}
}
(_, Or, _) => EvalError(TypeMismatch("tried to use && on non-bools".to_string())),
// LessThan
(Int(left_num), LessThan, Int(right_num)) => bool_variant(left_num < right_num),
(Frac(left_num), LessThan, Frac(right_num)) => bool_variant(left_num < right_num),
@ -289,7 +344,7 @@ fn eval_operator(left_expr: &Evaluated, op: Operator, right_expr: &Evaluated) ->
(_, GreaterThanOrEq, _) => EvalError(TypeMismatch("tried to check if one non-number >= another non-number".to_string())),
// Plus
(Int(left_num), Plus, Int(right_num)) => Int(left_num + right_num),
(Int(left_num), Plus, Int(right_num)) => Int(left_num.checked_add(*right_num).unwrap_or_else(|| panic!("Integer overflow on +"))),
(Frac(left_num), Plus, Frac(right_num)) => Frac(left_num + right_num),
(Int(_), Plus, Frac(_)) => EvalError(TypeMismatch("tried to add Frac to Int. Explicitly convert them to the same type first!".to_string())),
@ -299,7 +354,7 @@ fn eval_operator(left_expr: &Evaluated, op: Operator, right_expr: &Evaluated) ->
(_, Plus, _) => EvalError(TypeMismatch("tried to add non-numbers".to_string())),
// Star
(Int(left_num), Star, Int(right_num)) => Int(left_num * right_num),
(Int(left_num), Star, Int(right_num)) => Int(left_num.checked_mul(*right_num).unwrap_or_else(|| panic!("Integer overflow on *"))),
(Frac(left_num), Star, Frac(right_num)) => Frac(left_num * right_num),
(Int(_), Star, Frac(_)) => EvalError(TypeMismatch("tried to multiply Int by Frac. Explicitly convert them to the same type first!".to_string())),
@ -309,7 +364,7 @@ fn eval_operator(left_expr: &Evaluated, op: Operator, right_expr: &Evaluated) ->
(_, Star, _) => EvalError(TypeMismatch("tried to multiply non-numbers".to_string())),
// Minus
(Int(left_num), Minus, Int(right_num)) => Int(left_num - right_num),
(Int(left_num), Minus, Int(right_num)) => Int(left_num.checked_sub(*right_num).unwrap_or_else(|| panic!("Integer underflow on -"))),
(Frac(left_num), Minus, Frac(right_num)) => Frac(left_num - right_num),
(Int(_), Minus, Frac(_)) => EvalError(TypeMismatch("tried to subtract Frac from Int. Explicitly convert them to the same type first!".to_string())),
@ -318,8 +373,14 @@ fn eval_operator(left_expr: &Evaluated, op: Operator, right_expr: &Evaluated) ->
(_, Minus, _) => EvalError(TypeMismatch("tried to subtract non-numbers".to_string())),
// Caret
(Int(left_num), Caret, Int(right_num)) => Int(left_num.checked_pow(*right_num as u32 /* TODO panic if this cast fails */).unwrap_or_else(|| panic!("Integer underflow on ^"))),
(Frac(_), Caret, Frac(_)) => EvalError(TypeMismatch("tried to use ^ with a Frac, which is not yet supported on either side of the ^ operator.".to_string())),
(_, Caret, _) => EvalError(TypeMismatch("tried to use ^ on non-numbers".to_string())),
// Slash
(Int(left_num), Slash, Int(right_num)) => Int(left_num / right_num),
(Int(left_num), Slash, Int(right_num)) => Int(left_num.checked_div(*right_num).unwrap_or_else(|| panic!("Integer underflow on /"))),
(Frac(left_num), Slash, Frac(right_num)) => {
let answer = left_num / right_num;
@ -345,15 +406,38 @@ fn eval_operator(left_expr: &Evaluated, op: Operator, right_expr: &Evaluated) ->
(Frac(_), DoubleSlash, Int(_)) => EvalError(TypeMismatch("tried to integer-divide Frac by Int".to_string())),
(_, DoubleSlash, _) => EvalError(TypeMismatch("tried to do integer division on two non-numbers".to_string())),
// Percent
(Int(left_num), Percent, Int(right_num)) => Int(left_num % right_num),
(Frac(left_num), Percent, Frac(right_num)) => {
let answer = left_num % right_num;
if answer.is_finite() {
ok_variant(Frac(answer))
} else {
err_variant(ApplyVariant("DivisionByZero".to_string(), None))
}
},
(Int(_), Percent, Frac(_)) => EvalError(TypeMismatch("tried to do Int % Frac. Explicitly convert them to the same type first!".to_string())),
(Frac(_), Percent, Int(_)) => EvalError(TypeMismatch("tried to do Frac % Int. Explicitly convert them to the same type first!".to_string())),
(_, Percent, _) => EvalError(TypeMismatch("tried to use % on non-numbers".to_string())),
// Pizza
(_, Pizza, _) => { panic!("There was a |> operator that hadn't been removed prior to eval time. This should never happen!"); }
}
}
#[inline(always)]
fn eval_case (evaluated: Evaluated, branches: Vec<(Pattern, Box<Expr>)>, vars: &Scope) -> Evaluated {
fn eval_case(evaluated: Evaluated, branches: Vec<(Located<Pattern>, Box<Located<Expr>>)>, vars: &Scope) -> Evaluated {
use self::Evaluated::*;
for (pattern, definition) in branches {
let mut branch_vars = vars.clone();
if pattern_match(&evaluated, &pattern, &mut branch_vars).is_ok() {
if pattern_match(&evaluated, &pattern.value, &mut branch_vars).is_ok() {
return scoped_eval(*definition, &branch_vars);
}
}
@ -362,6 +446,8 @@ fn eval_case (evaluated: Evaluated, branches: Vec<(Pattern, Box<Expr>)>, vars: &
}
fn pattern_match(evaluated: &Evaluated, pattern: &Pattern, vars: &mut Scope) -> Result<(), Problem> {
use self::Evaluated::*;
match pattern {
Identifier(name) => {
vars.insert(name.clone(), Rc::new(evaluated.clone()));
@ -433,7 +519,7 @@ fn pattern_match(evaluated: &Evaluated, pattern: &Pattern, vars: &mut Scope) ->
if pattern_contents.len() == applied_contents.len() {
// Recursively pattern match
for ( pattern_val, applied_val ) in pattern_contents.into_iter().zip(applied_contents) {
pattern_match(applied_val, pattern_val, vars)?;
pattern_match(applied_val, &pattern_val.value, vars)?;
}
Ok(())
@ -470,6 +556,8 @@ fn pattern_match(evaluated: &Evaluated, pattern: &Pattern, vars: &mut Scope) ->
impl fmt::Display for Evaluated {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Evaluated::*;
match self {
// PRIMITIVES
Int(num) => write!(f, "{}", *num),
@ -523,10 +611,10 @@ impl fmt::Display for Evaluated {
impl fmt::Display for Problem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Problem::UnrecognizedVarName(name) => write!(f, "Unrecognized var name `{}`", name),
Problem::UnrecognizedVarName(region, name) => write!(f, "Unrecognized var name `{}` at {:?}", name, region),
Problem::NoBranchesMatched => write!(f, "No branches matched in this case-expression"),
Problem::TypeMismatch(info) => write!(f, "Type Mismatch - {}", info),
Problem::ReassignedVarName(name) => write!(f, "Reassigned constant - {}", name),
Problem::ReassignedVarName(region, name) => write!(f, "Reassigned constant - {} at {:?}", name, region),
Problem::NotEqual => write!(f, "Pattern match on literal value failed; the branch wasn't equal."),
Problem::WrongArity(expected_arity, provided_arity) => {
if provided_arity > expected_arity {
@ -540,11 +628,11 @@ impl fmt::Display for Problem {
}
fn ok_variant(contents: Evaluated) -> Evaluated{
ApplyVariant("Ok".to_string(), Some(vec![contents]))
Evaluated::ApplyVariant("Ok".to_string(), Some(vec![contents]))
}
fn err_variant(contents: Evaluated) -> Evaluated {
ApplyVariant("Err".to_string(), Some(vec![contents]))
Evaluated::ApplyVariant("Err".to_string(), Some(vec![contents]))
}
fn fraction_from_i64s(numerator: i64, denominator: i64) -> Fraction {
@ -554,3 +642,47 @@ fn fraction_from_i64s(numerator: i64, denominator: i64) -> Fraction {
Fraction::new(numerator as u64, denominator as u64)
}
}
fn apply_pizza(expr: &Expr) -> Expr {
use expr::Expr::*;
expr.walk(&|sub_expr| {
// TODO can we avoid cloning here somehow, without resorting to a macro?
match sub_expr.clone() {
Operator(boxed_loc_left, loc_op, boxed_loc_right) => {
let loc_left = *boxed_loc_left;
let loc_right = *boxed_loc_right;
let left_region = loc_left.region;
let right_region = loc_left.region;
let op_region = loc_op.region;
match ( loc_left.value, loc_op.value, loc_right.value ) {
(left_arg, Pizza, Expr::Var(name)) => {
Expr::CallByName(
name,
vec![Located { region: left_region, value: left_arg }]
)
},
(left_arg, Pizza, Expr::CallByName(name, mut args)) => {
args.push(Located { region: left_region, value: left_arg });
CallByName(name, args)
},
(left_arg, Pizza, Expr::Apply(applied_expr, mut args)) => {
args.push(Located { region: left_region, value: left_arg });
Apply(applied_expr, args)
},
(left, op, right) => {
Operator(
Box::new(Located { region: left_region, value: left }),
Located { region: op_region, value: op },
Box::new(Located { region: right_region, value: right }),
)
}
}
},
other => other
}
})
}

View file

@ -37,12 +37,73 @@ pub type Ident = String;
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern {
Identifier(String),
Variant(String, Option<Vec<Pattern>>),
Variant(String, Option<Vec<Located<Pattern>>>),
Integer(i64),
Fraction(i64, i64),
EmptyRecordLiteral,
Underscore
}
impl Expr {
pub fn walk<F>(self: &Expr, transform: &F) -> Expr
where F: Fn(&Expr) -> Expr
{
use self::Expr::*;
let transformed = transform(self);
match transformed {
Int(_) | Frac(_, _) | EmptyStr | Str(_) | Char(_) | Var(_) | EmptyRecord | InterpolatedStr(_, _) => transformed,
Assign(pattern, expr1, expr2) => {
Assign(
pattern,
Box::new(expr1.with_value(expr1.value.walk(transform))),
Box::new(expr2.with_value(expr2.value.walk(transform)))
)
},
CallByName(ident, args) => {
CallByName(
ident,
args.into_iter().map(|arg| arg.with_value(arg.value.walk(transform))).collect()
)
},
Apply(fn_expr, args) => {
Apply(
Box::new(fn_expr.with_value(fn_expr.value.walk(transform))),
args.into_iter().map(|arg| arg.with_value(arg.value.walk(transform))).collect()
)
},
Closure(patterns, body) => Closure(patterns, Box::new(body.with_value(body.value.walk(transform)))),
ApplyVariant(_, None) => transformed,
ApplyVariant(name, Some(args)) => {
ApplyVariant(
name,
Some(
args.into_iter().map(|arg| arg.with_value(arg.value.walk(transform))).collect())
)
},
If(condition, if_true, if_false) => {
If(
Box::new(condition.with_value(condition.value.walk(transform))),
Box::new(if_true.with_value(if_true.value.walk(transform))),
Box::new(if_false.with_value(if_false.value.walk(transform)))
)
},
Case(condition, branches) => {
Case(
Box::new(condition.with_value(condition.value.walk(transform))),
branches.into_iter().map(|( pattern, body )|
( pattern, Box::new(body.with_value(body.value.walk(transform))) )
).collect()
)
},
Operator(loc_left, loc_op, loc_right) => {
Operator(
Box::new(loc_left.with_value(loc_left.value.walk(transform))),
loc_op,
Box::new(loc_right.with_value(loc_right.value.walk(transform)))
)
}
}
}
}

View file

@ -1,7 +1,7 @@
pub mod expr;
pub mod parse;
pub mod parse_state;
// pub mod eval;
pub mod eval;
pub mod operator;
pub mod region;
pub mod fast_fraction;

View file

@ -10,7 +10,7 @@ pub enum Operator {
Equals, LessThan, GreaterThan, LessThanOrEq, GreaterThanOrEq,
And,
Or,
LeftPizza, RightPizza
Pizza
// lowest precedence
}
@ -40,8 +40,8 @@ impl Operator {
use self::Associativity::*;
match self {
RightPizza | Star | Slash | DoubleSlash | Percent | Plus | Minus => LeftAssociative,
LeftPizza | And | Or | Caret => RightAssociative,
Pizza | Star | Slash | DoubleSlash | Percent | Plus | Minus => LeftAssociative,
And | Or | Caret => RightAssociative,
Equals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => NonAssociative
}
}
@ -54,7 +54,7 @@ impl Operator {
Equals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => 4,
And => 3,
Or => 2,
LeftPizza | RightPizza => 1
Pizza => 1
}
}
}

View file

@ -16,10 +16,10 @@ use combine::stream::state::{State};
pub const ERR_EMPTY_CHAR: &'static str = "EMPTY_CHAR";
pub fn parse_string(string: &str) -> Result<Expr, combine::easy::Errors<char, &str, IndentablePosition>> {
pub fn parse_string(string: &str) -> Result<Located<Expr>, combine::easy::Errors<char, &str, IndentablePosition>> {
let parse_state = State::with_positioner(string, IndentablePosition::default());
expr().skip(eof()).easy_parse(parse_state).map(|( expr, _ )| expr)
located(expr()).skip(eof()).easy_parse(parse_state).map(|( expr, _ )| expr)
}
pub fn expr<I>() -> impl Parser<Input = I, Output = Expr>
@ -233,7 +233,7 @@ parser! {
located(choice((
function_arg_expr(min_indent),
let_expr(min_indent),
assignment(min_indent),
apply_variant(min_indent),
func_or_var(min_indent),
)))
@ -405,6 +405,7 @@ where I: Stream<Item = char, Position = IndentablePosition>,
attempt(string("==")).map(|_| Operator::Equals),
attempt(string("<=")).map(|_| Operator::LessThanOrEq),
attempt(string(">=")).map(|_| Operator::GreaterThanOrEq),
attempt(string("|>")).map(|_| Operator::Pizza),
char('+').map(|_| Operator::Plus),
char('-').map(|_| Operator::Minus),
char('*').map(|_| Operator::Star),
@ -416,7 +417,7 @@ where I: Stream<Item = char, Position = IndentablePosition>,
))
}
pub fn let_expr<I>(min_indent: u32) -> impl Parser<Input = I, Output = Expr>
pub fn assignment<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>
{
@ -433,16 +434,16 @@ where I: Stream<Item = char, Position = IndentablePosition>,
.skip(whitespace())
.then(move |((var_pattern, original_indent), equals_sign_indent)| {
if original_indent < min_indent {
unexpected_any("this declaration is outdented too far").left()
unexpected_any("this assignment is outdented too far").left()
} else if equals_sign_indent < original_indent /* `<` because '=' should be same indent or greater */ {
unexpected_any("the = in this declaration seems outdented").left()
unexpected_any("the = in this assignment seems outdented").left()
} else {
located(expr_body(original_indent + 1 /* declaration body must be indented relative to original decl */))
.skip(whitespace1())
.and(located(expr_body(original_indent)).and(indentation()))
.then(move |(var_expr, (in_expr, in_expr_indent))| {
if in_expr_indent != original_indent {
unexpected_any("the return expression was indented differently from the original declaration").left()
unexpected_any("the return expression was indented differently from the original assignment").left()
} else {
value(Expr::Assign(var_pattern.to_owned(), Box::new(var_expr), Box::new(in_expr))).right()
}
@ -524,11 +525,14 @@ where I: Stream<Item = char, Position = IndentablePosition>,
indented_whitespaces(min_indent)
.with(
sep_by1(
pattern(min_indent),
char(',').skip(indented_whitespaces(min_indent))
located(pattern(min_indent)),
attempt(
indented_whitespaces1(min_indent)
.skip(not_followed_by(string("then")))
)
)
))))
.map(|(name, opt_args): (String, Option<Vec<Pattern>>)|
.map(|(name, opt_args): (String, Option<Vec<Located<Pattern>>>)|
// Use optional(sep_by1()) over sep_by() to avoid
// allocating a Vec in case the variant is empty
Pattern::Variant(name, opt_args)

View file

@ -18,3 +18,15 @@ impl<T> Located<T> {
Located { value, region }
}
}
impl<T> Located<T> {
pub fn with_value<U>(&self, value: U) -> Located<U> {
Located { region: self.region, value: value }
}
pub fn map<U, F>(&self, transform: F) -> Located<U>
where F: (FnOnce(&T) -> U)
{
Located { region: self.region, value: transform(&self.value) }
}
}

View file

@ -6,102 +6,122 @@ extern crate roc;
#[cfg(test)]
mod test_eval {
// use roc::operator::Operator::*;
// use roc::expr::Pattern::*;
// use roc::expr::Expr::*;
// use roc::eval::eval;
// use roc::eval::Evaluated;
// use fraction::Fraction;
use roc::operator::Operator::*;
use roc::expr::Pattern::*;
use roc::expr::Expr::*;
use roc::expr::Expr;
use roc::eval;
use roc::eval::Evaluated;
use roc::region::{Located, Region};
use fraction::Fraction;
// #[test]
// fn one_plus_one() {
// assert_eq!(
// eval(Operator(Box::new(Int(1)), Plus, Box::new(Int(1)))),
// Evaluated::Int(2)
// );
// }
fn loc_box<T>(val: T) -> Box<Located<T>> {
Box::new(loc(val))
}
// #[test]
// fn point_one_plus_point_two() {
// // 0.1 + 0.2 == 0.3 THAT'S WHAT'S UP
// assert_eq!(
// eval(Operator(Box::new(Frac(1, 10)), Plus, Box::new(Frac(2, 10)))),
// Evaluated::Frac(Fraction::new(3u64, 10u64))
// );
// }
fn eval(expr: Expr) -> Evaluated {
eval::eval(loc(expr))
}
// #[test]
// fn addition_reduces() {
// assert_eq!(
// eval(Operator(Box::new(Frac(1, 3)), Plus, Box::new(Frac(7, 14)))),
// Evaluated::Frac(Fraction::new(5u64, 6u64))
// );
// }
fn loc<T>(val: T) -> Located<T> {
Located::new(val, Region {
start_line: 0,
start_col: 0,
// #[test]
// fn division_reduces() {
// assert_eq!(
// eval(Operator(Box::new(Frac(1, 3)), Slash, Box::new(Frac(7, 14)))),
// Evaluated::ApplyVariant(
// "Ok".to_string(),
// Some(vec![Evaluated::Frac(Fraction::new(2u64, 3u64))])
// )
// );
// }
end_line: 0,
end_col: 0,
})
}
// #[test]
// fn division_by_zero() {
// assert_eq!(
// eval(Operator(Box::new(Frac(1, 10)), Slash, Box::new(Frac(0, 10)))),
// Evaluated::ApplyVariant(
// "Err".to_string(),
// Some(vec![Evaluated::ApplyVariant("DivisionByZero".to_string(), None)])
// )
// );
// }
#[test]
fn one_plus_one() {
assert_eq!(
eval(Operator(loc_box(Int(1)), loc(Plus), loc_box(Int(1)))),
Evaluated::Int(2)
);
}
// #[test]
// fn string_interpolation() {
// assert_eq!(
// eval(
// Assign(Identifier("foo".to_string()), Box::new(Str("one".to_string())),
// Box::new(Assign(Identifier("bar".to_string()), Box::new(Str("two".to_string())),
// Box::new(Assign(Identifier("baz".to_string()), Box::new(Str("three".to_string())),
// Box::new(InterpolatedStr(
// // "hi_\(foo)_\(bar)_\(baz)_string!"
// vec![
// ("hi_".to_string(), "foo".to_string()),
// ("_".to_string(), "bar".to_string()),
// ("_".to_string(), "baz".to_string()),
// ],
// "_string!".to_string()
// ))
// )))))
// ),
// Evaluated::Str("hi_one_two_three_string!".to_string())
// );
// }
#[test]
fn point_one_plus_point_two() {
// 0.1 + 0.2 == 0.3 THAT'S WHAT'S UP
assert_eq!(
eval(Operator(loc_box(Frac(1, 10)), loc(Plus), loc_box(Frac(2, 10)))),
Evaluated::Frac(Fraction::new(3u64, 10u64))
);
}
// #[test]
// fn if_else() {
// assert_eq!(
// eval(
// If(Box::new(ApplyVariant("True".to_string(), None)),
// Box::new(Operator(Box::new(Int(1)), Plus, Box::new(Int(2)))),
// Box::new(Operator(Box::new(Int(4)), Plus, Box::new(Int(5))))
// )
// ),
// Evaluated::Int(3)
// );
#[test]
fn addition_reduces() {
assert_eq!(
eval(Operator(loc_box(Frac(1, 3)), loc(Plus), loc_box(Frac(7, 14)))),
Evaluated::Frac(Fraction::new(5u64, 6u64))
);
}
// assert_eq!(
// eval(
// If(Box::new(ApplyVariant("False".to_string(), None)),
// Box::new(Operator(Box::new(Int(1)), Plus, Box::new(Int(2)))),
// Box::new(Operator(Box::new(Int(4)), Plus, Box::new(Int(5))))
// )
// ),
// Evaluated::Int(9)
// );
// }
#[test]
fn division_reduces() {
assert_eq!(
eval(Operator(loc_box(Frac(1, 3)), loc(Slash), loc_box(Frac(7, 14)))),
Evaluated::ApplyVariant(
"Ok".to_string(),
Some(vec![Evaluated::Frac(Fraction::new(2u64, 3u64))])
)
);
}
#[test]
fn division_by_zero() {
assert_eq!(
eval(Operator(loc_box(Frac(1, 10)), loc(Slash), loc_box(Frac(0, 10)))),
Evaluated::ApplyVariant(
"Err".to_string(),
Some(vec![Evaluated::ApplyVariant("DivisionByZero".to_string(), None)])
)
);
}
#[test]
fn string_interpolation() {
assert_eq!(
eval(
Assign(loc(Identifier("foo".to_string())), loc_box(Str("one".to_string())),
loc_box(Assign(loc(Identifier("bar".to_string())), loc_box(Str("two".to_string())),
loc_box(Assign(loc(Identifier("baz".to_string())), loc_box(Str("three".to_string())),
loc_box(InterpolatedStr(
// "hi_\(foo)_\(bar)_\(baz)_string!"
vec![
("hi_".to_string(), loc("foo".to_string())),
("_".to_string(), loc("bar".to_string())),
("_".to_string(), loc("baz".to_string())),
],
"_string!".to_string()
))
)))))
),
Evaluated::Str("hi_one_two_three_string!".to_string())
);
}
#[test]
fn if_else() {
assert_eq!(
eval(
If(loc_box(ApplyVariant("True".to_string(), None)),
loc_box(Operator(loc_box(Int(1)), loc(Plus), loc_box(Int(2)))),
loc_box(Operator(loc_box(Int(4)), loc(Plus), loc_box(Int(5))))
)
),
Evaluated::Int(3)
);
assert_eq!(
eval(
If(loc_box(ApplyVariant("False".to_string(), None)),
loc_box(Operator(loc_box(Int(1)), loc(Plus), loc_box(Int(2)))),
loc_box(Operator(loc_box(Int(4)), loc(Plus), loc_box(Int(5))))
)
),
Evaluated::Int(9)
);
}
}

View file

@ -7,7 +7,7 @@ extern crate roc;
mod test_parse {
use roc::expr::Expr::*;
use roc::expr::Pattern::*;
use roc::expr::{Expr};
use roc::expr::{Expr, Pattern};
use roc::operator::Operator::*;
use roc::region::{Located, Region};
use roc::parse;
@ -42,11 +42,23 @@ mod test_parse {
Case(condition, branches) =>
Case(
loc_box(zero_loc_expr((*condition).value)),
branches.into_iter().map(|( pattern, expr )| ( zero_loc(pattern), loc_box(zero_loc_expr((*expr).value)))).collect()
branches.into_iter().map(|( pattern, expr )| ( zero_loc_pattern(pattern), loc_box(zero_loc_expr((*expr).value)))).collect()
),
}
}
/// Zero out the parse locations on everything in this Pattern, so we can compare expected/actual without
/// having to account for that.
fn zero_loc_pattern(loc_pattern: Located<Pattern>) -> Located<Pattern> {
let pattern = loc_pattern.value;
match pattern {
Identifier(_) | Integer(_) | Fraction(_, _) | EmptyRecordLiteral | Underscore | Variant(_, None) => loc(pattern),
Variant(name, Some(opt_located_patterns)) =>
loc(Variant(name, Some(opt_located_patterns.into_iter().map(|loc_pat| zero_loc_pattern(loc_pat)).collect())))
}
}
fn zero_loc<T>(located_val: Located<T>) -> Located<T> {
loc(located_val.value)
}
@ -676,6 +688,28 @@ mod test_parse {
);
}
#[test]
fn case_matching_multi_arg_variant() {
assert_eq!(
parse_without_loc("case 1 when Foo bar baz then 2"),
Ok((
Case(
loc_box(Int(1)),
vec![(
loc(Variant("Foo".to_string(),
Some(vec![
loc(Identifier("bar".to_string())),
loc(Identifier("baz".to_string()))
])
)),
loc_box(Int(2)) )
]
),
""
))
);
}
#[test]
fn two_branch_case() {
assert_eq!(
@ -787,7 +821,7 @@ mod test_parse {
Case(
loc_box(Int(1)),
vec![
( loc(Variant("Foo".to_string(), Some(vec![Identifier("x".to_string())]))), loc_box(Int(3)) ),
( loc(Variant("Foo".to_string(), Some(vec![loc(Identifier("x".to_string()))]))), loc_box(Int(3)) ),
]
),
""