Allow if expressions.
This commit is contained in:
parent
a541a4507f
commit
ef6dd9414a
108
README.md
108
README.md
@ -258,7 +258,7 @@ let result = engine.eval_expression_with_scope::<i64>(&mut scope, "if x { 42 } e
|
|||||||
Values and types
|
Values and types
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
[`type_of`]: #values-and-types
|
[`type_of()`]: #values-and-types
|
||||||
|
|
||||||
The following primitive types are supported natively:
|
The following primitive types are supported natively:
|
||||||
|
|
||||||
@ -553,8 +553,8 @@ let result = engine.eval::<i64>("let x = new_ts(); x.foo()")?;
|
|||||||
println!("result: {}", result); // prints 1
|
println!("result: {}", result); // prints 1
|
||||||
```
|
```
|
||||||
|
|
||||||
[`type_of`] works fine with custom types and returns the name of the type. If `register_type_with_name` is used to register the custom type
|
[`type_of()`] works fine with custom types and returns the name of the type. If `register_type_with_name` is used to register the custom type
|
||||||
with a special "pretty-print" name, [`type_of`] will return that name instead.
|
with a special "pretty-print" name, [`type_of()`] will return that name instead.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
engine.register_type::<TestStruct>();
|
engine.register_type::<TestStruct>();
|
||||||
@ -850,9 +850,11 @@ String and char literals follow C-style formatting, with support for Unicode ('`
|
|||||||
|
|
||||||
Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, 32-bit extended Unicode code points.
|
Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, 32-bit extended Unicode code points.
|
||||||
|
|
||||||
Although internally Rhai strings are stored as UTF-8 just like in Rust (they _are_ Rust `String`s),
|
Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!), but there are major differences.
|
||||||
in the Rhai language they can be considered a stream of Unicode characters, and can be directly indexed (unlike Rust).
|
In Rhai a string is the same as an array of Unicode characters and can be directly indexed (unlike Rust).
|
||||||
Individual characters within a Rhai string can be replaced. In Rhai, there is no separate concepts of `String` and `&str` as in Rust.
|
This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte Unicode characters.
|
||||||
|
Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters.
|
||||||
|
In Rhai, there is also no separate concepts of `String` and `&str` as in Rust.
|
||||||
|
|
||||||
Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded if [`no_stdlib`]).
|
Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded if [`no_stdlib`]).
|
||||||
This is particularly useful when printing output.
|
This is particularly useful when printing output.
|
||||||
@ -944,9 +946,11 @@ Arrays
|
|||||||
------
|
------
|
||||||
|
|
||||||
Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices.
|
Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices.
|
||||||
Array literals are built within square brackets '`[`' ,, '`]`' and separated by commas '`,`'.
|
Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'.
|
||||||
|
|
||||||
The type of a Rhai array is `rhai::Array`. [`type_of()`] an array returns `"array"`.
|
The Rust type of a Rhai array is `rhai::Array`.
|
||||||
|
|
||||||
|
[`type_of()`] an array returns `"array"`.
|
||||||
|
|
||||||
Arrays are disabled via the [`no_index`] feature.
|
Arrays are disabled via the [`no_index`] feature.
|
||||||
|
|
||||||
@ -1093,17 +1097,13 @@ my_str += 12345;
|
|||||||
my_str == "abcABC12345"
|
my_str == "abcABC12345"
|
||||||
```
|
```
|
||||||
|
|
||||||
If statements
|
`if` statements
|
||||||
-------------
|
---------------
|
||||||
|
|
||||||
All branches of an `if` statement must be enclosed within braces '`{`' .. '`}`', even when there is only one statement.
|
|
||||||
|
|
||||||
Like Rust, there is no ambiguity regarding which `if` clause a statement belongs to.
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
if true {
|
if foo(x) {
|
||||||
print("It's true!");
|
print("It's true!");
|
||||||
} else if true {
|
} else if bar == baz {
|
||||||
print("It's true again!");
|
print("It's true again!");
|
||||||
} else if ... {
|
} else if ... {
|
||||||
:
|
:
|
||||||
@ -1112,13 +1112,28 @@ if true {
|
|||||||
} else {
|
} else {
|
||||||
print("It's finally false!");
|
print("It's finally false!");
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All branches of an `if` statement must be enclosed within braces '`{`' .. '`}`', even when there is only one statement.
|
||||||
|
Like Rust, there is no ambiguity regarding which `if` clause a statement belongs to.
|
||||||
|
|
||||||
|
```rust
|
||||||
if (decision) print("I've decided!");
|
if (decision) print("I've decided!");
|
||||||
// ^ syntax error, expecting '{' in statement block
|
// ^ syntax error, expecting '{' in statement block
|
||||||
```
|
```
|
||||||
|
|
||||||
While loops
|
Like Rust, `if` statements can also be used as _expressions_, replacing the `? :` conditional operators in other C-like languages.
|
||||||
-----------
|
|
||||||
|
```rust
|
||||||
|
let x = 1 + if true { 42 } else { 123 } / 2;
|
||||||
|
x == 22;
|
||||||
|
|
||||||
|
let x = if false { 42 }; // No else branch defaults to '()'
|
||||||
|
x == ();
|
||||||
|
```
|
||||||
|
|
||||||
|
`while` loops
|
||||||
|
-------------
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let x = 10;
|
let x = 10;
|
||||||
@ -1130,8 +1145,8 @@ while x > 0 {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Infinite loops
|
Infinite `loop`
|
||||||
--------------
|
---------------
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let x = 10;
|
let x = 10;
|
||||||
@ -1143,8 +1158,8 @@ loop {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For loops
|
`for` loops
|
||||||
---------
|
-----------
|
||||||
|
|
||||||
Iterating through a range or an array is provided by the `for` ... `in` loop.
|
Iterating through a range or an array is provided by the `for` ... `in` loop.
|
||||||
|
|
||||||
@ -1164,8 +1179,8 @@ for x in range(0, 50) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Returning values
|
`return`-ing values
|
||||||
----------------
|
-------------------
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
return; // equivalent to return ();
|
return; // equivalent to return ();
|
||||||
@ -1173,8 +1188,8 @@ return; // equivalent to return ();
|
|||||||
return 123 + 456; // returns 579
|
return 123 + 456; // returns 579
|
||||||
```
|
```
|
||||||
|
|
||||||
Errors and exceptions
|
Errors and `throw`-ing exceptions
|
||||||
---------------------
|
--------------------------------
|
||||||
|
|
||||||
All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information.
|
All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information.
|
||||||
To deliberately return an error during an evaluation, use the `throw` keyword.
|
To deliberately return an error during an evaluation, use the `throw` keyword.
|
||||||
@ -1215,8 +1230,10 @@ fn add(x, y) {
|
|||||||
print(add(2, 3));
|
print(add(2, 3));
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Implicit return
|
||||||
|
|
||||||
Just like in Rust, an implicit return can be used. In fact, the last statement of a block is _always_ the block's return value
|
Just like in Rust, an implicit return can be used. In fact, the last statement of a block is _always_ the block's return value
|
||||||
regardless of whether it is terminated with a semicolon `;`. This is different from Rust.
|
regardless of whether it is terminated with a semicolon `';'`. This is different from Rust.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn add(x, y) {
|
fn add(x, y) {
|
||||||
@ -1231,6 +1248,16 @@ print(add(2, 3)); // prints 5
|
|||||||
print(add2(42)); // prints 44
|
print(add2(42)); // prints 44
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### No access to external scope
|
||||||
|
|
||||||
|
Functions can only access their parameters. They cannot access external variables (even _global_ variables).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let x = 42;
|
||||||
|
|
||||||
|
fn foo() { x } // syntax error - variable 'x' doesn't exist
|
||||||
|
```
|
||||||
|
|
||||||
### Passing arguments by value
|
### Passing arguments by value
|
||||||
|
|
||||||
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
||||||
@ -1269,31 +1296,34 @@ fn do_addition(x) {
|
|||||||
|
|
||||||
### Functions overloading
|
### Functions overloading
|
||||||
|
|
||||||
Functions can be _overloaded_ based on the _number_ of parameters (but not parameter _types_, since all parameters are the same type - [`Dynamic`]).
|
Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters
|
||||||
New definitions of the same name and number of parameters overwrite previous definitions.
|
(but not parameter _types_, since all parameters are the same type - [`Dynamic`]).
|
||||||
|
New definitions _overwrite_ previous definitions of the same name and number of parameters.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn abc(x,y,z) { print("Three!!! " + x + "," + y + "," + z) }
|
fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z) }
|
||||||
fn abc(x) { print("One! " + x) }
|
fn foo(x) { print("One! " + x) }
|
||||||
fn abc(x,y) { print("Two! " + x + "," + y) }
|
fn foo(x,y) { print("Two! " + x + "," + y) }
|
||||||
fn abc() { print("None.") }
|
fn foo() { print("None.") }
|
||||||
fn abc(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
|
fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
|
||||||
|
|
||||||
abc(1,2,3); // prints "Three!!! 1,2,3"
|
foo(1,2,3); // prints "Three!!! 1,2,3"
|
||||||
abc(42); // prints "HA! NEW ONE! 42"
|
foo(42); // prints "HA! NEW ONE! 42"
|
||||||
abc(1,2); // prints "Two!! 1,2"
|
foo(1,2); // prints "Two!! 1,2"
|
||||||
abc(); // prints "None."
|
foo(); // prints "None."
|
||||||
```
|
```
|
||||||
|
|
||||||
Members and methods
|
Members and methods
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust:
|
Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let a = new_ts(); // constructor function
|
let a = new_ts(); // constructor function
|
||||||
a.field = 500; // property access
|
a.field = 500; // property access
|
||||||
a.update(); // method call
|
a.update(); // method call
|
||||||
|
|
||||||
|
update(a); // this works, but 'a' is unchanged because only a COPY of 'a' is passed to 'update' by VALUE
|
||||||
```
|
```
|
||||||
|
|
||||||
`print` and `debug`
|
`print` and `debug`
|
||||||
|
@ -1555,20 +1555,17 @@ fn parse_array_literal<'a>(
|
|||||||
arr.push(parse_expr(input, allow_stmt_expr)?);
|
arr.push(parse_expr(input, allow_stmt_expr)?);
|
||||||
|
|
||||||
match input.peek().ok_or_else(|| {
|
match input.peek().ok_or_else(|| {
|
||||||
ParseError(
|
PERR::MissingRightBracket("separating items in array literal".into()).into_err_eof()
|
||||||
PERR::MissingRightBracket("separating items in array literal".into()),
|
|
||||||
Position::eof(),
|
|
||||||
)
|
|
||||||
})? {
|
})? {
|
||||||
(Token::Comma, _) => {
|
(Token::Comma, _) => {
|
||||||
input.next();
|
input.next();
|
||||||
}
|
}
|
||||||
(Token::RightBracket, _) => break,
|
(Token::RightBracket, _) => break,
|
||||||
(_, pos) => {
|
(_, pos) => {
|
||||||
return Err(ParseError(
|
return Err(
|
||||||
PERR::MissingComma("separating items in array literal".into()),
|
PERR::MissingComma("separating items in array literal".into())
|
||||||
*pos,
|
.into_err(*pos),
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1660,6 +1657,14 @@ fn parse_unary<'a>(
|
|||||||
.peek()
|
.peek()
|
||||||
.ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())?
|
.ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())?
|
||||||
{
|
{
|
||||||
|
// If statement is allowed to act as expressions
|
||||||
|
(Token::If, pos) => {
|
||||||
|
let pos = *pos;
|
||||||
|
Ok(Expr::Stmt(
|
||||||
|
Box::new(parse_if(input, false, allow_stmt_expr)?),
|
||||||
|
pos,
|
||||||
|
))
|
||||||
|
}
|
||||||
// -expr
|
// -expr
|
||||||
(Token::UnaryMinus, pos) => {
|
(Token::UnaryMinus, pos) => {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
@ -1956,6 +1961,7 @@ fn parse_expr<'a>(
|
|||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
allow_stmt_expr: bool,
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
|
// Parse a real expression
|
||||||
let lhs = parse_unary(input, allow_stmt_expr)?;
|
let lhs = parse_unary(input, allow_stmt_expr)?;
|
||||||
parse_binary_op(input, 1, lhs, allow_stmt_expr)
|
parse_binary_op(input, 1, lhs, allow_stmt_expr)
|
||||||
}
|
}
|
||||||
@ -1967,10 +1973,10 @@ fn ensure_not_statement_expr<'a>(
|
|||||||
) -> Result<(), ParseError> {
|
) -> Result<(), ParseError> {
|
||||||
match input
|
match input
|
||||||
.peek()
|
.peek()
|
||||||
.ok_or_else(|| ParseError(PERR::ExprExpected(type_name.to_string()), Position::eof()))?
|
.ok_or_else(|| PERR::ExprExpected(type_name.to_string()).into_err_eof())?
|
||||||
{
|
{
|
||||||
// Disallow statement expressions
|
// Disallow statement expressions
|
||||||
(Token::LeftBrace, pos) => Err(ParseError(PERR::ExprExpected(type_name.to_string()), *pos)),
|
(Token::LeftBrace, pos) => Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)),
|
||||||
// No need to check for others at this time - leave it for the expr parser
|
// No need to check for others at this time - leave it for the expr parser
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
@ -2111,10 +2117,9 @@ fn parse_let<'a>(
|
|||||||
Ok(Stmt::Const(name, Box::new(init_value), pos))
|
Ok(Stmt::Const(name, Box::new(init_value), pos))
|
||||||
}
|
}
|
||||||
// const name = expr - error
|
// const name = expr - error
|
||||||
ScopeEntryType::Constant => Err(ParseError(
|
ScopeEntryType::Constant => {
|
||||||
PERR::ForbiddenConstantExpr(name),
|
Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position()))
|
||||||
init_value.position(),
|
}
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// let name
|
// let name
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#![cfg(not(feature = "no_function"))]
|
#![cfg(not(feature = "no_function"))]
|
||||||
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT};
|
use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fn() -> Result<(), EvalAltResult> {
|
fn test_fn() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
// Expect duplicated parameters error
|
// Expect duplicated parameters error
|
||||||
match engine
|
match engine
|
||||||
|
@ -12,14 +12,15 @@ fn test_expressions() -> Result<(), EvalAltResult> {
|
|||||||
engine.eval_expression_with_scope::<INT>(&mut scope, "2 + (x + 10) * 2")?,
|
engine.eval_expression_with_scope::<INT>(&mut scope, "2 + (x + 10) * 2")?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_expression_with_scope::<INT>(&mut scope, "if x > 0 { 42 } else { 123 }")?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
assert!(engine.eval_expression::<()>("40 + 2;").is_err());
|
assert!(engine.eval_expression::<()>("40 + 2;").is_err());
|
||||||
assert!(engine.eval_expression::<()>("40 + { 2 }").is_err());
|
assert!(engine.eval_expression::<()>("40 + { 2 }").is_err());
|
||||||
assert!(engine.eval_expression::<()>("x = 42").is_err());
|
assert!(engine.eval_expression::<()>("x = 42").is_err());
|
||||||
assert!(engine.compile_expression("let x = 42").is_err());
|
assert!(engine.compile_expression("let x = 42").is_err());
|
||||||
assert!(engine
|
|
||||||
.eval_expression_with_scope::<INT>(&mut scope, "if x > 0 { 42 } else { 123 }")
|
|
||||||
.is_err());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -27,3 +27,21 @@ fn test_if() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_if_expr() -> Result<(), EvalAltResult> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r"
|
||||||
|
let x = 42;
|
||||||
|
let y = 1 + if x > 40 { 100 } else { 0 } / x;
|
||||||
|
y
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -25,8 +25,10 @@ fn test_method_call() -> Result<(), EvalAltResult> {
|
|||||||
engine.register_fn("new_ts", TestStruct::new);
|
engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
|
||||||
let ts = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
let ts = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
||||||
|
|
||||||
assert_eq!(ts.x, 1001);
|
assert_eq!(ts.x, 1001);
|
||||||
|
|
||||||
|
let ts = engine.eval::<TestStruct>("let x = new_ts(); update(x); x")?;
|
||||||
|
assert_eq!(ts.x, 1);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user