Allow if expressions.

This commit is contained in:
Stephen Chung 2020-03-27 23:47:23 +08:00
parent a541a4507f
commit ef6dd9414a
6 changed files with 115 additions and 59 deletions

108
README.md
View File

@ -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`

View File

@ -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

View File

@ -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

View File

@ -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(())
} }

View File

@ -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(())
}

View File

@ -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(())
} }