mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
Restore eval, add |> operator
This commit is contained in:
parent
7bcc471d4a
commit
07a05b90fc
9 changed files with 450 additions and 175 deletions
|
@ -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);
|
||||
}
|
||||
|
|
258
src/eval.rs
258
src/eval.rs
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
63
src/expr.rs
63
src/expr.rs
|
@ -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)))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
24
src/parse.rs
24
src/parse.rs
|
@ -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)
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[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))
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[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))
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[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))])
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[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 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 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)
|
||||
// );
|
||||
|
||||
// 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)
|
||||
// );
|
||||
// }
|
||||
fn loc_box<T>(val: T) -> Box<Located<T>> {
|
||||
Box::new(loc(val))
|
||||
}
|
||||
|
||||
fn eval(expr: Expr) -> Evaluated {
|
||||
eval::eval(loc(expr))
|
||||
}
|
||||
|
||||
fn loc<T>(val: T) -> Located<T> {
|
||||
Located::new(val, Region {
|
||||
start_line: 0,
|
||||
start_col: 0,
|
||||
|
||||
end_line: 0,
|
||||
end_col: 0,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_plus_one() {
|
||||
assert_eq!(
|
||||
eval(Operator(loc_box(Int(1)), loc(Plus), loc_box(Int(1)))),
|
||||
Evaluated::Int(2)
|
||||
);
|
||||
}
|
||||
|
||||
#[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 addition_reduces() {
|
||||
assert_eq!(
|
||||
eval(Operator(loc_box(Frac(1, 3)), loc(Plus), loc_box(Frac(7, 14)))),
|
||||
Evaluated::Frac(Fraction::new(5u64, 6u64))
|
||||
);
|
||||
}
|
||||
|
||||
#[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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)) ),
|
||||
]
|
||||
),
|
||||
""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue