mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41: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
|
// 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)) => {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
42
src/parse.rs
42
src/parse.rs
|
@ -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>,
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 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('-')))
|
optional(attempt(char('-')))
|
||||||
// Do this lookahead to decide if we should parse this as a number.
|
.skip(look_ahead(digit()))
|
||||||
// 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()))
|
|
||||||
.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>
|
||||||
{
|
{
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue