Add f32_float feature.

This commit is contained in:
Stephen Chung 2020-11-01 15:48:48 +08:00
parent 629e02f9da
commit a2e2b5e2ef
14 changed files with 109 additions and 46 deletions

View File

@ -23,6 +23,7 @@ jobs:
- "--features sync"
- "--features no_optimize"
- "--features no_float"
- "--features f32_float"
- "--tests --features only_i32"
- "--features only_i64"
- "--features no_index"

View File

@ -32,6 +32,7 @@ unchecked = [] # unchecked arithmetic
sync = [] # restrict to only types that implement Send + Sync
no_optimize = [] # no script optimizer
no_float = [] # no floating-point
f32_float = [] # set FLOAT=f32
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
no_index = [] # no arrays and indexing

View File

@ -25,6 +25,7 @@ Fast
* Fairly low compile-time overhead.
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
An unofficial Fibonacci benchmark puts Rhai somewhere between Wren and Python.
* Scripts are [optimized][script optimization] (useful for template-based machine-generated scripts) for repeated evaluations.

View File

@ -6,35 +6,45 @@ What Rhai Isn't
Rhai's purpose is to provide a dynamic layer over Rust code, in the same spirit of _zero cost abstractions_.
It doesn't attempt to be a new language. For example:
* No classes. Well, Rust doesn't either. On the other hand...
* **No classes**. Well, Rust doesn't either. On the other hand...
* No traits... so it is also not Rust. Do your Rusty stuff in Rust.
* **No traits**... so it is also not Rust. Do your Rusty stuff in Rust.
* No structures/records/tuples - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
* **No structures/records/tuples** - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
There is, however, a built-in [object map] type which is adequate for most uses.
It is possible to simulate [object-oriented programming (OOP)][OOP] by storing [function pointers]
or [closures] in [object map] properties, turning them into _methods_.
* No first-class functions - Code your functions in Rust instead, and register them with Rhai.
* **No first-class functions** - Code your functions in Rust instead, and register them with Rhai.
There is, however, support for simple [function pointers] to allow runtime dispatch by function name.
* No garbage collection - this should be expected, so...
* **No garbage collection** - this should be expected, so...
* No first-class closures - do your closure magic in Rust instead: [turn a Rhai scripted function into a Rust closure]({{rootUrl}}/engine/call-fn.md).
* **No first-class closures** - do your closure magic in Rust instead: [turn a Rhai scripted function into a Rust closure]({{rootUrl}}/engine/call-fn.md).
There is, however, support for simulated [closures] via [currying] a [function pointer] with
captured shared variables.
* No byte-codes/JIT - Rhai has an AST-walking interpreter which will not win any speed races.
The purpose of Rhai is not to be extremely _fast_, but to make it as easy as possible to
* **No byte-codes/JIT** - Rhai has an optimized AST-walking interpreter which is fast enough for most usage scenarios.
Essential AST data structures are packed and kept together to maximize cache friendliness.
Functions are dispatched based on pre-calculated hashes and accessing variables are mostly through pre-calculated
offsets to the variables file (a [`Scope`]), so it is seldom necessary to look something up by text name.
In addition, Rhai's design deliberately avoids maintaining a _scope chain_ so function scopes do not
pay any speed penalty. This particular design also allows variables data to be kept together in a contiguous
block, avoiding allocations and fragmentation while being cache-friendly. In a typical script evaluation run,
no data is shared and nothing is locked.
Still, the purpose of Rhai is not to be super _fast_, but to make it as easy and versatile as possible to
integrate with native Rust applications.
* No formal language grammar - Rhai uses a hand-coded lexer, a hand-coded top-down recursive-descent parser
* **No formal language grammar** - Rhai uses a hand-coded lexer, a hand-coded top-down recursive-descent parser
for statements, and a hand-coded Pratt parser for expressions.
This lack of formalism allows the parser itself to be exposed as a service in order to support
This lack of formalism allows the _parser_ itself to be exposed as a service in order to support
[disabling keywords/operators][disable keywords and operators], adding [custom operators],
and defining [custom syntax].
@ -45,6 +55,7 @@ Do Not Write The Next 4D VR Game in Rhai
Due to this intended usage, Rhai deliberately keeps the language simple and small by omitting
advanced language features such as classes, inheritance, interfaces, generics,
first-class functions/closures, pattern matching, concurrency, byte-codes VM, JIT etc.
Focus is on _flexibility_ and _ease of use_ instead of raw speed.
Avoid the temptation to write full-fledge application logic entirely in Rhai -
that use case is best fulfilled by more complete languages such as JavaScript or Lua.

