Allow overloading of script functions.
This commit is contained in:
parent
1765d302b9
commit
e24d3a7ade
27
README.md
27
README.md
@ -365,7 +365,7 @@ fn to_int(num) {
|
|||||||
print("Ha! Gotcha! " + num);
|
print("Ha! Gotcha! " + num);
|
||||||
}
|
}
|
||||||
|
|
||||||
print(to_int(123)); // what will happen?
|
print(to_int(123)); // what happens?
|
||||||
```
|
```
|
||||||
|
|
||||||
Custom types and methods
|
Custom types and methods
|
||||||
@ -794,7 +794,7 @@ print(y.len()); // prints 0
|
|||||||
```
|
```
|
||||||
|
|
||||||
`push` and `pad` are only defined for standard built-in types. If you want to use them with
|
`push` and `pad` are only defined for standard built-in types. If you want to use them with
|
||||||
your own custom type, you need to define a specific override:
|
your own custom type, you need to register a type-specific version:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
engine.register_fn("push",
|
engine.register_fn("push",
|
||||||
@ -975,9 +975,8 @@ fn add(x, y) {
|
|||||||
print(add(2, 3));
|
print(add(2, 3));
|
||||||
```
|
```
|
||||||
|
|
||||||
Remember that functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type).
|
Functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type).
|
||||||
|
It is important to remember that all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments).
|
||||||
However, all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments).
|
|
||||||
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if you are not careful.
|
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if you are not careful.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -990,7 +989,7 @@ x.change();
|
|||||||
x == 500; // 'x' is NOT changed!
|
x == 500; // 'x' is NOT changed!
|
||||||
```
|
```
|
||||||
|
|
||||||
Furthermore, functions can only be defined at the top level, never inside a block or another function.
|
Functions can only be defined at the top level, never inside a block or another function.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Top level is OK
|
// Top level is OK
|
||||||
@ -1008,6 +1007,22 @@ fn do_addition(x) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Functions can be _overloaded_ based on the number of parameters (but not parameter types, since all parameters are `Dynamic`).
|
||||||
|
New definitions of the same name and number of parameters overwrite previous definitions.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn abc(x,y,z) { print("Three!!! " + x + "," + y + "," + z) }
|
||||||
|
fn abc(x) { print("One! " + x) }
|
||||||
|
fn abc(x,y) { print("Two! " + x + "," + y) }
|
||||||
|
fn abc() { print("None.") }
|
||||||
|
fn abc(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
|
||||||
|
|
||||||
|
abc(1,2,3); // prints "Three!!! 1,2,3"
|
||||||
|
abc(42); // prints "HA! NEW ONE! 42"
|
||||||
|
abc(1,2); // prints "Two!! 1,2"
|
||||||
|
abc(); // prints "None."
|
||||||
|
```
|
||||||
|
|
||||||
Members and methods
|
Members and methods
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
36
src/api.rs
36
src/api.rs
@ -174,9 +174,15 @@ impl<'e> Engine<'e> {
|
|||||||
let statements = {
|
let statements = {
|
||||||
let AST(statements, functions) = ast;
|
let AST(statements, functions) = ast;
|
||||||
|
|
||||||
functions.iter().for_each(|f| {
|
for f in functions {
|
||||||
engine.script_functions.push(f.clone());
|
match engine
|
||||||
});
|
.script_functions
|
||||||
|
.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len()))
|
||||||
|
{
|
||||||
|
Ok(n) => engine.script_functions[n] = f.clone(),
|
||||||
|
Err(n) => engine.script_functions.insert(n, f.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
statements
|
statements
|
||||||
};
|
};
|
||||||
@ -253,9 +259,15 @@ impl<'e> Engine<'e> {
|
|||||||
let statements = {
|
let statements = {
|
||||||
let AST(ref statements, ref functions) = ast;
|
let AST(ref statements, ref functions) = ast;
|
||||||
|
|
||||||
functions.iter().for_each(|f| {
|
for f in functions {
|
||||||
self.script_functions.push(f.clone());
|
match self
|
||||||
});
|
.script_functions
|
||||||
|
.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len()))
|
||||||
|
{
|
||||||
|
Ok(n) => self.script_functions[n] = f.clone(),
|
||||||
|
Err(n) => self.script_functions.insert(n, f.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
statements
|
statements
|
||||||
};
|
};
|
||||||
@ -308,9 +320,15 @@ impl<'e> Engine<'e> {
|
|||||||
ast: &AST,
|
ast: &AST,
|
||||||
args: FnCallArgs,
|
args: FnCallArgs,
|
||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
ast.1.iter().for_each(|f| {
|
for f in &ast.1 {
|
||||||
engine.script_functions.push(f.clone());
|
match engine
|
||||||
});
|
.script_functions
|
||||||
|
.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len()))
|
||||||
|
{
|
||||||
|
Ok(n) => engine.script_functions[n] = f.clone(),
|
||||||
|
Err(n) => engine.script_functions.insert(n, f.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let result = engine.call_fn_raw(name, args, None, Position::none());
|
let result = engine.call_fn_raw(name, args, None, Position::none());
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ enum IndexSourceType {
|
|||||||
Expression,
|
Expression,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||||
pub struct FnSpec<'a> {
|
pub struct FnSpec<'a> {
|
||||||
pub name: Cow<'a, str>,
|
pub name: Cow<'a, str>,
|
||||||
pub args: Option<Vec<TypeId>>,
|
pub args: Option<Vec<TypeId>>,
|
||||||
@ -82,10 +82,10 @@ impl Engine<'_> {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
// User-friendly names for built-in types
|
// User-friendly names for built-in types
|
||||||
let type_names = [
|
let type_names = [
|
||||||
(type_name::<String>(), "string"),
|
|
||||||
(type_name::<Dynamic>(), "dynamic"),
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
(type_name::<Array>(), "array"),
|
(type_name::<Array>(), "array"),
|
||||||
|
(type_name::<String>(), "string"),
|
||||||
|
(type_name::<Dynamic>(), "dynamic"),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
@ -135,22 +135,11 @@ impl Engine<'_> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// First search in script-defined functions (can override built-in)
|
// First search in script-defined functions (can override built-in)
|
||||||
if let Some(func) = self
|
if let Ok(n) = self
|
||||||
.script_functions
|
.script_functions
|
||||||
.iter()
|
.binary_search_by(|f| f.compare(fn_name, args.len()))
|
||||||
.rev()
|
|
||||||
.find(|fn_def| fn_def.name == fn_name)
|
|
||||||
.map(|fn_def| fn_def.clone())
|
|
||||||
{
|
{
|
||||||
// First check number of parameters
|
let func = self.script_functions[n].clone();
|
||||||
if func.params.len() != args.len() {
|
|
||||||
return Err(EvalAltResult::ErrorFunctionArgsMismatch(
|
|
||||||
fn_name.into(),
|
|
||||||
func.params.len(),
|
|
||||||
args.len(),
|
|
||||||
pos,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
@ -838,13 +827,9 @@ impl Engine<'_> {
|
|||||||
Expr::Array(contents, _) => {
|
Expr::Array(contents, _) => {
|
||||||
let mut arr = Vec::new();
|
let mut arr = Vec::new();
|
||||||
|
|
||||||
contents
|
for item in contents {
|
||||||
.iter()
|
arr.push(self.eval_expr(scope, item)?);
|
||||||
.try_for_each::<_, Result<_, EvalAltResult>>(|item| {
|
}
|
||||||
let arg = self.eval_expr(scope, item)?;
|
|
||||||
arr.push(arg);
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Box::new(arr))
|
Ok(Box::new(arr))
|
||||||
}
|
}
|
||||||
|
@ -1944,7 +1944,15 @@ fn parse_top_level<'a>(
|
|||||||
while input.peek().is_some() {
|
while input.peek().is_some() {
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
Some(&(Token::Fn, _)) => functions.push(parse_fn(input)?),
|
Some(&(Token::Fn, _)) => {
|
||||||
|
let f = parse_fn(input)?;
|
||||||
|
|
||||||
|
// Ensure list is sorted
|
||||||
|
match functions.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) {
|
||||||
|
Ok(n) => functions[n] = f, // Override previous definition
|
||||||
|
Err(n) => functions.insert(n, f), // New function definition
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => statements.push(parse_stmt(input)?),
|
_ => statements.push(parse_stmt(input)?),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,3 +30,25 @@ fn test_big_internal_fn() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_internal_fn_overloading() -> Result<(), EvalAltResult> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
fn abc(x,y,z) { 2*x + 3*y + 4*z + 888 }
|
||||||
|
fn abc(x) { x + 42 }
|
||||||
|
fn abc(x,y) { x + 2*y + 88 }
|
||||||
|
fn abc() { 42 }
|
||||||
|
fn abc(x) { x - 42 } // should override previous definition
|
||||||
|
|
||||||
|
abc() + abc(1) + abc(1,2) + abc(1,2,3)
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
1002
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user