From a2e2b5e2ef73b188c0d9a9273da9e356b9e65114 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 1 Nov 2020 15:48:48 +0800 Subject: [PATCH] Add f32_float feature. --- .github/workflows/build.yml | 1 + Cargo.toml | 1 + doc/src/about/features.md | 1 + doc/src/about/non-design.md | 31 ++++++++---- doc/src/language/values-and-types.md | 2 +- doc/src/links.md | 1 + doc/src/start/features.md | 1 + src/lib.rs | 8 +++ src/packages/arithmetic.rs | 76 +++++++++++++++++++--------- src/packages/math_basic.rs | 11 ++-- tests/float.rs | 8 +-- tests/modules.rs | 4 +- tests/power_of.rs | 2 +- tests/types.rs | 8 ++- 14 files changed, 109 insertions(+), 46 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1ffeec22..04b3be8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 11dc351b..6311ac79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/doc/src/about/features.md b/doc/src/about/features.md index 84a9e233..101c631d 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -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. diff --git a/doc/src/about/non-design.md b/doc/src/about/non-design.md index 897efbfa..a8b06ea8 100644 --- a/doc/src/about/non-design.md +++ b/doc/src/about/non-design.md @@ -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. diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md index f7446913..c8fe9181 100644 --- a/doc/src/language/values-and-types.md +++ b/doc/src/language/values-and-types.md @@ -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`,
`u32`, `i32` (default for [`only_i32`]),
`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` or `Arc`) | `"string"` | `"hello"` etc. | diff --git a/doc/src/links.md b/doc/src/links.md index 5093c412..8c2cc211 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -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 diff --git a/doc/src/start/features.md b/doc/src/start/features.md index e82b1df1..60508ed4 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -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 | diff --git a/src/lib.rs b/src/lib.rs index 7889c553..621aea86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 3f04b50d..d061ceeb 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -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> { + 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> { - Ok(Dynamic::from(x.powf(y))) - } - #[rhai_fn(name = "~", return_raw)] pub fn pow_f_i(x: f32, y: INT) -> Result> { 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> { + Ok(Dynamic::from(x.powf(y))) + } + } #[rhai_fn(name = "-")] pub fn neg(x: f64) -> f64 { -x diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index c723d853..ef55765f 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -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> { 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"))] diff --git a/tests/float.rs b/tests/float.rs index 534115e3..1e973e29 100644 --- a/tests/float.rs +++ b/tests/float.rs @@ -34,19 +34,19 @@ fn test_float_parse() -> Result<(), Box> { fn test_struct_with_float() -> Result<(), Box> { #[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; } diff --git a/tests/modules.rs b/tests/modules.rs index e43b8e71..be23ac6b 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -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> { #[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(()) }, diff --git a/tests/power_of.rs b/tests/power_of.rs index a3b6fd43..2843c37b 100644 --- a/tests/power_of.rs +++ b/tests/power_of.rs @@ -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> { diff --git a/tests/types.rs b/tests/types.rs index d5a5928e..df72afdd 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -16,7 +16,13 @@ fn test_type_of() -> Result<(), Box> { assert_eq!(engine.eval::("type_of(60 + 5)")?, "i32"); #[cfg(not(feature = "no_float"))] - assert_eq!(engine.eval::("type_of(1.0 + 2.0)")?, "f64"); + { + #[cfg(not(feature = "f32_float"))] + assert_eq!(engine.eval::("type_of(1.0 + 2.0)")?, "f64"); + + #[cfg(feature = "f32_float")] + assert_eq!(engine.eval::("type_of(1.0 + 2.0)")?, "f32"); + } #[cfg(not(feature = "no_index"))] assert_eq!(