Introduce Approx

This commit is contained in:
Richard Feldman 2019-07-11 23:14:32 -04:00
parent 5da8a29015
commit 67a507080d
5 changed files with 60 additions and 24 deletions

View file

@ -23,6 +23,7 @@ pub enum Evaluated {
// Literals // Literals
Int(i64), Int(i64),
Frac(Fraction), Frac(Fraction),
Approx(f64),
EmptyStr, EmptyStr,
Str(String), Str(String),
InterpolatedStr(Vec<(String, Ident)>, String), InterpolatedStr(Vec<(String, Ident)>, String),
@ -61,6 +62,7 @@ pub fn scoped_eval(expr: Located<Expr>, vars: &Scope) -> Evaluated {
Expr::EmptyStr => EmptyStr, Expr::EmptyStr => EmptyStr,
Expr::Str(string) => Str(string), Expr::Str(string) => Str(string),
Expr::Frac(numerator, denominator) => Frac(fraction_from_i64s(numerator, denominator)), Expr::Frac(numerator, denominator) => Frac(fraction_from_i64s(numerator, denominator)),
Expr::Approx(num) => Approx(num),
Expr::Char(ch) => Char(ch), Expr::Char(ch) => Char(ch),
Expr::Closure(args, body) => Closure(args.into_iter().map(|e| e.value).collect(), body, vars.clone()), Expr::Closure(args, body) => Closure(args.into_iter().map(|e| e.value).collect(), body, vars.clone()),
Expr::EmptyRecord => EmptyRecord, Expr::EmptyRecord => EmptyRecord,
@ -381,7 +383,6 @@ fn eval_operator(region: Region, left_expr: &Evaluated, op: Operator, right_expr
(_, Caret, _) => EvalError(region, TypeMismatch("tried to use ^ on non-numbers".to_string())), (_, Caret, _) => EvalError(region, TypeMismatch("tried to use ^ on non-numbers".to_string())),
// Slash // Slash
(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)) => { (Frac(left_num), Slash, Frac(right_num)) => {
let answer = left_num / right_num; let answer = left_num / right_num;
@ -392,22 +393,44 @@ fn eval_operator(region: Region, left_expr: &Evaluated, op: Operator, right_expr
} }
}, },
(Int(_), Slash, Int(_)) => EvalError(region, TypeMismatch("tried to divide two Int values. Explicitly convert them to Frac values, or use Int division (the // operator).".to_string())),
(Approx(_), Slash, Approx(_)) => EvalError(region, TypeMismatch("tried to divide two Approx values. Explicitly convert them to Frac values, or use Approx division (the ~/ operator).".to_string())),
(Int(_), Slash, Frac(_)) => EvalError(region, TypeMismatch("tried to divide Int by Frac. Explicitly convert them to the same type first!".to_string())), (Int(_), Slash, Frac(_)) => EvalError(region, TypeMismatch("tried to divide Int by Frac. Explicitly convert them to the same type first!".to_string())),
(Frac(_), Slash, Int(_)) => EvalError(region, TypeMismatch("tried to divide Frac by Int. Explicitly convert them to the same type first!".to_string())), (Frac(_), Slash, Int(_)) => EvalError(region, TypeMismatch("tried to divide Frac by Int. Explicitly convert them to the same type first!".to_string())),
(_, Slash, _) => EvalError(region, TypeMismatch("tried to divide non-numbers".to_string())), (_, Slash, _) => EvalError(region, TypeMismatch("tried to divide non-numbers".to_string())),
// DoubleSlash // DoubleSlash
(Int(left_num), DoubleSlash, Int(right_num)) => Int(left_num / right_num), (Int(left_num), DoubleSlash, Int(right_num)) => Int(left_num / right_num),
(Frac(_), DoubleSlash, Frac(_)) => EvalError(region, TypeMismatch("tried to do integer division on two Frac values".to_string())),
(Approx(_), DoubleSlash, Approx(_)) => EvalError(region, TypeMismatch("tried to do integer division on two Approx values. Explicitly convert them to Int values, or use Approx division (the ~/ operator).".to_string())),
(Frac(_), DoubleSlash, Frac(_)) => EvalError(region, TypeMismatch("tried to do integer division on two Frac values. Explicitly conver them to Int values, or use Frac division (the / operator).".to_string())),
(Int(_), DoubleSlash, Frac(_)) => EvalError(region,TypeMismatch("tried to integer-divide Int by Frac".to_string())), (Int(_), DoubleSlash, Frac(_)) => EvalError(region,TypeMismatch("tried to integer-divide Int by Frac".to_string())),
(Frac(_), DoubleSlash, Int(_)) => EvalError(region, TypeMismatch("tried to integer-divide Frac by Int".to_string())), (Frac(_), DoubleSlash, Int(_)) => EvalError(region, TypeMismatch("tried to integer-divide Frac by Int".to_string())),
(_, DoubleSlash, _) => EvalError(region, TypeMismatch("tried to do integer division on two non-numbers".to_string())), (_, DoubleSlash, _) => EvalError(region, TypeMismatch("tried to do integer division on two non-numbers".to_string())),
// TildeSlash
(Approx(left_num), TildeSlash, Approx(right_num)) => {
let answer = left_num / right_num;
if answer.is_finite() {
ok_variant(Approx(answer))
} else {
err_variant(ApplyVariant("DivisionByZero".to_string(), None))
}
},
(Int(_), TildeSlash, Int(_)) => EvalError(region, TypeMismatch("tried to do Approx division on two Int values. Explicitly convert them to Approx values, or use Int division (the // operator).".to_string())),
(Frac(_), TildeSlash, Frac(_)) => EvalError(region, TypeMismatch("tried to do Approx division on two Frac values. Explicitly conver them to Approx values, or use Frac division (the / operator).".to_string())),
(Int(_), TildeSlash, Approx(_)) => EvalError(region, TypeMismatch("tried to do Int ~/ Approx. Explicitly convert both to Approx first!".to_string())),
(Frac(_), TildeSlash, Approx(_)) => EvalError(region, TypeMismatch("tried to do Frac ~/ Approx. Explicitly convert both to Approx first!".to_string())),
(Approx(_), TildeSlash, Int(_)) => EvalError(region, TypeMismatch("tried to divide Approx ~/ Int. Explicitly convert both to Approx first!".to_string())),
(Approx(_), TildeSlash, Frac(_)) => EvalError(region, TypeMismatch("tried to divide Approx ~/ Frac. Explicitly convert both to Approx first!".to_string())),
(_, TildeSlash, _) => EvalError(region, TypeMismatch("tried to divide non-numbers".to_string())),
// Percent // Percent
(Int(left_num), Percent, Int(right_num)) => Int(left_num % right_num), (Int(left_num), Percent, Int(right_num)) => Int(left_num % right_num),
(Frac(left_num), Percent, Frac(right_num)) => { (Frac(left_num), Percent, Frac(right_num)) => {

View file

@ -9,6 +9,7 @@ pub enum Expr {
// Literals // Literals
Int(i64), Int(i64),
Frac(i64, i64), Frac(i64, i64),
Approx(f64),
EmptyStr, EmptyStr,
Str(String), Str(String),
InterpolatedStr(Vec<(String, Located<Ident>)>, String), InterpolatedStr(Vec<(String, Located<Ident>)>, String),
@ -55,7 +56,7 @@ impl Expr {
let transformed = transform(self); let transformed = transform(self);
match transformed { match transformed {
Int(_) | Frac(_, _) | EmptyStr | Str(_) | Char(_) | Var(_) | EmptyRecord | InterpolatedStr(_, _) => transformed, Int(_) | Frac(_, _) | Approx(_) | EmptyStr | Str(_) | Char(_) | Var(_) | EmptyRecord | InterpolatedStr(_, _) => transformed,
Assign(pattern, expr1, expr2) => { Assign(pattern, expr1, expr2) => {
Assign( Assign(
pattern, pattern,

View file

@ -5,7 +5,7 @@ use self::Operator::*;
pub enum Operator { pub enum Operator {
// highest precedence // highest precedence
Caret, Caret,
Star, Slash, DoubleSlash, Percent, Star, Slash, DoubleSlash, TildeSlash, Percent,
Plus, Minus, Plus, Minus,
Equals, LessThan, GreaterThan, LessThanOrEq, GreaterThanOrEq, Equals, LessThan, GreaterThan, LessThanOrEq, GreaterThanOrEq,
And, And,
@ -40,7 +40,7 @@ impl Operator {
use self::Associativity::*; use self::Associativity::*;
match self { match self {
Pizza | Star | Slash | DoubleSlash | Percent | Plus | Minus => LeftAssociative, Pizza | Star | Slash | DoubleSlash | TildeSlash | Percent | Plus | Minus => LeftAssociative,
And | Or | Caret => RightAssociative, And | Or | Caret => RightAssociative,
Equals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => NonAssociative Equals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => NonAssociative
} }
@ -49,7 +49,7 @@ impl Operator {
fn precedence(&self) -> u8 { fn precedence(&self) -> u8 {
match self { match self {
Caret => 7, Caret => 7,
Star | Slash | DoubleSlash | Percent => 6, Star | Slash | DoubleSlash | TildeSlash | Percent => 6,
Plus | Minus => 5, Plus | Minus => 5,
Equals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => 4, Equals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => 4,
And => 3, And => 3,

View file

@ -207,7 +207,7 @@ parser! {
apply_with_parens(min_indent), apply_with_parens(min_indent),
string("{}").with(value(Expr::EmptyRecord)), string("{}").with(value(Expr::EmptyRecord)),
string_literal(), string_literal(),
number_literal(), int_or_frac_literal(),
char_literal(), char_literal(),
if_expr(min_indent), if_expr(min_indent),
case_expr(min_indent), case_expr(min_indent),
@ -433,6 +433,18 @@ where I: Stream<Item = char, Position = IndentablePosition>,
char('>').map(|_| Operator:: Pizza) char('>').map(|_| Operator:: Pizza)
.or(char('|').map(|_| Operator::Or)) .or(char('|').map(|_| Operator::Or))
), ),
// either / or //
char('/').with(
optional(char('/'))
.map(|opt_slash| {
if opt_slash.is_none() {
Operator::Slash
} else {
Operator::DoubleSlash
}
})
),
string("~/").map(|_| Operator::TildeSlash),
char('+').map(|_| Operator::Plus), char('+').map(|_| Operator::Plus),
char('-').map(|_| Operator::Minus), char('-').map(|_| Operator::Minus),
char('*').map(|_| Operator::Star), char('*').map(|_| Operator::Star),
@ -526,7 +538,7 @@ parser! {
char('_').map(|_| Pattern::Underscore), char('_').map(|_| Pattern::Underscore),
string("{}").map(|_| Pattern::EmptyRecordLiteral), string("{}").map(|_| Pattern::EmptyRecordLiteral),
match_variant(min_indent), match_variant(min_indent),
number_pattern(), // This goes before ident() so number literals aren't mistaken for malformed idents. int_or_frac_pattern(), // This goes before ident() so number literals aren't mistaken for malformed idents.
ident().map(|name| Pattern::Identifier(name)), ident().map(|name| Pattern::Identifier(name)),
)) ))
} }
@ -787,7 +799,7 @@ where
}) })
} }
pub fn number_literal<I>() -> impl Parser<Input = I, Output = Expr> pub fn int_or_frac_literal<I>() -> impl Parser<Input = I, Output = Expr>
where I: Stream<Item = char, Position = IndentablePosition>, 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>
{ {
@ -810,24 +822,24 @@ where I: Stream<Item = char, Position = IndentablePosition>,
)) ))
); );
optional(attempt(char('-')))
// Do this lookahead to decide if we should parse this as a number. // Do this lookahead to decide if we should parse this as a number.
// This matters because once we commit to parsing it as a number, // This matters because once we commit to parsing it as a number,
// we may discover non-digit chars, indicating this is actually an // we may discover non-digit chars, indicating this is actually an
// invalid identifier. (e.g. "523foo" looks like a number, but turns // invalid identifier. (e.g. "523foo" looks like a number, but turns
// out to be an invalid identifier on closer inspection.) // out to be an invalid identifier on closer inspection.)
.and(look_ahead(digit())) optional(attempt(char('-')))
.skip(look_ahead(digit()))
.and(digits_before_decimal) .and(digits_before_decimal)
.and(optional(char('.').with(digits_after_decimal))) .and(optional(char('.').with(digits_after_decimal)))
.then(|(((opt_minus, _), int_digits), decimals): (((Option<char>, _), Vec<char>), Option<Vec<char>>)| { .then(|((opt_minus, int_digits), decimals): ((Option<char>, Vec<char>), Option<Vec<char>>)| {
let is_positive = opt_minus.is_none(); let is_non_negative = opt_minus.is_none();
// TODO check length of digits and make sure not to overflow // TODO check length of digits and make sure not to overflow
let int_str: String = int_digits.into_iter().collect(); let int_str: String = int_digits.into_iter().collect();
match ( int_str.parse::<i64>(), decimals ) { match ( int_str.parse::<i64>(), decimals ) {
(Ok(int_val), None) => { (Ok(int_val), None) => {
if is_positive { if is_non_negative {
value(Expr::Int(int_val as i64)).right() value(Expr::Int(int_val as i64)).right()
} else { } else {
value(Expr::Int(-int_val as i64)).right() value(Expr::Int(-int_val as i64)).right()
@ -844,7 +856,7 @@ where I: Stream<Item = char, Position = IndentablePosition>,
// Only the numerator may ever be signed! // Only the numerator may ever be signed!
let numerator = (int_val * denom) + (decimal as i64); let numerator = (int_val * denom) + (decimal as i64);
if is_positive { if is_non_negative {
value(Expr::Frac(numerator, denom)).right() value(Expr::Frac(numerator, denom)).right()
} else { } else {
value(Expr::Frac(-numerator, denom)).right() value(Expr::Frac(-numerator, denom)).right()
@ -861,11 +873,11 @@ where I: Stream<Item = char, Position = IndentablePosition>,
}) })
} }
/// TODO find a way to remove the code duplication between this and number_literal /// TODO find a way to remove the code duplication between this and int_or_frac_literal
/// without sacrificing performance. I attempted to do this in 0062e83d03d389f0f07e33e1e7929e77825d774f /// without sacrificing performance. I attempted to do this in 0062e83d03d389f0f07e33e1e7929e77825d774f
/// but couldn't figure out how to address the resulting compiler error, which was: /// but couldn't figure out how to address the resulting compiler error, which was:
/// "cannot move out of captured outer variable in an `FnMut` closure" /// "cannot move out of captured outer variable in an `FnMut` closure"
pub fn number_pattern<I>() -> impl Parser<Input = I, Output = Pattern> pub fn int_or_frac_pattern<I>() -> impl Parser<Input = I, Output = Pattern>
where I: Stream<Item = char, Position = IndentablePosition>, 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>
{ {

View file

@ -30,7 +30,7 @@ mod test_parse {
/// having to account for that. /// having to account for that.
fn zero_loc_expr(expr: Expr) -> Expr { fn zero_loc_expr(expr: Expr) -> Expr {
match expr { match expr {
Int(_) | Frac(_, _) | EmptyStr | Str(_) | Char(_) | Var(_) | EmptyRecord => expr, Int(_) | Frac(_, _) | Approx(_) | EmptyStr | Str(_) | Char(_) | Var(_) | EmptyRecord => expr,
InterpolatedStr(pairs, string) => InterpolatedStr(pairs.into_iter().map(|( prefix, ident )| ( prefix, zero_loc(ident))).collect(), string), InterpolatedStr(pairs, string) => InterpolatedStr(pairs.into_iter().map(|( prefix, ident )| ( prefix, zero_loc(ident))).collect(), string),
Assign(pattern, expr1, expr2) => Assign(loc(pattern.value), loc_box(zero_loc_expr((*expr1).value)), loc_box(zero_loc_expr((*expr2).value))), Assign(pattern, expr1, expr2) => Assign(loc(pattern.value), loc_box(zero_loc_expr((*expr1).value)), loc_box(zero_loc_expr((*expr2).value))),
CallByName(ident, args) => CallByName(ident, args.into_iter().map(|arg| loc(zero_loc_expr(arg.value))).collect()), CallByName(ident, args) => CallByName(ident, args.into_iter().map(|arg| loc(zero_loc_expr(arg.value))).collect()),