Merge pull request #439 from schungx/master

v1.1
This commit is contained in:
Stephen Chung 2021-08-21 13:05:04 +08:00 committed by GitHub
commit 4f4d024418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 718 additions and 242 deletions

View File

@ -4,7 +4,7 @@ on:
push: push:
branches: branches:
- master - master
- plugins - bug-fixes
pull_request: {} pull_request: {}
jobs: jobs:

View File

@ -1,6 +1,32 @@
Rhai Release Notes Rhai Release Notes
================== ==================
Version 1.1.0
=============
Bug fixes
---------
* Custom syntax starting with a disabled standard keyword now works properly.
* When calling `Engine::call_fn`, new variables defined during evaluation of the body script are removed and no longer spill into the function call.
Enhancements
------------
* `Engine::consume_XXX` methods are renamed to `Engine::run_XXX` to make meanings clearer. The `consume_XXX` API is deprecated.
* `$symbol$` is supported in custom syntax to match any symbol.
* Custom syntax with `$block$`, `}` or `;` as the last symbol are now self-terminating (i.e. no need to attach a terminating `;`).
* `Dynamic::as_string` and `Dynamic::as_immutable_string` are deprecated and replaced by `into_string` and `into_immutable_string` respectively.
* Added a number of constants to `Dynamic`.
* Added a number of constants and `fromXXX` constant methods to `Dynamic`.
* `parse_float()`, `PI()` and `E()` now defer to `Decimal` under `no_float` if `decimal` is turned on.
* Added `log10()` for `Decimal`.
* `ln` for `Decimal` is now checked and won't panic.
* `Scope::set_value` now takes anything that implements `Into<Cow<str>>`.
* Added `Scope::is_constant` to check if a variable is constant.
* Added `Scope::set_or_push` to add a new variable only if one doesn't already exist.
Version 1.0.2 Version 1.0.2
============= =============
@ -105,7 +131,7 @@ New features
* Each `Dynamic` value can now contain arbitrary data (type `i32`) in the form of a _tag_. This is to use up otherwise wasted space in the `Dynamic` type. * Each `Dynamic` value can now contain arbitrary data (type `i32`) in the form of a _tag_. This is to use up otherwise wasted space in the `Dynamic` type.
* A new internal feature `no_smartstring` to turn off `SmartString` for those rare cases that it is needed. * A new internal feature `no_smartstring` to turn off `SmartString` for those rare cases that it is needed.
* `DynamicReadLock` and `DynamicWriteLoc` are exposed under `internals`. * `DynamicReadLock` and `DynamicWriteLoc` are exposed under `internals`.
* `From<Shared<Locked<Dynamic>>>` is added for `Dynamic` mapping directly to a shared value, together with support for `Dynamic::from`. * `From< Shared< Locked<Dynamic> > >` is added for `Dynamic` mapping directly to a shared value, together with support for `Dynamic::from`.
* An indexer with string index acts as a _fallback_ to a property getter/setter. * An indexer with string index acts as a _fallback_ to a property getter/setter.
Enhancements Enhancements

View File

@ -3,7 +3,7 @@ members = [".", "codegen"]
[package] [package]
name = "rhai" name = "rhai"
version = "1.0.3" version = "1.1.0"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
@ -92,7 +92,7 @@ default-features = false
optional = true optional = true
[dependencies.rust_decimal] [dependencies.rust_decimal]
version = "1.14.2" version = "1.15"
default-features = false default-features = false
features = ["maths"] features = ["maths"]
optional = true optional = true

View File

@ -15,7 +15,7 @@ fn bench_eval_array_small_get(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -27,7 +27,7 @@ fn bench_eval_array_small_set(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -43,7 +43,7 @@ fn bench_eval_array_large_get(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -59,7 +59,7 @@ fn bench_eval_array_large_set(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -83,5 +83,5 @@ fn bench_eval_array_loop(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }

View File

@ -15,7 +15,7 @@ fn bench_eval_expression_single(bench: &mut Bencher) {
let ast = engine.compile_expression(script).unwrap(); let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -27,7 +27,7 @@ fn bench_eval_expression_number_literal(bench: &mut Bencher) {
let ast = engine.compile_expression(script).unwrap(); let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -39,7 +39,7 @@ fn bench_eval_expression_number_operators(bench: &mut Bencher) {
let ast = engine.compile_expression(script).unwrap(); let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -56,7 +56,7 @@ fn bench_eval_expression_optimized_simple(bench: &mut Bencher) {
engine.set_optimization_level(OptimizationLevel::Simple); engine.set_optimization_level(OptimizationLevel::Simple);
let ast = engine.compile_expression(script).unwrap(); let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -73,7 +73,7 @@ fn bench_eval_expression_optimized_full(bench: &mut Bencher) {
engine.set_optimization_level(OptimizationLevel::Full); engine.set_optimization_level(OptimizationLevel::Full);
let ast = engine.compile_expression(script).unwrap(); let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -120,7 +120,7 @@ fn bench_eval_loop_number(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -137,7 +137,7 @@ fn bench_eval_loop_strings_build(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -154,7 +154,7 @@ fn bench_eval_loop_strings_no_build(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -186,7 +186,7 @@ fn bench_eval_switch(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -216,5 +216,5 @@ fn bench_eval_nested_if(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }

View File

@ -15,7 +15,7 @@ fn bench_eval_map_small_get(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -27,7 +27,7 @@ fn bench_eval_map_small_set(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -47,7 +47,7 @@ fn bench_eval_map_large_get(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -67,5 +67,5 @@ fn bench_eval_map_large_set(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }

View File

@ -31,7 +31,7 @@ fn bench_eval_module(bench: &mut Bencher) {
) )
.unwrap(); .unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]
@ -49,5 +49,5 @@ fn bench_eval_function_call(bench: &mut Bencher) {
) )
.unwrap(); .unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }

View File

@ -18,7 +18,7 @@ fn bench_eval_scope_single(bench: &mut Bencher) {
let ast = engine.compile_expression(script).unwrap(); let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); bench.iter(|| engine.run_ast_with_scope(&mut scope, &ast).unwrap());
} }
#[bench] #[bench]
@ -34,7 +34,7 @@ fn bench_eval_scope_multiple(bench: &mut Bencher) {
let ast = engine.compile_expression(script).unwrap(); let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); bench.iter(|| engine.run_ast_with_scope(&mut scope, &ast).unwrap());
} }
#[bench] #[bench]
@ -50,7 +50,7 @@ fn bench_eval_scope_longer(bench: &mut Bencher) {
let ast = engine.compile_expression(script).unwrap(); let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); bench.iter(|| engine.run_ast_with_scope(&mut scope, &ast).unwrap());
} }
#[bench] #[bench]
@ -73,5 +73,5 @@ fn bench_eval_scope_complex(bench: &mut Bencher) {
let ast = engine.compile_expression(script).unwrap(); let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); bench.iter(|| engine.run_ast_with_scope(&mut scope, &ast).unwrap());
} }

View File

@ -41,7 +41,7 @@ fn bench_type_field(bench: &mut Bencher) {
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.push("foo", Test { x: 42 }); scope.push("foo", Test { x: 42 });
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); bench.iter(|| engine.run_ast_with_scope(&mut scope, &ast).unwrap());
} }
#[bench] #[bench]
@ -59,7 +59,7 @@ fn bench_type_method(bench: &mut Bencher) {
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.push("foo", Test { x: 42 }); scope.push("foo", Test { x: 42 });
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); bench.iter(|| engine.run_ast_with_scope(&mut scope, &ast).unwrap());
} }
#[bench] #[bench]
@ -77,7 +77,7 @@ fn bench_type_method_with_params(bench: &mut Bencher) {
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.push("foo", Test { x: 42 }); scope.push("foo", Test { x: 42 });
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); bench.iter(|| engine.run_ast_with_scope(&mut scope, &ast).unwrap());
} }
#[bench] #[bench]
@ -96,5 +96,5 @@ fn bench_type_method_nested(bench: &mut Bencher) {
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.push("foo", Test { x: 42 }); scope.push("foo", Test { x: 42 });
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); bench.iter(|| engine.run_ast_with_scope(&mut scope, &ast).unwrap());
} }

View File

@ -21,7 +21,7 @@ fn bench_iterations_1000(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }
#[bench] #[bench]

View File

@ -39,5 +39,5 @@ fn bench_eval_primes(bench: &mut Bencher) {
let ast = engine.compile(SCRIPT).unwrap(); let ast = engine.compile(SCRIPT).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap()); bench.iter(|| engine.run_ast(&ast).unwrap());
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai_codegen" name = "rhai_codegen"
version = "1.0.0" version = "1.1.0"
edition = "2018" edition = "2018"
authors = ["jhwgh1968", "Stephen Chung"] authors = ["jhwgh1968", "Stephen Chung"]
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust" description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"

View File

