mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
Introduce Approx
This commit is contained in:
parent
5da8a29015
commit
67a507080d
5 changed files with 60 additions and 24 deletions
31
src/eval.rs
31
src/eval.rs
|
@ -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)) => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
34
src/parse.rs
34
src/parse.rs
|
@ -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>,
|
|||
))
|
||||
);
|
||||
|
||||
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()))
|
||||
optional(attempt(char('-')))
|
||||
.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>
|
||||
{
|
||||
|
|
|
@ -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()),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue