mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 00:01:16 +00:00
Fix variants getting mixed up with bools
This commit is contained in:
parent
c2cec85202
commit
826b0d5ab0
5 changed files with 111 additions and 33 deletions
62
src/eval.rs
62
src/eval.rs
|
@ -31,7 +31,7 @@ fn problem(prob: Problem) -> Evaluated {
|
||||||
pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated {
|
pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated {
|
||||||
match expr {
|
match expr {
|
||||||
// Primitives need no further evaluation
|
// Primitives need no further evaluation
|
||||||
Error(_) | Int(_) | EmptyStr | Str(_) | InterpolatedStr(_, _) | Frac(_, _) | Char(_) | Bool(_) | Closure(_, _) | Expr::EmptyRecord => Evaluated(expr),
|
Error(_) | Int(_) | EmptyStr | Str(_) | InterpolatedStr(_, _) | Frac(_, _) | Char(_) | Closure(_, _) | Expr::EmptyRecord => Evaluated(expr),
|
||||||
|
|
||||||
// Resolve variable names
|
// Resolve variable names
|
||||||
Var(name) => match vars.get(&name) {
|
Var(name) => match vars.get(&name) {
|
||||||
|
@ -115,8 +115,13 @@ pub fn scoped_eval(expr: Expr, vars: &Scope) -> Evaluated {
|
||||||
|
|
||||||
If(condition, if_true, if_false) => {
|
If(condition, if_true, if_false) => {
|
||||||
match scoped_eval(*condition, vars) {
|
match scoped_eval(*condition, vars) {
|
||||||
Evaluated(Bool(true)) => scoped_eval(*if_true, vars),
|
Evaluated(ApplyVariant(variant_name, None)) => {
|
||||||
Evaluated(Bool(false)) => scoped_eval(*if_false, vars),
|
match variant_name.as_str() {
|
||||||
|
"True" => scoped_eval(*if_true, vars),
|
||||||
|
"False" => scoped_eval(*if_false, vars),
|
||||||
|
_ => problem(TypeMismatch("non-Bool used in `if` condition".to_string()))
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => problem(TypeMismatch("non-Bool used in `if` condition".to_string()))
|
_ => problem(TypeMismatch("non-Bool used in `if` condition".to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,6 +164,45 @@ fn eval_closure(args: Vec<Evaluated>, arg_patterns: SmallVec<[Pattern; 4]>, vars
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bool_variant(is_true: bool) -> Expr {
|
||||||
|
if is_true {
|
||||||
|
ApplyVariant("True".to_string(), None)
|
||||||
|
} else {
|
||||||
|
ApplyVariant("False".to_string(), None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eq(expr1: &Expr, expr2: &Expr) -> Expr {
|
||||||
|
match (expr1, expr2) {
|
||||||
|
// All functions are defined as equal
|
||||||
|
(Closure(_, _), Closure(_, _)) => bool_variant(true),
|
||||||
|
|
||||||
|
|
||||||
|
(ApplyVariant(left, None), ApplyVariant(right, None)) => {
|
||||||
|
bool_variant(left == right)
|
||||||
|
},
|
||||||
|
|
||||||
|
(ApplyVariant(left, Some(left_args)), ApplyVariant(right, Some(right_args))) => {
|
||||||
|
bool_variant(left == right && left_args.len() == right_args.len())
|
||||||
|
},
|
||||||
|
|
||||||
|
(ApplyVariant(_, None), ApplyVariant(_, Some(_))) => {
|
||||||
|
bool_variant(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
(ApplyVariant(_, Some(_)), ApplyVariant(_, None)) => {
|
||||||
|
bool_variant(false)
|
||||||
|
},
|
||||||
|
|
||||||
|
(Int(left), Int(right)) => bool_variant(left == right),
|
||||||
|
(Str(left), Str(right)) => bool_variant(left == right),
|
||||||
|
(Char(left), Char(right)) => bool_variant(left == right),
|
||||||
|
(Frac(_, _), Frac(_, _)) => panic!("Don't know how to == on Fracs yet"),
|
||||||
|
|
||||||
|
(_, _) => Error(TypeMismatch("tried to use == on two values with incompatible types".to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn eval_operator(Evaluated(left_expr): &Evaluated, op: Operator, Evaluated(right_expr): &Evaluated) -> Evaluated {
|
fn eval_operator(Evaluated(left_expr): &Evaluated, op: Operator, Evaluated(right_expr): &Evaluated) -> Evaluated {
|
||||||
// TODO in the future, replace these with named function calls to stdlib
|
// TODO in the future, replace these with named function calls to stdlib
|
||||||
|
@ -168,17 +212,7 @@ fn eval_operator(Evaluated(left_expr): &Evaluated, op: Operator, Evaluated(right
|
||||||
(_, _, Error(prob)) => problem(prob.clone()),
|
(_, _, Error(prob)) => problem(prob.clone()),
|
||||||
|
|
||||||
// Equals
|
// Equals
|
||||||
|
(left, Equals, right) => Evaluated(eq(left_expr, right_expr)),
|
||||||
// All functions are defined as equal
|
|
||||||
(Closure(_, _), Equals, Closure(_, _)) => Evaluated(Bool(true)),
|
|
||||||
|
|
||||||
(Bool(left), Equals, Bool(right)) => Evaluated(Bool(left == right)),
|
|
||||||
(Int(left), Equals, Int(right)) => Evaluated(Bool(left == right)),
|
|
||||||
(Str(left), Equals, Str(right)) => Evaluated(Bool(left == right)),
|
|
||||||
(Char(left), Equals, Char(right)) => Evaluated(Bool(left == right)),
|
|
||||||
(Frac(_, _), Equals, Frac(_, _)) => panic!("Don't know how to == on Fracs yet"),
|
|
||||||
|
|
||||||
(_, Equals, _) => problem(TypeMismatch("tried to use == on two values with incompatible types".to_string())),
|
|
||||||
|
|
||||||
// Plus
|
// Plus
|
||||||
(Int(left_num), Plus, Int(right_num)) => Evaluated(Int(left_num + right_num)),
|
(Int(left_num), Plus, Int(right_num)) => Evaluated(Int(left_num + right_num)),
|
||||||
|
|
|
@ -11,7 +11,6 @@ pub enum Expr {
|
||||||
Str(String),
|
Str(String),
|
||||||
InterpolatedStr(Vec<(String, Ident)>, String),
|
InterpolatedStr(Vec<(String, Ident)>, String),
|
||||||
Char(char),
|
Char(char),
|
||||||
Bool(bool),
|
|
||||||
|
|
||||||
Var(Ident),
|
Var(Ident),
|
||||||
Let(Pattern, Box<Expr>, Box<Expr>),
|
Let(Pattern, Box<Expr>, Box<Expr>),
|
||||||
|
@ -62,8 +61,6 @@ impl fmt::Display for Expr {
|
||||||
write!(f, "\"{}\"", escaped_str)
|
write!(f, "\"{}\"", escaped_str)
|
||||||
},
|
},
|
||||||
Char(ch) => write!(f, "'{}'", *ch),
|
Char(ch) => write!(f, "'{}'", *ch),
|
||||||
Bool(true) => write!(f, "True"),
|
|
||||||
Bool(false) => write!(f, "False"),
|
|
||||||
Closure(args, _) => write!(f, "<{}-argument function>", args.len()),
|
Closure(args, _) => write!(f, "<{}-argument function>", args.len()),
|
||||||
ApplyVariant(name, opt_exprs) => {
|
ApplyVariant(name, opt_exprs) => {
|
||||||
match opt_exprs {
|
match opt_exprs {
|
||||||
|
|
22
src/parse.rs
22
src/parse.rs
|
@ -6,7 +6,7 @@ use parse_state::{IndentablePosition};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
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::{take_until, many, count_min_max, sep_by1, skip_many, skip_many1};
|
use combine::parser::repeat::{many, count_min_max, sep_by1, skip_many, skip_many1};
|
||||||
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};
|
||||||
|
@ -84,13 +84,17 @@ where I: Stream<Item = char, Position = IndentablePosition>,
|
||||||
char(' ').with(value(())),
|
char(' ').with(value(())),
|
||||||
// If we hit a newline, it must be followed by:
|
// If we hit a newline, it must be followed by:
|
||||||
//
|
//
|
||||||
// - Any number of blank lines (which contain only spaces)
|
// - Any number of blank lines (which may contain only spaces)
|
||||||
// - At least min_indent spaces, or else eof()
|
// - At least min_indent spaces, or else eof()
|
||||||
char('\n')
|
char('\n')
|
||||||
.skip(skip_many(char('\n').skip(skip_many(char(' ')))))
|
.skip(
|
||||||
|
skip_many(
|
||||||
|
char('\n').skip(skip_many(char(' ')))
|
||||||
|
)
|
||||||
|
)
|
||||||
.skip(
|
.skip(
|
||||||
choice((
|
choice((
|
||||||
many1::<Vec<_>, _>(char(' ')).then(move |chars| {
|
many::<Vec<_>, _>(char(' ')).then(move |chars| {
|
||||||
if chars.len() < min_indent as usize {
|
if chars.len() < min_indent as usize {
|
||||||
unexpected("outdent").left()
|
unexpected("outdent").left()
|
||||||
} else {
|
} else {
|
||||||
|
@ -126,8 +130,6 @@ parser! {
|
||||||
closure(min_indent),
|
closure(min_indent),
|
||||||
parenthetical_expr(min_indent),
|
parenthetical_expr(min_indent),
|
||||||
string("{}").with(value(Expr::EmptyRecord)),
|
string("{}").with(value(Expr::EmptyRecord)),
|
||||||
string("True").with(value(Expr::Bool(true))),
|
|
||||||
string("False").with(value(Expr::Bool(false))),
|
|
||||||
string_literal(),
|
string_literal(),
|
||||||
number_literal(),
|
number_literal(),
|
||||||
char_literal(),
|
char_literal(),
|
||||||
|
@ -230,7 +232,7 @@ 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>
|
||||||
{
|
{
|
||||||
between(char('('), char(')'),
|
between(char('('), char(')'),
|
||||||
indented_whitespaces(min_indent).with(expr_body(min_indent)).skip(indented_whitespaces(min_indent))
|
indented_whitespaces(min_indent).with(expr_body(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 comma-separated expressions,
|
||||||
|
@ -249,14 +251,12 @@ 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>
|
||||||
{
|
{
|
||||||
indented_whitespaces1(min_indent)
|
indented_whitespaces1(min_indent)
|
||||||
// TODO figure out how to refactor out comma_separated()
|
|
||||||
// which takes a Parser<T> and returns a Parser<Optional<T>>
|
|
||||||
.with(
|
.with(
|
||||||
sep_by1(
|
sep_by1(
|
||||||
attempt(
|
attempt(
|
||||||
// Keywords like "then" and "else" are not function application!
|
// Keywords like "then" and "else" are not function application!
|
||||||
not_followed_by(choice((string("then"), string("else"), string("when"))))
|
not_followed_by(choice((string("then"), string("else"), string("when"))))
|
||||||
// Don't parse operators because they have a higher
|
// Don't parse operators, because they have a higher
|
||||||
// precedence than function application. If we see one,
|
// precedence than function application. If we see one,
|
||||||
// we're done!
|
// we're done!
|
||||||
.with(expr_body_without_operators(min_indent))
|
.with(expr_body_without_operators(min_indent))
|
||||||
|
@ -681,7 +681,7 @@ where I: Stream<Item = char, Position = IndentablePosition>,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(Err(_), _) =>
|
(Err(_), _) =>
|
||||||
unexpected_any("looked like a number but was actually malformed ident").left()
|
unexpected_any("looked like a number but was actually malformed identifier").left()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ mod eval_tests {
|
||||||
fn if_else() {
|
fn if_else() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
eval(
|
eval(
|
||||||
If(Box::new(Bool(true)),
|
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(1)), Plus, Box::new(Int(2)))),
|
||||||
Box::new(Operator(Box::new(Int(4)), Plus, Box::new(Int(5))))
|
Box::new(Operator(Box::new(Int(4)), Plus, Box::new(Int(5))))
|
||||||
)
|
)
|
||||||
|
@ -37,7 +37,7 @@ mod eval_tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
eval(
|
eval(
|
||||||
If(Box::new(Bool(false)),
|
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(1)), Plus, Box::new(Int(2)))),
|
||||||
Box::new(Operator(Box::new(Int(4)), Plus, Box::new(Int(5))))
|
Box::new(Operator(Box::new(Int(4)), Plus, Box::new(Int(5))))
|
||||||
)
|
)
|
||||||
|
|
|
@ -84,7 +84,7 @@ mod parse_tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn string_with_interpolation() {
|
fn string_with_interpolation() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_standalone("x=5\nx"),
|
parse_standalone("\"foo\\(bar)baz"),
|
||||||
Ok((
|
Ok((
|
||||||
Let(Identifier("x".to_string()), Box::new(Int(5)), Box::new(Var("x".to_string()))),
|
Let(Identifier("x".to_string()), Box::new(Int(5)), Box::new(Var("x".to_string()))),
|
||||||
"")
|
"")
|
||||||
|
@ -574,14 +574,61 @@ mod parse_tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VARIANT
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_variant() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_standalone("Abc"),
|
||||||
|
Ok((
|
||||||
|
ApplyVariant("Abc".to_string(), None),
|
||||||
|
""
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variant_regression() {
|
||||||
|
// Somehow parsing the variant "Abc" worked but "Foo" failed (?!)
|
||||||
|
assert_eq!(
|
||||||
|
parse_standalone("F"),
|
||||||
|
Ok((
|
||||||
|
ApplyVariant("F".to_string(), None),
|
||||||
|
""
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// COMPLEX EXPRESSIONS
|
// COMPLEX EXPRESSIONS
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nested_let_variant() {
|
fn nested_let_variant() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_standalone("one = Foo 1\ntwo = Bar\n\none"),
|
parse_standalone("one = Abc\n\ntwo = Bar\n\none"),
|
||||||
Ok((
|
Ok((
|
||||||
Int(1),
|
Let(
|
||||||
|
Identifier(
|
||||||
|
"one".to_string()
|
||||||
|
),
|
||||||
|
Box::new(ApplyVariant(
|
||||||
|
"Abc".to_string(),
|
||||||
|
None
|
||||||
|
)),
|
||||||
|
Box::new(Let(
|
||||||
|
Identifier(
|
||||||
|
"two".to_string()
|
||||||
|
),
|
||||||
|
Box::new(ApplyVariant(
|
||||||
|
"Bar".to_string(),
|
||||||
|
None
|
||||||
|
)),
|
||||||
|
Box::new(Var(
|
||||||
|
"one".to_string()
|
||||||
|
))
|
||||||
|
))
|
||||||
|
),
|
||||||
""
|
""
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue