Merge pull request #156 from schungx/master

Immutable strings and built-in common operators
This commit is contained in:
Stephen Chung 2020-06-01 10:54:59 +08:00 committed by GitHub
commit b2f7c50969
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 3800 additions and 2306 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "rhai"
version = "0.14.1"
version = "0.15.0"
edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust"
@ -20,26 +20,22 @@ categories = [ "no-std", "embedded", "parser-implementations" ]
num-traits = { version = "0.2.11", default-features = false }
[features]
#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_module", "no_float", "only_i32", "unchecked", "no_optimize", "sync"]
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
default = []
unchecked = [] # unchecked arithmetic
no_index = [] # no arrays and indexing
no_float = [] # no floating-point
no_function = [] # no script-defined functions
no_object = [] # no custom objects
sync = [] # restrict to only types that implement Send + Sync
no_optimize = [] # no script optimizer
no_module = [] # no modules
no_float = [] # no floating-point
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
sync = [] # restrict to only types that implement Send + Sync
no_index = [] # no arrays and indexing
no_object = [] # no custom objects
no_function = [] # no script-defined functions
no_module = [] # no modules
# compiling for no-std
no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
# other developer features
no_stdlib = [] # do not register the standard library
optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing
[profile.release]
lto = "fat"
codegen-units = 1

