commit
4d844019ba
13
CHANGELOG.md
13
CHANGELOG.md
@ -4,16 +4,22 @@ Rhai Release Notes
|
|||||||
Version 1.13.0
|
Version 1.13.0
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
This version attempts a number of optimizations that may yield small speed improvements:
|
||||||
|
|
||||||
|
* Simple operators (e.g. integer arithmetic) are inlined to avoid the overhead of a function call.
|
||||||
|
* The tokenizer uses pre-calculated tables (generated by GNU `gperf`) for keyword recognition.
|
||||||
|
* A black-arts trick (see `Engine::black_box`) is used to prevent LLVM from optimizing hand-tuned AST node matches back into a lookup table, which messes up branch prediction on modern CPU's.
|
||||||
|
|
||||||
Bug fixes
|
Bug fixes
|
||||||
---------
|
---------
|
||||||
|
|
||||||
* Complex indexing/dotting chains now parse correctly, for example: `a[b][c[d]].e`
|
* Complex indexing/dotting chains now parse correctly, for example: `a[b][c[d]].e`
|
||||||
* `map` and `filter` for arrays are marked `pure`. Warnings are added to the documentation of pure array methods that take `this` closures.
|
* `map` and `filter` for arrays are marked `pure`. Warnings are added to the documentation of pure array methods that take `this` closures.
|
||||||
* Syntax such as `foo.bar::baz` no longer panics, but returns a proper parse error.
|
* Syntax such as `foo.bar::baz` no longer panics, but returns a proper parse error.
|
||||||
* `x += y` where `x` and `y` are `char` now works correctly.
|
|
||||||
* Expressions such as `!inside` now parses correctly instead of as `!in` followed by `side`.
|
* Expressions such as `!inside` now parses correctly instead of as `!in` followed by `side`.
|
||||||
* Custom syntax starting with symbols now works correctly and no longer raises a parse error.
|
* Custom syntax starting with symbols now works correctly and no longer raises a parse error.
|
||||||
* Comparing different custom types now works correctly when the appropriate comparison operators are registered.
|
* Comparing different custom types now works correctly when the appropriate comparison operators are registered.
|
||||||
|
* Some op-assignments, such as `x += y` where `x` and `y` are `char`, now work correctly instead of failing silently.
|
||||||
* Op-assignments to bit flags or bit ranges now work correctly.
|
* Op-assignments to bit flags or bit ranges now work correctly.
|
||||||
|
|
||||||
Potentially breaking changes
|
Potentially breaking changes
|
||||||
@ -30,8 +36,9 @@ Enhancements
|
|||||||
* The functions `min` and `max` are added for numbers.
|
* The functions `min` and `max` are added for numbers.
|
||||||
* Range cases in `switch` statements now also match floating-point and decimal values. In order to support this, however, small numeric ranges cases are no longer unrolled.
|
* Range cases in `switch` statements now also match floating-point and decimal values. In order to support this, however, small numeric ranges cases are no longer unrolled.
|
||||||
* Loading a module via `import` now gives the module access to the current scope, including variables and constants defined inside.
|
* Loading a module via `import` now gives the module access to the current scope, including variables and constants defined inside.
|
||||||
* Some very simple operator calls (e.g. integer add) are short-circuited to avoid the overhead of a function call, resulting in a small speed improvement.
|
* Some very simple operator calls (e.g. integer add) are inlined to avoid the overhead of a function call, resulting in a small speed improvement.
|
||||||
* The tokenizer now uses table-driven keyword recognizers generated by GNU gperf. At least _theoretically_ it should be faster...
|
* The tokenizer now uses table-driven keyword recognizers generated by GNU `gperf`. At least _theoretically_ it should be faster...
|
||||||
|
* The field `isAnonymous` is added to JSON functions metadata.
|
||||||
|
|
||||||
|
|
||||||
Version 1.12.0
|
Version 1.12.0
|
||||||
|
@ -242,7 +242,7 @@ impl Engine {
|
|||||||
ast.resolver().cloned(),
|
ast.resolver().cloned(),
|
||||||
);
|
);
|
||||||
|
|
||||||
auto_restore!(global => move |g| {
|
auto_restore! { global => move |g| {
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
{
|
||||||
g.embedded_module_resolver = orig_embedded_module_resolver;
|
g.embedded_module_resolver = orig_embedded_module_resolver;
|
||||||
@ -252,7 +252,7 @@ impl Engine {
|
|||||||
g.lib.truncate(orig_lib_len);
|
g.lib.truncate(orig_lib_len);
|
||||||
|
|
||||||
g.source = orig_source;
|
g.source = orig_source;
|
||||||
});
|
}}
|
||||||
|
|
||||||
let r = self.eval_global_statements(global, caches, scope, ast.statements())?;
|
let r = self.eval_global_statements(global, caches, scope, ast.statements())?;
|
||||||
|
|
||||||
|
@ -688,7 +688,7 @@ impl Engine {
|
|||||||
let reset =
|
let reset =
|
||||||
self.run_debugger_with_reset(global, caches, scope, this_ptr, rhs)?;
|
self.run_debugger_with_reset(global, caches, scope, this_ptr, rhs)?;
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
|
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
|
||||||
|
|
||||||
let crate::ast::FnCallExpr {
|
let crate::ast::FnCallExpr {
|
||||||
name, hashes, args, ..
|
name, hashes, args, ..
|
||||||
@ -857,7 +857,7 @@ impl Engine {
|
|||||||
let reset = self
|
let reset = self
|
||||||
.run_debugger_with_reset(global, caches, scope, _tp, _node)?;
|
.run_debugger_with_reset(global, caches, scope, _tp, _node)?;
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
|
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
|
||||||
|
|
||||||
let crate::ast::FnCallExpr {
|
let crate::ast::FnCallExpr {
|
||||||
name, hashes, args, ..
|
name, hashes, args, ..
|
||||||
@ -977,7 +977,7 @@ impl Engine {
|
|||||||
global, caches, scope, _tp, _node,
|
global, caches, scope, _tp, _node,
|
||||||
)?;
|
)?;
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
|
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
|
||||||
|
|
||||||
let crate::ast::FnCallExpr {
|
let crate::ast::FnCallExpr {
|
||||||
name, hashes, args, ..
|
name, hashes, args, ..
|
||||||
|
@ -243,7 +243,7 @@ impl Engine {
|
|||||||
let reset =
|
let reset =
|
||||||
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
|
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), expr)?;
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
|
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
|
||||||
|
|
||||||
self.track_operation(global, expr.position())?;
|
self.track_operation(global, expr.position())?;
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ impl Engine {
|
|||||||
global.scope_level += 1;
|
global.scope_level += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_restore!(global if restore_orig_state => move |g| {
|
auto_restore! { global if restore_orig_state => move |g| {
|
||||||
g.scope_level -= 1;
|
g.scope_level -= 1;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -63,7 +63,7 @@ impl Engine {
|
|||||||
// The impact of new local variables goes away at the end of a block
|
// The impact of new local variables goes away at the end of a block
|
||||||
// because any new variables introduced will go out of scope
|
// because any new variables introduced will go out of scope
|
||||||
g.always_search_scope = orig_always_search_scope;
|
g.always_search_scope = orig_always_search_scope;
|
||||||
});
|
}}
|
||||||
|
|
||||||
// Pop new function resolution caches at end of block
|
// Pop new function resolution caches at end of block
|
||||||
auto_restore! {
|
auto_restore! {
|
||||||
@ -271,7 +271,7 @@ impl Engine {
|
|||||||
let reset =
|
let reset =
|
||||||
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), stmt)?;
|
self.run_debugger_with_reset(global, caches, scope, this_ptr.as_deref_mut(), stmt)?;
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
|
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
|
||||||
|
|
||||||
self.track_operation(global, stmt.position())?;
|
self.track_operation(global, stmt.position())?;
|
||||||
|
|
||||||
|
@ -658,7 +658,7 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let orig_source = mem::replace(&mut global.source, source.clone());
|
let orig_source = mem::replace(&mut global.source, source.clone());
|
||||||
auto_restore!(global => move |g| g.source = orig_source);
|
auto_restore! { global => move |g| g.source = orig_source }
|
||||||
|
|
||||||
return if _is_method_call {
|
return if _is_method_call {
|
||||||
// Method call of script function - map first argument to `this`
|
// Method call of script function - map first argument to `this`
|
||||||
@ -686,7 +686,7 @@ impl Engine {
|
|||||||
backup.change_first_arg_to_copy(_args);
|
backup.change_first_arg_to_copy(_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_restore!(args = (_args) if swap => move |a| backup.restore_first_arg(a));
|
auto_restore! { args = (_args) if swap => move |a| backup.restore_first_arg(a) }
|
||||||
|
|
||||||
self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos)
|
self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos)
|
||||||
}
|
}
|
||||||
@ -730,7 +730,7 @@ impl Engine {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset));
|
auto_restore! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }
|
||||||
|
|
||||||
self.eval_expr(global, caches, scope, this_ptr, arg_expr)
|
self.eval_expr(global, caches, scope, this_ptr, arg_expr)
|
||||||
.map(|r| (r, arg_expr.start_position()))
|
.map(|r| (r, arg_expr.start_position()))
|
||||||
@ -1457,7 +1457,7 @@ impl Engine {
|
|||||||
let scope = &mut Scope::new();
|
let scope = &mut Scope::new();
|
||||||
|
|
||||||
let orig_source = mem::replace(&mut global.source, module.id_raw().cloned());
|
let orig_source = mem::replace(&mut global.source, module.id_raw().cloned());
|
||||||
auto_restore!(global => move |g| g.source = orig_source);
|
auto_restore! { global => move |g| g.source = orig_source }
|
||||||
|
|
||||||
self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos)
|
self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos)
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,8 @@ struct FnMetadata<'a> {
|
|||||||
pub namespace: crate::FnNamespace,
|
pub namespace: crate::FnNamespace,
|
||||||
pub access: FnAccess,
|
pub access: FnAccess,
|
||||||
pub name: &'a str,
|
pub name: &'a str,
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub is_anonymous: bool,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub typ: FnType,
|
pub typ: FnType,
|
||||||
pub num_params: usize,
|
pub num_params: usize,
|
||||||
@ -83,6 +85,8 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> {
|
|||||||
namespace: info.metadata.namespace,
|
namespace: info.metadata.namespace,
|
||||||
access: info.metadata.access,
|
access: info.metadata.access,
|
||||||
name: &info.metadata.name,
|
name: &info.metadata.name,
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
is_anonymous: crate::parser::is_anonymous_fn(&info.metadata.name),
|
||||||
typ,
|
typ,
|
||||||
num_params: info.metadata.num_params,
|
num_params: info.metadata.num_params,
|
||||||
params: info
|
params: info
|
||||||
|
@ -305,7 +305,7 @@ impl fmt::Display for Token {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Table-driven keyword recognizer generated by GNU gperf on the file `tools/keywords.txt`.
|
// Table-driven keyword recognizer generated by GNU `gperf` on the file `tools/keywords.txt`.
|
||||||
//
|
//
|
||||||
// When adding new keywords, make sure to update `tools/keywords.txt` and re-generate this.
|
// When adding new keywords, make sure to update `tools/keywords.txt` and re-generate this.
|
||||||
|
|
||||||
@ -507,7 +507,7 @@ static KEYWORDS_LIST: [(&str, Token); 153] = [
|
|||||||
("#{", Token::MapStart),
|
("#{", Token::MapStart),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Table-driven reserved symbol recognizer generated by GNU gperf on the file `tools/reserved.txt`.
|
// Table-driven reserved symbol recognizer generated by GNU `gperf` on the file `tools/reserved.txt`.
|
||||||
//
|
//
|
||||||
// When adding new reserved symbols, make sure to update `tools/reserved.txt` and re-generate this.
|
// When adding new reserved symbols, make sure to update `tools/reserved.txt` and re-generate this.
|
||||||
|
|
||||||
@ -872,7 +872,7 @@ impl Token {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn lookup_symbol_from_syntax(syntax: &str) -> Option<Self> {
|
pub fn lookup_symbol_from_syntax(syntax: &str) -> Option<Self> {
|
||||||
// This implementation is based upon a pre-calculated table generated
|
// This implementation is based upon a pre-calculated table generated
|
||||||
// by GNU gperf on the list of keywords.
|
// by GNU `gperf` on the list of keywords.
|
||||||
let utf8 = syntax.as_bytes();
|
let utf8 = syntax.as_bytes();
|
||||||
let len = utf8.len();
|
let len = utf8.len();
|
||||||
let mut hash_val = len;
|
let mut hash_val = len;
|
||||||
@ -893,8 +893,8 @@ impl Token {
|
|||||||
|
|
||||||
match KEYWORDS_LIST[hash_val] {
|
match KEYWORDS_LIST[hash_val] {
|
||||||
(_, Token::EOF) => None,
|
(_, Token::EOF) => None,
|
||||||
// Fail early to avoid calling memcmp()
|
// Fail early to avoid calling memcmp().
|
||||||
// Since we are already working with bytes, mind as well check the first one
|
// Since we are already working with bytes, mind as well check the first one.
|
||||||
(s, ref t) if s.len() == len && s.as_bytes()[0] == utf8[0] && s == syntax => {
|
(s, ref t) if s.len() == len && s.as_bytes()[0] == utf8[0] && s == syntax => {
|
||||||
Some(t.clone())
|
Some(t.clone())
|
||||||
}
|
}
|
||||||
@ -2312,7 +2312,7 @@ pub fn is_id_continue(x: char) -> bool {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_reserved_keyword_or_symbol(syntax: &str) -> (bool, bool, bool) {
|
pub fn is_reserved_keyword_or_symbol(syntax: &str) -> (bool, bool, bool) {
|
||||||
// This implementation is based upon a pre-calculated table generated
|
// This implementation is based upon a pre-calculated table generated
|
||||||
// by GNU gperf on the list of keywords.
|
// by GNU `gperf` on the list of keywords.
|
||||||
let utf8 = syntax.as_bytes();
|
let utf8 = syntax.as_bytes();
|
||||||
let len = utf8.len();
|
let len = utf8.len();
|
||||||
let rounds = len.min(3);
|
let rounds = len.min(3);
|
||||||
@ -2333,8 +2333,8 @@ pub fn is_reserved_keyword_or_symbol(syntax: &str) -> (bool, bool, bool) {
|
|||||||
match RESERVED_LIST[hash_val] {
|
match RESERVED_LIST[hash_val] {
|
||||||
("", ..) => (false, false, false),
|
("", ..) => (false, false, false),
|
||||||
(s, true, a, b) => (
|
(s, true, a, b) => (
|
||||||
// Fail early to avoid calling memcmp()
|
// Fail early to avoid calling memcmp().
|
||||||
// Since we are already working with bytes, mind as well check the first one
|
// Since we are already working with bytes, mind as well check the first one.
|
||||||
s.len() == len && s.as_bytes()[0] == utf8[0] && s == syntax,
|
s.len() == len && s.as_bytes()[0] == utf8[0] && s == syntax,
|
||||||
a,
|
a,
|
||||||
b,
|
b,
|
||||||
|
Loading…
Reference in New Issue
Block a user