Fix variants getting mixed up with bools

This commit is contained in:
Richard Feldman 2019-06-05 01:51:04 -04:00
parent c2cec85202
commit 826b0d5ab0
5 changed files with 111 additions and 33 deletions

View file

@ -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)),

View file

@ -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 {

View file

@ -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()
} }
}) })
} }

View file

@ -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))))
) )

View file

@ -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()
))
))
),
"" ""
)) ))
); );