@ -743,7 +743,7 @@ impl ExportedFn {
is_string = true; is_string = true;
is_ref = true; is_ref = true;
quote_spanned!(arg_type.span() => quote_spanned!(arg_type.span() =>
mem::take(args[#i]).as_immutable_string().unwrap() mem::take(args[#i]).into_immutable_string().unwrap()
) )
} }
_ => panic!("internal error: why wasn't this found earlier!?"), _ => panic!("internal error: why wasn't this found earlier!?"),
@ -752,7 +752,7 @@ impl ExportedFn {
is_string = true; is_string = true;
is_ref = false; is_ref = false;
quote_spanned!(arg_type.span() => quote_spanned!(arg_type.span() =>
mem::take(args[#i]).as_string().unwrap() mem::take(args[#i]).into_string().unwrap()
) )
} }
_ => { _ => {

View File

@ -525,7 +525,7 @@ mod generate_tests {
impl PluginFunction for Token { impl PluginFunction for Token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).as_immutable_string().unwrap(); let arg0 = mem::take(args[0usize]).into_immutable_string().unwrap();
Ok(Dynamic::from(special_print(&arg0))) Ok(Dynamic::from(special_print(&arg0)))
} }

View File

@ -934,7 +934,7 @@ mod generate_tests {
impl PluginFunction for print_out_to_token { impl PluginFunction for print_out_to_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).as_immutable_string().unwrap(); let arg0 = mem::take(args[0usize]).into_immutable_string().unwrap();
Ok(Dynamic::from(print_out_to(&arg0))) Ok(Dynamic::from(print_out_to(&arg0)))
} }
@ -987,7 +987,7 @@ mod generate_tests {
impl PluginFunction for print_out_to_token { impl PluginFunction for print_out_to_token {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
let arg0 = mem::take(args[0usize]).as_string().unwrap(); let arg0 = mem::take(args[0usize]).into_string().unwrap();
Ok(Dynamic::from(print_out_to(arg0))) Ok(Dynamic::from(print_out_to(arg0)))
} }

View File

@ -4,7 +4,8 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
11 | pub fn test_fn(input: f32) -> NonClonable { 11 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
| |
::: $WORKSPACE/src/dynamic.rs note: required by a bound in `rhai::Dynamic::from`
--> $DIR/dynamic.rs:1127:30
| |
| pub fn from<T: Variant + Clone>(mut value: T) -> Self { 1127 | pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| ----- required by this bound in `rhai::Dynamic::from` | ^^^^^ required by this bound in `rhai::Dynamic::from`

View File

@ -4,7 +4,8 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
12 | pub fn test_fn(input: f32) -> NonClonable { 12 | pub fn test_fn(input: f32) -> NonClonable {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable`
| |
::: $WORKSPACE/src/dynamic.rs note: required by a bound in `rhai::Dynamic::from`
--> $DIR/dynamic.rs:1127:30
| |
| pub fn from<T: Variant + Clone>(mut value: T) -> Self { 1127 | pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| ----- required by this bound in `rhai::Dynamic::from` | ^^^^^ required by this bound in `rhai::Dynamic::from`

View File

@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult, INT};
fn main() -> Result<(), Box<EvalAltResult>> { fn main() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
engine.consume(r#"print("hello, world!")"#)?; engine.run(r#"print("hello, world!")"#)?;
let result = engine.eval::<INT>("40 + 2")?; let result = engine.eval::<INT>("40 + 2")?;

View File

@ -61,7 +61,7 @@ fn main() -> Result<(), Box<EvalAltResult>> {
println!("Line: {}", input.replace('\r', "\\r").replace('\n', "\\n")); println!("Line: {}", input.replace('\r', "\\r").replace('\n', "\\n"));
engine.consume_with_scope( engine.run_with_scope(
&mut scope, &mut scope,
r#" r#"
display("Length", x.len()); display("Length", x.len());

View File

@ -34,7 +34,7 @@ fn main() {
// Run script // Run script
engine engine
.consume( .run(
r#" r#"
print("Starting script loop..."); print("Starting script loop...");

View File

@ -1263,10 +1263,12 @@ impl Stmt {
// A No-op requires a semicolon in order to know it is an empty statement! // A No-op requires a semicolon in order to know it is an empty statement!
Self::Noop(_) => false, Self::Noop(_) => false,
Self::Expr(Expr::Custom(x, _)) if x.is_self_terminated() => true,
Self::Var(_, _, _, _) Self::Var(_, _, _, _)
| Self::Assignment(_, _) | Self::Assignment(_, _)
| Self::FnCall(_, _)
| Self::Expr(_) | Self::Expr(_)
| Self::FnCall(_, _)
| Self::Do(_, _, _, _) | Self::Do(_, _, _, _)
| Self::Continue(_) | Self::Continue(_)
| Self::Break(_) | Self::Break(_)
@ -1509,6 +1511,19 @@ pub struct CustomExpr {
pub scope_may_be_changed: bool, pub scope_may_be_changed: bool,
/// List of tokens actually parsed. /// List of tokens actually parsed.
pub tokens: StaticVec<Identifier>, pub tokens: StaticVec<Identifier>,
/// Is this custom syntax self-terminated?
pub self_terminated: bool,
}
impl CustomExpr {
/// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)?
///
/// A self-terminated custom syntax always ends in `$block$`, `}` or `;`
#[must_use]
#[inline(always)]
pub const fn is_self_terminated(&self) -> bool {
self.self_terminated
}
} }
/// _(internals)_ A binary expression. /// _(internals)_ A binary expression.
@ -1552,8 +1567,8 @@ impl OpAssignment<'_> {
let op_raw = op let op_raw = op
.map_op_assignment() .map_op_assignment()
.expect("never fails because token must be an op-assignment operator") .expect("never fails because token must be an op-assignment operator")
.keyword_syntax(); .literal_syntax();
let op_assignment = op.keyword_syntax(); let op_assignment = op.literal_syntax();
Self { Self {
hash_op_assign: calc_fn_hash(op_assignment, 2), hash_op_assign: calc_fn_hash(op_assignment, 2),

View File

@ -90,7 +90,7 @@ fn main() {
.map_err(|err| err.into()) .map_err(|err| err.into())
.and_then(|mut ast| { .and_then(|mut ast| {
ast.set_source(filename.to_string_lossy().to_string()); ast.set_source(filename.to_string_lossy().to_string());
engine.consume_ast(&ast) engine.run_ast(&ast)
}) })
{ {
let filename = filename.to_string_lossy(); let filename = filename.to_string_lossy();

View File

@ -14,21 +14,26 @@ use std::any::TypeId;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
/// Special marker for matching an expression. /// Collection of special markers for custom syntax definition.
pub const CUSTOM_SYNTAX_MARKER_EXPR: &str = "$expr$"; pub mod markers {
/// Special marker for matching a statements block. /// Special marker for matching an expression.
pub const CUSTOM_SYNTAX_MARKER_BLOCK: &str = "$block$"; pub const CUSTOM_SYNTAX_MARKER_EXPR: &str = "$expr$";
/// Special marker for matching an identifier. /// Special marker for matching a statements block.
pub const CUSTOM_SYNTAX_MARKER_IDENT: &str = "$ident$"; pub const CUSTOM_SYNTAX_MARKER_BLOCK: &str = "$block$";
/// Special marker for matching a string literal. /// Special marker for matching an identifier.
pub const CUSTOM_SYNTAX_MARKER_STRING: &str = "$string$"; pub const CUSTOM_SYNTAX_MARKER_IDENT: &str = "$ident$";
/// Special marker for matching an integer number. /// Special marker for matching a single symbol.
pub const CUSTOM_SYNTAX_MARKER_INT: &str = "$int$"; pub const CUSTOM_SYNTAX_MARKER_SYMBOL: &str = "$symbol$";
/// Special marker for matching a floating-point number. /// Special marker for matching a string literal.
#[cfg(not(feature = "no_float"))] pub const CUSTOM_SYNTAX_MARKER_STRING: &str = "$string$";
pub const CUSTOM_SYNTAX_MARKER_FLOAT: &str = "$float$"; /// Special marker for matching an integer number.
/// Special marker for matching a boolean value. pub const CUSTOM_SYNTAX_MARKER_INT: &str = "$int$";
pub const CUSTOM_SYNTAX_MARKER_BOOL: &str = "$bool$"; /// Special marker for matching a floating-point number.
#[cfg(not(feature = "no_float"))]
pub const CUSTOM_SYNTAX_MARKER_FLOAT: &str = "$float$";
/// Special marker for matching a boolean value.
pub const CUSTOM_SYNTAX_MARKER_BOOL: &str = "$bool$";
}
/// A general expression evaluation trait object. /// A general expression evaluation trait object.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
@ -184,6 +189,8 @@ impl Engine {
scope_may_be_changed: bool, scope_may_be_changed: bool,
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
) -> Result<&mut Self, ParseError> { ) -> Result<&mut Self, ParseError> {
use markers::*;
let mut segments: StaticVec<ImmutableString> = Default::default(); let mut segments: StaticVec<ImmutableString> = Default::default();
for s in keywords { for s in keywords {
@ -199,6 +206,7 @@ impl Engine {
let seg = match s { let seg = match s {
// Markers not in first position // Markers not in first position
CUSTOM_SYNTAX_MARKER_IDENT CUSTOM_SYNTAX_MARKER_IDENT
| CUSTOM_SYNTAX_MARKER_SYMBOL
| CUSTOM_SYNTAX_MARKER_EXPR | CUSTOM_SYNTAX_MARKER_EXPR
| CUSTOM_SYNTAX_MARKER_BLOCK | CUSTOM_SYNTAX_MARKER_BLOCK
| CUSTOM_SYNTAX_MARKER_BOOL | CUSTOM_SYNTAX_MARKER_BOOL
@ -222,11 +230,10 @@ impl Engine {
} }
s.into() s.into()
} }
// Standard keyword in first position // Standard keyword in first position but not disabled
s if segments.is_empty() _ if segments.is_empty()
&& token && token.as_ref().map_or(false, |v| v.is_standard_keyword())
.as_ref() && !self.disabled_symbols.contains(s) =>
.map_or(false, |v| v.is_keyword() || v.is_reserved()) =>
{ {
return Err(LexError::ImproperSymbol( return Err(LexError::ImproperSymbol(
s.to_string(), s.to_string(),
@ -239,13 +246,13 @@ impl Engine {
.into_err(Position::NONE)); .into_err(Position::NONE));
} }
// Identifier in first position // Identifier in first position
s if segments.is_empty() && is_valid_identifier(s.chars()) => { _ if segments.is_empty() && is_valid_identifier(s.chars()) => {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved
if (self.disabled_symbols.contains(s) if self.disabled_symbols.contains(s) || token.map_or(false, |v| v.is_reserved())
|| matches!(token, Some(Token::Reserved(_))))
&& !self.custom_keywords.contains_key(s)
{ {
self.custom_keywords.insert(s.into(), None); if !self.custom_keywords.contains_key(s) {
self.custom_keywords.insert(s.into(), None);
}
} }
s.into() s.into()
} }

143
src/deprecated.rs Normal file
View File

@ -0,0 +1,143 @@
//! Module containing all deprecated API that will be removed in the next major version.
use crate::{Dynamic, Engine, EvalAltResult, ImmutableString, Scope, AST};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
impl Engine {
/// Evaluate a file, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// Not available under `no_std` or `WASM`.
///
/// # Deprecated
///
/// This method is deprecated. Use [`run_file`][Engine::run_file] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_file` instead")]
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[inline(always)]
pub fn consume_file(&self, path: std::path::PathBuf) -> Result<(), Box<EvalAltResult>> {
self.run_file(path)
}
/// Evaluate a file with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// Not available under `no_std` or `WASM`.
///
/// # Deprecated
///
/// This method is deprecated. Use [`run_file_with_scope`][Engine::run_file_with_scope] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_file_with_scope` instead")]
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[inline(always)]
pub fn consume_file_with_scope(
&self,
scope: &mut Scope,
path: std::path::PathBuf,
) -> Result<(), Box<EvalAltResult>> {
self.run_file_with_scope(scope, path)
}
/// Evaluate a string, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// # Deprecated
///
/// This method is deprecated. Use [`run`][Engine::run] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run` instead")]
#[inline(always)]
pub fn consume(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
self.run(script)
}
/// Evaluate a string with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// # Deprecated
///
/// This method is deprecated. Use [`run_with_scope`][Engine::run_with_scope] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_with_scope` instead")]
#[inline(always)]
pub fn consume_with_scope(
&self,
scope: &mut Scope,
script: &str,
) -> Result<(), Box<EvalAltResult>> {
self.run_with_scope(scope, script)
}
/// Evaluate an AST, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// # Deprecated
///
/// This method is deprecated. Use [`run_ast`][Engine::run_ast] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_ast` instead")]
#[inline(always)]
pub fn consume_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> {
self.run_ast(ast)
}
/// Evaluate an [`AST`] with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
///
/// # Deprecated
///
/// This method is deprecated. Use [`run_ast_with_scope`][Engine::run_ast_with_scope] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_ast_with_scope` instead")]
#[inline(always)]
pub fn consume_ast_with_scope(
&self,
scope: &mut Scope,
ast: &AST,
) -> Result<(), Box<EvalAltResult>> {
self.run_ast_with_scope(scope, ast)
}
}
impl Dynamic {
/// Convert the [`Dynamic`] into a [`String`] and return it.
/// If there are other references to the same string, a cloned copy is returned.
/// Returns the name of the actual type if the cast fails.
///
/// # Deprecated
///
/// This method is deprecated. Use [`into_string`][Dynamic::into_string] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `into_string` instead")]
#[inline(always)]
pub fn as_string(self) -> Result<String, &'static str> {
self.into_string()
}
/// Convert the [`Dynamic`] into an [`ImmutableString`] and return it.
/// Returns the name of the actual type if the cast fails.
///
/// # Deprecated
///
/// This method is deprecated. Use [`into_immutable_string`][Dynamic::into_immutable_string] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `into_immutable_string` instead")]
#[inline(always)]
pub fn as_immutable_string(self) -> Result<ImmutableString, &'static str> {
self.into_immutable_string()
}
}

View File

@ -10,6 +10,7 @@ use std::{
fmt, fmt,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
str::FromStr,
}; };
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -819,66 +820,149 @@ impl Default for Dynamic {
impl Dynamic { impl Dynamic {
/// A [`Dynamic`] containing a `()`. /// A [`Dynamic`] containing a `()`.
pub const UNIT: Dynamic = Self(Union::Unit((), DEFAULT_TAG_VALUE, ReadWrite)); pub const UNIT: Self = Self(Union::Unit((), DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing a `true`. /// A [`Dynamic`] containing a `true`.
pub const TRUE: Dynamic = Self(Union::Bool(true, DEFAULT_TAG_VALUE, ReadWrite)); pub const TRUE: Self = Self::from_bool(true);
/// A [`Dynamic`] containing a [`false`]. /// A [`Dynamic`] containing a [`false`].
pub const FALSE: Dynamic = Self(Union::Bool(false, DEFAULT_TAG_VALUE, ReadWrite)); pub const FALSE: Self = Self::from_bool(false);
/// A [`Dynamic`] containing the integer zero. /// A [`Dynamic`] containing the integer zero.
pub const ZERO: Dynamic = Self(Union::Int(0, DEFAULT_TAG_VALUE, ReadWrite)); pub const ZERO: Self = Self::from_int(0);
/// A [`Dynamic`] containing the integer one. /// A [`Dynamic`] containing the integer one.
pub const ONE: Dynamic = Self(Union::Int(1, DEFAULT_TAG_VALUE, ReadWrite)); pub const ONE: Self = Self::from_int(1);
/// A [`Dynamic`] containing the integer two. /// A [`Dynamic`] containing the integer two.
pub const TWO: Dynamic = Self(Union::Int(2, DEFAULT_TAG_VALUE, ReadWrite)); pub const TWO: Self = Self::from_int(2);
/// A [`Dynamic`] containing the integer three.
pub const THREE: Self = Self::from_int(3);
/// A [`Dynamic`] containing the integer ten. /// A [`Dynamic`] containing the integer ten.
pub const TEN: Dynamic = Self(Union::Int(10, DEFAULT_TAG_VALUE, ReadWrite)); pub const TEN: Self = Self::from_int(10);
/// A [`Dynamic`] containing the integer one hundred.
pub const HUNDRED: Self = Self::from_int(100);
/// A [`Dynamic`] containing the integer one thousand.
pub const THOUSAND: Self = Self::from_int(1000);
/// A [`Dynamic`] containing the integer negative one. /// A [`Dynamic`] containing the integer negative one.
pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, DEFAULT_TAG_VALUE, ReadWrite)); pub const NEGATIVE_ONE: Self = Self::from_int(-1);
/// A [`Dynamic`] containing `0.0`. /// A [`Dynamic`] containing `0.0`.
/// ///
/// Not available under `no_float`. /// Not available under `no_float`.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_ZERO: Dynamic = Self(Union::Float( pub const FLOAT_ZERO: Self = Self::from_float(0.0);
FloatWrapper::new_const(0.0),
DEFAULT_TAG_VALUE,
ReadWrite,
));
/// A [`Dynamic`] containing `1.0`. /// A [`Dynamic`] containing `1.0`.
/// ///
/// Not available under `no_float`. /// Not available under `no_float`.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_ONE: Dynamic = Self(Union::Float( pub const FLOAT_ONE: Self = Self::from_float(1.0);
FloatWrapper::new_const(1.0),
DEFAULT_TAG_VALUE,
ReadWrite,
));
/// A [`Dynamic`] containing `2.0`. /// A [`Dynamic`] containing `2.0`.
/// ///
/// Not available under `no_float`. /// Not available under `no_float`.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_TWO: Dynamic = Self(Union::Float( pub const FLOAT_TWO: Self = Self::from_float(2.0);
FloatWrapper::new_const(2.0),
DEFAULT_TAG_VALUE,
ReadWrite,
));
/// A [`Dynamic`] containing `10.0`. /// A [`Dynamic`] containing `10.0`.
/// ///
/// Not available under `no_float`. /// Not available under `no_float`.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_TEN: Dynamic = Self(Union::Float( pub const FLOAT_TEN: Self = Self::from_float(10.0);
FloatWrapper::new_const(10.0), /// A [`Dynamic`] containing `100.0`.
DEFAULT_TAG_VALUE,
ReadWrite,
));
/// A [`Dynamic`] containing the `-1.0`.
/// ///
/// Not available under `no_float`. /// Not available under `no_float`.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float( pub const FLOAT_HUNDRED: Self = Self::from_float(100.0);
FloatWrapper::new_const(-1.0), /// A [`Dynamic`] containing `1000.0`.
DEFAULT_TAG_VALUE, ///
ReadWrite, /// Not available under `no_float`.
)); #[cfg(not(feature = "no_float"))]
pub const FLOAT_THOUSAND: Self = Self::from_float(1000.0);
/// A [`Dynamic`] containing `-1.0`.
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))]
pub const FLOAT_NEGATIVE_ONE: Self = Self::from_float(-1.0);
/// A [`Dynamic`] containing π.
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "f32_float"))]
pub const FLOAT_PI: Self = Self::from_float(
#[cfg(not(feature = "f32_float"))]
{
std::f64::consts::PI
},
#[cfg(feature = "f32_float")]
{
std::f32::consts::PI
},
);
/// A [`Dynamic`] containing π/2.
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))]
pub const FLOAT_HALF_PI: Self = Self::from_float(
#[cfg(not(feature = "f32_float"))]
{
std::f64::consts::PI / 2.0
},
#[cfg(feature = "f32_float")]
{
std::f32::consts::PI / 2.0
},
);
/// A [`Dynamic`] containing 2π.
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))]
pub const FLOAT_TWO_PI: Self = Self::from_float(
#[cfg(not(feature = "f32_float"))]
{
std::f64::consts::PI * 2.0
},
#[cfg(feature = "f32_float")]
{
std::f32::consts::PI * 2.0
},
);
/// Create a new [`Dynamic`] from a [`bool`].
#[inline(always)]
pub const fn from_bool(value: bool) -> Self {
Self(Union::Bool(value, DEFAULT_TAG_VALUE, ReadWrite))
}
/// Create a new [`Dynamic`] from an [`INT`].
#[inline(always)]
pub const fn from_int(value: INT) -> Self {
Self(Union::Int(value, DEFAULT_TAG_VALUE, ReadWrite))
}
/// Create a new [`Dynamic`] from a [`char`].
#[inline(always)]
pub const fn from_char(value: char) -> Self {
Self(Union::Char(value, DEFAULT_TAG_VALUE, ReadWrite))
}
/// Create a new [`Dynamic`] from a [`FLOAT`].
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))]
#[inline(always)]
pub const fn from_float(value: FLOAT) -> Self {
Self(Union::Float(
FloatWrapper::new_const(value),
DEFAULT_TAG_VALUE,
ReadWrite,
))
}
/// Create a new [`Dynamic`] from a [`Decimal`](https://docs.rs/rust_decimal).
///
/// Exported under the `decimal` feature only.
#[cfg(feature = "decimal")]
#[inline(always)]
pub fn from_decimal(value: Decimal) -> Self {
Self(Union::Decimal(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
}
/// Create a new [`Dynamic`] from an [`Instant`].
///
/// Not available under `no-std`.
#[cfg(not(feature = "no_std"))]
#[inline(always)]
pub fn from_timestamp(value: Instant) -> Self {
Self(Union::TimeStamp(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
}
/// Get the [`AccessMode`] for this [`Dynamic`]. /// Get the [`AccessMode`] for this [`Dynamic`].
#[must_use] #[must_use]
@ -1816,14 +1900,15 @@ impl Dynamic {
/// Convert the [`Dynamic`] into a [`String`] and return it. /// Convert the [`Dynamic`] into a [`String`] and return it.
/// If there are other references to the same string, a cloned copy is returned. /// If there are other references to the same string, a cloned copy is returned.
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
#[inline] #[inline(always)]
pub fn as_string(self) -> Result<String, &'static str> { pub fn into_string(self) -> Result<String, &'static str> {
self.as_immutable_string().map(ImmutableString::into_owned) self.into_immutable_string()
.map(ImmutableString::into_owned)
} }
/// Convert the [`Dynamic`] into an [`ImmutableString`] and return it. /// Convert the [`Dynamic`] into an [`ImmutableString`] 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.
#[inline] #[inline]
pub fn as_immutable_string(self) -> Result<ImmutableString, &'static str> { pub fn into_immutable_string(self) -> Result<ImmutableString, &'static str> {
match self.0 { match self.0 {
Union::Str(s, _, _) => Ok(s), Union::Str(s, _, _) => Ok(s),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -1900,6 +1985,13 @@ impl From<&ImmutableString> for Dynamic {
value.clone().into() value.clone().into()
} }
} }
impl FromStr for Dynamic {
type Err = ();
fn from_str(value: &str) -> Result<Self, Self::Err> {
Ok(Self(Union::Str(value.into(), DEFAULT_TAG_VALUE, ReadWrite)))
}
}
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
impl Dynamic { impl Dynamic {
/// Create a [`Dynamic`] from an [`Array`]. /// Create a [`Dynamic`] from an [`Array`].

View File

@ -1357,9 +1357,9 @@ impl Engine {
// Trims the JSON string and add a '#' in front // Trims the JSON string and add a '#' in front
let json_text = json.trim_start(); let json_text = json.trim_start();
let scripts = if json_text.starts_with(Token::MapStart.keyword_syntax()) { let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) {
[json_text, ""] [json_text, ""]
} else if json_text.starts_with(Token::LeftBrace.keyword_syntax()) { } else if json_text.starts_with(Token::LeftBrace.literal_syntax()) {
["#", json_text] ["#", json_text]
} else { } else {
return Err(crate::ParseErrorType::MissingToken( return Err(crate::ParseErrorType::MissingToken(
@ -1741,40 +1741,36 @@ impl Engine {
let lib = &[ast.lib()]; let lib = &[ast.lib()];
self.eval_global_statements(scope, mods, &mut state, statements, lib, level) self.eval_global_statements(scope, mods, &mut state, statements, lib, level)
} }
/// Evaluate a file, but throw away the result and only return error (if any). /// Evaluate a file, returning any error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Not available under `no_std` or `WASM`. /// Not available under `no_std` or `WASM`.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[inline(always)] #[inline(always)]
pub fn consume_file(&self, path: std::path::PathBuf) -> Result<(), Box<EvalAltResult>> { pub fn run_file(&self, path: std::path::PathBuf) -> Result<(), Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| self.consume(&contents)) Self::read_file(path).and_then(|contents| self.run(&contents))
} }
/// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Evaluate a file with own scope, returning any error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Not available under `no_std` or `WASM`. /// Not available under `no_std` or `WASM`.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[inline(always)] #[inline(always)]
pub fn consume_file_with_scope( pub fn run_file_with_scope(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
path: std::path::PathBuf, path: std::path::PathBuf,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
Self::read_file(path).and_then(|contents| self.consume_with_scope(scope, &contents)) Self::read_file(path).and_then(|contents| self.run_with_scope(scope, &contents))
} }
/// Evaluate a string, but throw away the result and only return error (if any). /// Evaluate a script, returning any error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline(always)] #[inline(always)]
pub fn consume(&self, script: &str) -> Result<(), Box<EvalAltResult>> { pub fn run(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
self.consume_with_scope(&mut Default::default(), script) self.run_with_scope(&mut Default::default(), script)
} }
/// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Evaluate a script with own scope, returning any error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline] #[inline]
pub fn consume_with_scope( pub fn run_with_scope(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
script: &str, script: &str,
@ -1790,18 +1786,16 @@ impl Engine {
self.optimization_level, self.optimization_level,
)?; )?;
self.consume_ast_with_scope(scope, &ast) self.run_ast_with_scope(scope, &ast)
} }
/// Evaluate an AST, but throw away the result and only return error (if any). /// Evaluate an AST, returning any error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline(always)] #[inline(always)]
pub fn consume_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> { pub fn run_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> {
self.consume_ast_with_scope(&mut Default::default(), ast) self.run_ast_with_scope(&mut Default::default(), ast)
} }
/// Evaluate an [`AST`] with own scope, but throw away the result and only return error (if any). /// Evaluate an [`AST`] with own scope, returning any error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline] #[inline]
pub fn consume_ast_with_scope( pub fn run_ast_with_scope(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
@ -1985,7 +1979,10 @@ impl Engine {
let statements = ast.statements(); let statements = ast.statements();
if eval_ast && !statements.is_empty() { if eval_ast && !statements.is_empty() {
// Make sure new variables introduced at global level do not _spill_ into the function call
let orig_scope_len = scope.len();
self.eval_global_statements(scope, mods, state, statements, lib, 0)?; self.eval_global_statements(scope, mods, state, statements, lib, 0)?;
scope.rewind(orig_scope_len);
} }
let fn_def = ast let fn_def = ast
@ -2151,8 +2148,8 @@ impl Engine {
/// } /// }
/// }); /// });
/// ///
/// engine.consume("for x in range(0, 50000) {}") /// engine.run("for x in range(0, 50000) {}")
/// .expect_err("should error"); /// .expect_err("should error");
/// ///
/// assert_eq!(*result.read().unwrap(), 9600); /// assert_eq!(*result.read().unwrap(), 9600);
/// ///
@ -2186,7 +2183,7 @@ impl Engine {
/// let logger = result.clone(); /// let logger = result.clone();
/// engine.on_print(move |s| logger.write().unwrap().push_str(s)); /// engine.on_print(move |s| logger.write().unwrap().push_str(s));
/// ///
/// engine.consume("print(40 + 2);")?; /// engine.run("print(40 + 2);")?;
/// ///
/// assert_eq!(*result.read().unwrap(), "42"); /// assert_eq!(*result.read().unwrap(), "42");
/// # Ok(()) /// # Ok(())
@ -2219,7 +2216,7 @@ impl Engine {
/// ///
/// let mut ast = engine.compile(r#"let x = "hello"; debug(x);"#)?; /// let mut ast = engine.compile(r#"let x = "hello"; debug(x);"#)?;
/// ast.set_source("world"); /// ast.set_source("world");
/// engine.consume_ast(&ast)?; /// engine.run_ast(&ast)?;
/// ///
/// #[cfg(not(feature = "no_position"))] /// #[cfg(not(feature = "no_position"))]
/// assert_eq!(*result.read().unwrap(), r#"world @ 1:18 > "hello""#); /// assert_eq!(*result.read().unwrap(), r#"world @ 1:18 > "hello""#);

View File

@ -307,13 +307,13 @@ impl Engine {
None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (), None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (),
// Active standard keywords cannot be made custom // Active standard keywords cannot be made custom
// Disabled keywords are OK // Disabled keywords are OK
Some(token) if token.is_keyword() => { Some(token) if token.is_standard_keyword() => {
if !self.disabled_symbols.contains(token.syntax().as_ref()) { if !self.disabled_symbols.contains(token.syntax().as_ref()) {
return Err(format!("'{}' is a reserved keyword", keyword.as_ref())); return Err(format!("'{}' is a reserved keyword", keyword.as_ref()));
} }
} }
// Active standard symbols cannot be made custom // Active standard symbols cannot be made custom
Some(token) if token.is_symbol() => { Some(token) if token.is_standard_symbol() => {
if !self.disabled_symbols.contains(token.syntax().as_ref()) { if !self.disabled_symbols.contains(token.syntax().as_ref()) {
return Err(format!("'{}' is a reserved operator", keyword.as_ref())); return Err(format!("'{}' is a reserved operator", keyword.as_ref()));
} }

View File

@ -235,6 +235,8 @@ impl fmt::Display for ParseErrorType {
Self::MismatchedType(r, a) => write!(f, "Expecting {}, not {}", r, a), Self::MismatchedType(r, a) => write!(f, "Expecting {}, not {}", r, a),
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s), Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s), Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
Self::MissingSymbol(s) if s.is_empty() => f.write_str("Expecting a symbol"),
Self::MissingSymbol(s) => f.write_str(s), Self::MissingSymbol(s) => f.write_str(s),
Self::AssignmentToConstant(s) => match s.as_str() { Self::AssignmentToConstant(s) => match s.as_str() {

View File

@ -321,7 +321,7 @@ impl Engine {
return Ok(match name { return Ok(match name {
KEYWORD_PRINT => { KEYWORD_PRINT => {
if let Some(ref print) = self.print { if let Some(ref print) = self.print {
let text = result.as_immutable_string().map_err(|typ| { let text = result.into_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(), typ.into(),
@ -335,7 +335,7 @@ impl Engine {
} }
KEYWORD_DEBUG => { KEYWORD_DEBUG => {
if let Some(ref debug) = self.debug { if let Some(ref debug) = self.debug {
let text = result.as_immutable_string().map_err(|typ| { let text = result.into_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(), typ.into(),
@ -1100,7 +1100,7 @@ impl Engine {
// Fn - only in function call style // Fn - only in function call style
return arg return arg
.as_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos)) .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))
.and_then(FnPtr::try_from) .and_then(FnPtr::try_from)
.map(Into::<Dynamic>::into) .map(Into::<Dynamic>::into)
@ -1150,7 +1150,7 @@ impl Engine {
)?; )?;
let fn_name = arg let fn_name = arg
.as_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?; .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
let (arg, arg_pos) = self.get_arg_value( let (arg, arg_pos) = self.get_arg_value(
@ -1176,7 +1176,7 @@ impl Engine {
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
)?; )?;
let var_name = arg let var_name = arg
.as_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?; .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
return Ok(scope.contains(&var_name).into()); return Ok(scope.contains(&var_name).into());
} }
@ -1189,7 +1189,7 @@ impl Engine {
scope, mods, state, lib, this_ptr, level, args_expr, constants, 0, scope, mods, state, lib, this_ptr, level, args_expr, constants, 0,
)?; )?;
let script = &value let script = &value
.as_immutable_string() .into_immutable_string()
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?; .map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
let result = let result =
self.eval_script_expr_in_place(scope, mods, state, lib, script, pos, level + 1); self.eval_script_expr_in_place(scope, mods, state, lib, script, pos, level + 1);

View File

@ -53,7 +53,7 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
} else if TypeId::of::<T>() == TypeId::of::<String>() { } else if TypeId::of::<T>() == TypeId::of::<String>() {
// If T is `String`, data must be `ImmutableString`, so map directly to it // If T is `String`, data must be `ImmutableString`, so map directly to it
let value = mem::take(data) let value = mem::take(data)
.as_string() .into_string()
.expect("never fails because the type was checked"); .expect("never fails because the type was checked");
unsafe_try_cast(value).expect("never fails because the type was checked") unsafe_try_cast(value).expect("never fails because the type was checked")
} else { } else {

View File

@ -71,6 +71,7 @@ use std::prelude::v1::*;
mod ast; mod ast;
mod custom_syntax; mod custom_syntax;
mod deprecated;
mod dynamic; mod dynamic;
mod engine; mod engine;
mod engine_api; mod engine_api;

View File

@ -309,6 +309,22 @@ mod decimal_functions {
Decimal, MathematicalOps, Decimal, MathematicalOps,
}; };
#[cfg(feature = "no_float")]
pub mod float_polyfills {
#[rhai_fn(name = "PI")]
pub fn pi() -> Decimal {
Decimal::PI
}
#[rhai_fn(name = "E")]
pub fn e() -> Decimal {
Decimal::E
}
#[rhai_fn(return_raw)]
pub fn parse_float(s: &str) -> Result<Decimal, Box<EvalAltResult>> {
super::parse_decimal(s)
}
}
#[rhai_fn(return_raw)] #[rhai_fn(return_raw)]
pub fn sqrt(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> { pub fn sqrt(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
x.sqrt() x.sqrt()
@ -323,8 +339,23 @@ mod decimal_functions {
Ok(x.exp()) Ok(x.exp())
} }
} }
pub fn ln(x: Decimal) -> Decimal { #[rhai_fn(return_raw)]
x.ln() pub fn ln(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
x.checked_ln()
.ok_or_else(|| make_err(format!("Error taking the natural log of {}", x)))
} else {
Ok(x.ln())
}
}
#[rhai_fn(name = "log", return_raw)]
pub fn log10(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
x.checked_log10()
.ok_or_else(|| make_err(format!("Error taking the log of {}", x)))
} else {
Ok(x.log10())
}
} }
#[rhai_fn(name = "floor", get = "floor")] #[rhai_fn(name = "floor", get = "floor")]
pub fn floor(x: Decimal) -> Decimal { pub fn floor(x: Decimal) -> Decimal {

View File

@ -30,7 +30,7 @@ pub fn print_with_func(
) -> crate::ImmutableString { ) -> crate::ImmutableString {
match ctx.call_fn_dynamic_raw(fn_name, true, &mut [value]) { match ctx.call_fn_dynamic_raw(fn_name, true, &mut [value]) {
Ok(result) if result.is::<crate::ImmutableString>() => result Ok(result) if result.is::<crate::ImmutableString>() => result
.as_immutable_string() .into_immutable_string()
.expect("never fails as the result is `ImmutableString`"), .expect("never fails as the result is `ImmutableString`"),
Ok(result) => ctx.engine().map_type_name(result.type_name()).into(), Ok(result) => ctx.engine().map_type_name(result.type_name()).into(),
Err(_) => ctx.engine().map_type_name(value.type_name()).into(), Err(_) => ctx.engine().map_type_name(value.type_name()).into(),

View File

@ -4,10 +4,7 @@ use crate::ast::{
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ReturnType, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ReturnType,
ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*,
}; };
use crate::custom_syntax::{ use crate::custom_syntax::{markers::*, CustomSyntax};
CustomSyntax, CUSTOM_SYNTAX_MARKER_BLOCK, CUSTOM_SYNTAX_MARKER_BOOL, CUSTOM_SYNTAX_MARKER_EXPR,
CUSTOM_SYNTAX_MARKER_IDENT, CUSTOM_SYNTAX_MARKER_INT, CUSTOM_SYNTAX_MARKER_STRING,
};
use crate::dynamic::AccessMode; use crate::dynamic::AccessMode;
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
use crate::fn_hash::get_hasher; use crate::fn_hash::get_hasher;
@ -17,8 +14,8 @@ use crate::token::{
is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl, is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl,
}; };
use crate::{ use crate::{
calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Engine, Identifier, LexError, calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Engine, Identifier,
ParseError, ParseErrorType, Position, Scope, Shared, StaticVec, AST, ImmutableString, LexError, ParseError, ParseErrorType, Position, Scope, Shared, StaticVec, AST,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -29,7 +26,7 @@ use std::{
}; };
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::{custom_syntax::CUSTOM_SYNTAX_MARKER_FLOAT, FLOAT}; use crate::{custom_syntax::markers::CUSTOM_SYNTAX_MARKER_FLOAT, FLOAT};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::FnAccess; use crate::FnAccess;
@ -389,6 +386,20 @@ fn parse_var_name(input: &mut TokenStream) -> Result<(String, Position), ParseEr
} }
} }
/// Parse a symbol.
fn parse_symbol(input: &mut TokenStream) -> Result<(String, Position), ParseError> {
match input.next().expect(NEVER_ENDS) {
// Symbol
(token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)),
// Reserved symbol
(Token::Reserved(s), pos) if !is_valid_identifier(s.chars()) => Ok((s, pos)),
// Bad identifier
(Token::LexError(err), pos) => Err(err.into_err(pos)),
// Not a symbol
(_, pos) => Err(PERR::MissingSymbol(Default::default()).into_err(pos)),
}
}
/// Parse `(` expr `)` /// Parse `(` expr `)`
fn parse_paren_expr( fn parse_paren_expr(
input: &mut TokenStream, input: &mut TokenStream,
@ -1672,15 +1683,9 @@ fn make_dot_expr(
op_pos: Position, op_pos: Position,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
Ok(match (lhs, rhs) { Ok(match (lhs, rhs) {
// lhs[???]...[???].rhs
(Expr::Index(mut x, false, pos), rhs) if !terminate_chaining => {
// Attach dot chain to the bottom level of indexing chain
x.rhs = make_dot_expr(state, x.rhs, false, rhs, op_pos)?;
Expr::Index(x, false, pos)
}
// lhs[idx_expr].rhs // lhs[idx_expr].rhs
(Expr::Index(mut x, _, pos), rhs) => { (Expr::Index(mut x, term, pos), rhs) => {
x.rhs = make_dot_expr(state, x.rhs, true, rhs, op_pos)?; x.rhs = make_dot_expr(state, x.rhs, term || terminate_chaining, rhs, op_pos)?;
Expr::Index(x, false, pos) Expr::Index(x, false, pos)
} }
// lhs.id // lhs.id
@ -1993,16 +1998,17 @@ fn parse_custom_syntax(
} }
let parse_func = syntax.parse.as_ref(); let parse_func = syntax.parse.as_ref();
let mut required_token: ImmutableString = key.into();
segments.push(key.into()); tokens.push(required_token.clone().into());
tokens.push(key.into()); segments.push(required_token.clone());
loop { loop {
let (fwd_token, fwd_pos) = input.peek().expect(NEVER_ENDS); let (fwd_token, fwd_pos) = input.peek().expect(NEVER_ENDS);
settings.pos = *fwd_pos; settings.pos = *fwd_pos;
let settings = settings.level_up(); let settings = settings.level_up();
let required_token = match parse_func(&segments, fwd_token.syntax().as_ref()) { required_token = match parse_func(&segments, fwd_token.syntax().as_ref()) {
Ok(Some(seg)) => seg, Ok(Some(seg)) => seg,
Ok(None) => break, Ok(None) => break,
Err(err) => return Err(err.0.into_err(settings.pos)), Err(err) => return Err(err.0.into_err(settings.pos)),
@ -2016,6 +2022,13 @@ fn parse_custom_syntax(
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_IDENT)); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_IDENT));
keywords.push(Expr::Variable(None, pos, (None, None, name).into())); keywords.push(Expr::Variable(None, pos, (None, None, name).into()));
} }
CUSTOM_SYNTAX_MARKER_SYMBOL => {
let (symbol, pos) = parse_symbol(input)?;
let symbol: ImmutableString = state.get_identifier(symbol).into();
segments.push(symbol.clone());
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_SYMBOL));
keywords.push(Expr::StringConstant(symbol, pos));
}
CUSTOM_SYNTAX_MARKER_EXPR => { CUSTOM_SYNTAX_MARKER_EXPR => {
keywords.push(parse_expr(input, state, lib, settings)?); keywords.push(parse_expr(input, state, lib, settings)?);
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_EXPR); let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_EXPR);
@ -2034,9 +2047,8 @@ fn parse_custom_syntax(
CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) { CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) {
(b @ Token::True, pos) | (b @ Token::False, pos) => { (b @ Token::True, pos) | (b @ Token::False, pos) => {
keywords.push(Expr::BoolConstant(b == Token::True, pos)); keywords.push(Expr::BoolConstant(b == Token::True, pos));
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_BOOL); segments.push(state.get_identifier(b.literal_syntax()).into());
segments.push(keyword.clone().into()); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_BOOL));
tokens.push(keyword);
} }
(_, pos) => { (_, pos) => {
return Err( return Err(
@ -2048,9 +2060,8 @@ fn parse_custom_syntax(
CUSTOM_SYNTAX_MARKER_INT => match input.next().expect(NEVER_ENDS) { CUSTOM_SYNTAX_MARKER_INT => match input.next().expect(NEVER_ENDS) {
(Token::IntegerConstant(i), pos) => { (Token::IntegerConstant(i), pos) => {
keywords.push(Expr::IntegerConstant(i, pos)); keywords.push(Expr::IntegerConstant(i, pos));
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_INT); segments.push(i.to_string().into());
segments.push(keyword.clone().into()); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_INT));
tokens.push(keyword);
} }
(_, pos) => { (_, pos) => {
return Err( return Err(
@ -2063,9 +2074,8 @@ fn parse_custom_syntax(
CUSTOM_SYNTAX_MARKER_FLOAT => match input.next().expect(NEVER_ENDS) { CUSTOM_SYNTAX_MARKER_FLOAT => match input.next().expect(NEVER_ENDS) {
(Token::FloatConstant(f), pos) => { (Token::FloatConstant(f), pos) => {
keywords.push(Expr::FloatConstant(f, pos)); keywords.push(Expr::FloatConstant(f, pos));
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_FLOAT); segments.push(f.to_string().into());
segments.push(keyword.clone().into()); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_FLOAT));
tokens.push(keyword);
} }
(_, pos) => { (_, pos) => {
return Err(PERR::MissingSymbol( return Err(PERR::MissingSymbol(
@ -2076,10 +2086,10 @@ fn parse_custom_syntax(
}, },
CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) { CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) {
(Token::StringConstant(s), pos) => { (Token::StringConstant(s), pos) => {
keywords.push(Expr::StringConstant(state.get_identifier(s).into(), pos)); let s: ImmutableString = state.get_identifier(s).into();
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_STRING); keywords.push(Expr::StringConstant(s.clone(), pos));
segments.push(keyword.clone().into()); segments.push(s);
tokens.push(keyword); tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_STRING));
} }
(_, pos) => { (_, pos) => {
return Err(PERR::MissingSymbol("Expecting a string".to_string()).into_err(pos)) return Err(PERR::MissingSymbol("Expecting a string".to_string()).into_err(pos))
@ -2105,11 +2115,23 @@ fn parse_custom_syntax(
keywords.shrink_to_fit(); keywords.shrink_to_fit();
tokens.shrink_to_fit(); tokens.shrink_to_fit();
const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax();
const KEYWORD_CLOSE_BRACE: &str = Token::RightBrace.literal_syntax();
let self_terminated = match required_token.as_str() {
// It is self-terminating if the last symbol is a block
CUSTOM_SYNTAX_MARKER_BLOCK => true,
// If the last symbol is `;` or `}`, it is self-terminating
KEYWORD_SEMICOLON | KEYWORD_CLOSE_BRACE => true,
_ => false,
};
Ok(Expr::Custom( Ok(Expr::Custom(
CustomExpr { CustomExpr {
keywords, keywords,
tokens, tokens,
scope_may_be_changed: syntax.scope_may_be_changed, scope_may_be_changed: syntax.scope_may_be_changed,
self_terminated,
} }
.into(), .into(),
pos, pos,

View File

@ -339,6 +339,75 @@ impl<'a> Scope<'a> {
.find(|(_, (key, _))| name == key.as_ref()) .find(|(_, (key, _))| name == key.as_ref())
.and_then(|(index, _)| self.values[index].flatten_clone().try_cast()) .and_then(|(index, _)| self.values[index].flatten_clone().try_cast())
} }
/// Check if the named entry in the [`Scope`] is constant.
///
/// Search starts backwards from the last, stopping at the first entry matching the specified name.
///
/// Returns [`None`] if no entry matching the specified name is found.
///
/// # Example
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push_constant("x", 42_i64);
/// assert_eq!(my_scope.is_constant("x"), Some(true));
/// assert_eq!(my_scope.is_constant("y"), None);
/// ```
#[inline]
pub fn is_constant(&self, name: &str) -> Option<bool> {
self.get_index(name).and_then(|(_, access)| match access {
AccessMode::ReadWrite => None,
AccessMode::ReadOnly => Some(true),
})
}
/// Update the value of the named entry in the [`Scope`] if it already exists and is not constant.
/// Push a new entry with the value into the [`Scope`] if the name doesn't exist or if the
/// existing entry is constant.
///
/// Search starts backwards from the last, and only the first entry matching the specified name is updated.
///
/// # Example
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.set_or_push("x", 42_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// assert_eq!(my_scope.len(), 1);
///
/// my_scope.set_or_push("x", 0_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0);
/// assert_eq!(my_scope.len(), 1);
///
/// my_scope.set_or_push("y", 123_i64);
/// assert_eq!(my_scope.get_value::<i64>("y").unwrap(), 123);
/// assert_eq!(my_scope.len(), 2);
/// ```
#[inline]
pub fn set_or_push(
&mut self,
name: impl AsRef<str> + Into<Cow<'a, str>>,
value: impl Variant + Clone,
) -> &mut Self {
match self.get_index(name.as_ref()) {
None | Some((_, AccessMode::ReadOnly)) => {
self.push(name, value);
}
Some((index, AccessMode::ReadWrite)) => {
let value_ref = self
.values
.get_mut(index)
.expect("never fails because the index is returned by `get_index`");
*value_ref = Dynamic::from(value);
}
}
self
}
/// Update the value of the named entry in the [`Scope`]. /// Update the value of the named entry in the [`Scope`].
/// ///
/// Search starts backwards from the last, and only the first entry matching the specified name is updated. /// Search starts backwards from the last, and only the first entry matching the specified name is updated.
@ -362,12 +431,16 @@ impl<'a> Scope<'a> {
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0);
/// ``` /// ```
#[inline] #[inline]
pub fn set_value(&mut self, name: &'a str, value: impl Variant + Clone) -> &mut Self { pub fn set_value(
match self.get_index(name) { &mut self,
name: impl AsRef<str> + Into<Cow<'a, str>>,
value: impl Variant + Clone,
) -> &mut Self {
match self.get_index(name.as_ref()) {
None => { None => {
self.push(name, value); self.push(name, value);
} }
Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name), Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name.as_ref()),
Some((index, AccessMode::ReadWrite)) => { Some((index, AccessMode::ReadWrite)) => {
let value_ref = self let value_ref = self
.values .values

View File

@ -547,7 +547,7 @@ impl SerializeMap for DynamicSerializer {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
let key = std::mem::take(&mut self._key) let key = std::mem::take(&mut self._key)
.as_immutable_string() .into_immutable_string()
.map_err(|typ| { .map_err(|typ| {
EvalAltResult::ErrorMismatchDataType( EvalAltResult::ErrorMismatchDataType(
"string".into(), "string".into(),
@ -577,7 +577,7 @@ impl SerializeMap for DynamicSerializer {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
let _key: Dynamic = _key.serialize(&mut *self)?; let _key: Dynamic = _key.serialize(&mut *self)?;
let _key = _key.as_immutable_string().map_err(|typ| { let _key = _key.into_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE) EvalAltResult::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE)
})?; })?;
let _value = _value.serialize(&mut *self)?; let _value = _value.serialize(&mut *self)?;

View File

@ -483,9 +483,9 @@ pub enum Token {
} }
impl Token { impl Token {
/// Get the syntax of the token if it is a keyword. /// Get the literal syntax of the token.
#[must_use] #[must_use]
pub const fn keyword_syntax(&self) -> &'static str { pub const fn literal_syntax(&self) -> &'static str {
use Token::*; use Token::*;
match self { match self {
@ -595,7 +595,7 @@ impl Token {
EOF => "{EOF}".into(), EOF => "{EOF}".into(),
token => token.keyword_syntax().into(), token => token.literal_syntax().into(),
} }
} }
@ -912,7 +912,7 @@ impl Token {
/// Is this token a standard symbol used in the language? /// Is this token a standard symbol used in the language?
#[must_use] #[must_use]
pub const fn is_symbol(&self) -> bool { pub const fn is_standard_symbol(&self) -> bool {
use Token::*; use Token::*;
match self { match self {
@ -928,10 +928,10 @@ impl Token {
} }
} }
/// Is this token an active standard keyword? /// Is this token a standard keyword?
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn is_keyword(&self) -> bool { pub const fn is_standard_keyword(&self) -> bool {
use Token::*; use Token::*;
match self { match self {
@ -948,7 +948,7 @@ impl Token {
} }
} }
/// Is this token a reserved symbol? /// Is this token a reserved keyword or symbol?
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn is_reserved(&self) -> bool { pub const fn is_reserved(&self) -> bool {

View File

@ -343,7 +343,7 @@ fn test_closures_external() -> Result<(), Box<EvalAltResult>> {
// Closure 'f' captures: the engine, the AST, and the curried function pointer // Closure 'f' captures: the engine, the AST, and the curried function pointer
let f = move |x: INT| fn_ptr.call_dynamic(&context, None, [x.into()]); let f = move |x: INT| fn_ptr.call_dynamic(&context, None, [x.into()]);
assert_eq!(f(42)?.as_string(), Ok("hello42".to_string())); assert_eq!(f(42)?.into_string(), Ok("hello42".to_string()));
Ok(()) Ok(())
} }

View File

@ -15,7 +15,7 @@ fn test_constant() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
assert!(matches!( assert!(matches!(
*engine.consume("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"), *engine.run("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"),
EvalAltResult::ErrorAssignmentToConstant(x, _) if x == "x" EvalAltResult::ErrorAssignmentToConstant(x, _) if x == "x"
)); ));
@ -30,7 +30,7 @@ fn test_constant_scope() -> Result<(), Box<EvalAltResult>> {
scope.push_constant("x", 42 as INT); scope.push_constant("x", 42 as INT);
assert!(matches!( assert!(matches!(
*engine.consume_with_scope(&mut scope, "x = 1").expect_err("expects error"), *engine.run_with_scope(&mut scope, "x = 1").expect_err("expects error"),
EvalAltResult::ErrorAssignmentToConstant(x, _) if x == "x" EvalAltResult::ErrorAssignmentToConstant(x, _) if x == "x"
)); ));
@ -80,7 +80,7 @@ fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.consume( .run(
" "
const MY_NUMBER = new_ts(); const MY_NUMBER = new_ts();
MY_NUMBER.value = 42; MY_NUMBER.value = 42;
@ -118,7 +118,7 @@ fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.consume_with_scope(&mut scope, "MY_NUMBER.value = 42;") .run_with_scope(&mut scope, "MY_NUMBER.value = 42;")
.expect_err("should error"), .expect_err("should error"),
EvalAltResult::ErrorAssignmentToConstant(_, _) EvalAltResult::ErrorAssignmentToConstant(_, _)
)); ));

View File

@ -1,10 +1,12 @@
use rhai::{Dynamic, Engine, EvalAltResult, LexError, ParseErrorType, Position, INT}; use rhai::{
Dynamic, Engine, EvalAltResult, ImmutableString, LexError, ParseErrorType, Position, Scope, INT,
};
#[test] #[test]
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> { fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.consume("while false {}")?; engine.run("while false {}")?;
// Disable 'while' and make sure it still works with custom syntax // Disable 'while' and make sure it still works with custom syntax
engine.disable_symbol("while"); engine.disable_symbol("while");
@ -19,21 +21,32 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.register_custom_syntax( engine.register_custom_syntax(
&[ &[
"exec", "[", "$ident$", ";", "$int$", "]", "->", "$block$", "while", "$expr$", "exec", "[", "$ident$", "$symbol$", "$int$", "]", "->", "$block$", "while", "$expr$",
], ],
true, true,
|context, inputs| { |context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_variable_name().unwrap().to_string();
let max = inputs[1].get_literal_value::<INT>().unwrap(); let op = inputs[1].get_literal_value::<ImmutableString>().unwrap();
let stmt = &inputs[2]; let max = inputs[2].get_literal_value::<INT>().unwrap();
let condition = &inputs[3]; let stmt = &inputs[3];
let condition = &inputs[4];
context.scope_mut().push(var_name.clone(), 0 as INT); context.scope_mut().push(var_name.clone(), 0 as INT);
let mut count: INT = 0; let mut count: INT = 0;
loop { loop {
if count >= max { let done = match op.as_str() {
"<" => count >= max,
"<=" => count > max,
">" => count <= max,
">=" => count < max,
"==" => count != max,
"!=" => count == max,
_ => return Err(format!("Unsupported operator: {}", op).into()),
};
if done {
break; break;
} }
@ -64,11 +77,18 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
}, },
)?; )?;
assert!(matches!(
*engine
.run("let foo = (exec [x<<15] -> { x += 2 } while x < 42) * 10;")
.expect_err("should error"),
EvalAltResult::ErrorRuntime(_, _)
));
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
" "
let x = 0; let x = 0;
let foo = (exec [x;15] -> { x += 2 } while x < 42) * 10; let foo = (exec [x<15] -> { x += 2 } while x < 42) * 10;
foo foo
" "
)?, )?,
@ -78,7 +98,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>( engine.eval::<INT>(
" "
let x = 0; let x = 0;
exec [x;100] -> { x += 1 } while x < 42; exec [x<100] -> { x += 1 } while x < 42;
x x
" "
)?, )?,
@ -87,7 +107,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
" "
exec [x;100] -> { x += 1 } while x < 42; exec [x<100] -> { x += 1 } while x < 42;
x x
" "
)?, )?,
@ -97,7 +117,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>( engine.eval::<INT>(
" "
let foo = 123; let foo = 123;
exec [x;15] -> { x += 1 } while x < 42; exec [x<15] -> { x += 1 } while x < 42;
foo + x + x1 + x2 + x3 foo + x + x1 + x2 + x3
" "
)?, )?,
@ -116,6 +136,51 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
)) ))
); );
// Check self-termination
engine
.register_custom_syntax(&["test1", "$block$"], true, |_, _| Ok(Dynamic::UNIT))?
.register_custom_syntax(&["test2", "}"], true, |_, _| Ok(Dynamic::UNIT))?
.register_custom_syntax(&["test3", ";"], true, |_, _| Ok(Dynamic::UNIT))?;
assert_eq!(engine.eval::<INT>("test1 { x = y + z; } 42")?, 42);
assert_eq!(engine.eval::<INT>("test2 } 42")?, 42);
assert_eq!(engine.eval::<INT>("test3; 42")?, 42);
// Register the custom syntax: var x = ???
engine.register_custom_syntax(
&["var", "$ident$", "=", "$expr$"],
true,
|context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string();
let expr = &inputs[1];
// Evaluate the expression
let value = context.eval_expression_tree(expr)?;
if !context.scope().is_constant(&var_name).unwrap_or(false) {
context.scope_mut().set_value(var_name, value);
Ok(Dynamic::UNIT)
} else {
Err(format!("variable {} is constant", var_name).into())
}
},
)?;
let mut scope = Scope::new();
assert_eq!(
engine.eval_with_scope::<INT>(&mut scope, "var foo = 42; foo")?,
42
);
assert_eq!(scope.get_value::<INT>("foo"), Some(42));
assert_eq!(scope.len(), 1);
assert_eq!(
engine.eval_with_scope::<INT>(&mut scope, "var foo = 123; foo")?,
123
);
assert_eq!(scope.get_value::<INT>("foo"), Some(123));
assert_eq!(scope.len(), 1);
Ok(()) Ok(())
} }

View File

@ -208,7 +208,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.consume( .run(
" "
let x = #{}; let x = #{};
loop { x.a = x; } loop { x.a = x; }

View File

@ -368,7 +368,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
); );
assert!(matches!( assert!(matches!(
*engine *engine
.consume(r#"import "testing" as ttt; ttt::hidden()"#) .run(r#"import "testing" as ttt; ttt::hidden()"#)
.expect_err("should error"), .expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "ttt::hidden ()" EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "ttt::hidden ()"
)); ));
@ -498,7 +498,7 @@ fn test_module_ast_namespace2() -> Result<(), Box<EvalAltResult>> {
static_modules.insert("test_module", module); static_modules.insert("test_module", module);
engine.set_module_resolver(static_modules); engine.set_module_resolver(static_modules);
engine.consume(SCRIPT)?; engine.run(SCRIPT)?;
Ok(()) Ok(())
} }

View File

@ -87,11 +87,11 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
{ {
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; a.foo")?, 1); assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; a.foo")?, 1);
engine.consume("const A = [1, 2, 3]; A.no_effect(42);")?; engine.run("const A = [1, 2, 3]; A.no_effect(42);")?;
engine.consume("const A = [1, 2, 3]; A.no_effect = 42;")?; engine.run("const A = [1, 2, 3]; A.no_effect = 42;")?;
assert!( assert!(
matches!(*engine.consume("const A = [1, 2, 3]; A.test(42);").expect_err("should error"), matches!(*engine.run("const A = [1, 2, 3]; A.test(42);").expect_err("should error"),
EvalAltResult::ErrorAssignmentToConstant(x, _) if x == "array") EvalAltResult::ErrorAssignmentToConstant(x, _) if x == "array")
) )
} }

View File

@ -50,10 +50,10 @@ fn test_print_debug() -> Result<(), Box<EvalAltResult>> {
}); });
// Evaluate script // Evaluate script
engine.consume("print(40 + 2)")?; engine.run("print(40 + 2)")?;
let mut ast = engine.compile(r#"let x = "hello!"; debug(x)"#)?; let mut ast = engine.compile(r#"let x = "hello!"; debug(x)"#)?;
ast.set_source("world"); ast.set_source("world");
engine.consume_ast(&ast)?; engine.run_ast(&ast)?;
// 'logbook' captures all the 'print' and 'debug' output // 'logbook' captures all the 'print' and 'debug' output
assert_eq!(logbook.read().unwrap().len(), 2); assert_eq!(logbook.read().unwrap().len(), 2);
@ -96,7 +96,7 @@ fn test_print_custom_type() -> Result<(), Box<EvalAltResult>> {
.register_fn("debug", |x: &mut MyStruct| x.to_string()) .register_fn("debug", |x: &mut MyStruct| x.to_string())
.register_fn("new_ts", || MyStruct { field: 42 }); .register_fn("new_ts", || MyStruct { field: 42 });
engine.consume("let x = new_ts(); debug(x);")?; engine.run("let x = new_ts(); debug(x);")?;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
assert_eq!( assert_eq!(

View File

@ -102,7 +102,7 @@ fn test_serde_ser_struct() -> Result<(), Box<EvalAltResult>> {
assert!(obj["b"].as_bool().unwrap()); assert!(obj["b"].as_bool().unwrap());
assert_eq!(Ok(42), map["int"].as_int()); assert_eq!(Ok(42), map["int"].as_int());
assert_eq!(seq.len(), 3); assert_eq!(seq.len(), 3);
assert_eq!("kitty", seq.remove(1).as_string().unwrap()); assert_eq!("kitty", seq.remove(1).into_string().unwrap());
Ok(()) Ok(())
} }
@ -116,10 +116,10 @@ fn test_serde_ser_unit_enum() -> Result<(), Box<EvalAltResult>> {
} }
let d = to_dynamic(MyEnum::VariantFoo)?; let d = to_dynamic(MyEnum::VariantFoo)?;
assert_eq!("VariantFoo", d.as_string().unwrap()); assert_eq!("VariantFoo", d.into_string().unwrap());
let d = to_dynamic(MyEnum::VariantBar)?; let d = to_dynamic(MyEnum::VariantBar)?;
assert_eq!("VariantBar", d.as_string().unwrap()); assert_eq!("VariantBar", d.into_string().unwrap());
Ok(()) Ok(())
} }
@ -145,7 +145,7 @@ fn test_serde_ser_externally_tagged_enum() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
"VariantUnit", "VariantUnit",
to_dynamic(MyEnum::VariantUnit)? to_dynamic(MyEnum::VariantUnit)?
.as_immutable_string() .into_immutable_string()
.unwrap() .unwrap()
.as_str() .as_str()
); );
@ -203,7 +203,7 @@ fn test_serde_ser_internally_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantEmptyStruct", "VariantEmptyStruct",
map.remove("tag") map.remove("tag")
.unwrap() .unwrap()
.as_immutable_string() .into_immutable_string()
.unwrap() .unwrap()
.as_str() .as_str()
); );
@ -214,7 +214,7 @@ fn test_serde_ser_internally_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantStruct", "VariantStruct",
map.remove("tag") map.remove("tag")
.unwrap() .unwrap()
.as_immutable_string() .into_immutable_string()
.unwrap() .unwrap()
.as_str() .as_str()
); );
@ -247,7 +247,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantUnit", "VariantUnit",
map.remove("tag") map.remove("tag")
.unwrap() .unwrap()
.as_immutable_string() .into_immutable_string()
.unwrap() .unwrap()
.as_str() .as_str()
); );
@ -260,7 +260,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantUnitTuple", "VariantUnitTuple",
map.remove("tag") map.remove("tag")
.unwrap() .unwrap()
.as_immutable_string() .into_immutable_string()
.unwrap() .unwrap()
.as_str() .as_str()
); );
@ -274,7 +274,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantNewtype", "VariantNewtype",
map.remove("tag") map.remove("tag")
.unwrap() .unwrap()
.as_immutable_string() .into_immutable_string()
.unwrap() .unwrap()
.as_str() .as_str()
); );
@ -289,7 +289,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantTuple", "VariantTuple",
map.remove("tag") map.remove("tag")
.unwrap() .unwrap()
.as_immutable_string() .into_immutable_string()
.unwrap() .unwrap()
.as_str() .as_str()
); );
@ -305,7 +305,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
"VariantEmptyStruct", "VariantEmptyStruct",
map.remove("tag") map.remove("tag")
.unwrap() .unwrap()
.as_immutable_string() .into_immutable_string()
.unwrap() .unwrap()
.as_str() .as_str()
); );
@ -316,7 +316,7 @@ fn test_serde_ser_adjacently_tagged_enum() -> Result<(), Box<EvalAltResult>> {
let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::<Map>(); let mut map = to_dynamic(MyEnum::VariantStruct { a: 123 })?.cast::<Map>();
assert_eq!( assert_eq!(
"VariantStruct", "VariantStruct",
map.remove("tag").unwrap().as_string().unwrap() map.remove("tag").unwrap().into_string().unwrap()
); );
let mut map_inner = map.remove("content").unwrap().cast::<Map>(); let mut map_inner = map.remove("content").unwrap().cast::<Map>();
assert!(map.is_empty()); assert!(map.is_empty());

View File

@ -74,7 +74,7 @@ fn test_side_effects_print() -> Result<(), Box<EvalAltResult>> {
let logger = result.clone(); let logger = result.clone();
engine.on_print(move |s| logger.write().unwrap().push_str(s)); engine.on_print(move |s| logger.write().unwrap().push_str(s));
engine.consume("print(40 + 2);")?; engine.run("print(40 + 2);")?;
assert_eq!(*result.read().unwrap(), "42"); assert_eq!(*result.read().unwrap(), "42");
Ok(()) Ok(())

View File

@ -57,9 +57,9 @@ fn test_infinite_loops() -> Result<(), Box<EvalAltResult>> {
engine.set_max_operations(1024); engine.set_max_operations(1024);
assert!(engine.consume("loop {}").is_err()); assert!(engine.run("loop {}").is_err());
assert!(engine.consume("while true {}").is_err()); assert!(engine.run("while true {}").is_err());
assert!(engine.consume("do {} while true").is_err()); assert!(engine.run("do {} while true").is_err());
Ok(()) Ok(())
} }