View File

@ -8,7 +8,7 @@ The following primitive types are supported natively:
| Category | Equivalent Rust types | [`type_of()`] | `to_string()` |
| -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- |
| **Integer number** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
| **Floating-point number** (disabled with [`no_float`]) | `f32` (default for [`f32_float`]), `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |

View File

@ -3,6 +3,7 @@
[`sync`]: {{rootUrl}}/start/features.md
[`no_optimize`]: {{rootUrl}}/start/features.md
[`no_float`]: {{rootUrl}}/start/features.md
[`f32_float`]: {{rootUrl}}/start/features.md
[`only_i32`]: {{rootUrl}}/start/features.md
[`only_i64`]: {{rootUrl}}/start/features.md
[`no_index`]: {{rootUrl}}/start/features.md

View File

@ -17,6 +17,7 @@ more control over what a script can (or cannot) do.
| `sync` | no | restricts all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync` |
| `no_optimize` | no | disables [script optimization] |
| `no_float` | no | disables floating-point numbers and math |
| `f32_float` | no | sets the system floating-point type to `f32` instead of `f64` |
| `only_i32` | no | sets the system integer type to `i32` and disable all other integer types. `INT` is set to `i32` |
| `only_i64` | no | sets the system integer type to `i64` and disable all other integer types. `INT` is set to `i64` |
| `no_index` | no | disables [arrays] and indexing features |

View File

@ -99,8 +99,16 @@ pub type INT = i32;
///
/// Not available under the `no_float` feature.
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "f32_float"))]
pub type FLOAT = f64;
/// The system floating-point type.
///
/// Not available under the `no_float` feature.
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "f32_float")]
pub type FLOAT = f32;
pub use ast::AST;
pub use dynamic::Dynamic;
pub use engine::{Engine, EvalContext};

View File

