commit
82cc4b5553
@ -17,6 +17,11 @@ Bug fixes
|
|||||||
* Exporting a variable that contains a local function pointer (including anonymous function or closure) now raises a runtime error.
|
* Exporting a variable that contains a local function pointer (including anonymous function or closure) now raises a runtime error.
|
||||||
* Full optimization is now skipped for method calls.
|
* Full optimization is now skipped for method calls.
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* [Type aliases](https://doc.rust-lang.org/reference/items/type-aliases.html) in plugin modules are now used as friendly names for custom types. This makes plugin modules more self-contained when they are used to define a custom type's API.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@ -26,6 +31,10 @@ Enhancements
|
|||||||
* Separation of constants in function calls is removed as its performance benefit is dubious.
|
* Separation of constants in function calls is removed as its performance benefit is dubious.
|
||||||
* A function `sleep` is added to block the current thread by a specified number of seconds.
|
* A function `sleep` is added to block the current thread by a specified number of seconds.
|
||||||
* `Scope::set_alias` is added to export a variable under a particular alias name.
|
* `Scope::set_alias` is added to export a variable under a particular alias name.
|
||||||
|
* `starts_with` and `ends_with` are added for strings.
|
||||||
|
* Variables in modules registered via `register_global_module` can now be accessed in the global namespace.
|
||||||
|
* `Dynamic::into_read_only` is added to convert a `Dynamic` value into constant.
|
||||||
|
* `Module` now holds a collection of custom types with an API.
|
||||||
|
|
||||||
|
|
||||||
Version 1.5.0
|
Version 1.5.0
|
||||||
|
@ -61,6 +61,7 @@ Protected against attacks
|
|||||||
* [Sand-boxed](https://rhai.rs/book/safety/sandbox.html) - the scripting engine, if declared immutable, cannot mutate the containing environment unless [explicitly permitted](https://rhai.rs/book/patterns/control.html).
|
* [Sand-boxed](https://rhai.rs/book/safety/sandbox.html) - the scripting engine, if declared immutable, cannot mutate the containing environment unless [explicitly permitted](https://rhai.rs/book/patterns/control.html).
|
||||||
* Rugged - protected against malicious attacks (such as [stack-overflow](https://rhai.rs/book/safety/max-call-stack.html), [over-sized data](https://rhai.rs/book/safety/max-string-size.html), and [runaway scripts](https://rhai.rs/book/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts.
|
* Rugged - protected against malicious attacks (such as [stack-overflow](https://rhai.rs/book/safety/max-call-stack.html), [over-sized data](https://rhai.rs/book/safety/max-string-size.html), and [runaway scripts](https://rhai.rs/book/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts.
|
||||||
* Track script evaluation [progress](https://rhai.rs/book/safety/progress.html) and manually terminate a script run.
|
* Track script evaluation [progress](https://rhai.rs/book/safety/progress.html) and manually terminate a script run.
|
||||||
|
* Passes Miri.
|
||||||
|
|
||||||
|
|
||||||
For those who actually want their own language
|
For those who actually want their own language
|
||||||
|
@ -16,7 +16,7 @@ default = []
|
|||||||
metadata = []
|
metadata = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rhai = { path = "..", version = "1.4", features = ["metadata"] }
|
rhai = { path = "..", version = "1.6", features = ["metadata"] }
|
||||||
trybuild = "1"
|
trybuild = "1"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -269,7 +269,7 @@ pub fn exported_module(module_path: proc_macro::TokenStream) -> proc_macro::Toke
|
|||||||
pub fn combine_with_exported_module(args: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn combine_with_exported_module(args: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
match crate::register::parse_register_macro(args) {
|
match crate::register::parse_register_macro(args) {
|
||||||
Ok((module_expr, _export_name, module_path)) => proc_macro::TokenStream::from(quote! {
|
Ok((module_expr, _export_name, module_path)) => proc_macro::TokenStream::from(quote! {
|
||||||
#module_path::rhai_generate_into_module(#module_expr, true);
|
#module_path::rhai_generate_into_module(#module_expr, true)
|
||||||
}),
|
}),
|
||||||
Err(e) => e.to_compile_error().into(),
|
Err(e) => e.to_compile_error().into(),
|
||||||
}
|
}
|
||||||
@ -303,7 +303,7 @@ pub fn register_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenS
|
|||||||
Ok((engine_expr, export_name, rust_mod_path)) => {
|
Ok((engine_expr, export_name, rust_mod_path)) => {
|
||||||
let gen_mod_path = crate::register::generated_module_path(&rust_mod_path);
|
let gen_mod_path = crate::register::generated_module_path(&rust_mod_path);
|
||||||
proc_macro::TokenStream::from(quote! {
|
proc_macro::TokenStream::from(quote! {
|
||||||
#engine_expr.register_result_fn(#export_name, #gen_mod_path::dynamic_result_fn);
|
#engine_expr.register_result_fn(#export_name, #gen_mod_path::dynamic_result_fn)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Err(e) => e.to_compile_error().into(),
|
Err(e) => e.to_compile_error().into(),
|
||||||
@ -352,7 +352,7 @@ pub fn set_exported_fn(args: proc_macro::TokenStream) -> proc_macro::TokenStream
|
|||||||
#module_expr.set_fn(#export_name, FnNamespace::Internal, FnAccess::Public,
|
#module_expr.set_fn(#export_name, FnNamespace::Internal, FnAccess::Public,
|
||||||
#param_names,
|
#param_names,
|
||||||
&#gen_mod_path::Token::param_types(),
|
&#gen_mod_path::Token::param_types(),
|
||||||
#gen_mod_path::Token().into());
|
#gen_mod_path::Token().into())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Err(e) => e.to_compile_error().into(),
|
Err(e) => e.to_compile_error().into(),
|
||||||
@ -401,7 +401,7 @@ pub fn set_exported_global_fn(args: proc_macro::TokenStream) -> proc_macro::Toke
|
|||||||
#module_expr.set_fn(#export_name, FnNamespace::Global, FnAccess::Public,
|
#module_expr.set_fn(#export_name, FnNamespace::Global, FnAccess::Public,
|
||||||
#param_names,
|
#param_names,
|
||||||
&#gen_mod_path::Token::param_types(),
|
&#gen_mod_path::Token::param_types(),
|
||||||
#gen_mod_path::Token().into());
|
#gen_mod_path::Token().into())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Err(e) => e.to_compile_error().into(),
|
Err(e) => e.to_compile_error().into(),
|
||||||
|
@ -140,20 +140,18 @@ impl Parse for Module {
|
|||||||
for item in content.iter() {
|
for item in content.iter() {
|
||||||
match item {
|
match item {
|
||||||
syn::Item::Const(syn::ItemConst {
|
syn::Item::Const(syn::ItemConst {
|
||||||
vis,
|
vis: syn::Visibility::Public(..),
|
||||||
ref expr,
|
ref expr,
|
||||||
ident,
|
ident,
|
||||||
attrs,
|
attrs,
|
||||||
ty,
|
ty,
|
||||||
..
|
..
|
||||||
}) if matches!(vis, syn::Visibility::Public(..)) => {
|
}) => consts.push(ExportedConst {
|
||||||
consts.push(ExportedConst {
|
|
||||||
name: ident.to_string(),
|
name: ident.to_string(),
|
||||||
typ: ty.clone(),
|
typ: ty.clone(),
|
||||||
expr: expr.as_ref().clone(),
|
expr: expr.as_ref().clone(),
|
||||||
cfg_attrs: crate::attrs::collect_cfg_attr(&attrs),
|
cfg_attrs: crate::attrs::collect_cfg_attr(&attrs),
|
||||||
})
|
}),
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,18 +159,16 @@ impl Parse for Module {
|
|||||||
for item in content.iter() {
|
for item in content.iter() {
|
||||||
match item {
|
match item {
|
||||||
syn::Item::Type(syn::ItemType {
|
syn::Item::Type(syn::ItemType {
|
||||||
vis,
|
vis: syn::Visibility::Public(..),
|
||||||
ident,
|
ident,
|
||||||
attrs,
|
attrs,
|
||||||
ty,
|
ty,
|
||||||
..
|
..
|
||||||
}) if matches!(vis, syn::Visibility::Public(..)) => {
|
}) => custom_types.push(ExportedType {
|
||||||
custom_types.push(ExportedType {
|
|
||||||
name: ident.to_string(),
|
name: ident.to_string(),
|
||||||
typ: ty.clone(),
|
typ: ty.clone(),
|
||||||
cfg_attrs: crate::attrs::collect_cfg_attr(&attrs),
|
cfg_attrs: crate::attrs::collect_cfg_attr(&attrs),
|
||||||
})
|
}),
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,7 +178,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
crate::func::call::ensure_no_data_race(name, &mut args, false)?;
|
crate::func::call::ensure_no_data_race(name, &mut args, false)?;
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
self.call_script_fn(
|
||||||
scope,
|
scope,
|
||||||
global,
|
global,
|
||||||
state,
|
state,
|
||||||
@ -189,8 +189,6 @@ impl Engine {
|
|||||||
rewind_scope,
|
rewind_scope,
|
||||||
Position::NONE,
|
Position::NONE,
|
||||||
0,
|
0,
|
||||||
);
|
)
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,6 @@ impl Engine {
|
|||||||
|
|
||||||
let mut ast = self.compile_scripts_with_scope(scope, &[script])?;
|
let mut ast = self.compile_scripts_with_scope(scope, &[script])?;
|
||||||
|
|
||||||
if let Some(ref module_resolver) = self.module_resolver {
|
|
||||||
let mut resolver = StaticModuleResolver::new();
|
let mut resolver = StaticModuleResolver::new();
|
||||||
let mut imports = BTreeSet::new();
|
let mut imports = BTreeSet::new();
|
||||||
|
|
||||||
@ -125,16 +124,19 @@ impl Engine {
|
|||||||
while let Some(path) = imports.iter().next() {
|
while let Some(path) = imports.iter().next() {
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
|
|
||||||
match module_resolver.resolve_ast(self, None, &path, crate::Position::NONE) {
|
match self
|
||||||
Some(Ok(module_ast)) => {
|
.module_resolver
|
||||||
collect_imports(&module_ast, &resolver, &mut imports)
|
.resolve_ast(self, None, &path, crate::Position::NONE)
|
||||||
}
|
{
|
||||||
|
Some(Ok(module_ast)) => collect_imports(&module_ast, &resolver, &mut imports),
|
||||||
Some(err) => return err,
|
Some(err) => return err,
|
||||||
None => (),
|
None => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let module =
|
let module =
|
||||||
module_resolver.resolve(self, None, &path, crate::Position::NONE)?;
|
self.module_resolver
|
||||||
|
.resolve(self, None, &path, crate::Position::NONE)?;
|
||||||
|
|
||||||
let module = shared_take_or_clone(module);
|
let module = shared_take_or_clone(module);
|
||||||
|
|
||||||
imports.remove(&path);
|
imports.remove(&path);
|
||||||
@ -142,7 +144,6 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
ast.set_resolver(resolver);
|
ast.set_resolver(resolver);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ast)
|
Ok(ast)
|
||||||
}
|
}
|
||||||
@ -200,7 +201,11 @@ impl Engine {
|
|||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
scripts: impl AsRef<[S]>,
|
scripts: impl AsRef<[S]>,
|
||||||
) -> ParseResult<AST> {
|
) -> ParseResult<AST> {
|
||||||
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
|
self.compile_with_scope_and_optimization_level(
|
||||||
|
scope,
|
||||||
|
scripts,
|
||||||
|
self.options.optimization_level,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level.
|
/// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level.
|
||||||
///
|
///
|
||||||
@ -296,7 +301,12 @@ impl Engine {
|
|||||||
|
|
||||||
let mut peekable = stream.peekable();
|
let mut peekable = stream.peekable();
|
||||||
let mut state = ParseState::new(self, tokenizer_control);
|
let mut state = ParseState::new(self, tokenizer_control);
|
||||||
self.parse_global_expr(&mut peekable, &mut state, scope, self.optimization_level)
|
self.parse_global_expr(
|
||||||
|
&mut peekable,
|
||||||
|
&mut state,
|
||||||
|
scope,
|
||||||
|
self.options.optimization_level,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/// Parse a JSON string into an [object map][crate::Map].
|
/// Parse a JSON string into an [object map][crate::Map].
|
||||||
/// This is a light-weight alternative to using, say,
|
/// This is a light-weight alternative to using, say,
|
||||||
|
@ -67,7 +67,7 @@ impl Engine {
|
|||||||
let ast = self.compile_with_scope_and_optimization_level(
|
let ast = self.compile_with_scope_and_optimization_level(
|
||||||
scope,
|
scope,
|
||||||
&[script],
|
&[script],
|
||||||
self.optimization_level,
|
self.options.optimization_level,
|
||||||
)?;
|
)?;
|
||||||
self.eval_ast_with_scope(scope, &ast)
|
self.eval_ast_with_scope(scope, &ast)
|
||||||
}
|
}
|
||||||
@ -227,9 +227,9 @@ impl Engine {
|
|||||||
ast.as_ref(),
|
ast.as_ref(),
|
||||||
];
|
];
|
||||||
let lib = if lib.first().map(|m: &&Module| m.is_empty()).unwrap_or(true) {
|
let lib = if lib.first().map(|m: &&Module| m.is_empty()).unwrap_or(true) {
|
||||||
&lib[0..0]
|
&[]
|
||||||
} else {
|
} else {
|
||||||
&lib
|
&lib[..]
|
||||||
};
|
};
|
||||||
self.eval_global_statements(scope, global, &mut state, statements, lib, level)
|
self.eval_global_statements(scope, global, &mut state, statements, lib, level)
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self {
|
pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self {
|
||||||
self.print = Some(Box::new(callback));
|
self.print = Box::new(callback);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Override default action of `debug` (print to stdout using [`println!`])
|
/// Override default action of `debug` (print to stdout using [`println!`])
|
||||||
@ -332,7 +332,7 @@ impl Engine {
|
|||||||
&mut self,
|
&mut self,
|
||||||
callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static,
|
callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.debug = Some(Box::new(callback));
|
self.debug = Box::new(callback);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// _(debugging)_ Register a callback for debugging.
|
/// _(debugging)_ Register a callback for debugging.
|
||||||
|
@ -71,7 +71,7 @@ impl Engine {
|
|||||||
&mut self,
|
&mut self,
|
||||||
resolver: impl crate::ModuleResolver + 'static,
|
resolver: impl crate::ModuleResolver + 'static,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.module_resolver = Some(Box::new(resolver));
|
self.module_resolver = Box::new(resolver);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ impl Engine {
|
|||||||
/// Not available under `no_optimize`.
|
/// Not available under `no_optimize`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) -> &mut Self {
|
pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) -> &mut Self {
|
||||||
self.optimization_level = optimization_level;
|
self.options.optimization_level = optimization_level;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ impl Engine {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn optimization_level(&self) -> OptimizationLevel {
|
pub const fn optimization_level(&self) -> OptimizationLevel {
|
||||||
self.optimization_level
|
self.options.optimization_level
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize the [`AST`] with constants defined in an external Scope.
|
/// Optimize the [`AST`] with constants defined in an external Scope.
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
//! Settings for [`Engine`]'s language options.
|
//! Settings for [`Engine`]'s language options.
|
||||||
|
|
||||||
use crate::Engine;
|
use crate::{Engine, OptimizationLevel};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
/// A type containing all language options for the [`Engine`].
|
/// A type containing all language options for the [`Engine`].
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct LanguageOptions {
|
pub struct LanguageOptions {
|
||||||
|
/// Script optimization level.
|
||||||
|
pub optimization_level: OptimizationLevel,
|
||||||
/// Is `if`-expression allowed?
|
/// Is `if`-expression allowed?
|
||||||
pub allow_if_expr: bool,
|
pub allow_if_expr: bool,
|
||||||
/// Is `switch` expression allowed?
|
/// Is `switch` expression allowed?
|
||||||
@ -33,6 +35,11 @@ impl LanguageOptions {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
optimization_level: OptimizationLevel::Simple,
|
||||||
|
#[cfg(feature = "no_optimize")]
|
||||||
|
optimization_level: (),
|
||||||
|
|
||||||
allow_if_expr: true,
|
allow_if_expr: true,
|
||||||
allow_switch_expr: true,
|
allow_switch_expr: true,
|
||||||
allow_stmt_expr: true,
|
allow_stmt_expr: true,
|
||||||
|
@ -273,7 +273,7 @@ impl Engine {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) -> &mut Self {
|
pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) -> &mut Self {
|
||||||
self.custom_types.add_type::<T>(name);
|
self.global_namespace_mut().set_custom_type::<T>(name);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Register a custom type for use with the [`Engine`], with a pretty-print name
|
/// Register a custom type for use with the [`Engine`], with a pretty-print name
|
||||||
@ -289,7 +289,8 @@ impl Engine {
|
|||||||
name: impl Into<Identifier>,
|
name: impl Into<Identifier>,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
// Add the pretty-print type name into the map
|
// Add the pretty-print type name into the map
|
||||||
self.custom_types.add(fully_qualified_type_path, name);
|
self.global_namespace_mut()
|
||||||
|
.set_custom_type_raw(fully_qualified_type_path, name);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Register an type iterator for an iterable type with the [`Engine`].
|
/// Register an type iterator for an iterable type with the [`Engine`].
|
||||||
|
@ -30,7 +30,7 @@ impl Engine {
|
|||||||
&mut stream.peekable(),
|
&mut stream.peekable(),
|
||||||
&mut state,
|
&mut state,
|
||||||
scope,
|
scope,
|
||||||
self.optimization_level,
|
self.options.optimization_level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.run_ast_with_scope(scope, &ast)
|
self.run_ast_with_scope(scope, &ast)
|
||||||
|
@ -97,7 +97,6 @@ impl Engine {
|
|||||||
#[cfg(feature = "no_module")]
|
#[cfg(feature = "no_module")]
|
||||||
return None;
|
return None;
|
||||||
})
|
})
|
||||||
.or_else(|| self.custom_types.get(name))
|
|
||||||
.unwrap_or_else(|| map_std_type_name(name, true))
|
.unwrap_or_else(|| map_std_type_name(name, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,8 +118,18 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
self.custom_types
|
self.global_modules
|
||||||
.get(name)
|
.iter()
|
||||||
|
.find_map(|m| m.get_custom_type(name))
|
||||||
|
.or_else(|| {
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
return self
|
||||||
|
.global_sub_modules
|
||||||
|
.iter()
|
||||||
|
.find_map(|(_, m)| m.get_custom_type(name));
|
||||||
|
#[cfg(feature = "no_module")]
|
||||||
|
return None;
|
||||||
|
})
|
||||||
.unwrap_or_else(|| match name {
|
.unwrap_or_else(|| match name {
|
||||||
"INT" => return type_name::<crate::INT>(),
|
"INT" => return type_name::<crate::INT>(),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
@ -150,9 +150,10 @@ impl AST {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn source(&self) -> Option<&str> {
|
pub fn source(&self) -> Option<&str> {
|
||||||
match self.source.as_str() {
|
if self.source.is_empty() {
|
||||||
"" => None,
|
None
|
||||||
s => Some(s),
|
} else {
|
||||||
|
Some(self.source.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get a reference to the source.
|
/// Get a reference to the source.
|
||||||
|
@ -89,6 +89,26 @@ pub struct ConditionalStmtBlock {
|
|||||||
pub statements: StmtBlock,
|
pub statements: StmtBlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<B: Into<StmtBlock>> From<B> for ConditionalStmtBlock {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: B) -> Self {
|
||||||
|
Self {
|
||||||
|
condition: None,
|
||||||
|
statements: value.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Into<StmtBlock>> From<(Expr, B)> for ConditionalStmtBlock {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: (Expr, B)) -> Self {
|
||||||
|
Self {
|
||||||
|
condition: Some(value.0),
|
||||||
|
statements: value.1.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<B: Into<StmtBlock>> From<(Option<Expr>, B)> for ConditionalStmtBlock {
|
impl<B: Into<StmtBlock>> From<(Option<Expr>, B)> for ConditionalStmtBlock {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(value: (Option<Expr>, B)) -> Self {
|
fn from(value: (Option<Expr>, B)) -> Self {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use rhai::plugin::*;
|
||||||
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST, INT};
|
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST, INT};
|
||||||
use rustyline::config::Builder;
|
use rustyline::config::Builder;
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
@ -58,7 +59,7 @@ fn print_help() {
|
|||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
println!("functions => print all functions defined");
|
println!("functions => print all functions defined");
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
println!("json => output all functions in JSON format");
|
println!("json => output all functions to `metadata.json`");
|
||||||
println!("ast => print the last AST (optimized)");
|
println!("ast => print the last AST (optimized)");
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
println!("astu => print the last raw, un-optimized AST");
|
println!("astu => print the last raw, un-optimized AST");
|
||||||
@ -267,6 +268,41 @@ fn setup_editor() -> Editor<()> {
|
|||||||
rl
|
rl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[export_module]
|
||||||
|
mod sample_functions {
|
||||||
|
/// This is a sample function.
|
||||||
|
///
|
||||||
|
/// It takes two numbers and prints them to a string.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let result = test(42, 123);
|
||||||
|
///
|
||||||
|
/// print(result); // prints "42 123"
|
||||||
|
/// ```
|
||||||
|
pub fn test(x: INT, y: INT) -> String {
|
||||||
|
format!("{} {}", x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a sample method for integers.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let x = 42;
|
||||||
|
///
|
||||||
|
/// x.test(123, "hello");
|
||||||
|
///
|
||||||
|
/// print(x); // prints 170
|
||||||
|
/// ```
|
||||||
|
#[rhai_fn(name = "test")]
|
||||||
|
pub fn test2(x: &mut INT, y: INT, z: &str) {
|
||||||
|
*x += y + (z.len() as INT);
|
||||||
|
println!("{} {} {}", x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION"));
|
let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION"));
|
||||||
println!("{}", title);
|
println!("{}", title);
|
||||||
@ -296,12 +332,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register sample functions
|
// Register sample functions
|
||||||
engine
|
engine.register_global_module(exported_module!(sample_functions).into());
|
||||||
.register_fn("test", |x: INT, y: INT| format!("{} {}", x, y))
|
|
||||||
.register_fn("test", |x: &mut INT, y: INT, z: &str| {
|
|
||||||
*x += y;
|
|
||||||
println!("{} {} {}", x, y, z);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create scope
|
// Create scope
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
@ -463,12 +494,15 @@ fn main() {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
"json" => {
|
"json" => {
|
||||||
println!(
|
use std::io::Write;
|
||||||
"{}",
|
|
||||||
engine
|
let json = engine
|
||||||
.gen_fn_metadata_with_ast_to_json(&main_ast, true)
|
.gen_fn_metadata_with_ast_to_json(&main_ast, false)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
);
|
let mut f = std::fs::File::create("metadata.json")
|
||||||
|
.expect("Unable to create `metadata.json`");
|
||||||
|
f.write_all(json.as_bytes()).expect("Unable to write data");
|
||||||
|
println!("Functions metadata written to `metadata.json`.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
"!!" => {
|
"!!" => {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
//! Main module defining the script evaluation [`Engine`].
|
//! Main module defining the script evaluation [`Engine`].
|
||||||
|
|
||||||
use crate::api::custom_syntax::CustomSyntax;
|
use crate::api::custom_syntax::CustomSyntax;
|
||||||
|
use crate::api::options::LanguageOptions;
|
||||||
use crate::func::native::{
|
use crate::func::native::{
|
||||||
OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback,
|
OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback,
|
||||||
};
|
};
|
||||||
use crate::packages::{Package, StandardPackage};
|
use crate::packages::{Package, StandardPackage};
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::Token;
|
||||||
use crate::types::{dynamic::Union, CustomTypesCollection};
|
use crate::types::dynamic::Union;
|
||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, Identifier, ImmutableString, Module, OptimizationLevel, Position, RhaiResult, Shared,
|
Dynamic, Identifier, ImmutableString, Module, Position, RhaiResult, Shared, StaticVec,
|
||||||
StaticVec,
|
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -102,10 +102,7 @@ pub struct Engine {
|
|||||||
|
|
||||||
/// A module resolution service.
|
/// A module resolution service.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub(crate) module_resolver: Option<Box<dyn crate::ModuleResolver>>,
|
pub(crate) module_resolver: Box<dyn crate::ModuleResolver>,
|
||||||
|
|
||||||
/// A map mapping type names to pretty-print names.
|
|
||||||
pub(crate) custom_types: CustomTypesCollection,
|
|
||||||
|
|
||||||
/// An empty [`ImmutableString`] for cloning purposes.
|
/// An empty [`ImmutableString`] for cloning purposes.
|
||||||
pub(crate) empty_string: ImmutableString,
|
pub(crate) empty_string: ImmutableString,
|
||||||
@ -124,18 +121,15 @@ pub struct Engine {
|
|||||||
pub(crate) token_mapper: Option<Box<OnParseTokenCallback>>,
|
pub(crate) token_mapper: Option<Box<OnParseTokenCallback>>,
|
||||||
|
|
||||||
/// Callback closure for implementing the `print` command.
|
/// Callback closure for implementing the `print` command.
|
||||||
pub(crate) print: Option<Box<OnPrintCallback>>,
|
pub(crate) print: Box<OnPrintCallback>,
|
||||||
/// Callback closure for implementing the `debug` command.
|
/// Callback closure for implementing the `debug` command.
|
||||||
pub(crate) debug: Option<Box<OnDebugCallback>>,
|
pub(crate) debug: Box<OnDebugCallback>,
|
||||||
/// Callback closure for progress reporting.
|
/// Callback closure for progress reporting.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
pub(crate) progress: Option<Box<crate::func::native::OnProgressCallback>>,
|
pub(crate) progress: Option<Box<crate::func::native::OnProgressCallback>>,
|
||||||
|
|
||||||
/// Optimize the [`AST`][crate::AST] after compilation.
|
|
||||||
pub(crate) optimization_level: OptimizationLevel,
|
|
||||||
|
|
||||||
/// Language options.
|
/// Language options.
|
||||||
pub(crate) options: crate::api::options::LanguageOptions,
|
pub(crate) options: LanguageOptions,
|
||||||
|
|
||||||
/// Max limits.
|
/// Max limits.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -157,25 +151,18 @@ impl fmt::Debug for Engine {
|
|||||||
f.field("global_modules", &self.global_modules);
|
f.field("global_modules", &self.global_modules);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
f.field("global_sub_modules", &self.global_sub_modules)
|
f.field("global_sub_modules", &self.global_sub_modules);
|
||||||
.field("module_resolver", &self.module_resolver.is_some());
|
|
||||||
|
|
||||||
f.field("type_names", &self.custom_types)
|
f.field("disabled_symbols", &self.disabled_symbols)
|
||||||
.field("disabled_symbols", &self.disabled_symbols)
|
|
||||||
.field("custom_keywords", &self.custom_keywords)
|
.field("custom_keywords", &self.custom_keywords)
|
||||||
.field("custom_syntax", &(!self.custom_syntax.is_empty()))
|
.field("custom_syntax", &(!self.custom_syntax.is_empty()))
|
||||||
.field("def_var_filter", &self.def_var_filter.is_some())
|
.field("def_var_filter", &self.def_var_filter.is_some())
|
||||||
.field("resolve_var", &self.resolve_var.is_some())
|
.field("resolve_var", &self.resolve_var.is_some())
|
||||||
.field("token_mapper", &self.token_mapper.is_some())
|
.field("token_mapper", &self.token_mapper.is_some());
|
||||||
.field("print", &self.print.is_some())
|
|
||||||
.field("debug", &self.debug.is_some());
|
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
f.field("progress", &self.progress.is_some());
|
f.field("progress", &self.progress.is_some());
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
|
||||||
f.field("optimization_level", &self.optimization_level);
|
|
||||||
|
|
||||||
f.field("options", &self.options);
|
f.field("options", &self.options);
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -226,16 +213,15 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
{
|
{
|
||||||
engine.module_resolver =
|
engine.module_resolver = Box::new(crate::module::resolvers::FileModuleResolver::new());
|
||||||
Some(Box::new(crate::module::resolvers::FileModuleResolver::new()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// default print/debug implementations
|
// default print/debug implementations
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
{
|
{
|
||||||
engine.print = Some(Box::new(|s| println!("{}", s)));
|
engine.print = Box::new(|s| println!("{}", s));
|
||||||
engine.debug = Some(Box::new(|s, source, pos| {
|
engine.debug = Box::new(|s, source, pos| {
|
||||||
if let Some(source) = source {
|
if let Some(source) = source {
|
||||||
println!("{} @ {:?} | {}", source, pos, s);
|
println!("{} @ {:?} | {}", source, pos, s);
|
||||||
} else if pos.is_none() {
|
} else if pos.is_none() {
|
||||||
@ -243,12 +229,7 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
println!("{:?} | {}", pos, s);
|
println!("{:?} | {}", pos, s);
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
}
|
|
||||||
#[cfg(any(feature = "no_std", target_family = "wasm"))]
|
|
||||||
{
|
|
||||||
engine.print = None;
|
|
||||||
engine.debug = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.register_global_module(StandardPackage::new().as_shared_module());
|
engine.register_global_module(StandardPackage::new().as_shared_module());
|
||||||
@ -269,9 +250,8 @@ impl Engine {
|
|||||||
global_sub_modules: BTreeMap::new(),
|
global_sub_modules: BTreeMap::new(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
module_resolver: None,
|
module_resolver: Box::new(crate::module::resolvers::DummyModuleResolver::new()),
|
||||||
|
|
||||||
custom_types: CustomTypesCollection::new(),
|
|
||||||
empty_string: ImmutableString::new(),
|
empty_string: ImmutableString::new(),
|
||||||
disabled_symbols: BTreeSet::new(),
|
disabled_symbols: BTreeSet::new(),
|
||||||
custom_keywords: BTreeMap::new(),
|
custom_keywords: BTreeMap::new(),
|
||||||
@ -281,15 +261,13 @@ impl Engine {
|
|||||||
resolve_var: None,
|
resolve_var: None,
|
||||||
token_mapper: None,
|
token_mapper: None,
|
||||||
|
|
||||||
print: None,
|
print: Box::new(|_| {}),
|
||||||
debug: None,
|
debug: Box::new(|_, _, _| {}),
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
progress: None,
|
progress: None,
|
||||||
|
|
||||||
optimization_level: OptimizationLevel::default(),
|
options: LanguageOptions::new(),
|
||||||
|
|
||||||
options: crate::api::options::LanguageOptions::new(),
|
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
limits: crate::api::limits::Limits::new(),
|
limits: crate::api::limits::Limits::new(),
|
||||||
|
@ -35,9 +35,10 @@ impl<'x, 'px, 'm, 'pm, 'pt> EvalContext<'_, 'x, 'px, 'm, 'pm, '_, '_, '_, '_, 'p
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn source(&self) -> Option<&str> {
|
pub fn source(&self) -> Option<&str> {
|
||||||
match self.global.source.as_str() {
|
if self.global.source.is_empty() {
|
||||||
"" => None,
|
None
|
||||||
s => Some(s),
|
} else {
|
||||||
|
Some(self.global.source.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// The current [`Scope`].
|
/// The current [`Scope`].
|
||||||
|
@ -28,7 +28,7 @@ pub struct EvalState<'a> {
|
|||||||
/// Stack of function resolution caches.
|
/// Stack of function resolution caches.
|
||||||
fn_resolution_caches: StaticVec<FnResolutionCache>,
|
fn_resolution_caches: StaticVec<FnResolutionCache>,
|
||||||
/// Take care of the lifetime parameter
|
/// Take care of the lifetime parameter
|
||||||
dummy: PhantomData<Option<&'a ()>>,
|
dummy: PhantomData<&'a ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EvalState<'_> {
|
impl EvalState<'_> {
|
||||||
@ -40,7 +40,7 @@ impl EvalState<'_> {
|
|||||||
always_search_scope: false,
|
always_search_scope: false,
|
||||||
scope_level: 0,
|
scope_level: 0,
|
||||||
fn_resolution_caches: StaticVec::new_const(),
|
fn_resolution_caches: StaticVec::new_const(),
|
||||||
dummy: PhantomData::default(),
|
dummy: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get the number of function resolution cache(s) in the stack.
|
/// Get the number of function resolution cache(s) in the stack.
|
||||||
|
@ -165,11 +165,8 @@ impl Engine {
|
|||||||
this_ptr,
|
this_ptr,
|
||||||
level,
|
level,
|
||||||
};
|
};
|
||||||
match resolve_var(
|
let var_name = expr.get_variable_name(true).expect("`Expr::Variable`");
|
||||||
expr.get_variable_name(true).expect("`Expr::Variable`"),
|
match resolve_var(var_name, index, &context) {
|
||||||
index,
|
|
||||||
&context,
|
|
||||||
) {
|
|
||||||
Ok(Some(mut result)) => {
|
Ok(Some(mut result)) => {
|
||||||
result.set_access_mode(AccessMode::ReadOnly);
|
result.set_access_mode(AccessMode::ReadOnly);
|
||||||
return Ok((result.into(), var_pos));
|
return Ok((result.into(), var_pos));
|
||||||
@ -184,10 +181,18 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
// Find the variable in the scope
|
// Find the variable in the scope
|
||||||
let var_name = expr.get_variable_name(true).expect("`Expr::Variable`");
|
let var_name = expr.get_variable_name(true).expect("`Expr::Variable`");
|
||||||
scope
|
|
||||||
.get_index(var_name)
|
match scope.get_index(var_name) {
|
||||||
.ok_or_else(|| ERR::ErrorVariableNotFound(var_name.to_string(), var_pos))?
|
Some((index, _)) => index,
|
||||||
.0
|
None => {
|
||||||
|
return match self.global_modules.iter().find_map(|m| m.get_var(var_name)) {
|
||||||
|
Some(val) => Ok((val.into(), var_pos)),
|
||||||
|
None => {
|
||||||
|
Err(ERR::ErrorVariableNotFound(var_name.to_string(), var_pos).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let val = scope.get_mut_by_index(index);
|
let val = scope.get_mut_by_index(index);
|
||||||
|
@ -244,9 +244,10 @@ impl GlobalRuntimeState<'_> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn source(&self) -> Option<&str> {
|
pub fn source(&self) -> Option<&str> {
|
||||||
match self.source.as_str() {
|
if self.source.is_empty() {
|
||||||
"" => None,
|
None
|
||||||
s => Some(s),
|
} else {
|
||||||
|
Some(self.source.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get the pre-calculated index getter hash.
|
/// Get the pre-calculated index getter hash.
|
||||||
|
@ -952,9 +952,10 @@ impl Engine {
|
|||||||
result => Some(result),
|
result => Some(result),
|
||||||
})
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
|
Some(
|
||||||
self.module_resolver
|
self.module_resolver
|
||||||
.as_ref()
|
.resolve_raw(self, global, &path, path_pos),
|
||||||
.map(|r| r.resolve_raw(self, global, &path, path_pos))
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into())
|
Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into())
|
||||||
|
@ -464,30 +464,23 @@ impl Engine {
|
|||||||
// See if the function match print/debug (which requires special processing)
|
// See if the function match print/debug (which requires special processing)
|
||||||
return Ok(match name {
|
return Ok(match name {
|
||||||
KEYWORD_PRINT => {
|
KEYWORD_PRINT => {
|
||||||
if let Some(ref print) = self.print {
|
|
||||||
let text = result.into_immutable_string().map_err(|typ| {
|
let text = result.into_immutable_string().map_err(|typ| {
|
||||||
let t = self.map_type_name(type_name::<ImmutableString>()).into();
|
let t = self.map_type_name(type_name::<ImmutableString>()).into();
|
||||||
ERR::ErrorMismatchOutputType(t, typ.into(), pos)
|
ERR::ErrorMismatchOutputType(t, typ.into(), pos)
|
||||||
})?;
|
})?;
|
||||||
(print(&text).into(), false)
|
((&*self.print)(&text).into(), false)
|
||||||
} else {
|
|
||||||
(Dynamic::UNIT, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
KEYWORD_DEBUG => {
|
KEYWORD_DEBUG => {
|
||||||
if let Some(ref debug) = self.debug {
|
|
||||||
let text = result.into_immutable_string().map_err(|typ| {
|
let text = result.into_immutable_string().map_err(|typ| {
|
||||||
let t = self.map_type_name(type_name::<ImmutableString>()).into();
|
let t = self.map_type_name(type_name::<ImmutableString>()).into();
|
||||||
ERR::ErrorMismatchOutputType(t, typ.into(), pos)
|
ERR::ErrorMismatchOutputType(t, typ.into(), pos)
|
||||||
})?;
|
})?;
|
||||||
let source = match global.source.as_str() {
|
let source = if global.source.is_empty() {
|
||||||
"" => None,
|
None
|
||||||
s => Some(s),
|
|
||||||
};
|
|
||||||
(debug(&text, source, pos).into(), false)
|
|
||||||
} else {
|
} else {
|
||||||
(Dynamic::UNIT, false)
|
Some(global.source.as_str())
|
||||||
}
|
};
|
||||||
|
((&*self.debug)(&text, source, pos).into(), false)
|
||||||
}
|
}
|
||||||
_ => (result, is_method),
|
_ => (result, is_method),
|
||||||
});
|
});
|
||||||
|
@ -421,6 +421,15 @@ impl Module {
|
|||||||
pub fn set_custom_type<T>(&mut self, name: &str) {
|
pub fn set_custom_type<T>(&mut self, name: &str) {
|
||||||
self.custom_types.add_type::<T>(name)
|
self.custom_types.add_type::<T>(name)
|
||||||
}
|
}
|
||||||
|
/// Map a custom type to a friendly display name.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_custom_type_raw(
|
||||||
|
&mut self,
|
||||||
|
type_name: impl Into<Identifier>,
|
||||||
|
name: impl Into<Identifier>,
|
||||||
|
) {
|
||||||
|
self.custom_types.add(type_name, name)
|
||||||
|
}
|
||||||
/// Get the display name of a registered custom type.
|
/// Get the display name of a registered custom type.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_custom_type(&self, key: &str) -> Option<&str> {
|
pub fn get_custom_type(&self, key: &str) -> Option<&str> {
|
||||||
|
@ -9,8 +9,8 @@ use crate::func::hashing::get_hasher;
|
|||||||
use crate::tokenizer::{Span, Token};
|
use crate::tokenizer::{Span, Token};
|
||||||
use crate::types::dynamic::AccessMode;
|
use crate::types::dynamic::AccessMode;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Position, Scope,
|
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Identifier,
|
||||||
StaticVec, AST, INT, INT_BITS,
|
Position, Scope, StaticVec, AST, INT, INT_BITS,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -46,7 +46,7 @@ struct OptimizerState<'a> {
|
|||||||
/// Has the [`AST`] been changed during this pass?
|
/// Has the [`AST`] been changed during this pass?
|
||||||
changed: bool,
|
changed: bool,
|
||||||
/// Collection of constants to use for eager function evaluations.
|
/// Collection of constants to use for eager function evaluations.
|
||||||
variables: StaticVec<(String, AccessMode, Option<Dynamic>)>,
|
variables: StaticVec<(Identifier, AccessMode, Option<Dynamic>)>,
|
||||||
/// Activate constants propagation?
|
/// Activate constants propagation?
|
||||||
propagate_constants: bool,
|
propagate_constants: bool,
|
||||||
/// An [`Engine`] instance for eager function evaluation.
|
/// An [`Engine`] instance for eager function evaluation.
|
||||||
@ -100,7 +100,7 @@ impl<'a> OptimizerState<'a> {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn push_var(
|
pub fn push_var(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: impl Into<String>,
|
name: impl Into<Identifier>,
|
||||||
access: AccessMode,
|
access: AccessMode,
|
||||||
value: Option<Dynamic>,
|
value: Option<Dynamic>,
|
||||||
) {
|
) {
|
||||||
@ -1236,6 +1236,16 @@ fn optimize_top_level(
|
|||||||
optimization_level,
|
optimization_level,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add constants from global modules
|
||||||
|
for (name, value) in engine
|
||||||
|
.global_modules
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.flat_map(|m| m.iter_var())
|
||||||
|
{
|
||||||
|
state.push_var(name, AccessMode::ReadOnly, Some(value.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
// Add constants and variables from the scope
|
// Add constants and variables from the scope
|
||||||
for (name, constant, value) in scope.iter() {
|
for (name, constant, value) in scope.iter() {
|
||||||
if !constant {
|
if !constant {
|
||||||
|
@ -5,9 +5,6 @@ use crate::{Dynamic, RhaiResultOf, ERR, INT};
|
|||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
|
||||||
use crate::FLOAT;
|
|
||||||
|
|
||||||
def_package! {
|
def_package! {
|
||||||
/// Package of core language features.
|
/// Package of core language features.
|
||||||
pub LanguageCorePackage(lib) {
|
pub LanguageCorePackage(lib) {
|
||||||
@ -84,7 +81,7 @@ mod core_functions {
|
|||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
#[rhai_fn(name = "sleep")]
|
#[rhai_fn(name = "sleep")]
|
||||||
pub fn sleep_float(seconds: FLOAT) {
|
pub fn sleep_float(seconds: crate::FLOAT) {
|
||||||
if seconds <= 0.0 {
|
if seconds <= 0.0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -458,6 +458,35 @@ mod string_functions {
|
|||||||
*character = to_lower_char(*character)
|
*character = to_lower_char(*character)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return `true` if the string starts with a specified string.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let text = "hello, world!";
|
||||||
|
///
|
||||||
|
/// print(text.starts_with("hello")); // prints true
|
||||||
|
///
|
||||||
|
/// print(text.starts_with("world")); // prints false
|
||||||
|
/// ```
|
||||||
|
pub fn starts_with(string: &str, match_string: &str) -> bool {
|
||||||
|
string.starts_with(match_string)
|
||||||
|
}
|
||||||
|
/// Return `true` if the string ends with a specified string.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rhai
|
||||||
|
/// let text = "hello, world!";
|
||||||
|
///
|
||||||
|
/// print(text.ends_with("world!")); // prints true
|
||||||
|
///
|
||||||
|
/// print(text.ends_with("hello")); // prints false
|
||||||
|
/// ```
|
||||||
|
pub fn ends_with(string: &str, match_string: &str) -> bool {
|
||||||
|
string.ends_with(match_string)
|
||||||
|
}
|
||||||
|
|
||||||
/// Find the specified `character` in the string, starting from the specified `start` position,
|
/// Find the specified `character` in the string, starting from the specified `start` position,
|
||||||
/// and return the first index where it is found.
|
/// and return the first index where it is found.
|
||||||
/// If the `character` is not found, `-1` is returned.
|
/// If the `character` is not found, `-1` is returned.
|
||||||
|
@ -5,7 +5,7 @@ use crate::engine::{
|
|||||||
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||||
};
|
};
|
||||||
use crate::func::native::OnParseTokenCallback;
|
use crate::func::native::OnParseTokenCallback;
|
||||||
use crate::{Engine, LexError, SmartString, StaticVec, INT, UNSIGNED_INT};
|
use crate::{Engine, Identifier, LexError, SmartString, StaticVec, INT, UNSIGNED_INT};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
@ -363,7 +363,7 @@ pub enum Token {
|
|||||||
#[cfg(feature = "decimal")]
|
#[cfg(feature = "decimal")]
|
||||||
DecimalConstant(rust_decimal::Decimal),
|
DecimalConstant(rust_decimal::Decimal),
|
||||||
/// An identifier.
|
/// An identifier.
|
||||||
Identifier(SmartString),
|
Identifier(Identifier),
|
||||||
/// A character constant.
|
/// A character constant.
|
||||||
CharConstant(char),
|
CharConstant(char),
|
||||||
/// A string constant.
|
/// A string constant.
|
||||||
@ -1113,8 +1113,8 @@ pub fn parse_string_literal(
|
|||||||
allow_line_continuation: bool,
|
allow_line_continuation: bool,
|
||||||
allow_interpolation: bool,
|
allow_interpolation: bool,
|
||||||
) -> Result<(SmartString, bool, Position), (LexError, Position)> {
|
) -> Result<(SmartString, bool, Position), (LexError, Position)> {
|
||||||
let mut result = SmartString::new();
|
let mut result = SmartString::new_const();
|
||||||
let mut escape = SmartString::new();
|
let mut escape = SmartString::new_const();
|
||||||
|
|
||||||
let start = *pos;
|
let start = *pos;
|
||||||
let mut first_char = Position::NONE;
|
let mut first_char = Position::NONE;
|
||||||
@ -1323,7 +1323,7 @@ fn scan_block_comment(
|
|||||||
stream: &mut impl InputStream,
|
stream: &mut impl InputStream,
|
||||||
level: usize,
|
level: usize,
|
||||||
pos: &mut Position,
|
pos: &mut Position,
|
||||||
comment: Option<&mut String>,
|
comment: Option<&mut SmartString>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
let mut level = level;
|
let mut level = level;
|
||||||
let mut comment = comment;
|
let mut comment = comment;
|
||||||
@ -1418,7 +1418,7 @@ fn get_next_token_inner(
|
|||||||
if state.comment_level > 0 {
|
if state.comment_level > 0 {
|
||||||
let start_pos = *pos;
|
let start_pos = *pos;
|
||||||
let mut comment = if state.include_comments {
|
let mut comment = if state.include_comments {
|
||||||
Some(String::new())
|
Some(SmartString::new_const())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@ -1796,7 +1796,7 @@ fn get_next_token_inner(
|
|||||||
('/', '/') => {
|
('/', '/') => {
|
||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
|
|
||||||
let mut comment = match stream.peek_next() {
|
let mut comment: Option<SmartString> = match stream.peek_next() {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
Some('/') => {
|
Some('/') => {
|
||||||
@ -1805,10 +1805,10 @@ fn get_next_token_inner(
|
|||||||
// Long streams of `///...` are not doc-comments
|
// Long streams of `///...` are not doc-comments
|
||||||
match stream.peek_next() {
|
match stream.peek_next() {
|
||||||
Some('/') => None,
|
Some('/') => None,
|
||||||
_ => Some("///".to_string()),
|
_ => Some("///".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ if state.include_comments => Some("//".to_string()),
|
_ if state.include_comments => Some("//".into()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1832,14 +1832,14 @@ fn get_next_token_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(comment) = comment {
|
if let Some(comment) = comment {
|
||||||
return Some((Token::Comment(comment.into()), start_pos));
|
return Some((Token::Comment(comment), start_pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
('/', '*') => {
|
('/', '*') => {
|
||||||
state.comment_level = 1;
|
state.comment_level = 1;
|
||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
|
|
||||||
let mut comment = match stream.peek_next() {
|
let mut comment: Option<SmartString> = match stream.peek_next() {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(feature = "metadata")]
|
#[cfg(feature = "metadata")]
|
||||||
Some('*') => {
|
Some('*') => {
|
||||||
@ -1848,10 +1848,10 @@ fn get_next_token_inner(
|
|||||||
// Long streams of `/****...` are not doc-comments
|
// Long streams of `/****...` are not doc-comments
|
||||||
match stream.peek_next() {
|
match stream.peek_next() {
|
||||||
Some('*') => None,
|
Some('*') => None,
|
||||||
_ => Some("/**".to_string()),
|
_ => Some("/**".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ if state.include_comments => Some("/*".to_string()),
|
_ if state.include_comments => Some("/*".into()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1859,7 +1859,7 @@ fn get_next_token_inner(
|
|||||||
scan_block_comment(stream, state.comment_level, pos, comment.as_mut());
|
scan_block_comment(stream, state.comment_level, pos, comment.as_mut());
|
||||||
|
|
||||||
if let Some(comment) = comment {
|
if let Some(comment) = comment {
|
||||||
return Some((Token::Comment(comment.into()), start_pos));
|
return Some((Token::Comment(comment), start_pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,13 +27,18 @@ impl CustomTypesCollection {
|
|||||||
}
|
}
|
||||||
/// Register a custom type.
|
/// Register a custom type.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn add(&mut self, key: impl Into<Identifier>, name: impl Into<Identifier>) {
|
pub fn add(&mut self, type_name: impl Into<Identifier>, name: impl Into<Identifier>) {
|
||||||
self.0.insert(key.into(), name.into());
|
self.add_raw(type_name, name.into());
|
||||||
}
|
}
|
||||||
/// Register a custom type.
|
/// Register a custom type.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn add_type<T>(&mut self, name: &str) {
|
pub fn add_type<T>(&mut self, name: &str) {
|
||||||
self.0.insert(type_name::<T>().into(), name.into());
|
self.add_raw(type_name::<T>(), name.into());
|
||||||
|
}
|
||||||
|
/// Register a custom type.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn add_raw(&mut self, type_name: impl Into<Identifier>, custom_type: CustomType) {
|
||||||
|
self.0.insert(type_name.into(), custom_type);
|
||||||
}
|
}
|
||||||
/// Find a custom type.
|
/// Find a custom type.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -1051,6 +1051,13 @@ impl Dynamic {
|
|||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Make this [`Dynamic`] read-only (i.e. a constant).
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn into_read_only(self) -> Self {
|
||||||
|
let mut value = self;
|
||||||
|
value.set_access_mode(AccessMode::ReadOnly);
|
||||||
|
value
|
||||||
|
}
|
||||||
/// Is this [`Dynamic`] read-only?
|
/// Is this [`Dynamic`] read-only?
|
||||||
///
|
///
|
||||||
/// Constant [`Dynamic`] values are read-only.
|
/// Constant [`Dynamic`] values are read-only.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#![cfg(not(feature = "no_optimize"))]
|
#![cfg(not(feature = "no_optimize"))]
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, OptimizationLevel, Scope, INT};
|
use rhai::{Engine, EvalAltResult, Module, OptimizationLevel, Scope, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_optimizer() -> Result<(), Box<EvalAltResult>> {
|
fn test_optimizer() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -74,6 +74,7 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine.set_optimization_level(OptimizationLevel::Simple);
|
engine.set_optimization_level(OptimizationLevel::Simple);
|
||||||
|
|
||||||
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
||||||
@ -97,6 +98,22 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(42 @ 1:1)] }");
|
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(42 @ 1:1)] }");
|
||||||
|
|
||||||
|
let ast = engine.compile("NUMBER")?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
format!("{:?}", ast),
|
||||||
|
"AST { body: [Expr(Variable(NUMBER) @ 1:1)] }"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut module = Module::new();
|
||||||
|
module.set_var("NUMBER", 42 as INT);
|
||||||
|
|
||||||
|
engine.register_global_module(module.into());
|
||||||
|
|
||||||
|
let ast = engine.compile("NUMBER")?;
|
||||||
|
|
||||||
|
assert_eq!(format!("{:?}", ast), "AST { body: [Expr(42 @ 1:1)] }");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +85,8 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
reg_functions!(engine += greet::single(INT, bool, char));
|
reg_functions!(engine += greet::single(INT, bool, char));
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<INT>("MYSTIC_NUMBER")?, 42);
|
||||||
|
|
||||||
#[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);
|
||||||
|
Loading…
Reference in New Issue
Block a user