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
Int(i64),
Frac(Fraction),
Approx(f64),
EmptyStr,
Str(String),
InterpolatedStr(Vec<(String, Ident)>, String),
@ -61,6 +62,7 @@ pub fn scoped_eval(expr: Located<Expr>, vars: &Scope) -> Evaluated {
Expr::EmptyStr => EmptyStr,
Expr::Str(string) => Str(string),
Expr::Frac(numerator, denominator) => Frac(fraction_from_i64s(numerator, denominator)),
Expr::Approx(num) => Approx(num),
Expr::Char(ch) => Char(ch),
Expr::Closure(args, body) => Closure(args.into_iter().map(|e| e.value).collect(), body, vars.clone()),
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())),
// 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)) => {
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())),
(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())),
// DoubleSlash
(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())),
(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())),
// 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
(Int(left_num), Percent, Int(right_num)) => Int(left_num % right_num),
(Frac(left_num), Percent, Frac(right_num)) => {

View file

@ -9,6 +9,7 @@ pub enum Expr {
// Literals
Int(i64),
Frac(i64, i64),
Approx(f64),
EmptyStr,
Str(String),
InterpolatedStr(Vec<(String, Located<Ident>)>, String),
@ -55,7 +56,7 @@ impl Expr {
let transformed = transform(self);
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,

View file

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

View file

@ -207,7 +207,7 @@ parser! {
apply_with_parens(min_indent),
string("{}").with(value(Expr::EmptyRecord)),
string_literal(),
number_literal(),
int_or_frac_literal(),
char_literal(),
if_expr(min_indent),
case_expr(min_indent),
@ -433,6 +433,18 @@ where I: Stream<Item = char, Position = IndentablePosition>,
char('>').map(|_| Operator:: Pizza)
.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::Minus),
char('*').map(|_| Operator::Star),
@ -526,7 +538,7 @@ parser! {
char('_').map(|_| Pattern::Underscore),
string("{}").map(|_| Pattern::EmptyRecordLiteral),
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)),
))
}
@ -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>,
I::Error: ParseError<I::Item, I::Range, I::Position>
{
@ -810,24 +822,24 @@ where I: Stream<Item = char, Position = IndentablePosition>,
))
);
// 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,
// we may discover non-digit chars, indicating this is actually an
// invalid identifier. (e.g. "523foo" looks like a number, but turns
// out to be an invalid identifier on closer inspection.)
optional(attempt(char('-')))
// 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,
// we may discover non-digit chars, indicating this is actually an
// invalid identifier. (e.g. "523foo" looks like a number, but turns
// out to be an invalid identifier on closer inspection.)
.and(look_ahead(digit()))
.skip(look_ahead(digit()))
.and(digits_before_decimal)
.and(optional(char('.').with(digits_after_decimal)))
.then(|(((opt_minus, _), int_digits), decimals): (((Option<char>, _), Vec<char>), Option<Vec<char>>)| {
let is_positive = opt_minus.is_none();
.then(|((opt_minus, int_digits), decimals): ((Option<char>, Vec<char>), Option<Vec<char>>)| {
let is_non_negative = opt_minus.is_none();
// TODO check length of digits and make sure not to overflow
let int_str: String = int_digits.into_iter().collect();
match ( int_str.parse::<i64>(), decimals ) {
(Ok(int_val), None) => {
if is_positive {
if is_non_negative {
value(Expr::Int(int_val as i64)).right()
} else {
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!
let numerator = (int_val * denom) + (decimal as i64);
if is_positive {
if is_non_negative {
value(Expr::Frac(numerator, denom)).right()
} else {
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
/// 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"
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>,
I::Error: ParseError<I::Item, I::Range, I::Position>
{

View file

@ -30,7 +30,7 @@ mod test_parse {
/// having to account for that.
fn zero_loc_expr(expr: Expr) -> 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),
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()),