759
README.md

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,70 @@
Rhai Release Notes
==================
Version 0.14.2
==============
Regression fix
--------------
* Do not optimize script with `eval_expression` - it is assumed to be one-off and short.
Bug fixes
---------
* Indexing with an index or dot expression now works property (it compiled wrongly before).
For example, `let s = "hello"; s[s.len-1] = 'x';` now works property instead of causing a runtime error.
Breaking changes
----------------
* `Engine::compile_XXX` functions now return `ParseError` instead of `Box<ParseError>`.
* The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns
`Result<Dynamic, Box<EvalAltResult>>`.
* Default maximum limit on levels of nested function calls is fine-tuned and set to a different value.
* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even
under `Engine::new_raw`.
* Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`.
This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters
should switch to `rhai::ImmutableString` (which is either `Rc<String>` or `Arc<String>` depending on
whether the `sync` feature is used).
* Native Rust functions registered with the `Engine` also mutates the first argument when called in
normal function-call style (previously the first argument will be passed by _value_ if not called
in method-call style). Of course, if the first argument is a calculated value (e.g. result of an
expression), then mutating it has no effect, but at least it is not cloned.
* Some built-in methods (e.g. `len` for string, `floor` for `FLOAT`) now have _property_ versions in
addition to methods to simplify coding.
New features
------------
* Set limit on maximum level of nesting expressions and statements to avoid panics during parsing.
* New `EvalPackage` to disable `eval`.
* `Module::set_getter_fn`, `Module::set_setter_fn` and `Module:set_indexer_fn` to register getter/setter/indexer functions.
* `Engine::call_fn_dynamic` for more control in calling script functions.
Speed enhancements
------------------
* Common operators (e.g. `+`, `>`, `==`) now call into highly efficient built-in implementations for standard types
(i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function.
This yields a 5-10% speed benefit depending on script operator usage. Scripts running tight loops will see
significant speed-up.
* Common assignment operators (e.g. `+=`, `%=`) now call into highly efficient built-in implementations for
standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function.
* Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage`
(and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`.
* Operator-assignment statements (e.g. `+=`) are now handled directly and much faster.
* Strings are now _immutable_ and use the `rhai::ImmutableString` type, eliminating large amounts of cloning.
* For Native Rust functions taking a first `&mut` parameter, the first argument is passed by reference instead of
by value, even if not called in method-call style. This allows many functions declared with `&mut` parameter to avoid
excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result
in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference,
avoiding the cloning altogether.
* A custom hasher simply passes through `u64` keys without hashing to avoid function call hash keys
(which are by themselves `u64`) being hashed twice.
Version 0.14.1
==============

View File

@ -29,7 +29,7 @@ fn bench_engine_new_raw_core(bench: &mut Bencher) {
#[bench]
fn bench_engine_register_fn(bench: &mut Bencher) {
fn hello(a: INT, b: Array, c: Map) -> bool {
fn hello(_a: INT, _b: Array, _c: Map) -> bool {
true
}

View File

@ -61,3 +61,27 @@ fn bench_eval_array_large_set(bench: &mut Bencher) {
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_array_loop(bench: &mut Bencher) {
let script = r#"
let list = [];
for i in range(0, 10_000) {
list.push(i);
}
let sum = 0;
for i in list {
sum += i;
}
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}

View File

@ -41,3 +41,118 @@ fn bench_eval_expression_number_operators(bench: &mut Bencher) {
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_expression_optimized_simple(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Simple);
let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_expression_optimized_full(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Full);
let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_call_expression(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
let engine = Engine::new();
bench.iter(|| engine.eval_expression::<bool>(script).unwrap());
}
#[bench]
fn bench_eval_call(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
let engine = Engine::new();
bench.iter(|| engine.eval::<bool>(script).unwrap());
}
#[bench]
fn bench_eval_loop_number(bench: &mut Bencher) {
let script = r#"
let s = 0;
for x in range(0, 10000) {
s += 1;
}
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_loop_strings_build(bench: &mut Bencher) {
let script = r#"
let s = 0;
for x in range(0, 10000) {
s += "x";
}
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_loop_strings_no_build(bench: &mut Bencher) {
let script = r#"
let s = "hello";
for x in range(0, 10000) {
s += "";
}
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}

View File

@ -12,7 +12,7 @@ fn bench_iterations_1000(bench: &mut Bencher) {
let x = 1_000;
while x > 0 {
x = x - 1;
x -= 1;
}
"#;

View File

@ -32,7 +32,7 @@ fn bench_parse_full(bench: &mut Bencher) {
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len() <= #{prop:name}.len() &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
@ -94,7 +94,7 @@ fn bench_parse_primes(bench: &mut Bencher) {
}
print("Total " + total_primes_found + " primes.");
print("Run time = " + now.elapsed() + " seconds.");
print("Run time = " + now.elapsed + " seconds.");
"#;
let mut engine = Engine::new();
@ -102,3 +102,35 @@ fn bench_parse_primes(bench: &mut Bencher) {
bench.iter(|| engine.compile(script).unwrap());
}
#[bench]
fn bench_parse_optimize_simple(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Simple);
bench.iter(|| engine.compile_expression(script).unwrap());
}
#[bench]
fn bench_parse_optimize_full(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Full);
bench.iter(|| engine.compile_expression(script).unwrap());
}

View File

@ -17,7 +17,7 @@ let now = timestamp();
let result = fib(target);
print("Finished. Run time = " + now.elapsed() + " seconds.");
print("Finished. Run time = " + now.elapsed + " seconds.");
print("Fibonacci number #" + target + " = " + result);

22
scripts/for2.rhai Normal file
View File

@ -0,0 +1,22 @@
const MAX = 1_000_000;
print("Iterating an array with " + MAX + " items...");
print("Ready... Go!");
let now = timestamp();
let list = [];
for i in range(0, MAX) {
list.push(i);
}
let sum = 0;
for i in list {
sum += i;
}
print("Sum = " + sum);
print("Finished. Run time = " + now.elapsed + " seconds.");

View File

@ -6,7 +6,7 @@ let x = 10;
loop {
print(x);
x = x - 1;
x -= 1;
if x <= 0 { break; }
}

View File

@ -24,9 +24,9 @@ fn mat_gen(n) {
}
fn mat_mul(a, b) {
let m = a.len();
let n = a[0].len();
let p = b[0].len();
let m = a.len;
let n = a[0].len;
let p = b[0].len;
let b2 = new_mat(n, p);
@ -38,13 +38,13 @@ fn mat_mul(a, b) {
let c = new_mat(m, p);
for i in range(0, c.len()) {
for i in range(0, c.len) {
let ci = c[i];
for j in range(0, ci.len()) {
for j in range(0, ci.len) {
let b2j = b2[j];
ci[j] = 0.0;
for z in range(0, a[i].len()) {
for z in range(0, a[i].len) {
let x = a[i][z];
let y = b2j[z];
ci[j] += x * y;
@ -62,8 +62,10 @@ let a = mat_gen(SIZE);
let b = mat_gen(SIZE);
let c = mat_mul(a, b);
/*
for i in range(0, SIZE) {
print(c[i]);
}
*/
print("Finished. Run time = " + now.elapsed() + " seconds.");
print("Finished. Run time = " + now.elapsed + " seconds.");

View File

@ -2,7 +2,7 @@
let now = timestamp();
const MAX_NUMBER_TO_CHECK = 100_000; // 9592 primes <= 100000
const MAX_NUMBER_TO_CHECK = 1_000_000; // 9592 primes <= 100000
let prime_mask = [];
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
@ -15,7 +15,7 @@ let total_primes_found = 0;
for p in range(2, MAX_NUMBER_TO_CHECK) {
if !prime_mask[p] { continue; }
print(p);
//print(p);
total_primes_found += 1;
@ -26,8 +26,8 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
}
print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK);
print("Run time = " + now.elapsed() + " seconds.");
print("Run time = " + now.elapsed + " seconds.");
if total_primes_found != 9_592 {
print("The answer is WRONG! Should be 9,592!");
if total_primes_found != 78_498 {
print("The answer is WRONG! Should be 78,498!");
}

View File

@ -7,7 +7,7 @@ let x = 1_000_000;
print("Ready... Go!");
while x > 0 {
x = x - 1;
x -= 1;
}
print("Finished. Run time = " + now.elapsed() + " seconds.");
print("Finished. Run time = " + now.elapsed + " seconds.");

View File

@ -11,7 +11,7 @@ print("foo" >= "bar"); // string comparison
print("the answer is " + 42); // string building using non-string types
let s = "hello, world!"; // string variable
print("length=" + s.len()); // should be 13
print("length=" + s.len); // should be 13
s[s.len()-1] = '?'; // change the string
print(s); // should print 'hello, world?'
s[s.len-1] = '?'; // change the string
print("Question: " + s); // should print 'Question: hello, world?'

103
scripts/strings_map.rhai Normal file
View File

@ -0,0 +1,103 @@
print("Ready... Go!");
let now = timestamp();
let adverbs = [ "moderately", "really", "slightly", "very" ];
let adjectives = [
"abandoned", "able", "absolute", "academic", "acceptable", "acclaimed",
"accomplished", "accurate", "aching", "acidic", "acrobatic", "active",
"actual", "adept", "admirable", "admired", "adolescent", "adorable", "adored",
"advanced", "adventurous", "affectionate", "afraid", "aged", "aggravating",
"aggressive", "agile", "agitated", "agonizing", "agreeable", "ajar",
"alarmed", "alarming", "alert", "alienated", "alive", "all", "altruistic",
"amazing", "ambitious", "ample", "amused", "amusing", "anchored", "ancient",
"angelic", "angry", "anguished", "animated", "annual", "another", "antique",
"anxious", "any", "apprehensive", "appropriate", "apt", "arctic", "arid",
"aromatic", "artistic", "ashamed", "assured", "astonishing", "athletic",
"attached", "attentive", "attractive", "austere", "authentic", "authorized",
"automatic", "avaricious", "average", "aware", "awesome", "awful", "awkward",
"babyish", "back", "bad", "baggy", "bare", "barren", "basic", "beautiful",
"belated", "beloved", "beneficial", "best", "better", "bewitched", "big",
"big-hearted", "biodegradable", "bite-sized", "bitter", "black",
"black-and-white", "bland", "blank", "blaring", "bleak", "blind", "blissful",
"blond", "blue", "blushing", "bogus", "boiling", "bold", "bony", "boring",
"bossy", "both", "bouncy", "bountiful", "bowed", "brave", "breakable",
"brief", "bright", "brilliant", "brisk", "broken", "bronze", "brown",
"bruised", "bubbly", "bulky", "bumpy", "buoyant", "burdensome", "burly",
"bustling", "busy", "buttery", "buzzing", "calculating", "calm", "candid",
"canine", "capital", "carefree", "careful", "careless", "caring", "cautious",
"cavernous", "celebrated", "charming", "cheap", "cheerful", "cheery", "chief",
"chilly", "chubby", "circular", "classic", "clean", "clear", "clear-cut",
"clever", "close", "closed", "cloudy", "clueless", "clumsy", "cluttered",
"coarse", "cold", "colorful", "colorless", "colossal", "comfortable",
"common", "compassionate", "competent", "complete", "complex", "complicated",
"composed", "concerned", "concrete", "confused", "conscious", "considerate",
"constant", "content", "conventional", "cooked", "cool", "cooperative",
"coordinated", "corny", "corrupt", "costly", "courageous", "courteous",
"crafty"
];
let animals = [
"aardvark", "african buffalo", "albatross", "alligator", "alpaca", "ant",
"anteater", "antelope", "ape", "armadillo", "baboon", "badger", "barracuda",
"bat", "bear", "beaver", "bee", "bison", "black panther", "blue jay", "boar",
"butterfly", "camel", "capybara", "carduelis", "caribou", "cassowary", "cat",
"caterpillar", "cattle", "chamois", "cheetah", "chicken", "chimpanzee",
"chinchilla", "chough", "clam", "cobra", "cockroach", "cod", "cormorant",
"coyote", "crab", "crane", "crocodile", "crow", "curlew", "deer", "dinosaur",
"dog", "dolphin", "domestic pig", "donkey", "dotterel", "dove", "dragonfly",
"duck", "dugong", "dunlin", "eagle", "echidna", "eel", "elephant seal",
"elephant", "elk", "emu", "falcon", "ferret", "finch", "fish", "flamingo",
"fly", "fox", "frog", "gaur", "gazelle", "gerbil", "giant panda", "giraffe",
"gnat", "goat", "goldfish", "goose", "gorilla", "goshawk", "grasshopper",
"grouse", "guanaco", "guinea fowl", "guinea pig", "gull", "hamster", "hare",
"hawk", "hedgehog", "heron", "herring", "hippopotamus", "hornet", "horse",
"human", "hummingbird", "hyena", "ibex", "ibis", "jackal", "jaguar", "jay",
"jellyfish", "kangaroo", "kingfisher", "koala", "komodo dragon", "kookabura",
"kouprey", "kudu", "lapwing", "lark", "lemur", "leopard", "lion", "llama",
"lobster", "locust", "loris", "louse", "lyrebird", "magpie", "mallard",
"manatee", "mandrill", "mantis", "marten", "meerkat", "mink", "mole",
"mongoose", "monkey", "moose", "mosquito", "mouse", "mule", "narwhal", "newt",
"nightingale", "octopus", "okapi", "opossum", "oryx", "ostrich", "otter",
"owl", "oyster", "parrot", "partridge", "peafowl", "pelican", "penguin",
"pheasant", "pigeon", "pinniped", "polar bear", "pony", "porcupine",
"porpoise", "prairie dog", "quail", "quelea", "quetzal", "rabbit", "raccoon",
"ram", "rat", "raven", "red deer", "red panda", "reindeer", "rhinoceros",
"rook", "salamander", "salmon", "sand dollar", "sandpiper", "sardine",
"scorpion", "sea lion", "sea urchin", "seahorse", "shark", "sheep", "shrew",
"skunk", "snail", "snake", "sparrow", "spider", "spoonbill", "squid",
"wallaby", "wildebeest"
];
let keys = [];
for animal in animals {
for adjective in adjectives {
for adverb in adverbs {
keys.push(adverb + " " + adjective + " " + animal)
}
}
}
let map = #{};
let i = 0;
for key in keys {
map[key] = i;
i += 1;
}
let sum = 0;
for key in keys {
sum += map[key];
}
for key in keys {
map.remove(key);
}
print("Sum = " + sum);
print("Finished. Run time = " + now.elapsed + " seconds.");

View File

@ -4,5 +4,5 @@ let x = 10;
while x > 0 {
print(x);
x = x - 1;
x -= 1;
}

View File

@ -1,6 +1,6 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::parser::INT;
use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
#[cfg(not(feature = "no_module"))]
@ -151,7 +151,7 @@ pub struct Dynamic(pub(crate) Union);
pub enum Union {
Unit(()),
Bool(bool),
Str(Box<String>),
Str(ImmutableString),
Char(char),
Int(INT),
#[cfg(not(feature = "no_float"))]
@ -178,6 +178,10 @@ impl Dynamic {
/// Is the value held by this `Dynamic` a particular type?
pub fn is<T: Variant + Clone>(&self) -> bool {
self.type_id() == TypeId::of::<T>()
|| match self.0 {
Union::Str(_) => TypeId::of::<String>() == TypeId::of::<T>(),
_ => false,
}
}
/// Get the TypeId of the value held by this `Dynamic`.
@ -185,7 +189,7 @@ impl Dynamic {
match &self.0 {
Union::Unit(_) => TypeId::of::<()>(),
Union::Bool(_) => TypeId::of::<bool>(),
Union::Str(_) => TypeId::of::<String>(),
Union::Str(_) => TypeId::of::<ImmutableString>(),
Union::Char(_) => TypeId::of::<char>(),
Union::Int(_) => TypeId::of::<INT>(),
#[cfg(not(feature = "no_float"))]
@ -342,6 +346,12 @@ impl Dynamic {
return Self(result);
} else if let Some(result) = dyn_value.downcast_ref::<char>().cloned().map(Union::Char) {
return Self(result);
} else if let Some(result) = dyn_value
.downcast_ref::<ImmutableString>()
.cloned()
.map(Union::Str)
{
return Self(result);
}
#[cfg(not(feature = "no_float"))]
@ -358,7 +368,7 @@ impl Dynamic {
Err(var) => var,
};
var = match unsafe_cast_box::<_, String>(var) {
Ok(s) => return Self(Union::Str(s)),
Ok(s) => return Self(Union::Str(s.into())),
Err(var) => var,
};
#[cfg(not(feature = "no_index"))]
@ -395,14 +405,19 @@ impl Dynamic {
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
/// ```
pub fn try_cast<T: Variant>(self) -> Option<T> {
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
let type_id = TypeId::of::<T>();
if type_id == TypeId::of::<Dynamic>() {
return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v);
}
match self.0 {
Union::Unit(value) => unsafe_try_cast(value),
Union::Bool(value) => unsafe_try_cast(value),
Union::Str(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
Union::Str(value) if type_id == TypeId::of::<ImmutableString>() => {
unsafe_try_cast(value)
}
Union::Str(value) => unsafe_try_cast(value.into_owned()),
Union::Char(value) => unsafe_try_cast(value),
Union::Int(value) => unsafe_try_cast(value),
#[cfg(not(feature = "no_float"))]
@ -434,16 +449,19 @@ impl Dynamic {
/// assert_eq!(x.cast::<u32>(), 42);
/// ```
pub fn cast<T: Variant + Clone>(self) -> T {
//self.try_cast::<T>().unwrap()
let type_id = TypeId::of::<T>();
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
if type_id == TypeId::of::<Dynamic>() {
return *unsafe_cast_box::<_, T>(Box::new(self)).unwrap();
}
match self.0 {
Union::Unit(value) => unsafe_try_cast(value).unwrap(),
Union::Bool(value) => unsafe_try_cast(value).unwrap(),
Union::Str(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
Union::Str(value) if type_id == TypeId::of::<ImmutableString>() => {
unsafe_try_cast(value).unwrap()
}
Union::Str(value) => unsafe_try_cast(value.into_owned()).unwrap(),
Union::Char(value) => unsafe_try_cast(value).unwrap(),
Union::Int(value) => unsafe_try_cast(value).unwrap(),
#[cfg(not(feature = "no_float"))]
@ -469,7 +487,9 @@ impl Dynamic {
match &self.0 {
Union::Unit(value) => (value as &dyn Any).downcast_ref::<T>(),
Union::Bool(value) => (value as &dyn Any).downcast_ref::<T>(),
Union::Str(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
Union::Str(value) => (value as &dyn Any)
.downcast_ref::<T>()
.or_else(|| (value.as_ref() as &dyn Any).downcast_ref::<T>()),
Union::Char(value) => (value as &dyn Any).downcast_ref::<T>(),
Union::Int(value) => (value as &dyn Any).downcast_ref::<T>(),
#[cfg(not(feature = "no_float"))]
@ -495,7 +515,7 @@ impl Dynamic {
match &mut self.0 {
Union::Unit(value) => (value as &mut dyn Any).downcast_mut::<T>(),
Union::Bool(value) => (value as &mut dyn Any).downcast_mut::<T>(),
Union::Str(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
Union::Str(value) => (value as &mut dyn Any).downcast_mut::<T>(),
Union::Char(value) => (value as &mut dyn Any).downcast_mut::<T>(),
Union::Int(value) => (value as &mut dyn Any).downcast_mut::<T>(),
#[cfg(not(feature = "no_float"))]
@ -519,6 +539,16 @@ impl Dynamic {
}
}
/// Cast the `Dynamic` as the system floating-point type `FLOAT` and return it.
/// Returns the name of the actual type if the cast fails.
#[cfg(not(feature = "no_float"))]
pub fn as_float(&self) -> Result<FLOAT, &'static str> {
match self.0 {
Union::Float(n) => Ok(n),
_ => Err(self.type_name()),
}
}
/// Cast the `Dynamic` as a `bool` and return it.
/// Returns the name of the actual type if the cast fails.
pub fn as_bool(&self) -> Result<bool, &'static str> {
@ -550,7 +580,7 @@ impl Dynamic {
/// Returns the name of the actual type if the cast fails.
pub fn take_string(self) -> Result<String, &'static str> {
match self.0 {
Union::Str(s) => Ok(*s),
Union::Str(s) => Ok(s.into_owned()),
_ => Err(self.type_name()),
}
}
@ -584,7 +614,12 @@ impl From<char> for Dynamic {
}
impl From<String> for Dynamic {
fn from(value: String) -> Self {
Self(Union::Str(Box::new(value)))
Self(Union::Str(value.into()))
}
}
impl From<ImmutableString> for Dynamic {
fn from(value: ImmutableString) -> Self {
Self(Union::Str(value))
}
}
#[cfg(not(feature = "no_index"))]
@ -595,6 +630,14 @@ impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
)))
}
}
#[cfg(not(feature = "no_index"))]
impl<T: Variant + Clone> From<&[T]> for Dynamic {
fn from(value: &[T]) -> Self {
Self(Union::Array(Box::new(
value.iter().cloned().map(Dynamic::from).collect(),
)))
}
}
#[cfg(not(feature = "no_object"))]
impl<T: Variant + Clone> From<HashMap<String, T>> for Dynamic {
fn from(value: HashMap<String, T>) -> Self {

View File

@ -4,9 +4,7 @@ use crate::any::{Dynamic, Variant};
use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER};
use crate::error::ParseError;
use crate::fn_call::FuncArgs;
use crate::fn_native::{
IteratorCallback, ObjectGetCallback, ObjectIndexerCallback, ObjectSetCallback,
};
use crate::fn_native::{IteratorFn, SendSync};
use crate::fn_register::RegisterFn;
use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::parser::{parse, parse_global_expr, AST};
@ -123,8 +121,8 @@ impl Engine {
/// Register an iterator adapter for a type with the `Engine`.
/// This is an advanced feature.
pub fn register_iterator<T: Variant + Clone, F: IteratorCallback>(&mut self, f: F) {
self.global_module.set_iter(TypeId::of::<T>(), Box::new(f));
pub fn register_iterator<T: Variant + Clone>(&mut self, f: IteratorFn) {
self.global_module.set_iter(TypeId::of::<T>(), f);
}
/// Register a getter function for a member of a registered type with the `Engine`.
@ -164,11 +162,13 @@ impl Engine {
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_get<T, U, F>(&mut self, name: &str, callback: F)
where
pub fn register_get<T, U>(
&mut self,
name: &str,
callback: impl Fn(&mut T) -> U + SendSync + 'static,
) where
T: Variant + Clone,
U: Variant + Clone,
F: ObjectGetCallback<T, U>,
{
self.register_fn(&make_getter(name), callback);
}
@ -210,11 +210,13 @@ impl Engine {
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_set<T, U, F>(&mut self, name: &str, callback: F)
where
pub fn register_set<T, U>(
&mut self,
name: &str,
callback: impl Fn(&mut T, U) + SendSync + 'static,
) where
T: Variant + Clone,
U: Variant + Clone,
F: ObjectSetCallback<T, U>,
{
self.register_fn(&make_setter(name), callback);
}
@ -258,12 +260,14 @@ impl Engine {
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_get_set<T, U, G, S>(&mut self, name: &str, get_fn: G, set_fn: S)
where
pub fn register_get_set<T, U>(
&mut self,
name: &str,
get_fn: impl Fn(&mut T) -> U + SendSync + 'static,
set_fn: impl Fn(&mut T, U) + SendSync + 'static,
) where
T: Variant + Clone,
U: Variant + Clone,
G: ObjectGetCallback<T, U>,
S: ObjectSetCallback<T, U>,
{
self.register_get(name, get_fn);
self.register_set(name, set_fn);
@ -307,12 +311,13 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
pub fn register_indexer<T, X, U, F>(&mut self, callback: F)
where
pub fn register_indexer<T, X, U>(
&mut self,
callback: impl Fn(&mut T, X) -> U + SendSync + 'static,
) where
T: Variant + Clone,
U: Variant + Clone,
X: Variant + Clone,
F: ObjectIndexerCallback<T, X, U>,
{
self.register_fn(FUNC_INDEXER, callback);
}
@ -336,7 +341,7 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
pub fn compile(&self, script: &str) -> Result<AST, Box<ParseError>> {
pub fn compile(&self, script: &str) -> Result<AST, ParseError> {
self.compile_with_scope(&Scope::new(), script)
}
@ -378,7 +383,7 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, Box<ParseError>> {
pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, ParseError> {
self.compile_scripts_with_scope(scope, &[script])
}
@ -432,7 +437,7 @@ impl Engine {
&self,
scope: &Scope,
scripts: &[&str],
) -> Result<AST, Box<ParseError>> {
) -> Result<AST, ParseError> {
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
}
@ -442,9 +447,16 @@ impl Engine {
scope: &Scope,
scripts: &[&str],
optimization_level: OptimizationLevel,
) -> Result<AST, Box<ParseError>> {
) -> Result<AST, ParseError> {
let stream = lex(scripts);
parse(&mut stream.peekable(), self, scope, optimization_level)
parse(
&mut stream.peekable(),
self,
scope,
optimization_level,
(self.max_expr_depth, self.max_function_expr_depth),
)
}
/// Read the contents of a file into a string.
@ -571,6 +583,7 @@ impl Engine {
self,
&scope,
OptimizationLevel::None,
self.max_expr_depth,
)?;
// Handle null - map to ()
@ -601,7 +614,7 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
pub fn compile_expression(&self, script: &str) -> Result<AST, Box<ParseError>> {
pub fn compile_expression(&self, script: &str) -> Result<AST, ParseError> {
self.compile_expression_with_scope(&Scope::new(), script)
}
@ -648,13 +661,19 @@ impl Engine {
&self,
scope: &Scope,
script: &str,
) -> Result<AST, Box<ParseError>> {
) -> Result<AST, ParseError> {
let scripts = [script];
let stream = lex(&scripts);
{
let mut peekable = stream.peekable();
parse_global_expr(&mut peekable, self, scope, self.optimization_level)
parse_global_expr(
&mut peekable,
self,
scope,
self.optimization_level,
self.max_expr_depth,
)
}
}
@ -805,8 +824,15 @@ impl Engine {
) -> Result<T, Box<EvalAltResult>> {
let scripts = [script];
let stream = lex(&scripts);
// Since the AST will be thrown away afterwards, don't bother to optimize it
let ast = parse_global_expr(&mut stream.peekable(), self, scope, OptimizationLevel::None)?;
let ast = parse_global_expr(
&mut stream.peekable(),
self,
scope,
OptimizationLevel::None, // No need to optimize a lone expression
self.max_expr_depth,
)?;
self.eval_ast_with_scope(scope, &ast)
}
@ -883,12 +909,12 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
let mut state = State::new(ast.fn_lib());
let mut state = State::new();
ast.statements()
.iter()
.try_fold(().into(), |_, stmt| {
self.eval_stmt(scope, &mut state, stmt, 0)
self.eval_stmt(scope, &mut state, ast.lib(), stmt, 0)
})
.or_else(|err| match *err {
EvalAltResult::Return(out, _) => Ok(out),
@ -931,8 +957,13 @@ impl Engine {
let scripts = [script];
let stream = lex(&scripts);
// Since the AST will be thrown away afterwards, don't bother to optimize it
let ast = parse(&mut stream.peekable(), self, scope, OptimizationLevel::None)?;
let ast = parse(
&mut stream.peekable(),
self,
scope,
self.optimization_level,
(self.max_expr_depth, self.max_function_expr_depth),
)?;
self.consume_ast_with_scope(scope, &ast)
}
@ -949,12 +980,12 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
) -> Result<(), Box<EvalAltResult>> {
let mut state = State::new(ast.fn_lib());
let mut state = State::new();
ast.statements()
.iter()
.try_fold(().into(), |_, stmt| {
self.eval_stmt(scope, &mut state, stmt, 0)
self.eval_stmt(scope, &mut state, ast.lib(), stmt, 0)
})
.map_or_else(
|err| match *err {
@ -966,6 +997,7 @@ impl Engine {
}
/// Call a script function defined in an `AST` with multiple arguments.
/// Arguments are passed as a tuple.
///
/// # Example
///
@ -1009,29 +1041,81 @@ impl Engine {
args: A,
) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec();
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let fn_lib = ast.fn_lib();
let pos = Position::none();
let fn_def = fn_lib
.get_function_by_signature(name, args.len(), true)
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?;
let state = State::new(fn_lib);
let args = args.as_mut();
let (result, _) = self.call_script_fn(Some(scope), state, name, fn_def, args, pos, 0)?;
let result = self.call_fn_dynamic(scope, ast, name, arg_values.as_mut())?;
let return_type = self.map_type_name(result.type_name());
return result.try_cast().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
return_type.into(),
pos,
Position::none(),
))
});
}
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
///
/// ## WARNING
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
/// Do you use the arguments after this call. If you need them afterwards,
/// clone them _before_ calling this function.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::{Engine, Scope};
///
/// let engine = Engine::new();
///
/// let ast = engine.compile(r"
/// fn add(x, y) { len(x) + y + foo }
/// fn add1(x) { len(x) + 1 + foo }
/// fn bar() { foo/2 }
/// ")?;
///
/// let mut scope = Scope::new();
/// scope.push("foo", 42_i64);
///
/// // Call the script-defined function
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", &mut [ String::from("abc").into(), 123_i64.into() ])?;
/// assert_eq!(result.cast::<i64>(), 168);
///
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", &mut [ String::from("abc").into() ])?;
/// assert_eq!(result.cast::<i64>(), 46);
///
/// let result= engine.call_fn_dynamic(&mut scope, &ast, "bar", &mut [])?;
/// assert_eq!(result.cast::<i64>(), 21);
/// # }
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_function"))]
pub fn call_fn_dynamic(
&self,
scope: &mut Scope,
ast: &AST,
name: &str,
arg_values: &mut [Dynamic],
) -> Result<Dynamic, Box<EvalAltResult>> {
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let lib = ast.lib();
let pos = Position::none();
let fn_def = lib
.get_function_by_signature(name, args.len(), true)
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?;
let mut state = State::new();
let args = args.as_mut();
self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0)
}
/// Optimize the `AST` with constants defined in an external Scope.
/// An optimized copy of the `AST` is returned while the original `AST` is consumed.
///
@ -1050,14 +1134,14 @@ impl Engine {
mut ast: AST,
optimization_level: OptimizationLevel,
) -> AST {
let fn_lib = ast
.fn_lib()
let lib = ast
.lib()
.iter()
.map(|(_, fn_def)| fn_def.as_ref().clone())
.collect();
let stmt = mem::take(ast.statements_mut());
optimize_into_ast(self, scope, stmt, fn_lib, optimization_level)
optimize_into_ast(self, scope, stmt, lib, optimization_level)
}
/// Register a callback for script evaluation progress.
@ -1094,47 +1178,7 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "sync")]
pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + Send + Sync + 'static) {
self.progress = Some(Box::new(callback));
}
/// Register a callback for script evaluation progress.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use std::cell::Cell;
/// # use std::rc::Rc;
/// use rhai::Engine;
///
/// let result = Rc::new(Cell::new(0_u64));
/// let logger = result.clone();
///
/// let mut engine = Engine::new();
///
/// engine.on_progress(move |ops| {
/// if ops > 10000 {
/// false
/// } else if ops % 800 == 0 {
/// logger.set(ops);
/// true
/// } else {
/// true
/// }
/// });
///
/// engine.consume("for x in range(0, 50000) {}")
/// .expect_err("should error");
///
/// assert_eq!(result.get(), 9600);
///
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "sync"))]
pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + 'static) {
pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + SendSync + 'static) {
self.progress = Some(Box::new(callback));
}
@ -1162,36 +1206,7 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "sync")]
pub fn on_print(&mut self, callback: impl Fn(&str) + Send + Sync + 'static) {
self.print = Box::new(callback);
}
/// Override default action of `print` (print to stdout using `println!`)
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use std::cell::RefCell;
/// # use std::rc::Rc;
/// use rhai::Engine;
///
/// let result = Rc::new(RefCell::new(String::from("")));
///
/// let mut engine = Engine::new();
///
/// // Override action of 'print' function
/// let logger = result.clone();
/// engine.on_print(move |s| logger.borrow_mut().push_str(s));
///
/// engine.consume("print(40 + 2);")?;
///
/// assert_eq!(*result.borrow(), "42");
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "sync"))]
pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) {
pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) {
self.print = Box::new(callback);
}
@ -1219,36 +1234,7 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "sync")]
pub fn on_debug(&mut self, callback: impl Fn(&str) + Send + Sync + 'static) {
self.debug = Box::new(callback);
}
/// Override default action of `debug` (print to stdout using `println!`)
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use std::cell::RefCell;
/// # use std::rc::Rc;
/// use rhai::Engine;
///
/// let result = Rc::new(RefCell::new(String::from("")));
///
/// let mut engine = Engine::new();
///
/// // Override action of 'print' function
/// let logger = result.clone();
/// engine.on_debug(move |s| logger.borrow_mut().push_str(s));
///
/// engine.consume(r#"debug("hello");"#)?;
///
/// assert_eq!(*result.borrow(), r#""hello""#);
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "sync"))]
pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) {
pub fn on_debug(&mut self, callback: impl Fn(&str) + SendSync + 'static) {
self.debug = Box::new(callback);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -110,20 +110,24 @@ pub enum ParseErrorType {
AssignmentToCopy,
/// Assignment to an a constant variable.
AssignmentToConstant(String),
/// Expression exceeding the maximum levels of complexity.
///
/// Never appears under the `unchecked` feature.
ExprTooDeep,
/// Break statement not inside a loop.
LoopBreak,
}
impl ParseErrorType {
/// Make a `ParseError` using the current type and position.
pub(crate) fn into_err(self, pos: Position) -> Box<ParseError> {
Box::new(ParseError(self, pos))
pub(crate) fn into_err(self, pos: Position) -> ParseError {
ParseError(Box::new(self), pos)
}
}
/// Error when parsing a script.
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position);
pub struct ParseError(pub(crate) Box<ParseErrorType>, pub(crate) Position);
impl ParseError {
/// Get the parse error.
@ -137,7 +141,7 @@ impl ParseError {
}
pub(crate) fn desc(&self) -> &str {
match &self.0 {
match self.0.as_ref() {
ParseErrorType::BadInput(p) => p,
ParseErrorType::UnexpectedEOF => "Script is incomplete",
ParseErrorType::UnknownOperator(_) => "Unknown operator",
@ -158,7 +162,8 @@ impl ParseError {
ParseErrorType::DuplicatedExport(_) => "Duplicated variable/function in export statement",
ParseErrorType::WrongExport => "Export statement can only appear at global level",
ParseErrorType::AssignmentToCopy => "Only a copy of the value is change with this assignment",
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant value.",
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant value",
ParseErrorType::ExprTooDeep => "Expression exceeds maximum complexity",
ParseErrorType::LoopBreak => "Break statement should only be used inside a loop"
}
}
@ -168,7 +173,7 @@ impl Error for ParseError {}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
match self.0.as_ref() {
ParseErrorType::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
}

View File

@ -80,7 +80,7 @@ pub trait Func<ARGS, RET> {
self,
script: &str,
entry_point: &str,
) -> Result<Self::Output, Box<ParseError>>;
) -> Result<Self::Output, ParseError>;
}
macro_rules! def_anonymous_fn {
@ -103,7 +103,7 @@ macro_rules! def_anonymous_fn {
})
}
fn create_from_script(self, script: &str, entry_point: &str) -> Result<Self::Output, Box<ParseError>> {
fn create_from_script(self, script: &str, entry_point: &str) -> Result<Self::Output, ParseError> {
let ast = self.compile(script)?;
Ok(Func::<($($par,)*), RET>::create_from_ast(self, ast, entry_point))
}

View File

@ -1,139 +1,144 @@
use crate::any::Dynamic;
use crate::parser::FnDef;
use crate::result::EvalAltResult;
use crate::stdlib::{boxed::Box, rc::Rc, sync::Arc};
#[cfg(feature = "sync")]
pub trait SendSync: Send + Sync {}
#[cfg(feature = "sync")]
impl<T: Send + Sync> SendSync for T {}
#[cfg(not(feature = "sync"))]
pub trait SendSync {}
#[cfg(not(feature = "sync"))]
impl<T> SendSync for T {}
#[cfg(not(feature = "sync"))]
pub type Shared<T> = Rc<T>;
#[cfg(feature = "sync")]
pub type Shared<T> = Arc<T>;
/// Consume a `Shared` resource and return a mutable reference to the wrapped value.
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
#[cfg(not(feature = "sync"))]
{
Rc::make_mut(value)
}
#[cfg(feature = "sync")]
{
Arc::make_mut(value)
}
}
/// Consume a `Shared` resource, assuming that it is unique (i.e. not shared).
///
/// # Panics
///
/// Panics if the resource is shared (i.e. has other outstanding references).
pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
#[cfg(not(feature = "sync"))]
{
Rc::try_unwrap(value).map_err(|_| ()).unwrap()
}
#[cfg(feature = "sync")]
{
Arc::try_unwrap(value).map_err(|_| ()).unwrap()
}
}
pub type FnCallArgs<'a> = [&'a mut Dynamic];
#[cfg(feature = "sync")]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
#[cfg(not(feature = "sync"))]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
#[cfg(feature = "sync")]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
#[cfg(feature = "sync")]
pub type IteratorFn = dyn Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync;
#[cfg(not(feature = "sync"))]
pub type IteratorFn = dyn Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
#[cfg(feature = "sync")]
pub type PrintCallback = dyn Fn(&str) + Send + Sync + 'static;
#[cfg(not(feature = "sync"))]
pub type PrintCallback = dyn Fn(&str) + 'static;
#[cfg(feature = "sync")]
pub type ProgressCallback = dyn Fn(u64) -> bool + Send + Sync + 'static;
#[cfg(not(feature = "sync"))]
pub type ProgressCallback = dyn Fn(u64) -> bool + 'static;
// Define callback function types
#[cfg(feature = "sync")]
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T) -> U + Send + Sync + 'static, T, U> ObjectGetCallback<T, U> for F {}
#[cfg(not(feature = "sync"))]
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(&mut T) -> U + 'static, T, U> ObjectGetCallback<T, U> for F {}
#[cfg(feature = "sync")]
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T, U) + Send + Sync + 'static, T, U> ObjectSetCallback<T, U> for F {}
#[cfg(not(feature = "sync"))]
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(&mut T, U) + 'static, T, U> ObjectSetCallback<T, U> for F {}
#[cfg(feature = "sync")]
pub trait ObjectIndexerCallback<T, X, U>: Fn(&mut T, X) -> U + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T, X) -> U + Send + Sync + 'static, T, X, U> ObjectIndexerCallback<T, X, U> for F {}
#[cfg(not(feature = "sync"))]
pub trait ObjectIndexerCallback<T, X, U>: Fn(&mut T, X) -> U + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(&mut T, X) -> U + 'static, T, X, U> ObjectIndexerCallback<T, X, U> for F {}
#[cfg(feature = "sync")]
pub trait IteratorCallback:
Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync + 'static
{
}
#[cfg(feature = "sync")]
impl<F: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync + 'static> IteratorCallback
for F
{
/// A type encapsulating a function callable by Rhai.
#[derive(Clone)]
pub enum CallableFunction {
/// A pure native Rust function with all arguments passed by value.
Pure(Shared<FnAny>),
/// A native Rust object method with the first argument passed by reference,
/// and the rest passed by value.
Method(Shared<FnAny>),
/// An iterator function.
Iterator(IteratorFn),
/// A script-defined function.
Script(Shared<FnDef>),
}
#[cfg(not(feature = "sync"))]
pub trait IteratorCallback: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static> IteratorCallback for F {}
/// A type representing the type of ABI of a native Rust function.
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
pub enum NativeFunctionABI {
/// A pure function where all arguments are passed by value.
Pure,
/// An object method where the first argument is the object passed by mutable reference.
/// All other arguments are passed by value.
Method,
}
/// Trait implemented by all native Rust functions that are callable by Rhai.
#[cfg(not(feature = "sync"))]
pub trait NativeCallable {
/// Get the ABI type of a native Rust function.
fn abi(&self) -> NativeFunctionABI;
/// Call a native Rust function.
fn call(&self, args: &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
}
/// Trait implemented by all native Rust functions that are callable by Rhai.
#[cfg(feature = "sync")]
pub trait NativeCallable: Send + Sync {
/// Get the ABI type of a native Rust function.
fn abi(&self) -> NativeFunctionABI;
/// Call a native Rust function.
fn call(&self, args: &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
}
/// A type encapsulating a native Rust function callable by Rhai.
pub struct NativeFunction(Box<FnAny>, NativeFunctionABI);
impl NativeCallable for NativeFunction {
fn abi(&self) -> NativeFunctionABI {
self.1
impl CallableFunction {
/// Is this a pure native Rust function?
pub fn is_pure(&self) -> bool {
match self {
Self::Pure(_) => true,
Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false,
}
}
fn call(&self, args: &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> {
(self.0)(args)
/// Is this a pure native Rust method-call?
pub fn is_method(&self) -> bool {
match self {
Self::Method(_) => true,
Self::Pure(_) | Self::Iterator(_) | Self::Script(_) => false,
}
}
/// Is this an iterator function?
pub fn is_iter(&self) -> bool {
match self {
Self::Iterator(_) => true,
Self::Pure(_) | Self::Method(_) | Self::Script(_) => false,
}
}
/// Is this a Rhai-scripted function?
pub fn is_script(&self) -> bool {
match self {
Self::Script(_) => true,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
}
}
/// Get a reference to a native Rust function.
///
/// # Panics
///
/// Panics if the `CallableFunction` is not `Pure` or `Method`.
pub fn get_native_fn(&self) -> &FnAny {
match self {
Self::Pure(f) | Self::Method(f) => f.as_ref(),
Self::Iterator(_) | Self::Script(_) => panic!(),
}
}
/// Get a reference to a script-defined function definition.
///
/// # Panics
///
/// Panics if the `CallableFunction` is not `Script`.
pub fn get_fn_def(&self) -> &FnDef {
match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => panic!(),
Self::Script(f) => f,
}
}
/// Get a reference to an iterator function.
///
/// # Panics
///
/// Panics if the `CallableFunction` is not `Iterator`.
pub fn get_iter_fn(&self) -> IteratorFn {
match self {
Self::Iterator(f) => *f,
Self::Pure(_) | Self::Method(_) | Self::Script(_) => panic!(),
}
}
/// Create a new `CallableFunction::Pure`.
pub fn from_pure(func: Box<FnAny>) -> Self {
Self::Pure(func.into())
}
/// Create a new `CallableFunction::Method`.
pub fn from_method(func: Box<FnAny>) -> Self {
Self::Method(func.into())
}
}
impl From<(Box<FnAny>, NativeFunctionABI)> for NativeFunction {
fn from(func: (Box<FnAny>, NativeFunctionABI)) -> Self {
Self::new(func.0, func.1)
}
}
impl NativeFunction {
/// Create a new `NativeFunction`.
pub fn new(func: Box<FnAny>, abi: NativeFunctionABI) -> Self {
Self(func, abi)
}
}
/// An external native Rust function.
#[cfg(not(feature = "sync"))]
pub type SharedNativeFunction = Rc<Box<dyn NativeCallable>>;
/// An external native Rust function.
#[cfg(feature = "sync")]
pub type SharedNativeFunction = Arc<Box<dyn NativeCallable>>;
/// A type iterator function.
#[cfg(not(feature = "sync"))]
pub type SharedIteratorFunction = Rc<Box<IteratorFn>>;
/// An external native Rust function.
#[cfg(feature = "sync")]
pub type SharedIteratorFunction = Arc<Box<IteratorFn>>;

View File

@ -4,11 +4,11 @@
use crate::any::{Dynamic, Variant};
use crate::engine::Engine;
use crate::fn_native::{FnCallArgs, NativeFunctionABI::*};
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs};
use crate::parser::FnAccess;
use crate::result::EvalAltResult;
use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString};
use crate::stdlib::{any::TypeId, boxed::Box, mem};
/// Trait to register custom functions with the `Engine`.
pub trait RegisterFn<FN, ARGS, RET> {
@ -42,49 +42,22 @@ pub trait RegisterFn<FN, ARGS, RET> {
fn register_fn(&mut self, name: &str, f: FN);
}
/// Trait to register custom functions that return `Dynamic` values with the `Engine`.
pub trait RegisterDynamicFn<FN, ARGS> {
/// Register a custom function returning `Dynamic` values with the `Engine`.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, Dynamic, RegisterDynamicFn};
///
/// // Function that returns a Dynamic value
/// fn return_the_same_as_dynamic(x: i64) -> Dynamic {
/// Dynamic::from(x)
/// }
///
/// let mut engine = Engine::new();
///
/// // You must use the trait rhai::RegisterDynamicFn to get this method.
/// engine.register_dynamic_fn("get_any_number", return_the_same_as_dynamic);
///
/// assert_eq!(engine.eval::<i64>("get_any_number(42)")?, 42);
/// # Ok(())
/// # }
/// ```
fn register_dynamic_fn(&mut self, name: &str, f: FN);
}
/// Trait to register fallible custom functions returning `Result<_, Box<EvalAltResult>>` with the `Engine`.
pub trait RegisterResultFn<FN, ARGS, RET> {
/// Trait to register fallible custom functions returning `Result<Dynamic, Box<EvalAltResult>>` with the `Engine`.
pub trait RegisterResultFn<FN, ARGS> {
/// Register a custom fallible function with the `Engine`.
///
/// # Example
///
/// ```
/// use rhai::{Engine, RegisterResultFn, EvalAltResult};
/// use rhai::{Engine, Dynamic, RegisterResultFn, EvalAltResult};
///
/// // Normal function
/// fn div(x: i64, y: i64) -> Result<i64, Box<EvalAltResult>> {
/// fn div(x: i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
/// if y == 0 {
/// // '.into()' automatically converts to 'Box<EvalAltResult::ErrorRuntime>'
/// Err("division by zero!".into())
/// } else {
/// Ok(x / y)
/// Ok((x / y).into())
/// }
/// }
///
@ -155,7 +128,7 @@ macro_rules! make_func {
// Map the result
$map(r)
})
}) as Box<FnAny>
};
}
@ -167,28 +140,22 @@ pub fn map_dynamic<T: Variant + Clone>(data: T) -> Result<Dynamic, Box<EvalAltRe
/// To Dynamic mapping function.
#[inline(always)]
pub fn map_identity(data: Dynamic) -> Result<Dynamic, Box<EvalAltResult>> {
Ok(data)
}
/// To `Result<Dynamic, Box<EvalAltResult>>` mapping function.
#[inline(always)]
pub fn map_result<T: Variant + Clone>(
data: Result<T, Box<EvalAltResult>>,
pub fn map_result(
data: Result<Dynamic, Box<EvalAltResult>>,
) -> Result<Dynamic, Box<EvalAltResult>> {
data.map(|v| v.into_dynamic())
data
}
macro_rules! def_register {
() => {
def_register!(imp Pure;);
def_register!(imp from_pure :);
};
(imp $abi:expr ; $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => {
(imp $abi:ident : $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => {
// ^ function ABI type
// ^ function parameter generic type name (A, B, C etc.)
// ^ function parameter marker type (T, Ref<T> or Mut<T>)
// ^ function parameter actual type (T, &T or &mut T)
// ^ dereferencing function
// ^ function parameter generic type name (A, B, C etc.)
// ^ function parameter marker type (T, Ref<T> or Mut<T>)
// ^ function parameter actual type (T, &T or &mut T)
// ^ dereferencing function
impl<
$($par: Variant + Clone,)*
@ -202,9 +169,9 @@ macro_rules! def_register {
> RegisterFn<FN, ($($mark,)*), RET> for Engine
{
fn register_fn(&mut self, name: &str, f: FN) {
self.global_module.set_fn(name.to_string(), $abi, FnAccess::Public,
self.global_module.set_fn(name, FnAccess::Public,
&[$(TypeId::of::<$par>()),*],
make_func!(f : map_dynamic ; $($par => $clone),*)
CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*))
);
}
}
@ -213,35 +180,15 @@ macro_rules! def_register {
$($par: Variant + Clone,)*
#[cfg(feature = "sync")]
FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static,
FN: Fn($($param),*) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync + 'static,
#[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> Dynamic + 'static,
> RegisterDynamicFn<FN, ($($mark,)*)> for Engine
{
fn register_dynamic_fn(&mut self, name: &str, f: FN) {
self.global_module.set_fn(name.to_string(), $abi, FnAccess::Public,
&[$(TypeId::of::<$par>()),*],
make_func!(f : map_identity ; $($par => $clone),*)
);
}
}
impl<
$($par: Variant + Clone,)*
#[cfg(feature = "sync")]
FN: Fn($($param),*) -> Result<RET, Box<EvalAltResult>> + Send + Sync + 'static,
#[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> Result<RET, Box<EvalAltResult>> + 'static,
RET: Variant + Clone
> RegisterResultFn<FN, ($($mark,)*), RET> for Engine
FN: Fn($($param),*) -> Result<Dynamic, Box<EvalAltResult>> + 'static,
> RegisterResultFn<FN, ($($mark,)*)> for Engine
{
fn register_result_fn(&mut self, name: &str, f: FN) {
self.global_module.set_fn(name.to_string(), $abi, FnAccess::Public,
self.global_module.set_fn(name, FnAccess::Public,
&[$(TypeId::of::<$par>()),*],
make_func!(f : map_result ; $($par => $clone),*)
CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*))
);
}
}
@ -249,10 +196,11 @@ macro_rules! def_register {
//def_register!(imp_pop $($par => $mark => $param),*);
};
($p0:ident $(, $p:ident)*) => {
def_register!(imp Pure ; $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*);
def_register!(imp Method ; $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*);
// handle the first parameter ^ first parameter passed through
// ^ others passed by value (by_value)
def_register!(imp from_pure : $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*);
def_register!(imp from_method : $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*);
// ^ CallableFunction
// handle the first parameter ^ first parameter passed through
// ^ others passed by value (by_value)
// Currently does not support first argument which is a reference, as there will be
// conflicting implementations since &T: Any and T: Any cannot be distinguished

View File

@ -49,18 +49,18 @@
//!
//! ## Optional features
//!
//! | Feature | Description |
//! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
//! | `no_function` | Disable script-defined functions if not needed. |
//! | `no_index` | Disable arrays and indexing features if not needed. |
//! | `no_object` | Disable support for custom types and objects. |
//! | `no_float` | Disable floating-point numbers and math if not needed. |
//! | `no_optimize` | Disable the script optimizer. |
//! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. |
//! | Feature | Description |
//! | ------------- | ----------------------------------------------------------------------------------------------------------------------------------|
//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
//! | `no_function` | Disable script-defined functions if not needed. |
//! | `no_index` | Disable arrays and indexing features if not needed. |
//! | `no_object` | Disable support for custom types and objects. |
//! | `no_float` | Disable floating-point numbers and math if not needed. |
//! | `no_optimize` | Disable the script optimizer. |
//! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. |
//!
//! [Check out the README on GitHub for details on the Rhai language!](https://github.com/jonathandturner/rhai)
@ -91,10 +91,9 @@ mod utils;
pub use any::Dynamic;
pub use engine::Engine;
pub use error::{ParseError, ParseErrorType};
pub use fn_native::NativeCallable;
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
pub use fn_register::{RegisterFn, RegisterResultFn};
pub use module::Module;
pub use parser::{AST, INT};
pub use parser::{ImmutableString, AST, INT};
pub use result::EvalAltResult;
pub use scope::Scope;
pub use token::Position;

View File

@ -2,36 +2,32 @@
use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash;
use crate::engine::{Engine, FunctionsLib};
use crate::fn_native::{
FnAny, FnCallArgs, IteratorFn, NativeCallable, NativeFunction, NativeFunctionABI,
NativeFunctionABI::*, SharedIteratorFunction, SharedNativeFunction,
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib, FUNC_INDEXER};
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn};
use crate::parser::{
FnAccess,
FnAccess::{Private, Public},
AST,
};
use crate::parser::{FnAccess, FnDef, SharedFnDef, AST};
use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::{Position, Token};
use crate::utils::{StaticVec, EMPTY_TYPE_ID};
use crate::utils::{StaticVec, StraightHasherBuilder};
use crate::stdlib::{
any::TypeId,
boxed::Box,
collections::HashMap,
fmt,
iter::{empty, repeat},
iter::empty,
mem,
num::NonZeroUsize,
ops::{Deref, DerefMut},
rc::Rc,
string::{String, ToString},
sync::Arc,
vec,
vec::Vec,
};
/// Default function access mode.
const DEF_ACCESS: FnAccess = FnAccess::Public;
/// Return type of module-level Rust function.
pub type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
@ -48,22 +44,24 @@ pub struct Module {
variables: HashMap<String, Dynamic>,
/// Flattened collection of all module variables, including those in sub-modules.
all_variables: HashMap<u64, Dynamic>,
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
/// External Rust functions.
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, SharedNativeFunction)>,
/// Flattened collection of all external Rust functions, including those in sub-modules.
all_functions: HashMap<u64, SharedNativeFunction>,
functions: HashMap<
u64,
(String, FnAccess, StaticVec<TypeId>, CallableFunction),
StraightHasherBuilder,
>,
/// Script-defined functions.
fn_lib: FunctionsLib,
/// Flattened collection of all script-defined functions, including those in sub-modules.
all_fn_lib: FunctionsLib,
lib: FunctionsLib,
/// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, SharedIteratorFunction>,
type_iterators: HashMap<TypeId, IteratorFn>,
/// Flattened collection of all external Rust functions, native or scripted,
/// including those in sub-modules.
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
}
impl fmt::Debug for Module {
@ -73,7 +71,7 @@ impl fmt::Debug for Module {
"<module {:?}, functions={}, lib={}>",
self.variables,
self.functions.len(),
self.fn_lib.len()
self.lib.len()
)
}
}
@ -107,7 +105,7 @@ impl Module {
/// ```
pub fn new_with_capacity(capacity: usize) -> Self {
Self {
functions: HashMap::with_capacity(capacity),
functions: HashMap::with_capacity_and_hasher(capacity, StraightHasherBuilder),
..Default::default()
}
}
@ -170,7 +168,7 @@ impl Module {
/// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
/// ```
pub fn set_var<K: Into<String>, T: Variant + Clone>(&mut self, name: K, value: T) {
pub fn set_var(&mut self, name: impl Into<String>, value: impl Variant + Clone) {
self.variables.insert(name.into(), Dynamic::from(value));
}
@ -250,7 +248,7 @@ impl Module {
/// module.set_sub_module("question", sub_module);
/// assert!(module.get_sub_module("question").is_some());
/// ```
pub fn set_sub_module<K: Into<String>>(&mut self, name: K, sub_module: Module) {
pub fn set_sub_module(&mut self, name: impl Into<String>, sub_module: Module) {
self.modules.insert(name.into(), sub_module.into());
}
@ -277,24 +275,19 @@ impl Module {
/// If there is an existing Rust function of the same hash, it is replaced.
pub fn set_fn(
&mut self,
name: String,
abi: NativeFunctionABI,
name: impl Into<String>,
access: FnAccess,
params: &[TypeId],
func: Box<FnAny>,
func: CallableFunction,
) -> u64 {
let hash_fn = calc_fn_hash(empty(), &name, params.iter().cloned());
let name = name.into();
let f = Box::new(NativeFunction::from((func, abi))) as Box<dyn NativeCallable>;
#[cfg(not(feature = "sync"))]
let func = Rc::new(f);
#[cfg(feature = "sync")]
let func = Arc::new(f);
let hash_fn = calc_fn_hash(empty(), &name, params.len(), params.iter().cloned());
let params = params.into_iter().cloned().collect();
self.functions.insert(hash_fn, (name, access, params, func));
self.functions
.insert(hash_fn, (name, access, params, func.into()));
hash_fn
}
@ -312,15 +305,20 @@ impl Module {
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_0<K: Into<String>, T: Variant + Clone>(
pub fn set_fn_0<T: Variant + Clone>(
&mut self,
name: K,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn() -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |_: &mut FnCallArgs| func().map(Dynamic::from);
let arg_types = [];
self.set_fn(name.into(), Pure, DEF_ACCESS, &arg_types, Box::new(f))
let args = [];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
}
/// Set a Rust function taking one parameter into the module, returning a hash key.
@ -336,16 +334,21 @@ impl Module {
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_1<K: Into<String>, A: Variant + Clone, T: Variant + Clone>(
pub fn set_fn_1<A: Variant + Clone, T: Variant + Clone>(
&mut self,
name: K,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f =
move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::<A>()).map(Dynamic::from);
let arg_types = [TypeId::of::<A>()];
self.set_fn(name.into(), Pure, DEF_ACCESS, &arg_types, Box::new(f))
let args = [TypeId::of::<A>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
}
/// Set a Rust function taking one mutable parameter into the module, returning a hash key.
@ -361,17 +364,45 @@ impl Module {
/// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) });
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_1_mut<K: Into<String>, A: Variant + Clone, T: Variant + Clone>(
pub fn set_fn_1_mut<A: Variant + Clone, T: Variant + Clone>(
&mut self,
name: K,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |args: &mut FnCallArgs| {
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>()];
self.set_fn(name.into(), Method, DEF_ACCESS, &arg_types, Box::new(f))
let args = [TypeId::of::<A>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
}
/// Set a Rust getter function taking one mutable parameter, returning a hash key.
///
/// If there is a similar existing Rust getter function, it is replaced.
///
/// # Examples
///
/// ```
/// use rhai::Module;
///
/// let mut module = Module::new();
/// let hash = module.set_getter_fn("value", |x: &mut i64| { Ok(*x) });
/// assert!(module.get_fn(hash).is_some());
/// ```
#[cfg(not(feature = "no_object"))]
pub fn set_getter_fn<A: Variant + Clone, T: Variant + Clone>(
&mut self,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
self.set_fn_1_mut(make_getter(&name.into()), func)
}
/// Set a Rust function taking two parameters into the module, returning a hash key.
@ -381,17 +412,17 @@ impl Module {
/// # Examples
///
/// ```
/// use rhai::Module;
/// use rhai::{Module, ImmutableString};
///
/// let mut module = Module::new();
/// let hash = module.set_fn_2("calc", |x: i64, y: String| {
/// let hash = module.set_fn_2("calc", |x: i64, y: ImmutableString| {
/// Ok(x + y.len() as i64)
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_2<K: Into<String>, A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
pub fn set_fn_2<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
name: K,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
@ -401,32 +432,34 @@ impl Module {
func(a, b).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(name.into(), Pure, DEF_ACCESS, &arg_types, Box::new(f))
let args = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
}
/// Set a Rust function taking two parameters (the first one mutable) into the module,
/// returning a hash key.
///
/// If there is a similar existing Rust function, it is replaced.
///
/// # Examples
///
/// ```
/// use rhai::Module;
/// use rhai::{Module, ImmutableString};
///
/// let mut module = Module::new();
/// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: String| {
/// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: ImmutableString| {
/// *x += y.len() as i64; Ok(*x)
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_2_mut<
K: Into<String>,
A: Variant + Clone,
B: Variant + Clone,
T: Variant + Clone,
>(
pub fn set_fn_2_mut<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
name: K,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
@ -436,8 +469,66 @@ impl Module {
func(a, b).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(name.into(), Method, DEF_ACCESS, &arg_types, Box::new(f))
let args = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
}
/// Set a Rust setter function taking two parameters (the first one mutable) into the module,
/// returning a hash key.
///
/// If there is a similar existing setter Rust function, it is replaced.
///
/// # Examples
///
/// ```
/// use rhai::{Module, ImmutableString};
///
/// let mut module = Module::new();
/// let hash = module.set_setter_fn("value", |x: &mut i64, y: ImmutableString| {
/// *x = y.len() as i64;
/// Ok(())
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
#[cfg(not(feature = "no_object"))]
pub fn set_setter_fn<A: Variant + Clone, B: Variant + Clone>(
&mut self,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<()> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<()> + Send + Sync + 'static,
) -> u64 {
self.set_fn_2_mut(make_setter(&name.into()), func)
}
/// Set a Rust indexer function taking two parameters (the first one mutable) into the module,
/// returning a hash key.
///
/// If there is a similar existing setter Rust function, it is replaced.
///
/// # Examples
///
/// ```
/// use rhai::{Module, ImmutableString};
///
/// let mut module = Module::new();
/// let hash = module.set_indexer_fn(|x: &mut i64, y: ImmutableString| {
/// Ok(*x + y.len() as i64)
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
pub fn set_indexer_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
self.set_fn_2_mut(FUNC_INDEXER, func)
}
/// Set a Rust function taking three parameters into the module, returning a hash key.
@ -447,23 +538,22 @@ impl Module {
/// # Examples
///
/// ```
/// use rhai::Module;
/// use rhai::{Module, ImmutableString};
///
/// let mut module = Module::new();
/// let hash = module.set_fn_3("calc", |x: i64, y: String, z: i64| {
/// let hash = module.set_fn_3("calc", |x: i64, y: ImmutableString, z: i64| {
/// Ok(x + y.len() as i64 + z)
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_3<
K: Into<String>,
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: K,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
@ -474,8 +564,13 @@ impl Module {
func(a, b, c).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(name.into(), Pure, DEF_ACCESS, &arg_types, Box::new(f))
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
}
/// Set a Rust function taking three parameters (the first one mutable) into the module,
@ -486,23 +581,22 @@ impl Module {
/// # Examples
///
/// ```
/// use rhai::Module;
/// use rhai::{Module, ImmutableString};
///
/// let mut module = Module::new();
/// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: String, z: i64| {
/// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: ImmutableString, z: i64| {
/// *x += y.len() as i64 + z; Ok(*x)
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_3_mut<
K: Into<String>,
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: K,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
@ -513,8 +607,112 @@ impl Module {
func(a, b, c).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(name.into(), Method, DEF_ACCESS, &arg_types, Box::new(f))
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
}
/// Set a Rust function taking four parameters into the module, returning a hash key.
///
/// If there is a similar existing Rust function, it is replaced.
///
/// # Examples
///
/// ```
/// use rhai::{Module, ImmutableString};
///
/// let mut module = Module::new();
/// let hash = module.set_fn_4("calc", |x: i64, y: ImmutableString, z: i64, _w: ()| {
/// Ok(x + y.len() as i64 + z)
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_4<
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
D: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C, D) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B, C, D) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>();
let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>();
let d = mem::take(args[3]).cast::<D>();
func(a, b, c, d).map(Dynamic::from)
};
let args = [
TypeId::of::<A>(),
TypeId::of::<B>(),
TypeId::of::<C>(),
TypeId::of::<D>(),
];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
}
/// Set a Rust function taking four parameters (the first one mutable) into the module,
/// returning a hash key.
///
/// If there is a similar existing Rust function, it is replaced.
///
/// # Examples
///
/// ```
/// use rhai::{Module, ImmutableString};
///
/// let mut module = Module::new();
/// let hash = module.set_fn_4_mut("calc", |x: &mut i64, y: ImmutableString, z: i64, _w: ()| {
/// *x += y.len() as i64 + z; Ok(*x)
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_4_mut<
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
D: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C, D) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, C, D) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>();
let d = mem::take(args[3]).cast::<D>();
let a = args[0].downcast_mut::<A>().unwrap();
func(a, b, c, d).map(Dynamic::from)
};
let args = [
TypeId::of::<A>(),
TypeId::of::<B>(),
TypeId::of::<C>(),
TypeId::of::<C>(),
];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
}
/// Get a Rust function.
@ -531,35 +729,25 @@ impl Module {
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn get_fn(&self, hash_fn: u64) -> Option<&Box<dyn NativeCallable>> {
self.functions.get(&hash_fn).map(|(_, _, _, v)| v.as_ref())
pub fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> {
self.functions.get(&hash_fn).map(|(_, _, _, v)| v)
}
/// Get a modules-qualified function.
///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
/// It is also returned by the `set_fn_XXX` calls.
/// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match
/// the hash calculated by `index_all_sub_modules`.
pub(crate) fn get_qualified_fn(
&mut self,
name: &str,
hash_fn_native: u64,
) -> Result<&Box<dyn NativeCallable>, Box<EvalAltResult>> {
self.all_functions
.get(&hash_fn_native)
.map(|f| f.as_ref())
.ok_or_else(|| {
Box::new(EvalAltResult::ErrorFunctionNotFound(
name.to_string(),
Position::none(),
))
})
}
/// Get a modules-qualified script-defined functions.
///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
pub(crate) fn get_qualified_scripted_fn(&mut self, hash_fn_def: u64) -> Option<&FnDef> {
self.all_fn_lib.get_function(hash_fn_def)
) -> Result<&CallableFunction, Box<EvalAltResult>> {
self.all_functions.get(&hash_fn_native).ok_or_else(|| {
Box::new(EvalAltResult::ErrorFunctionNotFound(
name.to_string(),
Position::none(),
))
})
}
/// Create a new `Module` by evaluating an `AST`.
@ -607,7 +795,7 @@ impl Module {
},
);
module.fn_lib = module.fn_lib.merge(ast.fn_lib());
module.lib = module.lib.merge(ast.lib());
Ok(module)
}
@ -620,77 +808,66 @@ impl Module {
module: &'a Module,
qualifiers: &mut Vec<&'a str>,
variables: &mut Vec<(u64, Dynamic)>,
functions: &mut Vec<(u64, SharedNativeFunction)>,
fn_lib: &mut Vec<(u64, SharedFnDef)>,
functions: &mut Vec<(u64, CallableFunction)>,
) {
for (name, m) in &module.modules {
// Index all the sub-modules first.
qualifiers.push(name);
index_module(m, qualifiers, variables, functions, fn_lib);
index_module(m, qualifiers, variables, functions);
qualifiers.pop();
}
// Index all variables
for (var_name, value) in &module.variables {
// Qualifiers + variable name
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, empty());
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0, empty());
variables.push((hash_var, value.clone()));
}
// Index all Rust functions
for (name, access, params, func) in module.functions.values() {
match access {
// Private functions are not exported
FnAccess::Private => continue,
FnAccess::Public => (),
Private => continue,
Public => (),
}
// Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + dummy parameter types (one for each parameter).
let hash_fn_def = calc_fn_hash(
qualifiers.iter().map(|&v| v),
name,
repeat(EMPTY_TYPE_ID()).take(params.len()),
);
// 2) Calculate a second hash with no qualifiers, empty function name, and
// the actual list of parameter `TypeId`'.s
let hash_fn_args = calc_fn_hash(empty(), "", params.iter().cloned());
// i.e. qualifiers + function name + number of arguments.
let hash_fn_def =
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'.s
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
// 3) The final hash is the XOR of the two hashes.
let hash_fn_native = hash_fn_def ^ hash_fn_args;
functions.push((hash_fn_native, func.clone()));
}
// Index all script-defined functions
for fn_def in module.fn_lib.values() {
for fn_def in module.lib.values() {
match fn_def.access {
// Private functions are not exported
FnAccess::Private => continue,
DEF_ACCESS => (),
Private => continue,
Public => (),
}
// Qualifiers + function name + placeholders (one for each parameter)
// Qualifiers + function name + number of arguments.
let hash_fn_def = calc_fn_hash(
qualifiers.iter().map(|&v| v),
&fn_def.name,
repeat(EMPTY_TYPE_ID()).take(fn_def.params.len()),
fn_def.params.len(),
empty(),
);
fn_lib.push((hash_fn_def, fn_def.clone()));
functions.push((hash_fn_def, CallableFunction::Script(fn_def.clone()).into()));
}
}
let mut variables = Vec::new();
let mut functions = Vec::new();
let mut fn_lib = Vec::new();
index_module(
self,
&mut vec!["root"],
&mut variables,
&mut functions,
&mut fn_lib,
);
index_module(self, &mut vec!["root"], &mut variables, &mut functions);
self.all_variables = variables.into_iter().collect();
self.all_functions = functions.into_iter().collect();
self.all_fn_lib = fn_lib.into();
}
/// Does a type iterator exist in the module?
@ -699,16 +876,13 @@ impl Module {
}
/// Set a type iterator into the module.
pub fn set_iter(&mut self, typ: TypeId, func: Box<IteratorFn>) {
#[cfg(not(feature = "sync"))]
self.type_iterators.insert(typ, Rc::new(func));
#[cfg(feature = "sync")]
self.type_iterators.insert(typ, Arc::new(func));
pub fn set_iter(&mut self, typ: TypeId, func: IteratorFn) {
self.type_iterators.insert(typ, func);
}
/// Get the specified type iterator.
pub fn get_iter(&self, id: TypeId) -> Option<&SharedIteratorFunction> {
self.type_iterators.get(&id)
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.type_iterators.get(&id).cloned()
}
}
@ -952,6 +1126,9 @@ mod stat {
/// Module resolution service that serves modules added into it.
///
/// `StaticModuleResolver` is a smart pointer to a `HashMap<String, Module>`.
/// It can simply be treated as `&HashMap<String, Module>`.
///
/// # Examples
///
/// ```

View File

@ -1,11 +1,9 @@
use crate::any::Dynamic;
use crate::calc_fn_hash;
use crate::engine::{
Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF,
Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT,
KEYWORD_TYPE_OF,
};
use crate::fn_native::FnCallArgs;
use crate::module::Module;
use crate::packages::PackagesCollection;
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
@ -15,7 +13,6 @@ use crate::utils::StaticVec;
use crate::stdlib::{
boxed::Box,
iter::empty,
mem,
string::{String, ToString},
vec,
vec::Vec,
@ -40,6 +37,10 @@ impl OptimizationLevel {
pub fn is_none(self) -> bool {
self == Self::None
}
/// Is the `OptimizationLevel` Simple.
pub fn is_simple(self) -> bool {
self == Self::Simple
}
/// Is the `OptimizationLevel` Full.
pub fn is_full(self) -> bool {
self == Self::Full
@ -55,23 +56,19 @@ struct State<'a> {
/// An `Engine` instance for eager function evaluation.
engine: &'a Engine,
/// Library of script-defined functions.
fn_lib: &'a [(&'a str, usize)],
lib: &'a FunctionsLib,
/// Optimization level.
optimization_level: OptimizationLevel,
}
impl<'a> State<'a> {
/// Create a new State.
pub fn new(
engine: &'a Engine,
fn_lib: &'a [(&'a str, usize)],
level: OptimizationLevel,
) -> Self {
pub fn new(engine: &'a Engine, lib: &'a FunctionsLib, level: OptimizationLevel) -> Self {
Self {
changed: false,
constants: vec![],
engine,
fn_lib,
lib,
optimization_level: level,
}
}
@ -112,26 +109,40 @@ impl<'a> State<'a> {
}
/// Call a registered function
fn call_fn(
packages: &PackagesCollection,
global_module: &Module,
fn call_fn_with_constant_arguments(
state: &State,
fn_name: &str,
args: &mut FnCallArgs,
arg_values: &mut [Dynamic],
pos: Position,
) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
// Search built-in's and external functions
let hash = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id()));
let hash_fn = calc_fn_hash(
empty(),
fn_name,
arg_values.len(),
arg_values.iter().map(|a| a.type_id()),
);
global_module
.get_fn(hash)
.or_else(|| packages.get_fn(hash))
.map(|func| func.call(args))
.transpose()
.map_err(|err| err.new_position(pos))
state
.engine
.call_fn_raw(
&mut Scope::new(),
&mut Default::default(),
state.lib,
fn_name,
(hash_fn, 0),
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false,
None,
pos,
0,
)
.map(|(v, _)| Some(v))
.or_else(|_| Ok(None))
}
/// Optimize a statement.
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
match stmt {
// if expr { Noop }
Stmt::IfThenElse(x) if matches!(x.1, Stmt::Noop(_)) => {
@ -352,11 +363,13 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
}
/// Optimize an expression.
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// These keywords are handled specially
const DONT_EVAL_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL];
match expr {
// expr - do not promote because there is a reason it is wrapped in an `Expr::Expr`
Expr::Expr(x) => Expr::Expr(Box::new(optimize_expr(*x, state))),
// ( stmt )
Expr::Stmt(x) => match optimize_stmt(x.0, state, true) {
// ( Noop ) -> ()
@ -372,27 +385,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// ( stmt )
stmt => Expr::Stmt(Box::new((stmt, x.1))),
},
// id = expr
Expr::Assignment(x) => match x.1 {
//id = id2 = expr2
Expr::Assignment(x2) => match (x.0, x2.0) {
// var = var = expr2 -> var = expr2
// id op= expr
Expr::Assignment(x) => match x.2 {
//id = id2 op= rhs
Expr::Assignment(x2) if x.1.is_empty() => match (x.0, &x2.0) {
// var = var op= expr2 -> var op= expr2
(Expr::Variable(a), Expr::Variable(b))
if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 =>
{
// Assignment to the same variable - fold
state.set_dirty();
Expr::Assignment(Box::new((Expr::Variable(a), optimize_expr(x2.1, state), x.2)))
}
// id1 = id2 = expr2
(id1, id2) => {
Expr::Assignment(Box::new((
id1, Expr::Assignment(Box::new((id2, optimize_expr(x2.1, state), x2.2))), x.2,
)))
Expr::Assignment(Box::new((Expr::Variable(a), x2.1, optimize_expr(x2.2, state), x.3)))
}
// expr1 = expr2 op= rhs
(expr1, _) => Expr::Assignment(Box::new((expr1, x.1, optimize_expr(Expr::Assignment(x2), state), x.3))),
},
// id = expr
expr => Expr::Assignment(Box::new((x.0, optimize_expr(expr, state), x.2))),
// expr = rhs
expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))),
},
// lhs.rhs
@ -431,7 +440,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// All other items can be thrown away.
state.set_dirty();
let pos = m.1;
m.0.into_iter().find(|((name, _), _)| name == &s.0)
m.0.into_iter().find(|((name, _), _)| name == s.0.as_ref())
.map(|(_, expr)| expr.set_position(pos))
.unwrap_or_else(|| Expr::Unit(pos))
}
@ -459,7 +468,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// "xxx" in "xxxxx"
(Expr::StringConstant(a), Expr::StringConstant(b)) => {
state.set_dirty();
if b.0.contains(&a.0) { Expr::True(a.1) } else { Expr::False(a.1) }
if b.0.contains(a.0.as_ref()) { Expr::True(a.1) } else { Expr::False(a.1) }
}
// 'x' in "xxxxx"
(Expr::CharConstant(a), Expr::StringConstant(b)) => {
@ -469,7 +478,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// "xxx" in #{...}
(Expr::StringConstant(a), Expr::Map(b)) => {
state.set_dirty();
if b.0.iter().find(|((name, _), _)| name == &a.0).is_some() {
if b.0.iter().find(|((name, _), _)| name == a.0.as_ref()).is_some() {
Expr::True(a.1)
} else {
Expr::False(a.1)
@ -542,27 +551,30 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
&& state.optimization_level == OptimizationLevel::Full // full optimizations
&& x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> {
let ((name, pos), _, _, args, def_value) = x.as_mut();
let ((name, native_only, pos), _, _, args, def_value) = x.as_mut();
// First search in script-defined functions (can override built-in)
if state.fn_lib.iter().find(|(id, len)| *id == name && *len == args.len()).is_some() {
// Cater for both normal function call style and method call style (one additional arguments)
if !*native_only && state.lib.values().find(|f|
&f.name == name
&& (args.len()..=args.len() + 1).contains(&f.params.len())
).is_some() {
// A script-defined function overrides the built-in function - do not make the call
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
return Expr::FnCall(x);
}
let mut arg_values: StaticVec<_> = args.iter().map(Expr::get_constant_value).collect();
let mut call_args: StaticVec<_> = arg_values.iter_mut().collect();
// Save the typename of the first argument if it is `type_of()`
// This is to avoid `call_args` being passed into the closure
let arg_for_type_of = if name == KEYWORD_TYPE_OF && call_args.len() == 1 {
state.engine.map_type_name(call_args[0].type_name())
let arg_for_type_of = if name == KEYWORD_TYPE_OF && arg_values.len() == 1 {
state.engine.map_type_name(arg_values[0].type_name())
} else {
""
};
call_fn(&state.engine.packages, &state.engine.global_module, name, call_args.as_mut(), *pos).ok()
call_fn_with_constant_arguments(&state, name, arg_values.as_mut(), *pos).ok()
.and_then(|result|
result.or_else(|| {
if !arg_for_type_of.is_empty() {
@ -604,11 +616,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
}
}
fn optimize<'a>(
fn optimize(
statements: Vec<Stmt>,
engine: &Engine,
scope: &Scope,
fn_lib: &'a [(&'a str, usize)],
lib: &FunctionsLib,
level: OptimizationLevel,
) -> Vec<Stmt> {
// If optimization level is None then skip optimizing
@ -617,7 +629,7 @@ fn optimize<'a>(
}
// Set up the state
let mut state = State::new(engine, fn_lib, level);
let mut state = State::new(engine, lib, level);
// Add constants from the scope into the state
scope
@ -702,41 +714,35 @@ pub fn optimize_into_ast(
const level: OptimizationLevel = OptimizationLevel::None;
#[cfg(not(feature = "no_function"))]
let fn_lib_values: StaticVec<_> = functions
.iter()
.map(|fn_def| (fn_def.name.as_str(), fn_def.params.len()))
.collect();
#[cfg(not(feature = "no_function"))]
let fn_lib = fn_lib_values.as_ref();
#[cfg(feature = "no_function")]
const fn_lib: &[(&str, usize)] = &[];
#[cfg(not(feature = "no_function"))]
let lib = FunctionsLib::from_iter(functions.iter().cloned().map(|mut fn_def| {
let lib = {
if !level.is_none() {
let pos = fn_def.body.position();
let lib = FunctionsLib::from_iter(functions.iter().cloned());
// Optimize the function body
let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), fn_lib, level);
FunctionsLib::from_iter(functions.into_iter().map(|mut fn_def| {
let pos = fn_def.body.position();
// {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
Stmt::Expr(Box::new(x.1.unwrap()))
}
// { return; } -> ()
Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => {
Stmt::Expr(Box::new(Expr::Unit((x.0).1)))
}
// All others
stmt => stmt,
};
// Optimize the function body
let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &lib, level);
// {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
Stmt::Expr(Box::new(x.1.unwrap()))
}
// { return; } -> ()
Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => {
Stmt::Expr(Box::new(Expr::Unit((x.0).1)))
}
// All others
stmt => stmt,
};
fn_def
}))
} else {
FunctionsLib::from_iter(functions.into_iter())
}
fn_def
}));
};
#[cfg(feature = "no_function")]
let lib: FunctionsLib = Default::default();
@ -745,7 +751,7 @@ pub fn optimize_into_ast(
match level {
OptimizationLevel::None => statements,
OptimizationLevel::Simple | OptimizationLevel::Full => {
optimize(statements, engine, &scope, fn_lib, level)
optimize(statements, engine, &scope, &lib, level)
}
},
lib,

View File

@ -20,7 +20,7 @@ use crate::stdlib::{
};
// Checked add
fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
x.checked_add(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Addition overflow: {} + {}", x, y),
@ -29,7 +29,7 @@ fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
})
}
// Checked subtract
fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
x.checked_sub(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Subtraction underflow: {} - {}", x, y),
@ -38,7 +38,7 @@ fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
})
}
// Checked multiply
fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
x.checked_mul(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Multiplication overflow: {} * {}", x, y),
@ -47,7 +47,7 @@ fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
})
}
// Checked divide
fn div<T>(x: T, y: T) -> FuncReturn<T>
pub(crate) fn div<T>(x: T, y: T) -> FuncReturn<T>
where
T: Display + CheckedDiv + PartialEq + Zero,
{
@ -67,7 +67,7 @@ where
})
}
// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
pub(crate) fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
x.checked_neg().ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Negation overflow: -{}", x),
@ -76,7 +76,7 @@ fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
})
}
// Checked absolute
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncReturn<T> {
pub(crate) fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncReturn<T> {
// FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
// when the number is ::MIN instead of returning ::MIN itself.
if x >= <T as Zero>::zero() {
@ -133,7 +133,7 @@ fn binary_xor<T: BitXor>(x: T, y: T) -> FuncReturn<<T as BitXor>::Output> {
Ok(x ^ y)
}
// Checked left-shift
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
pub(crate) fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits
if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -150,7 +150,7 @@ fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
})
}
// Checked right-shift
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
pub(crate) fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits
if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -167,15 +167,15 @@ fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
})
}
// Unchecked left-shift - may panic if shifting by a negative number of bits
fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> {
pub(crate) fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> {
Ok(x.shl(y))
}
// Unchecked right-shift - may panic if shifting by a negative number of bits
fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
pub(crate) fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
Ok(x.shr(y))
}
// Checked modulo
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
x.checked_rem(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Modulo division by zero or overflow: {} % {}", x, y),
@ -188,7 +188,7 @@ fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
Ok(x % y)
}
// Checked power
fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
#[cfg(not(feature = "only_i32"))]
{
if y > (u32::MAX as INT) {
@ -229,17 +229,17 @@ fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
}
}
// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
Ok(x.pow(y as u32))
}
// Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))]
fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> {
pub(crate) fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> {
Ok(x.powf(y))
}
// Checked power
#[cfg(not(feature = "no_float"))]
fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
// Raise to power that is larger than an i32
if y > (i32::MAX as INT) {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -253,7 +253,7 @@ fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
// Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
#[cfg(feature = "unchecked")]
#[cfg(not(feature = "no_float"))]
fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
pub(crate) fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
Ok(x.powi(y as i32))
}
@ -269,119 +269,69 @@ macro_rules! reg_op {
}
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Checked basic arithmetic
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "+", add, INT);
reg_op!(lib, "-", sub, INT);
reg_op!(lib, "*", mul, INT);
reg_op!(lib, "/", div, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[cfg(not(feature = "unchecked"))]
{
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// Checked basic arithmetic
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div, i8, u8, i16, u16, i32, u32, u64, i128, u128);
// Checked bit shifts
reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
}
// Unchecked basic arithmetic
#[cfg(feature = "unchecked")]
{
reg_op!(lib, "+", add_u, INT);
reg_op!(lib, "-", sub_u, INT);
reg_op!(lib, "*", mul_u, INT);
reg_op!(lib, "/", div_u, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[cfg(feature = "unchecked")]
{
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// Unchecked basic arithmetic
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
// Unchecked bit shifts
reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
}
// Basic arithmetic for floating-point - no need to check
#[cfg(not(feature = "no_float"))]
{
reg_op!(lib, "+", add_u, f32, f64);
reg_op!(lib, "-", sub_u, f32, f64);
reg_op!(lib, "*", mul_u, f32, f64);
reg_op!(lib, "/", div_u, f32, f64);
reg_op!(lib, "+", add_u, f32);
reg_op!(lib, "-", sub_u, f32);
reg_op!(lib, "*", mul_u, f32);
reg_op!(lib, "/", div_u, f32);
}
// Bit operations
reg_op!(lib, "|", binary_or, INT);
reg_op!(lib, "&", binary_and, INT);
reg_op!(lib, "^", binary_xor, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
// Checked bit shifts
#[cfg(not(feature = "unchecked"))]
{
reg_op!(lib, "<<", shl, INT);
reg_op!(lib, ">>", shr, INT);
reg_op!(lib, "%", modulo, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
}
}
// Unchecked bit shifts
#[cfg(feature = "unchecked")]
{
reg_op!(lib, "<<", shl_u, INT, INT);
reg_op!(lib, ">>", shr_u, INT, INT);
reg_op!(lib, "%", modulo_u, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
}
}
// Checked power
#[cfg(not(feature = "unchecked"))]
{
lib.set_fn_2("~", pow_i_i);
#[cfg(not(feature = "no_float"))]
lib.set_fn_2("~", pow_f_i);
}
// Unchecked power
#[cfg(feature = "unchecked")]
{
lib.set_fn_2("~", pow_i_i_u);
#[cfg(not(feature = "no_float"))]
lib.set_fn_2("~", pow_f_i_u);
}
// Floating-point modulo and power
#[cfg(not(feature = "no_float"))]
{
reg_op!(lib, "%", modulo_u, f32, f64);
lib.set_fn_2("~", pow_f_f);
// Checked power
#[cfg(not(feature = "unchecked"))]
lib.set_fn_2("~", pow_f_i);
// Unchecked power
#[cfg(feature = "unchecked")]
lib.set_fn_2("~", pow_f_i_u);
// Floating-point modulo and power
reg_op!(lib, "%", modulo_u, f32);
// Floating-point unary
reg_unary!(lib, "-", neg_u, f32, f64);
reg_unary!(lib, "abs", abs_u, f32, f64);
}
// Checked unary
@ -411,11 +361,4 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64, i128);
}
}
// Floating-point unary
#[cfg(not(feature = "no_float"))]
{
reg_unary!(lib, "-", neg_u, f32, f64);
reg_unary!(lib, "abs", abs_u, f32, f64);
}
});