@ -224,25 +224,32 @@ gen_signed_functions!(signed_num_128 => i128);
#[cfg(not(feature = "no_float"))]
#[export_module]
mod f32_functions {
#[rhai_fn(name = "+")]
pub fn add(x: f32, y: f32) -> f32 {
x + y
}
#[rhai_fn(name = "-")]
pub fn subtract(x: f32, y: f32) -> f32 {
x - y
}
#[rhai_fn(name = "*")]
pub fn multiply(x: f32, y: f32) -> f32 {
x * y
}
#[rhai_fn(name = "/")]
pub fn divide(x: f32, y: f32) -> f32 {
x / y
}
#[rhai_fn(name = "%")]
pub fn modulo(x: f32, y: f32) -> f32 {
x % y
#[cfg(not(feature = "f32_float"))]
pub mod basic_arithmetic {
#[rhai_fn(name = "+")]
pub fn add(x: f32, y: f32) -> f32 {
x + y
}
#[rhai_fn(name = "-")]
pub fn subtract(x: f32, y: f32) -> f32 {
x - y
}
#[rhai_fn(name = "*")]
pub fn multiply(x: f32, y: f32) -> f32 {
x * y
}
#[rhai_fn(name = "/")]
pub fn divide(x: f32, y: f32) -> f32 {
x / y
}
#[rhai_fn(name = "%")]
pub fn modulo(x: f32, y: f32) -> f32 {
x % y
}
#[rhai_fn(name = "~", return_raw)]
pub fn pow_f_f(x: f32, y: f32) -> Result<Dynamic, Box<EvalAltResult>> {
Ok(Dynamic::from(x.powf(y)))
}
}
#[rhai_fn(name = "-")]
pub fn neg(x: f32) -> f32 {
@ -261,10 +268,6 @@ mod f32_functions {
}
}
#[rhai_fn(name = "~", return_raw)]
pub fn pow_f_f(x: f32, y: f32) -> Result<Dynamic, Box<EvalAltResult>> {
Ok(Dynamic::from(x.powf(y)))
}
#[rhai_fn(name = "~", return_raw)]
pub fn pow_f_i(x: f32, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) {
Err(make_err(format!(
@ -280,6 +283,33 @@ mod f32_functions {
#[cfg(not(feature = "no_float"))]
#[export_module]
mod f64_functions {
#[cfg(feature = "f32_float")]
pub mod basic_arithmetic {
#[rhai_fn(name = "+")]
pub fn add(x: f64, y: f64) -> f64 {
x + y
}
#[rhai_fn(name = "-")]
pub fn subtract(x: f64, y: f64) -> f64 {
x - y
}
#[rhai_fn(name = "*")]
pub fn multiply(x: f64, y: f64) -> f64 {
x * y
}
#[rhai_fn(name = "/")]
pub fn divide(x: f64, y: f64) -> f64 {
x / y
}
#[rhai_fn(name = "%")]
pub fn modulo(x: f64, y: f64) -> f64 {
x % y
}
#[rhai_fn(name = "~", return_raw)]
pub fn pow_f_f(x: f64, y: f64) -> Result<Dynamic, Box<EvalAltResult>> {
Ok(Dynamic::from(x.powf(y)))
}
}
#[rhai_fn(name = "-")]
pub fn neg(x: f64) -> f64 {
-x

View File

@ -215,10 +215,6 @@ mod float_functions {
Ok((x.trunc() as INT).into())
}
}
#[rhai_fn(name = "to_float")]
pub fn f32_to_float(x: f32) -> FLOAT {
x as FLOAT
}
#[rhai_fn(name = "to_int", return_raw)]
pub fn f64_to_int(x: f64) -> Result<Dynamic, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) && x > (MAX_INT as f64) {
@ -244,6 +240,13 @@ mod float_functions {
.into()
})
}
#[cfg(not(feature = "f32_float"))]
pub mod f32_f64 {
#[rhai_fn(name = "to_float")]
pub fn f32_to_f64(x: f32) -> f64 {
x as f64
}
}
}
#[cfg(not(feature = "no_float"))]

View File

@ -34,19 +34,19 @@ fn test_float_parse() -> Result<(), Box<EvalAltResult>> {
fn test_struct_with_float() -> Result<(), Box<EvalAltResult>> {
#[derive(Clone)]
struct TestStruct {
x: f64,
x: FLOAT,
}
impl TestStruct {
fn update(&mut self) {
self.x += 5.789_f64;
self.x += 5.789;
}
fn get_x(&mut self) -> f64 {
fn get_x(&mut self) -> FLOAT {
self.x
}
fn set_x(&mut self, new_x: f64) {
fn set_x(&mut self, new_x: FLOAT) {
self.x = new_x;
}

View File

@ -1,7 +1,7 @@
#![cfg(not(feature = "no_module"))]
use rhai::{
module_resolvers::StaticModuleResolver, Dynamic, Engine, EvalAltResult, ImmutableString,
Module, ParseError, ParseErrorType, Scope, INT,
Module, ParseError, ParseErrorType, Scope, FLOAT, INT,
};
#[test]
@ -81,7 +81,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_float"))]
module.set_fn_4_mut(
"sum_of_three_args".to_string(),
|target: &mut INT, a: INT, b: INT, c: f64| {
|target: &mut INT, a: INT, b: INT, c: FLOAT| {
*target = a + b + c as INT;
Ok(())
},

View File

@ -4,7 +4,7 @@ use rhai::{Engine, EvalAltResult, INT};
use rhai::FLOAT;
#[cfg(not(feature = "no_float"))]
const EPSILON: FLOAT = 0.000_000_000_1;
const EPSILON: FLOAT = 0.000_001;
#[test]
fn test_power_of() -> Result<(), Box<EvalAltResult>> {

View File

@ -16,7 +16,13 @@ fn test_type_of() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<String>("type_of(60 + 5)")?, "i32");
#[cfg(not(feature = "no_float"))]
assert_eq!(engine.eval::<String>("type_of(1.0 + 2.0)")?, "f64");
{
#[cfg(not(feature = "f32_float"))]
assert_eq!(engine.eval::<String>("type_of(1.0 + 2.0)")?, "f64");
#[cfg(feature = "f32_float")]
assert_eq!(engine.eval::<String>("type_of(1.0 + 2.0)")?, "f32");
}
#[cfg(not(feature = "no_index"))]
assert_eq!(