diff --git a/README.md b/README.md index 51c33149..922b0969 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ let result = engine.eval_expression_with_scope::(&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::("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::(); @@ -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` 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` diff --git a/src/parser.rs b/src/parser.rs index 3881e7ec..5f53e3e7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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>, allow_stmt_expr: bool, ) -> Result { + // 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 diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 3a1c37ec..39131fea 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -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 diff --git a/tests/expressions.rs b/tests/expressions.rs index 76d58353..3d42ed98 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -12,14 +12,15 @@ fn test_expressions() -> Result<(), EvalAltResult> { engine.eval_expression_with_scope::(&mut scope, "2 + (x + 10) * 2")?, 42 ); + assert_eq!( + engine.eval_expression_with_scope::(&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::(&mut scope, "if x > 0 { 42 } else { 123 }") - .is_err()); Ok(()) } diff --git a/tests/if_block.rs b/tests/if_block.rs index e8ddb25b..4699ee57 100644 --- a/tests/if_block.rs +++ b/tests/if_block.rs @@ -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::( + r" + let x = 42; + let y = 1 + if x > 40 { 100 } else { 0 } / x; + y + " + )?, + 3 + ); + + Ok(()) +} diff --git a/tests/method_call.rs b/tests/method_call.rs index 89e33138..9e217306 100644 --- a/tests/method_call.rs +++ b/tests/method_call.rs @@ -25,8 +25,10 @@ fn test_method_call() -> Result<(), EvalAltResult> { engine.register_fn("new_ts", TestStruct::new); let ts = engine.eval::("let x = new_ts(); x.update(); x")?; - assert_eq!(ts.x, 1001); + let ts = engine.eval::("let x = new_ts(); update(x); x")?; + assert_eq!(ts.x, 1); + Ok(()) }