View File

@ -4,9 +4,9 @@ use crate::any::{Dynamic, Variant};
use crate::def_package;
use crate::engine::Array;
use crate::module::FuncReturn;
use crate::parser::INT;
use crate::parser::{ImmutableString, INT};
use crate::stdlib::{any::TypeId, boxed::Box, string::String};
use crate::stdlib::{any::TypeId, boxed::Box};
// Register array utility functions
fn push<T: Variant + Clone>(list: &mut Array, item: T) -> FuncReturn<()> {
@ -45,14 +45,18 @@ macro_rules! reg_tri {
#[cfg(not(feature = "no_index"))]
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
reg_op!(lib, "push", push, INT, bool, char, String, Array, ());
reg_tri!(lib, "pad", pad, INT, bool, char, String, Array, ());
reg_tri!(lib, "insert", ins, INT, bool, char, String, Array, ());
reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ());
reg_tri!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ());
reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ());
lib.set_fn_2_mut("append", |x: &mut Array, y: Array| {
x.extend(y);
Ok(())
});
lib.set_fn_2_mut("+=", |x: &mut Array, y: Array| {
x.extend(y);
Ok(())
});
lib.set_fn_2(
"+",
|mut x: Array, y: Array| {
@ -101,6 +105,10 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
},
);
lib.set_fn_1_mut("len", |list: &mut Array| Ok(list.len() as INT));
#[cfg(not(feature = "no_object"))]
lib.set_getter_fn("len", |list: &mut Array| Ok(list.len() as INT));
lib.set_fn_1_mut("clear", |list: &mut Array| {
list.clear();
Ok(())
@ -120,8 +128,6 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
// Register array iterator
lib.set_iter(
TypeId::of::<Array>(),
Box::new(|arr| Box::new(
arr.cast::<Array>().into_iter()) as Box<dyn Iterator<Item = Dynamic>>
),
|arr| Box::new(arr.cast::<Array>().into_iter()) as Box<dyn Iterator<Item = Dynamic>>,
);
});

