Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ?? break/continue/return/throw. #878

Merged
merged 7 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ Bug fixes
* `NativeCallContext<'_>` (with a lifetime parameter) now parses correctly in the `#[export_module]`
macro. This is to allow for `rust_2018_idioms` lints (thanks [`@ltabis`](https://github.com/ltabis) [864](https://github.com/rhaiscript/rhai/issues/864)).
* The `sync` feature now works properly in `no-std` builds (thanks [`@misssonder`](https://github.com/misssonder) [874](https://github.com/rhaiscript/rhai/pull/874)).
* More data-race conditions are caught and returned as errors instead of panicking.
* Missing `min` and `max` functions where both operands are floats or `Decimal` are added.

New features
------------

* The `break`, `continue`, `return` and `throw` statements can now follow the `??` operator to short-circuit operations where the value is `()`.
* A new symbol, `$func$`, is added to custom syntax to allow parsing of anonymous functions.


Expand Down
2 changes: 1 addition & 1 deletion benches/eval_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ fn bench_eval_array_loop(bench: &mut Bencher) {
let script = "
let list = [];

for i in 0..1_888 {
for i in 0..10_000 {
list.push(i);
}

Expand Down
6 changes: 3 additions & 3 deletions benches/eval_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ fn bench_eval_deeply_nested(bench: &mut Bencher) {
fn bench_eval_loop_number(bench: &mut Bencher) {
let script = "
let s = 0;
for x in 0..1_888 {
for x in 0..10000 {
s += 1;
}
";
Expand All @@ -142,7 +142,7 @@ fn bench_eval_loop_number(bench: &mut Bencher) {
fn bench_eval_loop_strings_build(bench: &mut Bencher) {
let script = r#"
let s;
for x in 0..1_888 {
for x in 0..10000 {
s = "hello, world!" + "hello, world!";
}
"#;
Expand All @@ -159,7 +159,7 @@ fn bench_eval_loop_strings_build(bench: &mut Bencher) {
fn bench_eval_loop_strings_no_build(bench: &mut Bencher) {
let script = r#"
let s;
for x in 0..1_888 {
for x in 0..10000 {
s = "hello" + "";
}
"#;
Expand Down
2 changes: 1 addition & 1 deletion benches/iterations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use test::Bencher;
fn bench_iterations_1000(bench: &mut Bencher) {
let script = "
let x = 1_000;

while x > 0 {
x -= 1;
}
Expand Down
6 changes: 4 additions & 2 deletions src/api/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ impl Engine {
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use rhai::{Engine, Dynamic, EvalAltResult, Position};
/// # use std::convert::TryInto;
/// let mut engine = Engine::new();
///
/// engine.on_invalid_array_index(|arr, index, _| match index
Expand All @@ -365,7 +366,7 @@ impl Engine {
/// arr.push((42_i64).into());
/// // Return a mutable reference to an element
/// let value_ref = arr.last_mut().unwrap();
/// Ok(value_ref.into())
/// value_ref.try_into()
/// }
/// 100 => {
/// let value = Dynamic::from(100_i64);
Expand Down Expand Up @@ -433,6 +434,7 @@ impl Engine {
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use rhai::{Engine, Dynamic, EvalAltResult, Position};
/// # use std::convert::TryInto;
/// let mut engine = Engine::new();
///
/// engine.on_map_missing_property(|map, prop, _| match prop
Expand All @@ -442,7 +444,7 @@ impl Engine {
/// map.insert("y".into(), (42_i64).into());
/// // Return a mutable reference to an element
/// let value_ref = map.get_mut("y").unwrap();
/// Ok(value_ref.into())
/// value_ref.try_into()
/// }
/// "z" => {
/// // Return a temporary value (not a reference)
Expand Down
12 changes: 6 additions & 6 deletions src/eval/chaining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::types::dynamic::Union;
use crate::{
calc_fn_hash, Dynamic, Engine, ExclusiveRange, FnArgsVec, InclusiveRange, OnceCell, Position,

Check warning on line 9 in src/eval/chaining.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging, sta...

unused imports: `ExclusiveRange`, `InclusiveRange`
RhaiResult, RhaiResultOf, Scope, ERR,
};
use std::hash::Hash;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{convert::TryInto, hash::Hash};

/// Function call hashes to index getters and setters.
static INDEXER_HASHES: OnceCell<(u64, u64)> = OnceCell::new();
Expand Down Expand Up @@ -146,7 +146,7 @@
}
};

Ok(arr.get_mut(arr_idx).map(Target::from).unwrap())
arr.get_mut(arr_idx).unwrap().try_into()
}

#[cfg(not(feature = "no_index"))]
Expand Down Expand Up @@ -192,7 +192,7 @@
}

if let Some(value) = map.get_mut(index.as_str()) {
Ok(Target::from(value))
value.try_into()
} else if self.fail_on_invalid_map_property() {
Err(ERR::ErrorPropertyNotFound(index.to_string(), idx_pos).into())
} else {
Expand Down Expand Up @@ -508,7 +508,7 @@
this_ptr.map_or_else(
|| Err(ERR::ErrorUnboundThis(*var_pos).into()),
|this_ptr| {
let target = &mut this_ptr.into();
let target = &mut this_ptr.try_into()?;
let scope = Some(scope);
self.eval_dot_index_chain_raw(
global, caches, scope, None, lhs, expr, target, rhs, idx_values,
Expand Down Expand Up @@ -969,7 +969,7 @@
})?;

{
let orig_val = &mut (&mut orig_val).into();
let orig_val = &mut (&mut orig_val).try_into()?;

self.eval_op_assignment(
global, caches, op_info, root, orig_val, new_val,
Expand Down Expand Up @@ -1139,7 +1139,7 @@
_ => Err(err),
})?;

let val = &mut (&mut val).into();
let val = &mut (&mut val).try_into()?;

let (result, may_be_changed) = self.eval_dot_index_chain_raw(
global, caches, s, _this_ptr, root, rhs, val, &x.rhs,
Expand Down
4 changes: 2 additions & 2 deletions src/eval/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::types::dynamic::AccessMode;
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, SmartString, ERR};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{fmt::Write, num::NonZeroUsize};
use std::{convert::TryInto, fmt::Write, num::NonZeroUsize};

impl Engine {
/// Search for a module within an imports stack.
Expand Down Expand Up @@ -142,7 +142,7 @@ impl Engine {

let val = scope.get_mut_by_index(index);

Ok(val.into())
val.try_into()
}
/// Search for a variable within the scope or within imports,
/// depending on whether the variable name is namespace-qualified.
Expand Down
7 changes: 5 additions & 2 deletions src/eval/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
use crate::tokenizer::Token;
use crate::types::dynamic::{AccessMode, Union};
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, VarDefInfo, ERR, INT};
use std::hash::{Hash, Hasher};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
convert::TryInto,

Check warning on line 14 in src/eval/stmt.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,sync,no_time,no_function,no_float,no_position,no...

unused import: `convert::TryInto`

Check warning on line 14 in src/eval/stmt.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_function,serde,metadata,internals,debugging, ...

unused import: `convert::TryInto`

Check warning on line 14 in src/eval/stmt.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_time,no_function,no_float,no_position,no_inde...

unused import: `convert::TryInto`
hash::{Hash, Hasher},
};

impl Engine {
/// If the value is a string, intern it.
Expand Down Expand Up @@ -310,7 +313,7 @@

self.track_operation(global, lhs.position())?;

let target = &mut this_ptr.unwrap().into();
let target = &mut this_ptr.unwrap().try_into()?;

self.eval_op_assignment(global, caches, op_info, lhs, target, rhs_val)?;
}
Expand Down
23 changes: 15 additions & 8 deletions src/eval/target.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//! Type to hold a mutable reference to the target of an evaluation.

use crate::{Dynamic, Position, RhaiResultOf};
use std::borrow::{Borrow, BorrowMut};
use crate::{Dynamic, EvalAltResult, Position, RhaiError, RhaiResultOf};

Check warning on line 3 in src/eval/target.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,sync,no_time,no_function,no_float,no_position,no...

unused import: `EvalAltResult`

Check warning on line 3 in src/eval/target.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_function,serde,metadata,internals,debugging, ...

unused import: `EvalAltResult`

Check warning on line 3 in src/eval/target.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_time,no_function,no_float,no_position,no_inde...

unused import: `EvalAltResult`

Check warning on line 3 in src/eval/target.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_closure,serde,metadata,internals,debugging, s...

unused import: `EvalAltResult`
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
borrow::{Borrow, BorrowMut},
convert::TryFrom,
};

/// Calculate an offset+len pair given an actual length of the underlying array.
///
Expand Down Expand Up @@ -413,21 +416,25 @@
}
}

impl<'a> From<&'a mut Dynamic> for Target<'a> {
impl<'a> TryFrom<&'a mut Dynamic> for Target<'a> {
type Error = RhaiError;

#[inline]
fn from(value: &'a mut Dynamic) -> Self {
fn try_from(value: &'a mut Dynamic) -> Result<Self, Self::Error> {
#[cfg(not(feature = "no_closure"))]
if value.is_shared() {
// Cloning is cheap for a shared value
let shared_value = value.clone();
let guard = value.write_lock::<Dynamic>().unwrap();
return Self::SharedValue {
let Some(guard) = value.write_lock::<Dynamic>() else {
return Err(EvalAltResult::ErrorDataRace(String::new(), Position::NONE).into());
};
return Ok(Self::SharedValue {
guard,
shared_value,
};
});
}

Self::RefMut(value)
Ok(Self::RefMut(value))
}
}

Expand Down
48 changes: 48 additions & 0 deletions src/packages/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ mod min_max_functions {
mod float_functions {
use crate::INT;

#[rhai_fn(name = "max")]
pub fn max_ff_32(x: f32, y: f32) -> f32 {
if x >= y {
x
} else {
y
}
}
#[rhai_fn(name = "max")]
pub fn max_if_32(x: INT, y: f32) -> f32 {
let (x, y) = (x as f32, y);
Expand All @@ -134,6 +142,14 @@ mod float_functions {
}
}
#[rhai_fn(name = "min")]
pub fn min_ff_32(x: f32, y: f32) -> f32 {
if x <= y {
x
} else {
y
}
}
#[rhai_fn(name = "min")]
pub fn min_if_32(x: INT, y: f32) -> f32 {
let (x, y) = (x as f32, y);
if x <= y {
Expand All @@ -152,6 +168,14 @@ mod float_functions {
}
}
#[rhai_fn(name = "max")]
pub fn max_ff_64(x: f64, y: f64) -> f64 {
if x >= y {
x
} else {
y
}
}
#[rhai_fn(name = "max")]
pub fn max_if_64(x: INT, y: f64) -> f64 {
let (x, y) = (x as f64, y);
if x >= y {
Expand All @@ -170,6 +194,14 @@ mod float_functions {
}
}
#[rhai_fn(name = "min")]
pub fn min_ff_64(x: f64, y: f64) -> f64 {
if x <= y {
x
} else {
y
}
}
#[rhai_fn(name = "min")]
pub fn min_if_64(x: INT, y: f64) -> f64 {
let (x, y) = (x as f64, y);
if x <= y {
Expand Down Expand Up @@ -419,6 +451,14 @@ mod decimal_functions {
use crate::INT;
use rust_decimal::Decimal;

#[rhai_fn(name = "max")]
pub fn max_dd(x: Decimal, y: Decimal) -> Decimal {
if x >= y {
x
} else {
y
}
}
#[rhai_fn(name = "max")]
pub fn max_id(x: INT, y: Decimal) -> Decimal {
let x = x.into();
Expand All @@ -438,6 +478,14 @@ mod decimal_functions {
}
}
#[rhai_fn(name = "min")]
pub fn min_dd(x: Decimal, y: Decimal) -> Decimal {
if x <= y {
x
} else {
y
}
}
#[rhai_fn(name = "min")]
pub fn min_id(x: INT, y: Decimal) -> Decimal {
let x = x.into();
if x <= y {
Expand Down
33 changes: 22 additions & 11 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2265,20 +2265,31 @@ impl Engine {

// Parse the RHS
let rhs = match op_token {
Token::DoubleQuestion
if matches!(
state.input.peek().unwrap().0,
Token::Break | Token::Continue | Token::Return | Token::Throw
) =>
{
let stmt = self.parse_stmt(state, settings)?;
let block: StmtBlock = stmt.into();
Expr::Stmt(block.into())
}
// [xxx..] | (xxx..) | {xxx..} | xxx.., | xxx..; | xxx.. =>
// [xxx..=] | (xxx..=) | {xxx..=} | xxx..=, | xxx..=; | xxx..= =>
Token::ExclusiveRange | Token::InclusiveRange => {
let (next_op, next_pos) = state.input.peek().unwrap();

match next_op {
Token::ExclusiveRange | Token::InclusiveRange
if matches!(
state.input.peek().unwrap().0,
Token::RightBracket
| Token::RightParen
| Token::RightBrace
| Token::Comma
| Token::SemiColon
| Token::DoubleArrow => Expr::Unit(*next_pos),
_ => self.parse_unary(state, settings)?,
}
| Token::RightParen
| Token::RightBrace
| Token::Comma
| Token::SemiColon
| Token::DoubleArrow
) =>
{
let (_, next_pos) = state.input.peek().unwrap();
Expr::Unit(*next_pos)
}
_ => self.parse_unary(state, settings)?,
};
Expand Down
1 change: 1 addition & 0 deletions src/types/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorIndexNotFound(s, ..) => write!(f, "Invalid index: {s}")?,
Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {s}")?,
Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {s}")?,
Self::ErrorDataRace(s, ..) if s.is_empty() => write!(f, "Data race detected")?,
Self::ErrorDataRace(s, ..) => write!(f, "Data race detected on variable '{s}'")?,

Self::ErrorDotExpr(s, ..) if s.is_empty() => f.write_str("Malformed dot expression")?,
Expand Down
4 changes: 2 additions & 2 deletions tests/arrays.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![cfg(not(feature = "no_index"))]
use rhai::{Array, Dynamic, Engine, EvalAltResult, ParseErrorType, Position, INT};

Check warning on line 2 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (stable, macos-latest, false)

unused imports: `EvalAltResult`, `Position`

Check warning on line 2 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (nightly, ubuntu-latest, true, --features unstable)

unused imports: `EvalAltResult` and `Position`

Check warning on line 2 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,metadata, stable, false)

unused imports: `EvalAltResult`, `Position`

Check warning on line 2 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,unicode-xid-ident, stable, false)

unused imports: `EvalAltResult`, `Position`

Check warning on line 2 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, stable, false)

unused imports: `EvalAltResult`, `Position`
use std::iter::FromIterator;
use std::{convert::TryInto, iter::FromIterator};

Check warning on line 3 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (stable, macos-latest, false)

unused import: `convert::TryInto`

Check warning on line 3 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (beta, ubuntu-latest, false, --features unstable)

unused import: `convert::TryInto`

Check warning on line 3 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,serde, stable, false)

unused import: `convert::TryInto`

Check warning on line 3 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,metadata, stable, false)

unused import: `convert::TryInto`

Check warning on line 3 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (stable, windows-latest, false)

unused import: `convert::TryInto`

Check warning on line 3 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,decimal, stable, false)

unused import: `convert::TryInto`

Check warning on line 3 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,unicode-xid-ident, stable, false)

unused import: `convert::TryInto`

Check warning on line 3 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_float,decimal, stable, false)

unused import: `convert::TryInto`

Check warning on line 3 in tests/arrays.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, stable, false)

unused import: `convert::TryInto`

#[test]
fn test_arrays() {
Expand Down Expand Up @@ -509,7 +509,7 @@
engine.on_invalid_array_index(|arr, index, _| match index {
-100 => {
arr.push((42 as INT).into());
Ok(arr.last_mut().unwrap().into())
arr.last_mut().unwrap().try_into()
}
100 => Ok(Dynamic::from(100 as INT).into()),
_ => Err(EvalAltResult::ErrorArrayBounds(arr.len(), index, Position::NONE).into()),
Expand Down
Loading
Loading