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] [package]
name = "rhai" name = "rhai"
version = "0.14.1" version = "0.15.0"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
@ -20,26 +20,22 @@ categories = [ "no-std", "embedded", "parser-implementations" ]
num-traits = { version = "0.2.11", default-features = false } num-traits = { version = "0.2.11", default-features = false }
[features] [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 = [] default = []
unchecked = [] # unchecked arithmetic unchecked = [] # unchecked arithmetic
no_index = [] # no arrays and indexing sync = [] # restrict to only types that implement Send + Sync
no_float = [] # no floating-point
no_function = [] # no script-defined functions
no_object = [] # no custom objects
no_optimize = [] # no script optimizer 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_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types 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 # compiling for no-std
no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] 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] [profile.release]
lto = "fat" lto = "fat"
codegen-units = 1 codegen-units = 1

757
README.md

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,70 @@
Rhai Release Notes 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 Version 0.14.1
============== ==============

View File

@ -29,7 +29,7 @@ fn bench_engine_new_raw_core(bench: &mut Bencher) {
#[bench] #[bench]
fn bench_engine_register_fn(bench: &mut Bencher) { 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 true
} }

View File

@ -61,3 +61,27 @@ fn bench_eval_array_large_set(bench: &mut Bencher) {
bench.iter(|| engine.consume_ast(&ast).unwrap()); 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.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; let x = 1_000;
while x > 0 { while x > 0 {
x = x - 1; x -= 1;
} }
"#; "#;

View File

@ -32,7 +32,7 @@ fn bench_parse_full(bench: &mut Bencher) {
2 > 1 && 2 > 1 &&
"something" != "nothing" || "something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && "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) modifierTest + 1000 / 2 > (80 * 100 % 2)
"#; "#;
@ -94,7 +94,7 @@ fn bench_parse_primes(bench: &mut Bencher) {
} }
print("Total " + total_primes_found + " primes."); print("Total " + total_primes_found + " primes.");
print("Run time = " + now.elapsed() + " seconds."); print("Run time = " + now.elapsed + " seconds.");
"#; "#;
let mut engine = Engine::new(); let mut engine = Engine::new();
@ -102,3 +102,35 @@ fn bench_parse_primes(bench: &mut Bencher) {
bench.iter(|| engine.compile(script).unwrap()); 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); let result = fib(target);
print("Finished. Run time = " + now.elapsed() + " seconds."); print("Finished. Run time = " + now.elapsed + " seconds.");
print("Fibonacci number #" + target + " = " + result); 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 { loop {
print(x); print(x);
x = x - 1; x -= 1;
if x <= 0 { break; } if x <= 0 { break; }
} }

View File