12
src/packages/eval.rs Normal file
View File

@ -0,0 +1,12 @@
use crate::def_package;
use crate::module::FuncReturn;
use crate::parser::ImmutableString;
def_package!(crate:EvalPackage:"Disable 'eval'.", lib, {
lib.set_fn_1(
"eval",
|_: ImmutableString| -> FuncReturn<()> {
Err("eval is evil!".into())
},
);
});

View File

@ -14,13 +14,10 @@ fn reg_range<T: Variant + Clone>(lib: &mut Module)
where
Range<T>: Iterator<Item = T>,
{
lib.set_iter(
TypeId::of::<Range<T>>(),
Box::new(|source| {
Box::new(source.cast::<Range<T>>().map(|x| x.into_dynamic()))
as Box<dyn Iterator<Item = Dynamic>>
}),
);
lib.set_iter(TypeId::of::<Range<T>>(), |source| {
Box::new(source.cast::<Range<T>>().map(|x| x.into_dynamic()))
as Box<dyn Iterator<Item = Dynamic>>
});
}
fn get_range<T: Variant + Clone>(from: T, to: T) -> FuncReturn<Range<T>> {
@ -58,13 +55,10 @@ where
T: Variant + Clone + PartialOrd,
StepRange<T>: Iterator<Item = T>,
{
lib.set_iter(
TypeId::of::<StepRange<T>>(),
Box::new(|source| {
Box::new(source.cast::<StepRange<T>>().map(|x| x.into_dynamic()))
as Box<dyn Iterator<Item = Dynamic>>
}),
);
lib.set_iter(TypeId::of::<StepRange<T>>(), |source| {
Box::new(source.cast::<StepRange<T>>().map(|x| x.into_dynamic()))
as Box<dyn Iterator<Item = Dynamic>>
});
}
fn get_step_range<T>(from: T, to: T, step: T) -> FuncReturn<StepRange<T>>

View File

@ -1,8 +1,5 @@
use crate::def_package;
use crate::module::FuncReturn;
use crate::parser::INT;
use crate::stdlib::string::String;
// Comparison operators
pub fn lt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
@ -25,12 +22,6 @@ pub fn ne<T: PartialEq>(x: T, y: T) -> FuncReturn<bool> {
}
// Logic operators
fn and(x: bool, y: bool) -> FuncReturn<bool> {
Ok(x && y)
}
fn or(x: bool, y: bool) -> FuncReturn<bool> {
Ok(x || y)
}
fn not(x: bool) -> FuncReturn<bool> {
Ok(!x)
}
@ -42,48 +33,26 @@ macro_rules! reg_op {
}
def_package!(crate:LogicPackage:"Logical operators.", lib, {
reg_op!(lib, "<", lt, INT, char);
reg_op!(lib, "<=", lte, INT, char);
reg_op!(lib, ">", gt, INT, char);
reg_op!(lib, ">=", gte, INT, char);
reg_op!(lib, "==", eq, INT, char, bool, ());
reg_op!(lib, "!=", ne, INT, char, bool, ());
// Special versions for strings - at least avoid copying the first string
lib.set_fn_2_mut("<", |x: &mut String, y: String| Ok(*x < y));
lib.set_fn_2_mut("<=", |x: &mut String, y: String| Ok(*x <= y));
lib.set_fn_2_mut(">", |x: &mut String, y: String| Ok(*x > y));
lib.set_fn_2_mut(">=", |x: &mut String, y: String| Ok(*x >= y));
lib.set_fn_2_mut("==", |x: &mut String, y: String| Ok(*x == y));
lib.set_fn_2_mut("!=", |x: &mut String, y: String| Ok(*x != y));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
#[cfg(not(feature = "no_float"))]
{
reg_op!(lib, "<", lt, f32, f64);
reg_op!(lib, "<=", lte, f32, f64);
reg_op!(lib, ">", gt, f32, f64);
reg_op!(lib, ">=", gte, f32, f64);
reg_op!(lib, "==", eq, f32, f64);
reg_op!(lib, "!=", ne, f32, f64);
reg_op!(lib, "<", lt, f32);
reg_op!(lib, "<=", lte, f32);
reg_op!(lib, ">", gt, f32);
reg_op!(lib, ">=", gte, f32);
reg_op!(lib, "==", eq, f32);
reg_op!(lib, "!=", ne, f32);
}
// `&&` and `||` are treated specially as they short-circuit.
// They are implemented as special `Expr` instances, not function calls.
//reg_op!(lib, "||", or, bool);
//reg_op!(lib, "&&", and, bool);
lib.set_fn_2("|", or);
lib.set_fn_2("&", and);
lib.set_fn_1("!", not);
});

View File

@ -4,12 +4,9 @@ use crate::any::Dynamic;
use crate::def_package;
use crate::engine::Map;
use crate::module::FuncReturn;
use crate::parser::INT;
use crate::parser::{ImmutableString, INT};
use crate::stdlib::{
string::{String, ToString},
vec::Vec,
};
use crate::stdlib::{string::ToString, vec::Vec};
fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
Ok(map.iter().map(|(k, _)| k.to_string().into()).collect())
@ -22,7 +19,7 @@ fn map_get_values(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
lib.set_fn_2_mut(
"has",
|map: &mut Map, prop: String| Ok(map.contains_key(&prop)),
|map: &mut Map, prop: ImmutableString| Ok(map.contains_key(prop.as_str())),
);
lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT));
lib.set_fn_1_mut("clear", |map: &mut Map| {
@ -31,7 +28,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
});
lib.set_fn_2_mut(
"remove",
|x: &mut Map, name: String| Ok(x.remove(&name).unwrap_or_else(|| ().into())),
|x: &mut Map, name: ImmutableString| Ok(x.remove(name.as_str()).unwrap_or_else(|| ().into())),
);
lib.set_fn_2_mut(
"mixin",
@ -42,6 +39,15 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
Ok(())
},
);
lib.set_fn_2_mut(
"+=",
|map1: &mut Map, map2: Map| {
map2.into_iter().for_each(|(key, value)| {
map1.insert(key, value);
});
Ok(())
},
);
lib.set_fn_2(
"+",
|mut map1: Map, map2: Map| {

View File

@ -43,6 +43,18 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
lib.set_fn_1("is_finite", |x: FLOAT| Ok(x.is_finite()));
lib.set_fn_1("is_infinite", |x: FLOAT| Ok(x.is_infinite()));
#[cfg(not(feature = "no_object"))]
{
lib.set_getter_fn("floor", |x: &mut FLOAT| Ok(x.floor()));
lib.set_getter_fn("ceiling", |x: &mut FLOAT| Ok(x.ceil()));
lib.set_getter_fn("round", |x: &mut FLOAT| Ok(x.ceil()));
lib.set_getter_fn("int", |x: &mut FLOAT| Ok(x.trunc()));
lib.set_getter_fn("fraction", |x: &mut FLOAT| Ok(x.fract()));
lib.set_getter_fn("is_nan", |x: &mut FLOAT| Ok(x.is_nan()));
lib.set_getter_fn("is_finite", |x: &mut FLOAT| Ok(x.is_finite()));
lib.set_getter_fn("is_infinite", |x: &mut FLOAT| Ok(x.is_infinite()));
}
// Register conversion functions
lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT));

