Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ decimal = ["rust_decimal"]
serde = ["dep:serde", "smartstring/serde", "smallvec/serde", "thin-vec/serde"]
## Allow [Unicode Standard Annex #31](https://unicode.org/reports/tr31/) for identifiers.
unicode-xid-ident = ["unicode-xid"]
## Enable default parameter values in function definitions.
default-parameters = []
## Enable functions metadata (including doc-comments); implies [`serde`](#feature-serde).
metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"]
## Expose internal data structures (e.g. `AST` nodes).
Expand Down
1 change: 1 addition & 0 deletions src/api/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ impl Engine {
Token::lookup_symbol_from_syntax(op).as_ref(),
Some(&lhs),
&[rhs],
&[],
FnCallHashes::from_native_only(calc_fn_hash(None, op, 2)),
false,
Position::NONE,
Expand Down
5 changes: 5 additions & 0 deletions src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ pub struct FnCallExpr {
pub hashes: FnCallHashes,
/// List of function call argument expressions.
pub args: FnArgsVec<Expr>,
/// Named arguments: (parameter_name, expression).
#[cfg(feature = "default-parameters")]
pub named_args: FnArgsVec<(ImmutableString, Expr)>,
/// Does this function call capture the parent scope?
pub capture_parent_scope: bool,
/// Is this function call a native operator?
Expand Down Expand Up @@ -603,6 +606,8 @@ impl Expr {
name: KEYWORD_FN_PTR.into(),
hashes: FnCallHashes::from_hash(calc_fn_hash(None, f.fn_name(), 1)),
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
#[cfg(feature = "default-parameters")]
named_args: FnArgsVec::new(),
capture_parent_scope: false,
op_token: None,
}
Expand Down
35 changes: 30 additions & 5 deletions src/ast/script_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

use super::{FnAccess, StmtBlock};
use crate::{FnArgsVec, ImmutableString};
#[cfg(feature = "default-parameters")]
use super::Expr;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{fmt, hash::Hash};
Expand All @@ -23,6 +25,10 @@ pub struct ScriptFuncDef {
pub this_type: Option<ImmutableString>,
/// Names of function parameters.
pub params: FnArgsVec<ImmutableString>,
/// Default expressions for function parameters (parallel array to `params`).
/// `None` means no default value, `Some(Expr)` is the default expression to evaluate.
#[cfg(feature = "default-parameters")]
pub defaults: FnArgsVec<Option<Box<Expr>>>,
/// _(metadata)_ Function doc-comments (if any). Exported under the `metadata` feature only.
///
/// Doc-comments are comment lines beginning with `///` or comment blocks beginning with `/**`,
Expand Down Expand Up @@ -53,6 +59,8 @@ impl ScriptFuncDef {
#[cfg(not(feature = "no_object"))]
this_type: self.this_type.clone(),
params: self.params.clone(),
#[cfg(feature = "default-parameters")]
defaults: self.defaults.clone(),
#[cfg(feature = "metadata")]
comments: <_>::default(),
}
Expand All @@ -70,6 +78,27 @@ impl fmt::Display for ScriptFuncDef {
#[cfg(feature = "no_object")]
let this_type = "";

#[cfg(feature = "default-parameters")]
let params_str = self
.params
.iter()
.zip(self.defaults.iter())
.map(|(name, default)| {
if default.is_some() {
format!("{} = <expr>", name)
} else {
name.to_string()
}
})
.collect::<FnArgsVec<_>>()
.join(", ");
#[cfg(not(feature = "default-parameters"))]
let params_str = self
.params
.iter()
.map(ImmutableString::as_str)
.collect::<FnArgsVec<_>>()
.join(", ");
write!(
f,
"{}{}{}({})",
Expand All @@ -79,11 +108,7 @@ impl fmt::Display for ScriptFuncDef {
},
this_type,
self.name,
self.params
.iter()
.map(ImmutableString::as_str)
.collect::<FnArgsVec<_>>()
.join(", ")
params_str
)
}
}
Expand Down
98 changes: 94 additions & 4 deletions src/eval/chaining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,9 @@ impl Engine {
}
// xxx.fn_name(arg_expr_list)
(Expr::MethodCall(x, pos), None, ..) => {
#[cfg(not(feature = "default-parameters"))]
use crate::ImmutableString;

#[cfg(not(feature = "no_module"))]
debug_assert!(
!x.is_qualified(),
Expand All @@ -882,17 +885,47 @@ impl Engine {
defer! { global if Some(reset) => move |g| g.debugger_mut().reset_status(reset) }

let crate::ast::FnCallExpr {
name, hashes, args, ..
name,
hashes,
args,
#[cfg(feature = "default-parameters")]
named_args: named_args_expr,
..
} = &**x;

// Evaluate named arguments
#[cfg(feature = "default-parameters")]
let mut named_arg_values = FnArgsVec::new();
#[cfg(feature = "default-parameters")]
for (param_name, expr) in &*named_args_expr {
let (value, ..) = self.get_arg_value(
global,
caches,
x!(s, b),
this_ptr.as_deref_mut(),
expr,
)?;
named_arg_values.push((param_name.clone(), value.flatten()));
}
#[cfg(not(feature = "default-parameters"))]
let named_arg_values: FnArgsVec<(ImmutableString, Dynamic)> = FnArgsVec::new();

// Truncate the index values upon exit
defer! { idx_values => truncate; let offset = idx_values.len() - args.len(); }

let call_args = &mut idx_values[offset..];
let arg1_pos = args.first().map_or(Position::NONE, Expr::position);

self.make_method_call(
global, caches, name, *hashes, target, call_args, arg1_pos, *pos,
global,
caches,
name,
*hashes,
target,
call_args,
arg1_pos,
*pos,
&named_arg_values,
)
}
// {xxx:map}.id op= ???
Expand Down Expand Up @@ -1080,6 +1113,9 @@ impl Engine {
}
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::MethodCall(ref x, pos) => {
#[cfg(not(feature = "default-parameters"))]
use crate::ImmutableString;

#[cfg(not(feature = "no_module"))]
debug_assert!(
!x.is_qualified(),
Expand All @@ -1101,8 +1137,33 @@ impl Engine {
let call_args = &mut idx_values[offset..];
let arg1_pos = args.first().map_or(Position::NONE, Expr::position);

// Evaluate named arguments if any
#[cfg(feature = "default-parameters")]
let mut named_arg_values = FnArgsVec::new();
#[cfg(feature = "default-parameters")]
for (param_name, expr) in &*x.named_args {
let (value, ..) = self.get_arg_value(
global,
caches,
x!(s, b),
this_ptr.as_deref_mut(),
expr,
)?;
named_arg_values.push((param_name.clone(), value.flatten()));
}
#[cfg(not(feature = "default-parameters"))]
let named_arg_values: FnArgsVec<(ImmutableString, Dynamic)> = FnArgsVec::new();

self.make_method_call(
global, caches, name, *hashes, target, call_args, arg1_pos, pos,
global,
caches,
name,
*hashes,
target,
call_args,
arg1_pos,
pos,
&named_arg_values,
)?
.0
.into()
Expand Down Expand Up @@ -1208,6 +1269,9 @@ impl Engine {
);

let val = {
#[cfg(not(feature = "default-parameters"))]
use crate::ImmutableString;

#[cfg(feature = "debugging")]
let reset =
self.dbg_reset(global, caches, x!(s, b), _tp, _node)?;
Expand All @@ -1224,8 +1288,34 @@ impl Engine {
let call_args = &mut idx_values[offset..];
let pos1 = args.first().map_or(Position::NONE, Expr::position);

// Evaluate named arguments if any
#[cfg(feature = "default-parameters")]
let mut named_arg_values = FnArgsVec::new();
#[cfg(feature = "default-parameters")]
for (param_name, expr) in &*f.named_args {
let (value, ..) = self.get_arg_value(
global,
caches,
x!(s, b),
_this_ptr.as_deref_mut(),
expr,
)?;
named_arg_values
.push((param_name.clone(), value.flatten()));
}
#[cfg(not(feature = "default-parameters"))]
let named_arg_values: FnArgsVec<(ImmutableString, Dynamic)> = FnArgsVec::new();

self.make_method_call(
global, caches, name, *hashes, target, call_args, pos1, pos,
global,
caches,
name,
*hashes,
target,
call_args,
pos1,
pos,
&named_arg_values,
)?
.0
};
Expand Down
34 changes: 20 additions & 14 deletions src/eval/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,26 @@ impl Engine {
match scope.search(var_name) {
Some(index) => index,
None => {
return self
.global_modules
.iter()
.find_map(|m| m.get_var(var_name))
.map_or_else(
|| {
Err(ERR::ErrorVariableNotFound(
var_name.to_string(),
expr.position(),
)
.into())
},
|val| Ok(val.into()),
)
// Try global modules first
if let Some(val) = self.global_modules.iter().find_map(|m| m.get_var(var_name)) {
return Ok(val.into());
}

// Try global constants (for default parameter expressions)
#[cfg(not(feature = "no_module"))]
if let Some(ref constants) = global.constants {
if let Some(guard) = crate::func::locked_read(constants) {
if let Some(value) = guard.get(var_name) {
return Ok(value.clone().into());
}
}
}

return Err(ERR::ErrorVariableNotFound(
var_name.to_string(),
expr.position(),
)
.into());
}
}
};
Expand Down
Loading