From b9d562eba4c3b8c9391c6b233dde9521190075ec Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 9 Mar 2020 11:42:10 +0800 Subject: [PATCH] Add standard math functions and make power functions checked. --- README.md | 98 +++++++++++++++++++++++++++++++++++---------- src/builtin.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 176 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index ebfc0909..45371f98 100644 --- a/README.md +++ b/README.md @@ -157,12 +157,14 @@ let result: i64 = engine.call_fn("hello", &ast, (&mut String::from("abc"), &mut The following primitive types are supported natively: -* Integer: `i32`, `u32`, `i64` (default), `u64` -* Floating-point: `f32`, `f64` (default) -* Character: `char` -* Boolean: `bool` -* Array: `rhai::Array` -* Dynamic (i.e. can be anything): `rhai::Dynamic` +| Category | Types | +| ------------------------------ | -------------------------------------- | +| Integer | `i32`, `u32`, `i64` _(default)_, `u64` | +| Floating-point | `f32`, `f64` _(default)_ | +| Character | `char` | +| Boolean | `bool` | +| Array | `rhai::Array` | +| Dynamic (i.e. can be anything) | `rhai::Dynamic` | # Value conversions @@ -508,16 +510,60 @@ fn main() { ## Variables +Variables in `Rhai` follow normal naming rules: + +* Must start with an ASCII letter +* Must contain only ASCII letters, digits and `_` underscores + +Example: + ```rust let x = 3; ``` +## Numbers + +| Format | Type | +| ---------------- | ------------------------------------------------------ | +| `123_345`, `-42` | `i64` in decimal, '`_`' separator can be used anywhere | +| `0o07_76` | `i64` in octal, '`_`' separator can be used anywhere | +| `0xabcd_ef` | `i64` in hex, '`_`' separator can be used anywhere | +| `0b0101_1001` | `i64` in binary, '`_`' separator can be used anywhere | +| `123_456.789` | `f64`, '`_`' separator can be used anywhere | + ## Numeric operators ```rust -let x = (1 + 2) * (6 - 4) / 2; +let x = (1 + 2) * (6 - 4) / 2; // arithmetic +let reminder = 42 % 10; // modulo +let power = 42 ~ 2; // power (i64 and f64 only) +let left_shifted = 42 << 3; // left shift +let right_shifted = 42 >> 3; // right shift +let bit_op = 42 | 99; // bit masking ``` +## Numeric functions + +The following standard functions (defined in the standard library but excluded if you use the `no_stdlib` feature) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: + +| Category | Functions | +| -------- | -------------- | +| `abs` | absolute value | + +## Floating-point functions + +The following standard functions (defined in the standard library but excluded if you use the `no_stdlib` feature) operate on `f64` only: + +| Category | Functions | +| ---------------- | ------------------------------------------------------------ | +| Trigonometry | `sin`, `cos`, `tan`, `sinh`, `cosh`, `tanh` in degrees | +| Arc-trigonometry | `asin`, `acos`, `atan`, `asinh`, `acosh`, `atanh` in degrees | +| Square root | `sqrt` | +| Exponential | `exp` (base _e_) | +| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) | +| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` | +| Tests | `is_nan`, `is_finite`, `is_infinite` | + ## Comparison operators You can compare most values of the same data type. If you compare two values of _different_ data types, the result is always `false`. @@ -664,13 +710,17 @@ You can create arrays of values, and then access them with numeric indices. The following functions (defined in the standard library but excluded if you use the `no_stdlib` feature) operate on arrays: -* `push` - inserts an element at the end -* `pop` - removes the last element and returns it (() if empty) -* `shift` - removes the first element and returns it (() if empty) -* `len` - returns the number of elements -* `pad` - pads the array with an element until a specified length -* `clear` - empties the array -* `truncate` - cuts off the array at exactly a specified length (discarding all subsequent elements) +| Function | Description | +| ---------- | ------------------------------------------------------------------------------------- | +| `push` | inserts an element at the end | +| `pop` | removes the last element and returns it (`()` if empty) | +| `shift` | removes the first element and returns it (`()` if empty) | +| `len` | returns the number of elements | +| `pad` | pads the array with an element until a specified length | +| `clear` | empties the array | +| `truncate` | cuts off the array at exactly a specified length (discarding all subsequent elements) | + +Examples: ```rust let y = [1, 2, 3]; // 3 elements @@ -813,14 +863,18 @@ record == "Bob X. Davis: age 42 ❤\n"; The following standard functions (defined in the standard library but excluded if you use the `no_stdlib` feature) operate on strings: -* `len` - returns the number of characters (not number of bytes) in the string -* `pad` - pads the string with an character until a specified number of characters -* `append` - Adds a character or a string to the end of another string -* `clear` - empties the string -* `truncate` - cuts off the string at exactly a specified number of characters -* `contains` - checks if a certain character or sub-string occurs in the string -* `replace` - replaces a substring with another -* `trim` - trims the string +| Function | Description | +| ---------- | ------------------------------------------------------------------------ | +| `len` | returns the number of characters (not number of bytes) in the string | +| `pad` | pads the string with an character until a specified number of characters | +| `append` | Adds a character or a string to the end of another string | +| `clear` | empties the string | +| `truncate` | cuts off the string at exactly a specified number of characters | +| `contains` | checks if a certain character or sub-string occurs in the string | +| `replace` | replaces a substring with another | +| `trim` | trims the string | + +Examples: ```rust let full_name == " Bob C. Davis "; diff --git a/src/builtin.rs b/src/builtin.rs index e7cfb45b..9003b13b 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -6,6 +6,7 @@ use crate::engine::{Array, Engine}; use crate::fn_register::RegisterFn; use std::fmt::{Debug, Display}; use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Sub}; +use std::{i32, i64, u32}; #[cfg(feature = "unchecked")] use std::ops::{Shl, Shr}; @@ -293,12 +294,41 @@ impl Engine<'_> { fn modulo_u(x: T, y: T) -> ::Output { x % y } + #[cfg(not(feature = "unchecked"))] + fn pow_i64_i64_u(x: i64, y: i64) -> Result { + if y > (u32::MAX as i64) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )); + } + + x.checked_pow(y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + ) + }) + } + #[cfg(feature = "unchecked")] fn pow_i64_i64(x: i64, y: i64) -> i64 { - x.pow(y as u32) + x.powi(y as u32) } fn pow_f64_f64(x: f64, y: f64) -> f64 { x.powf(y) } + #[cfg(not(feature = "unchecked"))] + fn pow_f64_i64_u(x: f64, y: i64) -> Result { + if y > (i32::MAX as i64) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )); + } + + Ok(x.powi(y as i32)) + } + #[cfg(feature = "unchecked")] fn pow_f64_i64(x: f64, y: i64) -> f64 { x.powi(y as i32) } @@ -361,9 +391,19 @@ impl Engine<'_> { reg_op!(self, "%", modulo_u, f32, f64); - self.register_fn("~", pow_i64_i64); self.register_fn("~", pow_f64_f64); - self.register_fn("~", pow_f64_i64); + + #[cfg(not(feature = "unchecked"))] + { + self.register_result_fn("~", pow_i64_i64_u); + self.register_result_fn("~", pow_f64_i64_u); + } + + #[cfg(feature = "unchecked")] + { + self.register_fn("~", pow_i64_i64); + self.register_fn("~", pow_f64_i64); + } #[cfg(not(feature = "unchecked"))] { @@ -427,6 +467,33 @@ impl Engine<'_> { pub(crate) fn register_stdlib(&mut self) { use crate::fn_register::RegisterDynamicFn; + // Advanced math functions + self.register_fn("sin", |x: f64| x.to_radians().sin()); + self.register_fn("cos", |x: f64| x.to_radians().cos()); + self.register_fn("tan", |x: f64| x.to_radians().tan()); + self.register_fn("sinh", |x: f64| x.to_radians().sinh()); + self.register_fn("cosh", |x: f64| x.to_radians().cosh()); + self.register_fn("tanh", |x: f64| x.to_radians().tanh()); + self.register_fn("asin", |x: f64| x.asin().to_degrees()); + self.register_fn("acos", |x: f64| x.acos().to_degrees()); + self.register_fn("atan", |x: f64| x.atan().to_degrees()); + self.register_fn("asinh", |x: f64| x.asinh().to_degrees()); + self.register_fn("acosh", |x: f64| x.acosh().to_degrees()); + self.register_fn("atanh", |x: f64| x.atanh().to_degrees()); + self.register_fn("sqrt", |x: f64| x.sqrt()); + self.register_fn("exp", |x: f64| x.exp()); + self.register_fn("ln", |x: f64| x.ln()); + self.register_fn("log", |x: f64, base: f64| x.log(base)); + self.register_fn("log10", |x: f64| x.log10()); + self.register_fn("floor", |x: f64| x.floor()); + self.register_fn("ceiling", |x: f64| x.ceil()); + self.register_fn("round", |x: f64| x.ceil()); + self.register_fn("int", |x: f64| x.trunc()); + self.register_fn("fraction", |x: f64| x.fract()); + self.register_fn("is_nan", |x: f64| x.is_nan()); + self.register_fn("is_finite", |x: f64| x.is_finite()); + self.register_fn("is_infinite", |x: f64| x.is_infinite()); + // Register conversion functions self.register_fn("to_float", |x: i8| x as f64); self.register_fn("to_float", |x: u8| x as f64); @@ -445,11 +512,38 @@ impl Engine<'_> { self.register_fn("to_int", |x: i32| x as i64); self.register_fn("to_int", |x: u32| x as i64); self.register_fn("to_int", |x: u64| x as i64); - self.register_fn("to_int", |x: f32| x as i64); - self.register_fn("to_int", |x: f64| x as i64); - self.register_fn("to_int", |ch: char| ch as i64); + #[cfg(not(feature = "unchecked"))] + { + self.register_result_fn("to_int", |x: f32| { + if x > (i64::MAX as f32) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + )); + } + + Ok(x.trunc() as i64) + }); + self.register_result_fn("to_int", |x: f64| { + if x > (i64::MAX as f64) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + )); + } + + Ok(x.trunc() as i64) + }); + } + + #[cfg(feature = "unchecked")] + { + self.register_fn("to_int", |x: f32| x as i64); + self.register_fn("to_int", |x: f64| x as i64); + } + // Register array utility functions fn push(list: &mut Array, item: T) { list.push(Box::new(item));