@ -24,9 +24,9 @@ fn mat_gen(n) {
} }
fn mat_mul(a, b) { fn mat_mul(a, b) {
let m = a.len(); let m = a.len;
let n = a[0].len(); let n = a[0].len;
let p = b[0].len(); let p = b[0].len;
let b2 = new_mat(n, p); let b2 = new_mat(n, p);
@ -38,13 +38,13 @@ fn mat_mul(a, b) {
let c = new_mat(m, p); let c = new_mat(m, p);
for i in range(0, c.len()) { for i in range(0, c.len) {
let ci = c[i]; let ci = c[i];
for j in range(0, ci.len()) { for j in range(0, ci.len) {
let b2j = b2[j]; let b2j = b2[j];
ci[j] = 0.0; 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 x = a[i][z];
let y = b2j[z]; let y = b2j[z];
ci[j] += x * y; ci[j] += x * y;
@ -62,8 +62,10 @@ let a = mat_gen(SIZE);
let b = mat_gen(SIZE); let b = mat_gen(SIZE);
let c = mat_mul(a, b); let c = mat_mul(a, b);
/*
for i in range(0, SIZE) { for i in range(0, SIZE) {
print(c[i]); 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(); 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 = []; let prime_mask = [];
prime_mask.pad(MAX_NUMBER_TO_CHECK, true); 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) { for p in range(2, MAX_NUMBER_TO_CHECK) {
if !prime_mask[p] { continue; } if !prime_mask[p] { continue; }
print(p); //print(p);
total_primes_found += 1; 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("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 { if total_primes_found != 78_498 {
print("The answer is WRONG! Should be 9,592!"); print("The answer is WRONG! Should be 78,498!");
} }

View File

@ -7,7 +7,7 @@ let x = 1_000_000;
print("Ready... Go!"); print("Ready... Go!");
while x > 0 { 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 print("the answer is " + 42); // string building using non-string types
let s = "hello, world!"; // string variable 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 s[s.len-1] = '?'; // change the string
print(s); // should print 'hello, world?' 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 { while x > 0 {
print(x); 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. //! 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}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -151,7 +151,7 @@ pub struct Dynamic(pub(crate) Union);
pub enum Union { pub enum Union {
Unit(()), Unit(()),
Bool(bool), Bool(bool),
Str(Box<String>), Str(ImmutableString),
Char(char), Char(char),
Int(INT), Int(INT),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -178,6 +178,10 @@ impl Dynamic {
/// Is the value held by this `Dynamic` a particular type? /// Is the value held by this `Dynamic` a particular type?
pub fn is<T: Variant + Clone>(&self) -> bool { pub fn is<T: Variant + Clone>(&self) -> bool {
self.type_id() == TypeId::of::<T>() 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`. /// Get the TypeId of the value held by this `Dynamic`.
@ -185,7 +189,7 @@ impl Dynamic {
match &self.0 { match &self.0 {
Union::Unit(_) => TypeId::of::<()>(), Union::Unit(_) => TypeId::of::<()>(),
Union::Bool(_) => TypeId::of::<bool>(), Union::Bool(_) => TypeId::of::<bool>(),
Union::Str(_) => TypeId::of::<String>(), Union::Str(_) => TypeId::of::<ImmutableString>(),
Union::Char(_) => TypeId::of::<char>(), Union::Char(_) => TypeId::of::<char>(),
Union::Int(_) => TypeId::of::<INT>(), Union::Int(_) => TypeId::of::<INT>(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -342,6 +346,12 @@ impl Dynamic {
return Self(result); return Self(result);
} else if let Some(result) = dyn_value.downcast_ref::<char>().cloned().map(Union::Char) { } else if let Some(result) = dyn_value.downcast_ref::<char>().cloned().map(Union::Char) {
return Self(result); 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"))] #[cfg(not(feature = "no_float"))]
@ -358,7 +368,7 @@ impl Dynamic {
Err(var) => var, Err(var) => var,
}; };
var = match unsafe_cast_box::<_, String>(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, Err(var) => var,
}; };
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -395,14 +405,19 @@ impl Dynamic {
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42); /// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
/// ``` /// ```
pub fn try_cast<T: Variant>(self) -> Option<T> { 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); return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v);
} }
match self.0 { match self.0 {
Union::Unit(value) => unsafe_try_cast(value), Union::Unit(value) => unsafe_try_cast(value),
Union::Bool(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::Char(value) => unsafe_try_cast(value),
Union::Int(value) => unsafe_try_cast(value), Union::Int(value) => unsafe_try_cast(value),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -434,16 +449,19 @@ impl Dynamic {
/// assert_eq!(x.cast::<u32>(), 42); /// assert_eq!(x.cast::<u32>(), 42);
/// ``` /// ```
pub fn cast<T: Variant + Clone>(self) -> T { 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(); return *unsafe_cast_box::<_, T>(Box::new(self)).unwrap();
} }
match self.0 { match self.0 {
Union::Unit(value) => unsafe_try_cast(value).unwrap(), Union::Unit(value) => unsafe_try_cast(value).unwrap(),
Union::Bool(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::Char(value) => unsafe_try_cast(value).unwrap(),
Union::Int(value) => unsafe_try_cast(value).unwrap(), Union::Int(value) => unsafe_try_cast(value).unwrap(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -469,7 +487,9 @@ impl Dynamic {
match &self.0 { match &self.0 {
Union::Unit(value) => (value as &dyn Any).downcast_ref::<T>(), Union::Unit(value) => (value as &dyn Any).downcast_ref::<T>(),
Union::Bool(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::Char(value) => (value as &dyn Any).downcast_ref::<T>(),
Union::Int(value) => (value as &dyn Any).downcast_ref::<T>(), Union::Int(value) => (value as &dyn Any).downcast_ref::<T>(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -495,7 +515,7 @@ impl Dynamic {
match &mut self.0 { match &mut self.0 {
Union::Unit(value) => (value as &mut dyn Any).downcast_mut::<T>(), Union::Unit(value) => (value as &mut dyn Any).downcast_mut::<T>(),
Union::Bool(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::Char(value) => (value as &mut dyn Any).downcast_mut::<T>(),
Union::Int(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"))] #[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. /// Cast the `Dynamic` as a `bool` and return it.
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
pub fn as_bool(&self) -> Result<bool, &'static str> { 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. /// Returns the name of the actual type if the cast fails.
pub fn take_string(self) -> Result<String, &'static str> { pub fn take_string(self) -> Result<String, &'static str> {
match self.0 { match self.0 {
Union::Str(s) => Ok(*s), Union::Str(s) => Ok(s.into_owned()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -584,7 +614,12 @@ impl From<char> for Dynamic {
} }
impl From<String> for Dynamic { impl From<String> for Dynamic {
fn from(value: String) -> Self { 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"))] #[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"))] #[cfg(not(feature = "no_object"))]
impl<T: Variant + Clone> From<HashMap<String, T>> for Dynamic { impl<T: Variant + Clone> From<HashMap<String, T>> for Dynamic {
fn from(value: HashMap<String, T>) -> Self { 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::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_call::FuncArgs; use crate::fn_call::FuncArgs;
use crate::fn_native::{ use crate::fn_native::{IteratorFn, SendSync};
IteratorCallback, ObjectGetCallback, ObjectIndexerCallback, ObjectSetCallback,
};
use crate::fn_register::RegisterFn; use crate::fn_register::RegisterFn;
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::parser::{parse, parse_global_expr, AST}; use crate::parser::{parse, parse_global_expr, AST};
@ -123,8 +121,8 @@ impl Engine {
/// Register an iterator adapter for a type with the `Engine`. /// Register an iterator adapter for a type with the `Engine`.
/// This is an advanced feature. /// This is an advanced feature.
pub fn register_iterator<T: Variant + Clone, F: IteratorCallback>(&mut self, f: F) { pub fn register_iterator<T: Variant + Clone>(&mut self, f: IteratorFn) {
self.global_module.set_iter(TypeId::of::<T>(), Box::new(f)); self.global_module.set_iter(TypeId::of::<T>(), f);
} }
/// Register a getter function for a member of a registered type with the `Engine`. /// 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"))] #[cfg(not(feature = "no_object"))]
pub fn register_get<T, U, F>(&mut self, name: &str, callback: F) pub fn register_get<T, U>(
where &mut self,
name: &str,
callback: impl Fn(&mut T) -> U + SendSync + 'static,
) where
T: Variant + Clone, T: Variant + Clone,
U: Variant + Clone, U: Variant + Clone,
F: ObjectGetCallback<T, U>,
{ {
self.register_fn(&make_getter(name), callback); self.register_fn(&make_getter(name), callback);
} }
@ -210,11 +210,13 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_set<T, U, F>(&mut self, name: &str, callback: F) pub fn register_set<T, U>(
where &mut self,
name: &str,
callback: impl Fn(&mut T, U) + SendSync + 'static,
) where
T: Variant + Clone, T: Variant + Clone,
U: Variant + Clone, U: Variant + Clone,
F: ObjectSetCallback<T, U>,
{ {
self.register_fn(&make_setter(name), callback); self.register_fn(&make_setter(name), callback);
} }
@ -258,12 +260,14 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_get_set<T, U, G, S>(&mut self, name: &str, get_fn: G, set_fn: S) pub fn register_get_set<T, U>(
where &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, T: Variant + Clone,
U: Variant + Clone, U: Variant + Clone,
G: ObjectGetCallback<T, U>,
S: ObjectSetCallback<T, U>,
{ {
self.register_get(name, get_fn); self.register_get(name, get_fn);
self.register_set(name, set_fn); self.register_set(name, set_fn);
@ -307,12 +311,13 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn register_indexer<T, X, U, F>(&mut self, callback: F) pub fn register_indexer<T, X, U>(
where &mut self,
callback: impl Fn(&mut T, X) -> U + SendSync + 'static,
) where
T: Variant + Clone, T: Variant + Clone,
U: Variant + Clone, U: Variant + Clone,
X: Variant + Clone, X: Variant + Clone,
F: ObjectIndexerCallback<T, X, U>,
{ {
self.register_fn(FUNC_INDEXER, callback); self.register_fn(FUNC_INDEXER, callback);
} }
@ -336,7 +341,7 @@ impl Engine {
/// # Ok(()) /// # 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) self.compile_with_scope(&Scope::new(), script)
} }
@ -378,7 +383,7 @@ impl Engine {
/// # Ok(()) /// # 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]) self.compile_scripts_with_scope(scope, &[script])
} }
@ -432,7 +437,7 @@ impl Engine {
&self, &self,
scope: &Scope, scope: &Scope,
scripts: &[&str], scripts: &[&str],
) -> Result<AST, Box<ParseError>> { ) -> Result<AST, ParseError> {
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level) self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
} }
@ -442,9 +447,16 @@ impl Engine {
scope: &Scope, scope: &Scope,
scripts: &[&str], scripts: &[&str],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, Box<ParseError>> { ) -> Result<AST, ParseError> {
let stream = lex(scripts); 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. /// Read the contents of a file into a string.
@ -571,6 +583,7 @@ impl Engine {
self, self,
&scope, &scope,
OptimizationLevel::None, OptimizationLevel::None,
self.max_expr_depth,
)?; )?;
// Handle null - map to () // Handle null - map to ()
@ -601,7 +614,7 @@ impl Engine {
/// # Ok(()) /// # 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) self.compile_expression_with_scope(&Scope::new(), script)
} }
@ -648,13 +661,19 @@ impl Engine {
&self, &self,
scope: &Scope, scope: &Scope,
script: &str, script: &str,
) -> Result<AST, Box<ParseError>> { ) -> Result<AST, ParseError> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts); let stream = lex(&scripts);
{ {
let mut peekable = stream.peekable(); 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>> { ) -> Result<T, Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts); 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) self.eval_ast_with_scope(scope, &ast)
} }
@ -883,12 +909,12 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<(Dynamic, u64), Box<EvalAltResult>> { ) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
let mut state = State::new(ast.fn_lib()); let mut state = State::new();
ast.statements() ast.statements()
.iter() .iter()
.try_fold(().into(), |_, stmt| { .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 { .or_else(|err| match *err {
EvalAltResult::Return(out, _) => Ok(out), EvalAltResult::Return(out, _) => Ok(out),
@ -931,8 +957,13 @@ impl Engine {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts); let stream = lex(&scripts);
// Since the AST will be thrown away afterwards, don't bother to optimize it let ast = parse(
let ast = parse(&mut stream.peekable(), self, scope, OptimizationLevel::None)?; &mut stream.peekable(),
self,
scope,
self.optimization_level,
(self.max_expr_depth, self.max_function_expr_depth),
)?;
self.consume_ast_with_scope(scope, &ast) self.consume_ast_with_scope(scope, &ast)
} }
@ -949,12 +980,12 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let mut state = State::new(ast.fn_lib()); let mut state = State::new();
ast.statements() ast.statements()
.iter() .iter()
.try_fold(().into(), |_, stmt| { .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( .map_or_else(
|err| match *err { |err| match *err {
@ -966,6 +997,7 @@ impl Engine {
} }
/// Call a script function defined in an `AST` with multiple arguments. /// Call a script function defined in an `AST` with multiple arguments.
/// Arguments are passed as a tuple.
/// ///
/// # Example /// # Example
/// ///
@ -1009,29 +1041,81 @@ impl Engine {
args: A, args: A,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec(); let mut arg_values = args.into_vec();
let mut args: StaticVec<_> = arg_values.iter_mut().collect(); let result = self.call_fn_dynamic(scope, ast, name, arg_values.as_mut())?;
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 return_type = self.map_type_name(result.type_name()); let return_type = self.map_type_name(result.type_name());
return result.try_cast().ok_or_else(|| { return result.try_cast().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType( Box::new(EvalAltResult::ErrorMismatchOutputType(
return_type.into(), 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. /// Optimize the `AST` with constants defined in an external Scope.
/// An optimized copy of the `AST` is returned while the original `AST` is consumed. /// An optimized copy of the `AST` is returned while the original `AST` is consumed.
/// ///
@ -1050,14 +1134,14 @@ impl Engine {
mut ast: AST, mut ast: AST,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> AST { ) -> AST {
let fn_lib = ast let lib = ast
.fn_lib() .lib()
.iter() .iter()
.map(|(_, fn_def)| fn_def.as_ref().clone()) .map(|(_, fn_def)| fn_def.as_ref().clone())
.collect(); .collect();
let stmt = mem::take(ast.statements_mut()); 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. /// Register a callback for script evaluation progress.
@ -1094,47 +1178,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "sync")] pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + SendSync + 'static) {
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) {
self.progress = Some(Box::new(callback)); self.progress = Some(Box::new(callback));
} }
@ -1162,36 +1206,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "sync")] pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) {
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) {
self.print = Box::new(callback); self.print = Box::new(callback);
} }
@ -1219,36 +1234,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "sync")] pub fn on_debug(&mut self, callback: impl Fn(&str) + SendSync + 'static) {
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) {
self.debug = Box::new(callback); 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, AssignmentToCopy,
/// Assignment to an a constant variable. /// Assignment to an a constant variable.
AssignmentToConstant(String), AssignmentToConstant(String),
/// Expression exceeding the maximum levels of complexity.
///
/// Never appears under the `unchecked` feature.
ExprTooDeep,
/// Break statement not inside a loop. /// Break statement not inside a loop.
LoopBreak, LoopBreak,
} }
impl ParseErrorType { impl ParseErrorType {
/// Make a `ParseError` using the current type and position. /// Make a `ParseError` using the current type and position.
pub(crate) fn into_err(self, pos: Position) -> Box<ParseError> { pub(crate) fn into_err(self, pos: Position) -> ParseError {
Box::new(ParseError(self, pos)) ParseError(Box::new(self), pos)
} }
} }
/// Error when parsing a script. /// Error when parsing a script.
#[derive(Debug, Eq, PartialEq, Clone, Hash)] #[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 { impl ParseError {
/// Get the parse error. /// Get the parse error.
@ -137,7 +141,7 @@ impl ParseError {
} }
pub(crate) fn desc(&self) -> &str { pub(crate) fn desc(&self) -> &str {
match &self.0 { match self.0.as_ref() {
ParseErrorType::BadInput(p) => p, ParseErrorType::BadInput(p) => p,
ParseErrorType::UnexpectedEOF => "Script is incomplete", ParseErrorType::UnexpectedEOF => "Script is incomplete",
ParseErrorType::UnknownOperator(_) => "Unknown operator", ParseErrorType::UnknownOperator(_) => "Unknown operator",
@ -158,7 +162,8 @@ impl ParseError {
ParseErrorType::DuplicatedExport(_) => "Duplicated variable/function in export statement", ParseErrorType::DuplicatedExport(_) => "Duplicated variable/function in export statement",
ParseErrorType::WrongExport => "Export statement can only appear at global level", ParseErrorType::WrongExport => "Export statement can only appear at global level",
ParseErrorType::AssignmentToCopy => "Only a copy of the value is change with this assignment", 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" ParseErrorType::LoopBreak => "Break statement should only be used inside a loop"
} }
} }
@ -168,7 +173,7 @@ impl Error for ParseError {}
impl fmt::Display for ParseError { impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 { match self.0.as_ref() {
ParseErrorType::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => { ParseErrorType::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })? write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
} }

View File

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

View File

@ -1,139 +1,144 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::parser::FnDef;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::stdlib::{boxed::Box, rc::Rc, sync::Arc}; 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]; 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"))] #[cfg(not(feature = "sync"))]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>; 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 = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
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>>;
#[cfg(feature = "sync")] /// A type encapsulating a function callable by Rhai.
pub type PrintCallback = dyn Fn(&str) + Send + Sync + 'static; #[derive(Clone)]
#[cfg(not(feature = "sync"))] pub enum CallableFunction {
pub type PrintCallback = dyn Fn(&str) + 'static; /// A pure native Rust function with all arguments passed by value.
Pure(Shared<FnAny>),
#[cfg(feature = "sync")] /// A native Rust object method with the first argument passed by reference,
pub type ProgressCallback = dyn Fn(u64) -> bool + Send + Sync + 'static; /// and the rest passed by value.
#[cfg(not(feature = "sync"))] Method(Shared<FnAny>),
pub type ProgressCallback = dyn Fn(u64) -> bool + 'static; /// An iterator function.
Iterator(IteratorFn),
// Define callback function types /// A script-defined function.
#[cfg(feature = "sync")] Script(Shared<FnDef>),
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
{
} }
#[cfg(not(feature = "sync"))] impl CallableFunction {
pub trait IteratorCallback: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static {} /// Is this a pure native Rust function?
#[cfg(not(feature = "sync"))] pub fn is_pure(&self) -> bool {
impl<F: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static> IteratorCallback for F {} match self {
Self::Pure(_) => true,
/// A type representing the type of ABI of a native Rust function. Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false,
#[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
} }
fn call(&self, args: &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> { /// Is this a pure native Rust method-call?
(self.0)(args) 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::any::{Dynamic, Variant};
use crate::engine::Engine; use crate::engine::Engine;
use crate::fn_native::{FnCallArgs, NativeFunctionABI::*}; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs};
use crate::parser::FnAccess; use crate::parser::FnAccess;
use crate::result::EvalAltResult; 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`. /// Trait to register custom functions with the `Engine`.
pub trait RegisterFn<FN, ARGS, RET> { 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); fn register_fn(&mut self, name: &str, f: FN);
} }
/// Trait to register custom functions that return `Dynamic` values with the `Engine`. /// Trait to register fallible custom functions returning `Result<Dynamic, Box<EvalAltResult>>` with the `Engine`.
pub trait RegisterDynamicFn<FN, ARGS> { pub trait RegisterResultFn<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> {
/// Register a custom fallible function with the `Engine`. /// Register a custom fallible function with the `Engine`.
/// ///
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::{Engine, RegisterResultFn, EvalAltResult}; /// use rhai::{Engine, Dynamic, RegisterResultFn, EvalAltResult};
/// ///
/// // Normal function /// // 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 { /// if y == 0 {
/// // '.into()' automatically converts to 'Box<EvalAltResult::ErrorRuntime>' /// // '.into()' automatically converts to 'Box<EvalAltResult::ErrorRuntime>'
/// Err("division by zero!".into()) /// Err("division by zero!".into())
/// } else { /// } else {
/// Ok(x / y) /// Ok((x / y).into())
/// } /// }
/// } /// }
/// ///
@ -155,7 +128,7 @@ macro_rules! make_func {
// Map the result // Map the result
$map(r) $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. /// To Dynamic mapping function.
#[inline(always)] #[inline(always)]
pub fn map_identity(data: Dynamic) -> Result<Dynamic, Box<EvalAltResult>> { pub fn map_result(
Ok(data) data: Result<Dynamic, Box<EvalAltResult>>,
}
/// To `Result<Dynamic, Box<EvalAltResult>>` mapping function.
#[inline(always)]
pub fn map_result<T: Variant + Clone>(
data: Result<T, Box<EvalAltResult>>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
data.map(|v| v.into_dynamic()) data
} }
macro_rules! def_register { 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 ABI type
// ^ function parameter generic type name (A, B, C etc.) // ^ function parameter generic type name (A, B, C etc.)
// ^ function parameter marker type (T, Ref<T> or Mut<T>) // ^ function parameter marker type (T, Ref<T> or Mut<T>)
// ^ function parameter actual type (T, &T or &mut T) // ^ function parameter actual type (T, &T or &mut T)
// ^ dereferencing function // ^ dereferencing function
impl< impl<
$($par: Variant + Clone,)* $($par: Variant + Clone,)*
@ -202,9 +169,9 @@ macro_rules! def_register {
> RegisterFn<FN, ($($mark,)*), RET> for Engine > RegisterFn<FN, ($($mark,)*), RET> for Engine
{ {
fn register_fn(&mut self, name: &str, f: FN) { 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>()),*], &[$(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,)* $($par: Variant + Clone,)*
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static, FN: Fn($($param),*) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync + 'static,
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> Dynamic + 'static, FN: Fn($($param),*) -> Result<Dynamic, Box<EvalAltResult>> + 'static,
> RegisterDynamicFn<FN, ($($mark,)*)> for Engine > RegisterResultFn<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 register_result_fn(&mut self, name: &str, f: FN) { 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>()),*], &[$(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),*); //def_register!(imp_pop $($par => $mark => $param),*);
}; };
($p0:ident $(, $p:ident)*) => { ($p0:ident $(, $p:ident)*) => {
def_register!(imp Pure ; $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*); def_register!(imp from_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)*); def_register!(imp from_method : $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*);
// handle the first parameter ^ first parameter passed through // ^ CallableFunction
// ^ others passed by value (by_value) // 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 // 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 // conflicting implementations since &T: Any and T: Any cannot be distinguished

View File

@ -49,18 +49,18 @@
//! //!
//! ## Optional features //! ## Optional features
//! //!
//! | Feature | Description | //! | Feature | Description |
//! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | //! | ------------- | ----------------------------------------------------------------------------------------------------------------------------------|
//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | //! | `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_function` | Disable script-defined functions if not needed. |
//! | `no_index` | Disable arrays and indexing features if not needed. | //! | `no_index` | Disable arrays and indexing features if not needed. |
//! | `no_object` | Disable support for custom types and objects. | //! | `no_object` | Disable support for custom types and objects. |
//! | `no_float` | Disable floating-point numbers and math if not needed. | //! | `no_float` | Disable floating-point numbers and math if not needed. |
//! | `no_optimize` | Disable the script optimizer. | //! | `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_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`. | //! | `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. | //! | `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`. | //! | `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) //! [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 any::Dynamic;
pub use engine::Engine; pub use engine::Engine;
pub use error::{ParseError, ParseErrorType}; pub use error::{ParseError, ParseErrorType};
pub use fn_native::NativeCallable; pub use fn_register::{RegisterFn, RegisterResultFn};
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
pub use module::Module; pub use module::Module;
pub use parser::{AST, INT}; pub use parser::{ImmutableString, AST, INT};
pub use result::EvalAltResult; pub use result::EvalAltResult;
pub use scope::Scope; pub use scope::Scope;
pub use token::Position; pub use token::Position;

View File

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

View File

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

View File

@ -20,7 +20,7 @@ use crate::stdlib::{
}; };
// Checked add // 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(|| { x.checked_add(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Addition overflow: {} + {}", x, y), format!("Addition overflow: {} + {}", x, y),
@ -29,7 +29,7 @@ fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
}) })
} }
// Checked subtract // 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(|| { x.checked_sub(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Subtraction underflow: {} - {}", x, y), format!("Subtraction underflow: {} - {}", x, y),
@ -38,7 +38,7 @@ fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
}) })
} }
// Checked multiply // 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(|| { x.checked_mul(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Multiplication overflow: {} * {}", x, y), format!("Multiplication overflow: {} * {}", x, y),
@ -47,7 +47,7 @@ fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
}) })
} }
// Checked divide // Checked divide
fn div<T>(x: T, y: T) -> FuncReturn<T> pub(crate) fn div<T>(x: T, y: T) -> FuncReturn<T>
where where
T: Display + CheckedDiv + PartialEq + Zero, T: Display + CheckedDiv + PartialEq + Zero,
{ {
@ -67,7 +67,7 @@ where
}) })
} }
// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX // 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(|| { x.checked_neg().ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Negation overflow: -{}", x), format!("Negation overflow: -{}", x),
@ -76,7 +76,7 @@ fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
}) })
} }
// Checked absolute // 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 // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
// when the number is ::MIN instead of returning ::MIN itself. // when the number is ::MIN instead of returning ::MIN itself.
if x >= <T as Zero>::zero() { 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) Ok(x ^ y)
} }
// Checked left-shift // 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 // Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -150,7 +150,7 @@ fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
}) })
} }
// Checked right-shift // 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 // Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic( 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 // 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)) Ok(x.shl(y))
} }
// Unchecked right-shift - may panic if shifting by a negative number of bits // 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)) Ok(x.shr(y))
} }
// Checked modulo // 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(|| { x.checked_rem(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Modulo division by zero or overflow: {} % {}", x, y), 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) Ok(x % y)
} }
// Checked power // 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"))] #[cfg(not(feature = "only_i32"))]
{ {
if y > (u32::MAX as INT) { 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) // 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)) Ok(x.pow(y as u32))
} }
// Floating-point power - always well-defined // Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))] #[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)) Ok(x.powf(y))
} }
// Checked power // Checked power
#[cfg(not(feature = "no_float"))] #[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 // Raise to power that is larger than an i32
if y > (i32::MAX as INT) { if y > (i32::MAX as INT) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( 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) // Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
#[cfg(not(feature = "no_float"))] #[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)) Ok(x.powi(y as i32))
} }
@ -269,119 +269,69 @@ macro_rules! reg_op {
} }
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Checked basic arithmetic #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "+", add, INT); #[cfg(not(feature = "unchecked"))]
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"))]
{ {
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); // Checked basic arithmetic
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div, i8, u8, i16, u16, i32, i64, 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")]
#[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"))]
{ {
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); // Unchecked basic arithmetic
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, i64, 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 // Basic arithmetic for floating-point - no need to check
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
reg_op!(lib, "+", add_u, f32, f64); reg_op!(lib, "+", add_u, f32);
reg_op!(lib, "-", sub_u, f32, f64); reg_op!(lib, "-", sub_u, f32);
reg_op!(lib, "*", mul_u, f32, f64); reg_op!(lib, "*", mul_u, f32);
reg_op!(lib, "/", div_u, f32, f64); 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_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "|", binary_or, 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, i64, 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, i64, 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"))] #[cfg(not(feature = "no_float"))]
{ {
reg_op!(lib, "%", modulo_u, f32, f64); // Checked power
lib.set_fn_2("~", pow_f_f); #[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 // Checked unary
@ -411,11 +361,4 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64, i128); 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::def_package;
use crate::engine::Array; use crate::engine::Array;
use crate::module::FuncReturn; 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 // Register array utility functions
fn push<T: Variant + Clone>(list: &mut Array, item: T) -> FuncReturn<()> { fn push<T: Variant + Clone>(list: &mut Array, item: T) -> FuncReturn<()> {
@ -45,14 +45,18 @@ macro_rules! reg_tri {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
reg_op!(lib, "push", push, INT, bool, char, String, Array, ()); reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ());
reg_tri!(lib, "pad", pad, INT, bool, char, String, Array, ()); reg_tri!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ());
reg_tri!(lib, "insert", ins, INT, bool, char, String, Array, ()); reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ());
lib.set_fn_2_mut("append", |x: &mut Array, y: Array| { lib.set_fn_2_mut("append", |x: &mut Array, y: Array| {
x.extend(y); x.extend(y);
Ok(()) Ok(())
}); });
lib.set_fn_2_mut("+=", |x: &mut Array, y: Array| {
x.extend(y);
Ok(())
});
lib.set_fn_2( lib.set_fn_2(
"+", "+",
|mut x: Array, y: Array| { |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)); 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| { lib.set_fn_1_mut("clear", |list: &mut Array| {
list.clear(); list.clear();
Ok(()) Ok(())
@ -120,8 +128,6 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
// Register array iterator // Register array iterator
lib.set_iter( lib.set_iter(
TypeId::of::<Array>(), TypeId::of::<Array>(),
Box::new(|arr| Box::new( |arr| Box::new(arr.cast::<Array>().into_iter()) as Box<dyn Iterator<Item = Dynamic>>,
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 where
Range<T>: Iterator<Item = T>, Range<T>: Iterator<Item = T>,
{ {
lib.set_iter( lib.set_iter(TypeId::of::<Range<T>>(), |source| {
TypeId::of::<Range<T>>(), Box::new(source.cast::<Range<T>>().map(|x| x.into_dynamic()))
Box::new(|source| { as Box<dyn Iterator<Item = Dynamic>>
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>> { fn get_range<T: Variant + Clone>(from: T, to: T) -> FuncReturn<Range<T>> {
@ -58,13 +55,10 @@ where
T: Variant + Clone + PartialOrd, T: Variant + Clone + PartialOrd,
StepRange<T>: Iterator<Item = T>, StepRange<T>: Iterator<Item = T>,
{ {
lib.set_iter( lib.set_iter(TypeId::of::<StepRange<T>>(), |source| {
TypeId::of::<StepRange<T>>(), Box::new(source.cast::<StepRange<T>>().map(|x| x.into_dynamic()))
Box::new(|source| { as Box<dyn Iterator<Item = Dynamic>>
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>> 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::def_package;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::INT;
use crate::stdlib::string::String;
// Comparison operators // Comparison operators
pub fn lt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> { 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 // 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> { fn not(x: bool) -> FuncReturn<bool> {
Ok(!x) Ok(!x)
} }
@ -42,48 +33,26 @@ macro_rules! reg_op {
} }
def_package!(crate:LogicPackage:"Logical operators.", lib, { 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_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "<", lt, 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, i64, 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, i64, 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, i64, 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, i64, 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, i64, u32, u64, i128, u128); reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64, i128, u128);
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
reg_op!(lib, "<", lt, f32, f64); reg_op!(lib, "<", lt, f32);
reg_op!(lib, "<=", lte, f32, f64); reg_op!(lib, "<=", lte, f32);
reg_op!(lib, ">", gt, f32, f64); reg_op!(lib, ">", gt, f32);
reg_op!(lib, ">=", gte, f32, f64); reg_op!(lib, ">=", gte, f32);
reg_op!(lib, "==", eq, f32, f64); reg_op!(lib, "==", eq, f32);
reg_op!(lib, "!=", ne, f32, f64); 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); lib.set_fn_1("!", not);
}); });

View File

@ -4,12 +4,9 @@ use crate::any::Dynamic;
use crate::def_package; use crate::def_package;
use crate::engine::Map; use crate::engine::Map;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::{ImmutableString, INT};
use crate::stdlib::{ use crate::stdlib::{string::ToString, vec::Vec};
string::{String, ToString},
vec::Vec,
};
fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> { fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
Ok(map.iter().map(|(k, _)| k.to_string().into()).collect()) 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, { def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
lib.set_fn_2_mut( lib.set_fn_2_mut(
"has", "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("len", |map: &mut Map| Ok(map.len() as INT));
lib.set_fn_1_mut("clear", |map: &mut Map| { 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( lib.set_fn_2_mut(
"remove", "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( lib.set_fn_2_mut(
"mixin", "mixin",
@ -42,6 +39,15 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
Ok(()) 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( lib.set_fn_2(
"+", "+",
|mut map1: Map, map2: Map| { |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_finite", |x: FLOAT| Ok(x.is_finite()));
lib.set_fn_1("is_infinite", |x: FLOAT| Ok(x.is_infinite())); 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 // Register conversion functions
lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: f32| 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. //! 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::module::Module;
use crate::utils::StaticVec; 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 array_basic;
mod eval;
mod iter_basic; mod iter_basic;
mod logic; mod logic;
mod map_basic; mod map_basic;
@ -21,6 +22,7 @@ mod time_basic;
pub use arithmetic::ArithmeticPackage; pub use arithmetic::ArithmeticPackage;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub use array_basic::BasicArrayPackage; pub use array_basic::BasicArrayPackage;
pub use eval::EvalPackage;
pub use iter_basic::BasicIteratorPackage; pub use iter_basic::BasicIteratorPackage;
pub use logic::LogicPackage; pub use logic::LogicPackage;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -42,35 +44,27 @@ pub trait Package {
fn get(&self) -> PackageLibrary; fn get(&self) -> PackageLibrary;
} }
/// Type which `Rc`-wraps a `Module` to facilitate sharing library instances. /// A sharable `Module` to facilitate sharing library instances.
#[cfg(not(feature = "sync"))] pub type PackageLibrary = Shared<Module>;
pub type PackageLibrary = Rc<Module>;
/// Type which `Arc`-wraps a `Module` to facilitate sharing library instances.
#[cfg(feature = "sync")]
pub type PackageLibrary = Arc<Module>;
/// Type containing a collection of `PackageLibrary` instances. /// Type containing a collection of `PackageLibrary` instances.
/// All function and type iterator keys in the loaded packages are indexed for fast access. /// All function and type iterator keys in the loaded packages are indexed for fast access.
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub(crate) struct PackagesCollection { pub(crate) struct PackagesCollection(StaticVec<PackageLibrary>);
/// Collection of `PackageLibrary` instances.
packages: StaticVec<PackageLibrary>,
}
impl PackagesCollection { impl PackagesCollection {
/// Add a `PackageLibrary` into the `PackagesCollection`. /// Add a `PackageLibrary` into the `PackagesCollection`.
pub fn push(&mut self, package: PackageLibrary) { pub fn push(&mut self, package: PackageLibrary) {
// Later packages override previous ones. // 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`? /// Does the specified function hash key exist in the `PackagesCollection`?
pub fn contains_fn(&self, hash: u64) -> bool { 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. /// Get specified function via its hash key.
pub fn get_fn(&self, hash: u64) -> Option<&Box<dyn NativeCallable>> { pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
self.packages self.0
.iter() .iter()
.map(|p| p.get_fn(hash)) .map(|p| p.get_fn(hash))
.find(|f| f.is_some()) .find(|f| f.is_some())
@ -78,11 +72,11 @@ impl PackagesCollection {
} }
/// Does the specified TypeId iterator exist in the `PackagesCollection`? /// Does the specified TypeId iterator exist in the `PackagesCollection`?
pub fn contains_iter(&self, id: TypeId) -> bool { 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. /// Get the specified TypeId iterator.
pub fn get_iter(&self, id: TypeId) -> Option<&SharedIteratorFunction> { pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.packages self.0
.iter() .iter()
.map(|p| p.get_iter(id)) .map(|p| p.get_iter(id))
.find(|f| f.is_some()) .find(|f| f.is_some())

View File

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

View File

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

View File

@ -10,6 +10,9 @@ use crate::token::Position;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
use crate::stdlib::time::Instant; use crate::stdlib::time::Instant;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
// Register date/time functions // 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("==", eq::<Instant>);
lib.set_fn_2("!=", ne::<Instant>); lib.set_fn_2("!=", ne::<Instant>);
lib.set_fn_1( #[cfg(not(feature = "no_float"))]
"elapsed", fn elapsed (timestamp: &mut Instant) -> Result<FLOAT, Box<EvalAltResult>> {
|timestamp: Instant| { Ok(timestamp.elapsed().as_secs_f64())
#[cfg(not(feature = "no_float"))] }
return Ok(timestamp.elapsed().as_secs_f64());
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
{ fn elapsed (timestamp: &mut Instant) -> Result<INT, Box<EvalAltResult>> {
let seconds = timestamp.elapsed().as_secs(); let seconds = timestamp.elapsed().as_secs();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
if seconds > (MAX_INT as u64) { if seconds > (MAX_INT as u64) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer overflow for timestamp.elapsed(): {}", seconds), format!("Integer overflow for timestamp.elapsed: {}", seconds),
Position::none(), Position::none(),
))); )));
}
}
return Ok(seconds as INT);
} }
}, }
); 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)] #[derive(Debug)]
pub enum EvalAltResult { pub enum EvalAltResult {
/// Syntax error. /// Syntax error.
ErrorParsing(Box<ParseError>), ErrorParsing(ParseError),
/// Error reading from a script file. Wrapped value is the path of the script file. /// 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. /// An error has occurred inside a called function.
/// Wrapped values re the name of the function and the interior error. /// Wrapped values re the name of the function and the interior error.
ErrorInFunctionCall(String, Box<EvalAltResult>, Position), 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. /// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
ErrorBooleanArgMismatch(String, Position), ErrorBooleanArgMismatch(String, Position),
/// Non-character value encountered where a character is required. /// Non-character value encountered where a character is required.
@ -108,9 +104,6 @@ impl EvalAltResult {
Self::ErrorParsing(p) => p.desc(), Self::ErrorParsing(p) => p.desc(),
Self::ErrorInFunctionCall(_, _, _) => "Error in called function", Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
Self::ErrorFunctionNotFound(_, _) => "Function not found", Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
"Function call with wrong number of arguments"
}
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
Self::ErrorCharMismatch(_) => "Character expected", Self::ErrorCharMismatch(_) => "Character expected",
Self::ErrorNumericIndexExpr(_) => { Self::ErrorNumericIndexExpr(_) => {
@ -208,21 +201,6 @@ impl fmt::Display for EvalAltResult {
Self::ErrorLoopBreak(_, pos) => write!(f, "{} ({})", desc, pos), Self::ErrorLoopBreak(_, pos) => write!(f, "{} ({})", desc, pos),
Self::Return(_, 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) => { Self::ErrorBooleanArgMismatch(op, pos) => {
write!(f, "{} operator expects boolean operands ({})", 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> { impl From<ParseError> for Box<EvalAltResult> {
fn from(err: ParseError) -> Self { 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)) Box::new(EvalAltResult::ErrorParsing(err))
} }
} }
@ -292,7 +265,6 @@ impl EvalAltResult {
Self::ErrorFunctionNotFound(_, pos) Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos) | Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, pos) | Self::ErrorArrayBounds(_, _, pos)
@ -331,7 +303,6 @@ impl EvalAltResult {
Self::ErrorFunctionNotFound(_, pos) Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos) | Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, pos) | Self::ErrorArrayBounds(_, _, pos)

View File

@ -21,8 +21,8 @@ type LERR = LexError;
/// A location (line number + character position) in the input script. /// 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, /// 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/characters per line. /// 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. /// 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)] #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position { 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 /// 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 /// 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 /// 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. //! 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::{ use crate::stdlib::{
any::TypeId, any::TypeId,
borrow::Borrow,
boxed::Box,
fmt, fmt,
hash::{Hash, Hasher}, hash::{BuildHasher, Hash, Hasher},
iter::FromIterator, iter::FromIterator,
mem, mem,
mem::MaybeUninit, mem::MaybeUninit,
ops::{Drop, Index, IndexMut}, ops::{Add, AddAssign, Deref, Drop, Index, IndexMut},
str::FromStr,
string::{String, ToString},
vec::Vec, vec::Vec,
}; };
@ -21,9 +27,46 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use ahash::AHasher; use ahash::AHasher;
#[inline(always)] /// A hasher that only takes one single `u64` and returns it as a hash key.
pub fn EMPTY_TYPE_ID() -> TypeId { ///
TypeId::of::<()>() /// # 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. /// 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>( pub fn calc_fn_spec<'a>(
modules: impl Iterator<Item = &'a str>, modules: impl Iterator<Item = &'a str>,
fn_name: &str, fn_name: &str,
num: usize,
params: impl Iterator<Item = TypeId>, params: impl Iterator<Item = TypeId>,
) -> u64 { ) -> u64 {
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -47,6 +91,7 @@ pub fn calc_fn_spec<'a>(
// We always skip the first module // We always skip the first module
modules.skip(1).for_each(|m| m.hash(&mut s)); modules.skip(1).for_each(|m| m.hash(&mut s));
s.write(fn_name.as_bytes()); s.write(fn_name.as_bytes());
s.write_usize(num);
params.for_each(|t| t.hash(&mut s)); params.for_each(|t| t.hash(&mut s));
s.finish() s.finish()
} }
@ -105,6 +150,7 @@ pub struct StaticVec<T> {
const MAX_STATIC_VEC: usize = 4; const MAX_STATIC_VEC: usize = 4;
impl<T> Drop for StaticVec<T> { impl<T> Drop for StaticVec<T> {
#[inline(always)]
fn drop(&mut self) { fn drop(&mut self) {
self.clear(); self.clear();
} }
@ -171,6 +217,7 @@ impl<T> FromIterator<T> for StaticVec<T> {
impl<T> StaticVec<T> { impl<T> StaticVec<T> {
/// Create a new `StaticVec`. /// Create a new `StaticVec`.
#[inline(always)]
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Default::default()
} }
@ -186,6 +233,7 @@ impl<T> StaticVec<T> {
self.len = 0; self.len = 0;
} }
/// Extract a `MaybeUninit` into a concrete initialized type. /// Extract a `MaybeUninit` into a concrete initialized type.
#[inline(always)]
fn extract(value: MaybeUninit<T>) -> T { fn extract(value: MaybeUninit<T>) -> T {
unsafe { value.assume_init() } unsafe { value.assume_init() }
} }
@ -247,6 +295,7 @@ impl<T> StaticVec<T> {
); );
} }
/// Is data stored in fixed-size storage? /// Is data stored in fixed-size storage?
#[inline(always)]
fn is_fixed_storage(&self) -> bool { fn is_fixed_storage(&self) -> bool {
self.len <= MAX_STATIC_VEC self.len <= MAX_STATIC_VEC
} }
@ -356,10 +405,12 @@ impl<T> StaticVec<T> {
result result
} }
/// Get the number of items in this `StaticVec`. /// Get the number of items in this `StaticVec`.
#[inline(always)]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.len self.len
} }
/// Is this `StaticVec` empty? /// Is this `StaticVec` empty?
#[inline(always)]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.len == 0 self.len == 0
} }
@ -563,3 +614,289 @@ impl<T> From<Vec<T>> for StaticVec<T> {
arr 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]; let y = [4, 5];
x.append(y); x.append(y);
x.len() + r x.len + r
" "
)?, )?,
14 14

View File

@ -22,7 +22,7 @@ fn test_float() -> Result<(), Box<EvalAltResult>> {
#[test] #[test]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn struct_with_float() -> Result<(), Box<EvalAltResult>> { fn test_struct_with_float() -> Result<(), Box<EvalAltResult>> {
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {
x: f64, x: f64,
@ -64,3 +64,16 @@ fn struct_with_float() -> Result<(), Box<EvalAltResult>> {
Ok(()) 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; sum += value;
} }
keys.len() + sum keys.len + sum
"#; "#;
assert_eq!(engine.eval::<INT>(script)?, 9); 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 } 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"))] #[cfg(not(feature = "no_object"))]
assert_eq!( assert_eq!(
engine.eval::<INT>("fn add(x, n) { x + n } let x = 40; x.add(2)")?, engine.eval::<INT>("fn add(x, n) { x + n } let x = 40; x.add(2)")?,
42 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); assert_eq!(engine.eval::<INT>("fn mul2(x) { x * 2 } mul2(21)")?, 42);
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -21,5 +32,11 @@ fn test_functions() -> Result<(), Box<EvalAltResult>> {
42 42
); );
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("fn mul2(x) { x *= 2; } let x = 21; x.mul2(); x")?,
21
);
Ok(()) Ok(())
} }

View File

@ -1,6 +1,6 @@
#![cfg(not(feature = "no_object"))] #![cfg(not(feature = "no_object"))]
use rhai::{Engine, EvalAltResult, RegisterFn, INT}; use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, INT};
#[test] #[test]
fn test_get_set() -> Result<(), Box<EvalAltResult>> { 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); engine.register_fn("new_ts", TestStruct::new);
#[cfg(not(feature = "no_index"))] #[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 = 500; a.x")?, 500);
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42); 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' 'o'
); );
assert_eq!(
engine.eval::<String>(r#"let a = [#{s:"hello"}]; a[0].s[2] = 'X'; a[0].s"#)?,
"heXlo"
);
} }
assert_eq!( assert_eq!(

View File

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

View File

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

View File

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