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
|
||||
----------------
|
||||
|
||||
[`type_of`]: #values-and-types
|
||||
[`type_of()`]: #values-and-types
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
[`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.
|
||||
[`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.
|
||||
|
||||
```rust
|
||||
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.
|
||||
|
||||
Although internally Rhai strings are stored as UTF-8 just like in Rust (they _are_ Rust `String`s),
|
||||
in the Rhai language they can be considered a stream 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.
|
||||
Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!), but there are major differences.
|
||||
In Rhai a string is the same as an array of Unicode characters and can be directly indexed (unlike 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`]).
|
||||
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.
|
||||
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.
|
||||
|
||||
@ -1093,17 +1097,13 @@ my_str += 12345;
|
||||
my_str == "abcABC12345"
|
||||
```
|
||||
|
||||
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.
|
||||
`if` statements
|
||||
---------------
|
||||
|
||||
```rust
|
||||
if true {
|
||||
if foo(x) {
|
||||
print("It's true!");
|
||||
} else if true {
|
||||
} else if bar == baz {
|
||||
print("It's true again!");
|
||||
} else if ... {
|
||||
:
|
||||
@ -1112,13 +1112,28 @@ if true {
|
||||
} else {
|
||||
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!");
|
||||
// ^ 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
|
||||
let x = 10;
|
||||
@ -1130,8 +1145,8 @@ while x > 0 {
|
||||
}
|
||||
```
|
||||
|
||||
Infinite loops
|
||||
--------------
|
||||
Infinite `loop`
|
||||
---------------
|
||||
|
||||
```rust
|
||||
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.
|
||||
|
||||
@ -1164,8 +1179,8 @@ for x in range(0, 50) {
|
||||
}
|
||||
```
|
||||
|
||||
Returning values
|
||||
----------------
|
||||
`return`-ing values
|
||||
-------------------
|
||||
|
||||
```rust
|
||||
return; // equivalent to return ();
|
||||
@ -1173,8 +1188,8 @@ return; // equivalent to return ();
|
||||
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.
|
||||
To deliberately return an error during an evaluation, use the `throw` keyword.
|
||||
@ -1215,8 +1230,10 @@ fn add(x, y) {
|
||||
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
|
||||
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
|
||||
fn add(x, y) {
|
||||
@ -1231,6 +1248,16 @@ print(add(2, 3)); // prints 5
|
||||
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
|
||||
|
||||
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 can be _overloaded_ based on the _number_ of parameters (but not parameter _types_, since all parameters are the same type - [`Dynamic`]).
|
||||
New definitions of the same name and number of parameters overwrite previous definitions.
|
||||
Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters
|
||||
(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
|
||||
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
|
||||
fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z) }
|
||||
fn foo(x) { print("One! " + x) }
|
||||
fn foo(x,y) { print("Two! " + x + "," + y) }
|
||||
fn foo() { print("None.") }
|
||||
fn foo(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."
|
||||
foo(1,2,3); // prints "Three!!! 1,2,3"
|
||||
foo(42); // prints "HA! NEW ONE! 42"
|
||||
foo(1,2); // prints "Two!! 1,2"
|
||||
foo(); // prints "None."
|
||||
```
|
||||
|
||||
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
|
||||
let a = new_ts(); // constructor function
|
||||
a.field = 500; // property access
|
||||
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`
|
||||
|
@ -1555,20 +1555,17 @@ fn parse_array_literal<'a>(
|
||||
arr.push(parse_expr(input, allow_stmt_expr)?);
|
||||
|
||||
match input.peek().ok_or_else(|| {
|
||||
ParseError(
|
||||
PERR::MissingRightBracket("separating items in array literal".into()),
|
||||
Position::eof(),
|
||||
)
|
||||
PERR::MissingRightBracket("separating items in array literal".into()).into_err_eof()
|
||||
})? {
|
||||
(Token::Comma, _) => {
|
||||
input.next();
|
||||
}
|
||||
(Token::RightBracket, _) => break,
|
||||
(_, pos) => {
|
||||
return Err(ParseError(
|
||||
PERR::MissingComma("separating items in array literal".into()),
|
||||
*pos,
|
||||
))
|
||||
return Err(
|
||||
PERR::MissingComma("separating items in array literal".into())
|
||||
.into_err(*pos),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1660,6 +1657,14 @@ fn parse_unary<'a>(
|
||||
.peek()
|
||||
.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
|
||||
(Token::UnaryMinus, pos) => {
|
||||
let pos = *pos;
|
||||
@ -1956,6 +1961,7 @@ fn parse_expr<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
allow_stmt_expr: bool,
|
||||
) -> Result<Expr, ParseError> {
|
||||
// Parse a real expression
|
||||
let lhs = parse_unary(input, allow_stmt_expr)?;
|
||||
parse_binary_op(input, 1, lhs, allow_stmt_expr)
|
||||
}
|
||||
@ -1967,10 +1973,10 @@ fn ensure_not_statement_expr<'a>(
|
||||
) -> Result<(), ParseError> {
|
||||
match input
|
||||
.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
|
||||
(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
|
||||
_ => Ok(()),
|
||||
}
|
||||
@ -2111,10 +2117,9 @@ fn parse_let<'a>(
|
||||
Ok(Stmt::Const(name, Box::new(init_value), pos))
|
||||
}
|
||||
// const name = expr - error
|
||||
ScopeEntryType::Constant => Err(ParseError(
|
||||
PERR::ForbiddenConstantExpr(name),
|
||||
init_value.position(),
|
||||
)),
|
||||
ScopeEntryType::Constant => {
|
||||
Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// let name
|
||||
|
@ -1,9 +1,9 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT};
|
||||
use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
|
||||
|
||||
#[test]
|
||||
fn test_fn() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
// Expect duplicated parameters error
|
||||
match engine
|
||||
|
@ -12,14 +12,15 @@ fn test_expressions() -> Result<(), EvalAltResult> {
|
||||
engine.eval_expression_with_scope::<INT>(&mut scope, "2 + (x + 10) * 2")?,
|
||||
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::<()>("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(())
|
||||
}
|
||||
|
@ -27,3 +27,21 @@ fn test_if() -> Result<(), EvalAltResult> {
|
||||
|
||||
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);
|
||||
|
||||
let ts = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
||||
|
||||
assert_eq!(ts.x, 1001);
|
||||
|
||||
let ts = engine.eval::<TestStruct>("let x = new_ts(); update(x); x")?;
|
||||
assert_eq!(ts.x, 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user