View File

@ -1,13 +1,14 @@
//! Module containing all built-in _packages_ available to Rhai, plus facilities to define custom packages.
use crate::fn_native::{NativeCallable, SharedIteratorFunction};
use crate::fn_native::{CallableFunction, IteratorFn, Shared};
use crate::module::Module;
use crate::utils::StaticVec;
use crate::stdlib::{any::TypeId, boxed::Box, collections::HashMap, rc::Rc, sync::Arc, vec::Vec};
use crate::stdlib::any::TypeId;
mod arithmetic;
pub(crate) mod arithmetic;
mod array_basic;
mod eval;
mod iter_basic;
mod logic;
mod map_basic;
@ -21,6 +22,7 @@ mod time_basic;
pub use arithmetic::ArithmeticPackage;
#[cfg(not(feature = "no_index"))]
pub use array_basic::BasicArrayPackage;
pub use eval::EvalPackage;
pub use iter_basic::BasicIteratorPackage;
pub use logic::LogicPackage;
#[cfg(not(feature = "no_object"))]
@ -42,35 +44,27 @@ pub trait Package {
fn get(&self) -> PackageLibrary;
}
/// Type which `Rc`-wraps a `Module` to facilitate sharing library instances.
#[cfg(not(feature = "sync"))]
pub type PackageLibrary = Rc<Module>;
/// Type which `Arc`-wraps a `Module` to facilitate sharing library instances.
#[cfg(feature = "sync")]
pub type PackageLibrary = Arc<Module>;
/// A sharable `Module` to facilitate sharing library instances.
pub type PackageLibrary = Shared<Module>;
/// Type containing a collection of `PackageLibrary` instances.
/// All function and type iterator keys in the loaded packages are indexed for fast access.
#[derive(Clone, Default)]
pub(crate) struct PackagesCollection {
/// Collection of `PackageLibrary` instances.
packages: StaticVec<PackageLibrary>,
}
pub(crate) struct PackagesCollection(StaticVec<PackageLibrary>);
impl PackagesCollection {
/// Add a `PackageLibrary` into the `PackagesCollection`.
pub fn push(&mut self, package: PackageLibrary) {
// Later packages override previous ones.
self.packages.insert(0, package);
self.0.insert(0, package);
}
/// Does the specified function hash key exist in the `PackagesCollection`?
pub fn contains_fn(&self, hash: u64) -> bool {
self.packages.iter().any(|p| p.contains_fn(hash))
self.0.iter().any(|p| p.contains_fn(hash))
}
/// Get specified function via its hash key.
pub fn get_fn(&self, hash: u64) -> Option<&Box<dyn NativeCallable>> {
self.packages
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
self.0
.iter()
.map(|p| p.get_fn(hash))
.find(|f| f.is_some())
@ -78,11 +72,11 @@ impl PackagesCollection {
}
/// Does the specified TypeId iterator exist in the `PackagesCollection`?
pub fn contains_iter(&self, id: TypeId) -> bool {
self.packages.iter().any(|p| p.contains_iter(id))
self.0.iter().any(|p| p.contains_iter(id))
}
/// Get the specified TypeId iterator.
pub fn get_iter(&self, id: TypeId) -> Option<&SharedIteratorFunction> {
self.packages
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.0
.iter()
.map(|p| p.get_iter(id))
.find(|f| f.is_some())

View File

@ -1,7 +1,7 @@
use crate::def_package;
use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
use crate::module::FuncReturn;
use crate::parser::INT;
use crate::parser::{ImmutableString, INT};
#[cfg(not(feature = "no_index"))]
use crate::engine::Array;
@ -12,19 +12,19 @@ use crate::engine::Map;
use crate::stdlib::{
fmt::{Debug, Display},
format,
string::{String, ToString},
string::ToString,
};
// Register print and debug
fn to_debug<T: Debug>(x: &mut T) -> FuncReturn<String> {
Ok(format!("{:?}", x))
fn to_debug<T: Debug>(x: &mut T) -> FuncReturn<ImmutableString> {
Ok(format!("{:?}", x).into())
}
fn to_string<T: Display>(x: &mut T) -> FuncReturn<String> {
Ok(format!("{}", x))
fn to_string<T: Display>(x: &mut T) -> FuncReturn<ImmutableString> {
Ok(format!("{}", x).into())
}
#[cfg(not(feature = "no_object"))]
fn format_map(x: &mut Map) -> FuncReturn<String> {
Ok(format!("#{:?}", x))
fn format_map(x: &mut Map) -> FuncReturn<ImmutableString> {
Ok(format!("#{:?}", x).into())
}
macro_rules! reg_op {
@ -41,10 +41,10 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
lib.set_fn_1(KEYWORD_PRINT, |_: ()| Ok("".to_string()));
lib.set_fn_1(FUNC_TO_STRING, |_: ()| Ok("".to_string()));
lib.set_fn_1_mut(KEYWORD_PRINT, |s: &mut String| Ok(s.clone()));
lib.set_fn_1_mut(FUNC_TO_STRING, |s: &mut String| Ok(s.clone()));
lib.set_fn_1(KEYWORD_PRINT, |s: ImmutableString| Ok(s));
lib.set_fn_1(FUNC_TO_STRING, |s: ImmutableString| Ok(s));
reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, String);
reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
@ -78,29 +78,8 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
lib.set_fn_1_mut(KEYWORD_DEBUG, format_map);
}
lib.set_fn_2(
"+",
|mut s: String, ch: char| {
s.push(ch);
Ok(s)
},
);
lib.set_fn_2(
"+",
|mut s: String, s2: String| {
s.push_str(&s2);
Ok(s)
},
);
lib.set_fn_2_mut("append", |s: &mut String, ch: char| {
s.push(ch);
Ok(())
});
lib.set_fn_2_mut(
"append",
|s: &mut String, s2: String| {
s.push_str(&s2);
Ok(())
}
);
lib.set_fn_2("+", |s: ImmutableString, ch: char| Ok(s + ch));
lib.set_fn_2_mut("+=", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) });
lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) });
lib.set_fn_2_mut("append", |s: &mut ImmutableString, s2: ImmutableString| { *s += &s2; Ok(()) });
});

View File

@ -1,6 +1,6 @@
use crate::def_package;
use crate::module::FuncReturn;
use crate::parser::INT;
use crate::parser::{ImmutableString, INT};
use crate::utils::StaticVec;
#[cfg(not(feature = "no_index"))]
@ -10,22 +10,21 @@ use crate::stdlib::{
fmt::Display,
format,
string::{String, ToString},
vec::Vec,
};
fn prepend<T: Display>(x: T, y: String) -> FuncReturn<String> {
Ok(format!("{}{}", x, y))
fn prepend<T: Display>(x: T, y: ImmutableString) -> FuncReturn<ImmutableString> {
Ok(format!("{}{}", x, y).into())
}
fn append<T: Display>(x: String, y: T) -> FuncReturn<String> {
Ok(format!("{}{}", x, y))
fn append<T: Display>(x: ImmutableString, y: T) -> FuncReturn<ImmutableString> {
Ok(format!("{}{}", x, y).into())
}
fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn<String> {
fn sub_string(s: ImmutableString, start: INT, len: INT) -> FuncReturn<ImmutableString> {
let offset = if s.is_empty() || len <= 0 {
return Ok("".to_string());
return Ok("".to_string().into());
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
return Ok("".to_string());
return Ok("".to_string().into());
} else {
start as usize
};
@ -38,36 +37,38 @@ fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn<String> {
len as usize
};
Ok(chars.iter().skip(offset).take(len).cloned().collect())
}
fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> {
let offset = if s.is_empty() || len <= 0 {
s.clear();
return Ok(());
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
s.clear();
return Ok(());
} else {
start as usize
};
let chars: StaticVec<_> = s.chars().collect();
let len = if offset + (len as usize) > chars.len() {
chars.len() - offset
} else {
len as usize
};
s.clear();
chars
Ok(chars
.iter()
.skip(offset)
.take(len)
.for_each(|&ch| s.push(ch));
.cloned()
.collect::<String>()
.into())
}
fn crop_string(s: &mut ImmutableString, start: INT, len: INT) -> FuncReturn<()> {
let offset = if s.is_empty() || len <= 0 {
s.make_mut().clear();
return Ok(());
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
s.make_mut().clear();
return Ok(());
} else {
start as usize
};
let chars: StaticVec<_> = s.chars().collect();
let len = if offset + (len as usize) > chars.len() {
chars.len() - offset
} else {
len as usize
};
let copy = s.make_mut();
copy.clear();
copy.extend(chars.iter().skip(offset).take(len));
Ok(())
}
@ -80,10 +81,10 @@ macro_rules! reg_op {
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
reg_op!(lib, "+", append, INT, bool, char);
lib.set_fn_2_mut( "+", |x: &mut String, _: ()| Ok(x.clone()));
lib.set_fn_2( "+", |x: ImmutableString, _: ()| Ok(x));
reg_op!(lib, "+", prepend, INT, bool, char);
lib.set_fn_2("+", |_: (), y: String| Ok(y));
lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
@ -100,22 +101,26 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
#[cfg(not(feature = "no_index"))]
{
lib.set_fn_2("+", |x: String, y: Array| Ok(format!("{}{:?}", x, y)));
lib.set_fn_2("+", |x: Array, y: String| Ok(format!("{:?}{}", x, y)));
lib.set_fn_2_mut("+", |x: &mut ImmutableString, y: Array| Ok(format!("{}{:?}", x, y)));
lib.set_fn_2_mut("+", |x: &mut Array, y: ImmutableString| Ok(format!("{:?}{}", x, y)));
}
lib.set_fn_1_mut("len", |s: &mut String| Ok(s.chars().count() as INT));
lib.set_fn_1_mut("len", |s: &mut ImmutableString| Ok(s.chars().count() as INT));
#[cfg(not(feature = "no_object"))]
lib.set_getter_fn("len", |s: &mut ImmutableString| Ok(s.chars().count() as INT));
lib.set_fn_2_mut(
"contains",
|s: &mut String, ch: char| Ok(s.contains(ch)),
|s: &mut ImmutableString, ch: char| Ok(s.contains(ch)),
);
lib.set_fn_2_mut(
"contains",
|s: &mut String, find: String| Ok(s.contains(&find)),
|s: &mut ImmutableString, find: ImmutableString| Ok(s.contains(find.as_str())),
);
lib.set_fn_3_mut(
"index_of",
|s: &mut String, ch: char, start: INT| {
|s: &mut ImmutableString, ch: char, start: INT| {
let start = if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
@ -132,7 +137,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
);
lib.set_fn_2_mut(
"index_of",
|s: &mut String, ch: char| {
|s: &mut ImmutableString, ch: char| {
Ok(s.find(ch)
.map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT))
@ -140,7 +145,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
);
lib.set_fn_3_mut(
"index_of",
|s: &mut String, find: String, start: INT| {
|s: &mut ImmutableString, find: ImmutableString, start: INT| {
let start = if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
@ -150,109 +155,106 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
};
Ok(s[start..]
.find(&find)
.find(find.as_str())
.map(|index| s[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT))
},
);
lib.set_fn_2_mut(
"index_of",
|s: &mut String, find: String| {
Ok(s.find(&find)
|s: &mut ImmutableString, find: ImmutableString| {
Ok(s.find(find.as_str())
.map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT))
},
);
lib.set_fn_1_mut("clear", |s: &mut String| {
s.clear();
lib.set_fn_1_mut("clear", |s: &mut ImmutableString| {
s.make_mut().clear();
Ok(())
});
lib.set_fn_2_mut( "append", |s: &mut String, ch: char| {
s.push(ch);
lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| {
s.make_mut().push(ch);
Ok(())
});
lib.set_fn_2_mut(
"append",
|s: &mut String, add: String| {
s.push_str(&add);
|s: &mut ImmutableString, add: ImmutableString| {
s.make_mut().push_str(add.as_str());
Ok(())
}
);
lib.set_fn_3_mut( "sub_string", sub_string);
lib.set_fn_2_mut(
lib.set_fn_3("sub_string", sub_string);
lib.set_fn_2(
"sub_string",
|s: &mut String, start: INT| sub_string(s, start, s.len() as INT),
|s: ImmutableString, start: INT| {
let len = s.len() as INT;
sub_string(s, start, len)
},
);
lib.set_fn_3_mut( "crop", crop_string);
lib.set_fn_3_mut("crop", crop_string);
lib.set_fn_2_mut(
"crop",
|s: &mut String, start: INT| crop_string(s, start, s.len() as INT),
|s: &mut ImmutableString, start: INT| crop_string(s, start, s.len() as INT),
);
lib.set_fn_2_mut(
"truncate",
|s: &mut String, len: INT| {
if len >= 0 {
let chars: StaticVec<_> = s.chars().take(len as usize).collect();
s.clear();
chars.iter().for_each(|&ch| s.push(ch));
|s: &mut ImmutableString, len: INT| {
if len > 0 {
let chars: StaticVec<_> = s.chars().collect();
let copy = s.make_mut();
copy.clear();
copy.extend(chars.into_iter().take(len as usize));
} else {
s.clear();
s.make_mut().clear();
}
Ok(())
},
);
lib.set_fn_3_mut(
"pad",
|s: &mut String, len: INT, ch: char| {
for _ in 0..s.chars().count() - len as usize {
s.push(ch);
|s: &mut ImmutableString, len: INT, ch: char| {
let copy = s.make_mut();
for _ in 0..copy.chars().count() - len as usize {
copy.push(ch);
}
Ok(())
},
);
lib.set_fn_3_mut(
"replace",
|s: &mut String, find: String, sub: String| {
let new_str = s.replace(&find, &sub);
s.clear();
s.push_str(&new_str);
|s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString| {
*s = s.replace(find.as_str(), sub.as_str()).into();
Ok(())
},
);
lib.set_fn_3_mut(
"replace",
|s: &mut String, find: String, sub: char| {
let new_str = s.replace(&find, &sub.to_string());
s.clear();
s.push_str(&new_str);
|s: &mut ImmutableString, find: ImmutableString, sub: char| {
*s = s.replace(find.as_str(), &sub.to_string()).into();
Ok(())
},
);
lib.set_fn_3_mut(
"replace",
|s: &mut String, find: char, sub: String| {
let new_str = s.replace(&find.to_string(), &sub);
s.clear();
s.push_str(&new_str);
|s: &mut ImmutableString, find: char, sub: ImmutableString| {
*s = s.replace(&find.to_string(), sub.as_str()).into();
Ok(())
},
);
lib.set_fn_3_mut(
"replace",
|s: &mut String, find: char, sub: char| {
let new_str = s.replace(&find.to_string(), &sub.to_string());
s.clear();
s.push_str(&new_str);
|s: &mut ImmutableString, find: char, sub: char| {
*s = s.replace(&find.to_string(), &sub.to_string()).into();
Ok(())
},
);
lib.set_fn_1_mut(
"trim",
|s: &mut String| {
|s: &mut ImmutableString| {
let trimmed = s.trim();
if trimmed.len() < s.len() {
*s = trimmed.to_string();
*s = trimmed.to_string().into();
}
Ok(())
},

View File

@ -10,6 +10,9 @@ use crate::token::Position;
#[cfg(not(feature = "no_std"))]
use crate::stdlib::time::Instant;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
#[cfg(not(feature = "no_std"))]
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
// Register date/time functions
@ -70,27 +73,29 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
lib.set_fn_2("==", eq::<Instant>);
lib.set_fn_2("!=", ne::<Instant>);
lib.set_fn_1(
"elapsed",
|timestamp: Instant| {
#[cfg(not(feature = "no_float"))]
return Ok(timestamp.elapsed().as_secs_f64());
#[cfg(not(feature = "no_float"))]
fn elapsed (timestamp: &mut Instant) -> Result<FLOAT, Box<EvalAltResult>> {
Ok(timestamp.elapsed().as_secs_f64())
}
#[cfg(feature = "no_float")]
{
let seconds = timestamp.elapsed().as_secs();
#[cfg(feature = "no_float")]
fn elapsed (timestamp: &mut Instant) -> Result<INT, Box<EvalAltResult>> {
let seconds = timestamp.elapsed().as_secs();
#[cfg(not(feature = "unchecked"))]
{
if seconds > (MAX_INT as u64) {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer overflow for timestamp.elapsed(): {}", seconds),
Position::none(),
)));
}
}
return Ok(seconds as INT);
#[cfg(not(feature = "unchecked"))]
{
if seconds > (MAX_INT as u64) {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer overflow for timestamp.elapsed: {}", seconds),
Position::none(),
)));
}
},
);
}
Ok(seconds as INT)
}
lib.set_fn_1_mut("elapsed", elapsed);
#[cfg(not(feature = "no_object"))]
lib.set_getter_fn("elapsed", elapsed);
});

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@ use crate::stdlib::path::PathBuf;
#[derive(Debug)]
pub enum EvalAltResult {
/// Syntax error.
ErrorParsing(Box<ParseError>),
ErrorParsing(ParseError),
/// Error reading from a script file. Wrapped value is the path of the script file.
///
@ -36,10 +36,6 @@ pub enum EvalAltResult {
/// An error has occurred inside a called function.
/// Wrapped values re the name of the function and the interior error.
ErrorInFunctionCall(String, Box<EvalAltResult>, Position),
/// Function call has incorrect number of arguments.
/// Wrapped values are the name of the function, the number of parameters required
/// and the actual number of arguments passed.
ErrorFunctionArgsMismatch(String, usize, usize, Position),
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
ErrorBooleanArgMismatch(String, Position),
/// Non-character value encountered where a character is required.
@ -108,9 +104,6 @@ impl EvalAltResult {
Self::ErrorParsing(p) => p.desc(),
Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
"Function call with wrong number of arguments"
}
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
Self::ErrorCharMismatch(_) => "Character expected",
Self::ErrorNumericIndexExpr(_) => {
@ -208,21 +201,6 @@ impl fmt::Display for EvalAltResult {
Self::ErrorLoopBreak(_, pos) => write!(f, "{} ({})", desc, pos),
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorFunctionArgsMismatch(fn_name, 0, n, pos) => write!(
f,
"Function '{}' expects no argument but {} found ({})",
fn_name, n, pos
),
Self::ErrorFunctionArgsMismatch(fn_name, 1, n, pos) => write!(
f,
"Function '{}' expects one argument but {} found ({})",
fn_name, n, pos
),
Self::ErrorFunctionArgsMismatch(fn_name, need, n, pos) => write!(
f,
"Function '{}' expects {} argument(s) but {} found ({})",
fn_name, need, n, pos
),
Self::ErrorBooleanArgMismatch(op, pos) => {
write!(f, "{} operator expects boolean operands ({})", op, pos)
}
@ -263,11 +241,6 @@ impl fmt::Display for EvalAltResult {
impl From<ParseError> for Box<EvalAltResult> {
fn from(err: ParseError) -> Self {
Box::new(EvalAltResult::ErrorParsing(Box::new(err)))
}
}
impl From<Box<ParseError>> for Box<EvalAltResult> {
fn from(err: Box<ParseError>) -> Self {
Box::new(EvalAltResult::ErrorParsing(err))
}
}
@ -292,7 +265,6 @@ impl EvalAltResult {
Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, pos)
@ -331,7 +303,6 @@ impl EvalAltResult {
Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, pos)

View File

@ -21,8 +21,8 @@ type LERR = LexError;
/// A location (line number + character position) in the input script.
///
/// In order to keep footprint small, both line number and character position have 16-bit resolution,
/// meaning they go up to a maximum of 65,535 lines/characters per line.
/// In order to keep footprint small, both line number and character position have 16-bit unsigned resolution,
/// meaning they go up to a maximum of 65,535 lines and characters per line.
/// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position {

View File

@ -42,6 +42,18 @@ pub fn unsafe_cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, B
}
}
/// # DANGEROUS!!!
///
/// A dangerous function that blindly casts a reference from one lifetime to another lifetime.
///
/// Force-casting a a reference to another lifetime saves on allocations and string cloning,
/// but must be used with the utmost care.
pub fn unsafe_mut_cast_to_lifetime<'a, T>(value: &mut T) -> &'a mut T {
unsafe { mem::transmute::<_, &'a mut T>(value) }
}
/// # DANGEROUS!!!
///
/// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow<str>` of
/// another lifetime. This is mainly used to let us push a block-local variable into the
/// current `Scope` without cloning the variable name. Doing this is safe because all local

View File

@ -4,14 +4,20 @@
//!
//! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types.
use crate::fn_native::{shared_make_mut, shared_take, Shared};
use crate::stdlib::{
any::TypeId,
borrow::Borrow,
boxed::Box,
fmt,
hash::{Hash, Hasher},
hash::{BuildHasher, Hash, Hasher},
iter::FromIterator,
mem,
mem::MaybeUninit,
ops::{Drop, Index, IndexMut},
ops::{Add, AddAssign, Deref, Drop, Index, IndexMut},
str::FromStr,
string::{String, ToString},
vec::Vec,
};
@ -21,9 +27,46 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")]
use ahash::AHasher;
#[inline(always)]
pub fn EMPTY_TYPE_ID() -> TypeId {
TypeId::of::<()>()
/// A hasher that only takes one single `u64` and returns it as a hash key.
///
/// # Panics
///
/// Panics when hashing any data type other than a `u64`.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct StraightHasher(u64);
impl Hasher for StraightHasher {
#[inline(always)]
fn finish(&self) -> u64 {
self.0
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
let mut key = [0_u8; 8];
key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes
self.0 = u64::from_le_bytes(key);
}
}
impl StraightHasher {
/// Create a `StraightHasher`.
#[inline(always)]
pub fn new() -> Self {
Self(0)
}
}
/// A hash builder for `StraightHasher`.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct StraightHasherBuilder;
impl BuildHasher for StraightHasherBuilder {
type Hasher = StraightHasher;
#[inline(always)]
fn build_hasher(&self) -> Self::Hasher {
StraightHasher::new()
}
}
/// Calculate a `u64` hash key from a module-qualified function name and parameter types.
@ -37,6 +80,7 @@ pub fn EMPTY_TYPE_ID() -> TypeId {
pub fn calc_fn_spec<'a>(
modules: impl Iterator<Item = &'a str>,
fn_name: &str,
num: usize,
params: impl Iterator<Item = TypeId>,
) -> u64 {
#[cfg(feature = "no_std")]
@ -47,6 +91,7 @@ pub fn calc_fn_spec<'a>(
// We always skip the first module
modules.skip(1).for_each(|m| m.hash(&mut s));
s.write(fn_name.as_bytes());
s.write_usize(num);
params.for_each(|t| t.hash(&mut s));
s.finish()
}
@ -105,6 +150,7 @@ pub struct StaticVec<T> {
const MAX_STATIC_VEC: usize = 4;
impl<T> Drop for StaticVec<T> {
#[inline(always)]
fn drop(&mut self) {
self.clear();
}
@ -171,6 +217,7 @@ impl<T> FromIterator<T> for StaticVec<T> {
impl<T> StaticVec<T> {
/// Create a new `StaticVec`.
#[inline(always)]
pub fn new() -> Self {
Default::default()
}
@ -186,6 +233,7 @@ impl<T> StaticVec<T> {
self.len = 0;
}
/// Extract a `MaybeUninit` into a concrete initialized type.
#[inline(always)]
fn extract(value: MaybeUninit<T>) -> T {
unsafe { value.assume_init() }
}
@ -247,6 +295,7 @@ impl<T> StaticVec<T> {
);
}
/// Is data stored in fixed-size storage?
#[inline(always)]
fn is_fixed_storage(&self) -> bool {
self.len <= MAX_STATIC_VEC
}
@ -356,10 +405,12 @@ impl<T> StaticVec<T> {
result
}
/// Get the number of items in this `StaticVec`.
#[inline(always)]
pub fn len(&self) -> usize {
self.len
}
/// Is this `StaticVec` empty?
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len == 0
}
@ -563,3 +614,289 @@ impl<T> From<Vec<T>> for StaticVec<T> {
arr
}
}
/// The system immutable string type.
///
/// An `ImmutableString` wraps an `Rc<String>` (or `Arc<String>` under the `sync` feature)
/// so that it can be simply shared and not cloned.
///
/// # Examples
///
/// ```
/// use rhai::ImmutableString;
///
/// let s1: ImmutableString = "hello".into();
///
/// // No actual cloning of the string is involved below.
/// let s2 = s1.clone();
/// let s3 = s2.clone();
///
/// assert_eq!(s1, s2);
///
/// // Clones the underlying string (because it is already shared) and extracts it.
/// let mut s: String = s1.into_owned();
///
/// // Changing the clone has no impact on the previously shared version.
/// s.push_str(", world!");
///
/// // The old version still exists.
/// assert_eq!(s2, s3);
/// assert_eq!(s2.as_str(), "hello");
///
/// // Not equals!
/// assert_ne!(s2.as_str(), s.as_str());
/// assert_eq!(s, "hello, world!");
/// ```
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct ImmutableString(Shared<String>);
impl Deref for ImmutableString {
type Target = String;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<String> for ImmutableString {
#[inline(always)]
fn as_ref(&self) -> &String {
&self.0
}
}
impl Borrow<str> for ImmutableString {
#[inline(always)]
fn borrow(&self) -> &str {
self.0.as_str()
}
}
impl From<&str> for ImmutableString {
#[inline(always)]
fn from(value: &str) -> Self {
Self(value.to_string().into())
}
}
impl From<String> for ImmutableString {
#[inline(always)]
fn from(value: String) -> Self {
Self(value.into())
}
}
impl From<Box<String>> for ImmutableString {
#[inline(always)]
fn from(value: Box<String>) -> Self {
Self(value.into())
}
}
impl From<ImmutableString> for String {
#[inline(always)]
fn from(value: ImmutableString) -> Self {
value.into_owned()
}
}
impl FromStr for ImmutableString {
type Err = ();
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string().into()))
}
}
impl FromIterator<char> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a char> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
Self(iter.into_iter().cloned().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a str> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<String> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl fmt::Display for ImmutableString {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.0.as_str(), f)
}
}
impl fmt::Debug for ImmutableString {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.0.as_str(), f)
}
}
impl Add for ImmutableString {
type Output = Self;
fn add(mut self, rhs: Self) -> Self::Output {
if rhs.is_empty() {
self
} else if self.is_empty() {
rhs
} else {
self.make_mut().push_str(rhs.0.as_str());
self
}
}
}
impl Add for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: Self) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else if self.is_empty() {
rhs.clone()
} else {
let mut s = self.clone();
s.make_mut().push_str(rhs.0.as_str());
s
}
}
}
impl AddAssign<&ImmutableString> for ImmutableString {
fn add_assign(&mut self, rhs: &ImmutableString) {
if !rhs.is_empty() {
if self.is_empty() {
self.0 = rhs.0.clone();
} else {
self.make_mut().push_str(rhs.0.as_str());
}
}
}
}
impl Add<&str> for ImmutableString {
type Output = Self;
fn add(mut self, rhs: &str) -> Self::Output {
if rhs.is_empty() {
self
} else {
self.make_mut().push_str(rhs);
self
}
}
}
impl Add<&str> for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: &str) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else {
let mut s = self.clone();
s.make_mut().push_str(rhs);
s
}
}
}
impl AddAssign<&str> for ImmutableString {
fn add_assign(&mut self, rhs: &str) {
if !rhs.is_empty() {
self.make_mut().push_str(rhs);
}
}
}
impl Add<String> for ImmutableString {
type Output = Self;
fn add(mut self, rhs: String) -> Self::Output {
if rhs.is_empty() {
self
} else if self.is_empty() {
rhs.into()
} else {
self.make_mut().push_str(&rhs);
self
}
}
}
impl Add<String> for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: String) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else if self.is_empty() {
rhs.into()
} else {
let mut s = self.clone();
s.make_mut().push_str(&rhs);
s
}
}
}
impl Add<char> for ImmutableString {
type Output = Self;
fn add(mut self, rhs: char) -> Self::Output {
self.make_mut().push(rhs);
self
}
}
impl Add<char> for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: char) -> Self::Output {
let mut s = self.clone();
s.make_mut().push(rhs);
s
}
}
impl AddAssign<char> for ImmutableString {
#[inline(always)]
fn add_assign(&mut self, rhs: char) {
self.make_mut().push(rhs);
}
}
impl ImmutableString {
/// Consume the `ImmutableString` and convert it into a `String`.
/// If there are other references to the same string, a cloned copy is returned.
pub fn into_owned(mut self) -> String {
self.make_mut(); // Make sure it is unique reference
shared_take(self.0) // Should succeed
}
/// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references).
/// Then return a mutable reference to the `String`.
#[inline(always)]
pub fn make_mut(&mut self) -> &mut String {
shared_make_mut(&mut self.0)
}
}

View File

@ -26,7 +26,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
let y = [4, 5];
x.append(y);
x.len() + r
x.len + r
"
)?,
14

View File

@ -22,7 +22,7 @@ fn test_float() -> Result<(), Box<EvalAltResult>> {
#[test]
#[cfg(not(feature = "no_object"))]
fn struct_with_float() -> Result<(), Box<EvalAltResult>> {
fn test_struct_with_float() -> Result<(), Box<EvalAltResult>> {
#[derive(Clone)]
struct TestStruct {
x: f64,
@ -64,3 +64,16 @@ fn struct_with_float() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_float_func() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.register_fn("sum", |x: FLOAT, y: FLOAT, z: FLOAT, w: FLOAT| {
x + y + z + w
});
assert_eq!(engine.eval::<FLOAT>("sum(1.0, 2.0, 3.0, 4.0)")?, 10.0);
Ok(())
}

View File

@ -48,7 +48,7 @@ fn test_for_object() -> Result<(), Box<EvalAltResult>> {
sum += value;
}
keys.len() + sum
keys.len + sum
"#;
assert_eq!(engine.eval::<INT>(script)?, 9);

View File

@ -7,12 +7,23 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("fn add(x, n) { x + n } add(40, 2)")?, 42);
assert_eq!(
engine.eval::<INT>("fn add(x, n) { x + n } let a = 40; add(a, 2); a")?,
40
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn add(x, n) { x + n } let x = 40; x.add(2)")?,
42
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn add(x, n) { x += n; } let x = 40; x.add(2); x")?,
40
);
assert_eq!(engine.eval::<INT>("fn mul2(x) { x * 2 } mul2(21)")?, 42);
#[cfg(not(feature = "no_object"))]
@ -21,5 +32,11 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
42
);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn mul2(x) { x *= 2; } let x = 21; x.mul2(); x")?,
21
);
Ok(())
}

View File

@ -1,6 +1,6 @@
#![cfg(not(feature = "no_object"))]
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, INT};
#[test]
fn test_get_set() -> Result<(), Box<EvalAltResult>> {
@ -43,7 +43,9 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("new_ts", TestStruct::new);
#[cfg(not(feature = "no_index"))]
engine.register_indexer(|value: &mut TestStruct, index: String| value.array[index.len()]);
engine.register_indexer(|value: &mut TestStruct, index: ImmutableString| {
value.array[index.len()]
});
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500);
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42);

View File

@ -21,6 +21,10 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
)?,
'o'
);
assert_eq!(
engine.eval::<String>(r#"let a = [#{s:"hello"}]; a[0].s[2] = 'X'; a[0].s"#)?,
"heXlo"
);
}
assert_eq!(

View File

@ -33,7 +33,7 @@ fn test_method_call() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<TestStruct>("let x = new_ts(); update(x, 1000); x")?,
TestStruct { x: 1 }
TestStruct { x: 1001 }
);
Ok(())

View File

@ -63,6 +63,9 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
let mut module = Module::new();
module.set_var("answer", 42 as INT);
module.set_fn_4("sum".to_string(), |x: INT, y: INT, z: INT, w: INT| {
Ok(x + y + z + w)
});
resolver.insert("hello".to_string(), module);
@ -74,69 +77,72 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
r#"
import "hello" as h1;
import "hello" as h2;
h2::answer
h1::sum(h2::answer, -10, 3, 7)
"#
)?,
42
);
engine.set_max_modules(5);
#[cfg(not(feature = "unchecked"))]
{
engine.set_max_modules(5);
assert!(matches!(
*engine
.eval::<INT>(
r#"
let x = 0;
assert!(matches!(
*engine
.eval::<INT>(
r#"
let sum = 0;
for x in range(0, 10) {
import "hello" as h;
x += h::answer;
}
for x in range(0, 10) {
import "hello" as h;
sum += h::answer;
}
x
sum
"#
)
.expect_err("should error"),
EvalAltResult::ErrorTooManyModules(_)
));
)
.expect_err("should error"),
EvalAltResult::ErrorTooManyModules(_)
));
#[cfg(not(feature = "no_function"))]
assert!(matches!(
*engine
.eval::<INT>(
r#"
let x = 0;
#[cfg(not(feature = "no_function"))]
assert!(matches!(
*engine
.eval::<INT>(
r#"
let sum = 0;
fn foo() {
import "hello" as h;
x += h::answer;
}
fn foo() {
import "hello" as h;
sum += h::answer;
}
for x in range(0, 10) {
foo();
}
for x in range(0, 10) {
foo();
}
x
sum
"#
)
.expect_err("should error"),
EvalAltResult::ErrorInFunctionCall(fn_name, _, _) if fn_name == "foo"
));
)
.expect_err("should error"),
EvalAltResult::ErrorInFunctionCall(fn_name, _, _) if fn_name == "foo"
));
engine.set_max_modules(0);
engine.set_max_modules(0);
#[cfg(not(feature = "no_function"))]
engine.eval::<()>(
r#"
fn foo() {
import "hello" as h;
}
#[cfg(not(feature = "no_function"))]
engine.eval::<()>(
r#"
fn foo() {
import "hello" as h;
}
for x in range(0, 10) {
foo();
}
for x in range(0, 10) {
foo();
}
"#,
)?;
)?;
}
Ok(())
}

File diff suppressed because one or more lines are too long

View File

@ -20,21 +20,20 @@ fn test_string() -> Result<(), Box<EvalAltResult>> {
assert!(engine.eval::<bool>(r#"let y = "hello, world!"; 'w' in y"#)?);
assert!(!engine.eval::<bool>(r#"let y = "hello, world!"; "hey" in y"#)?);
#[cfg(not(feature = "no_stdlib"))]
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
#[cfg(not(feature = "no_stdlib"))]
#[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42");
#[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<char>(r#"let y = "hello"; y[y.len-1]"#)?, 'o');
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "no_stdlib"))]
assert_eq!(engine.eval::<String>(r#""foo" + 123.4556"#)?, "foo123.4556");
Ok(())
}
#[cfg(not(feature = "no_stdlib"))]
#[cfg(not(feature = "no_object"))]
#[test]
fn test_string_substring() -> Result<(), Box<EvalAltResult>> {

View File

@ -1,4 +1,3 @@
#![cfg(not(feature = "no_stdlib"))]
#![cfg(not(feature = "no_std"))]
use rhai::{Engine, EvalAltResult, INT};