Add counter variable to for statement.
This commit is contained in:
parent
989cb702c0
commit
1e66f1963a
@ -17,6 +17,7 @@ Breaking changes
|
|||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
* New syntax for `for` statement to include counter variable.
|
||||||
* An integer value can now be indexed to get/set a single bit.
|
* An integer value can now be indexed to get/set a single bit.
|
||||||
* The `bits` method of an integer can be used to iterate through its bits.
|
* The `bits` method of an integer can be used to iterate through its bits.
|
||||||
|
|
||||||
|
10
src/ast.rs
10
src/ast.rs
@ -944,8 +944,8 @@ pub enum Stmt {
|
|||||||
While(Expr, Box<StmtBlock>, Position),
|
While(Expr, Box<StmtBlock>, Position),
|
||||||
/// `do` `{` stmt `}` `while`|`until` expr
|
/// `do` `{` stmt `}` `while`|`until` expr
|
||||||
Do(Box<StmtBlock>, Expr, bool, Position),
|
Do(Box<StmtBlock>, Expr, bool, Position),
|
||||||
/// `for` id `in` expr `{` stmt `}`
|
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
|
||||||
For(Expr, Box<(Ident, StmtBlock)>, Position),
|
For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position),
|
||||||
/// \[`export`\] `let` id `=` expr
|
/// \[`export`\] `let` id `=` expr
|
||||||
Let(Expr, Box<Ident>, bool, Position),
|
Let(Expr, Box<Ident>, bool, Position),
|
||||||
/// \[`export`\] `const` id `=` expr
|
/// \[`export`\] `const` id `=` expr
|
||||||
@ -1166,7 +1166,7 @@ impl Stmt {
|
|||||||
Self::While(condition, block, _) | Self::Do(block, condition, _, _) => {
|
Self::While(condition, block, _) | Self::Do(block, condition, _, _) => {
|
||||||
condition.is_pure() && block.0.iter().all(Stmt::is_pure)
|
condition.is_pure() && block.0.iter().all(Stmt::is_pure)
|
||||||
}
|
}
|
||||||
Self::For(iterable, x, _) => iterable.is_pure() && (x.1).0.iter().all(Stmt::is_pure),
|
Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.iter().all(Stmt::is_pure),
|
||||||
Self::Let(_, _, _, _)
|
Self::Let(_, _, _, _)
|
||||||
| Self::Const(_, _, _, _)
|
| Self::Const(_, _, _, _)
|
||||||
| Self::Assignment(_, _)
|
| Self::Assignment(_, _)
|
||||||
@ -1286,7 +1286,7 @@ impl Stmt {
|
|||||||
if !e.walk(path, on_node) {
|
if !e.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for s in &(x.1).0 {
|
for s in &(x.2).0 {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1777,7 +1777,7 @@ impl fmt::Debug for Expr {
|
|||||||
}
|
}
|
||||||
f.write_str(&x.2)?;
|
f.write_str(&x.2)?;
|
||||||
match i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
|
match i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
|
||||||
Some(n) => write!(f, ", {}", n)?,
|
Some(n) => write!(f, " #{}", n)?,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
f.write_str(")")
|
f.write_str(")")
|
||||||
|
@ -14,7 +14,7 @@ use crate::token::Token;
|
|||||||
use crate::utils::get_hasher;
|
use crate::utils::get_hasher;
|
||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope,
|
Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope,
|
||||||
Shared, StaticVec,
|
Shared, StaticVec, INT,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -2521,7 +2521,7 @@ impl Engine {
|
|||||||
|
|
||||||
// For loop
|
// For loop
|
||||||
Stmt::For(expr, x, _) => {
|
Stmt::For(expr, x, _) => {
|
||||||
let (Ident { name, .. }, statements) = x.as_ref();
|
let (Ident { name, .. }, counter, statements) = x.as_ref();
|
||||||
let iter_obj = self
|
let iter_obj = self
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||||
.flatten();
|
.flatten();
|
||||||
@ -2550,17 +2550,40 @@ impl Engine {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if let Some(func) = func {
|
if let Some(func) = func {
|
||||||
// Add the loop variable
|
// Add the loop variables
|
||||||
let var_name: Cow<'_, str> = if state.is_global() {
|
let orig_scope_len = scope.len();
|
||||||
name.to_string().into()
|
let counter_index = if let Some(Ident { name, .. }) = counter {
|
||||||
|
scope.push(unsafe_cast_var_name_to_lifetime(name), 0 as INT);
|
||||||
|
Some(scope.len() - 1)
|
||||||
} else {
|
} else {
|
||||||
unsafe_cast_var_name_to_lifetime(name).into()
|
None
|
||||||
};
|
};
|
||||||
scope.push(var_name, ());
|
scope.push(unsafe_cast_var_name_to_lifetime(name), ());
|
||||||
let index = scope.len() - 1;
|
let index = scope.len() - 1;
|
||||||
state.scope_level += 1;
|
state.scope_level += 1;
|
||||||
|
|
||||||
for iter_value in func(iter_obj) {
|
for (x, iter_value) in func(iter_obj).enumerate() {
|
||||||
|
// Increment counter
|
||||||
|
if let Some(c) = counter_index {
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
if x > INT::MAX as usize {
|
||||||
|
return EvalAltResult::ErrorArithmetic(
|
||||||
|
format!("for-loop counter overflow: {}", x),
|
||||||
|
counter
|
||||||
|
.as_ref()
|
||||||
|
.expect("never fails because `counter` is `Some`")
|
||||||
|
.pos,
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut counter_var = scope
|
||||||
|
.get_mut_by_index(c)
|
||||||
|
.write_lock::<INT>()
|
||||||
|
.expect("never fails because the counter always holds an `INT`");
|
||||||
|
*counter_var = x as INT;
|
||||||
|
}
|
||||||
|
|
||||||
let loop_var = scope.get_mut_by_index(index);
|
let loop_var = scope.get_mut_by_index(index);
|
||||||
let value = iter_value.flatten();
|
let value = iter_value.flatten();
|
||||||
|
|
||||||
@ -2600,7 +2623,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.scope_level -= 1;
|
state.scope_level -= 1;
|
||||||
scope.rewind(scope.len() - 1);
|
scope.rewind(orig_scope_len);
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
} else {
|
} else {
|
||||||
EvalAltResult::ErrorFor(expr.position()).into()
|
EvalAltResult::ErrorFor(expr.position()).into()
|
||||||
@ -2672,8 +2695,6 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
_ => {
|
_ => {
|
||||||
use crate::INT;
|
|
||||||
|
|
||||||
let mut err_map: Map = Default::default();
|
let mut err_map: Map = Default::default();
|
||||||
let err_pos = err.take_position();
|
let err_pos = err.take_position();
|
||||||
|
|
||||||
|
@ -594,8 +594,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
|||||||
// for id in expr { block }
|
// for id in expr { block }
|
||||||
Stmt::For(iterable, x, _) => {
|
Stmt::For(iterable, x, _) => {
|
||||||
optimize_expr(iterable, state, false);
|
optimize_expr(iterable, state, false);
|
||||||
let body = mem::take(x.1.statements()).into_vec();
|
let body = mem::take(x.2.statements()).into_vec();
|
||||||
*x.1.statements() = optimize_stmt_block(body, state, false, true, false).into();
|
*x.2.statements() = optimize_stmt_block(body, state, false, true, false).into();
|
||||||
}
|
}
|
||||||
// let id = expr;
|
// let id = expr;
|
||||||
Stmt::Let(expr, _, _, _) => optimize_expr(expr, state, false),
|
Stmt::Let(expr, _, _, _) => optimize_expr(expr, state, false),
|
||||||
|
@ -116,6 +116,8 @@ pub enum ParseErrorType {
|
|||||||
DuplicatedProperty(String),
|
DuplicatedProperty(String),
|
||||||
/// A `switch` case is duplicated.
|
/// A `switch` case is duplicated.
|
||||||
DuplicatedSwitchCase,
|
DuplicatedSwitchCase,
|
||||||
|
/// A variable name is duplicated. Wrapped value is the variable name.
|
||||||
|
DuplicatedVariable(String),
|
||||||
/// The default case of a `switch` statement is not the last.
|
/// The default case of a `switch` statement is not the last.
|
||||||
WrongSwitchDefaultCase,
|
WrongSwitchDefaultCase,
|
||||||
/// The case condition of a `switch` statement is not appropriate.
|
/// The case condition of a `switch` statement is not appropriate.
|
||||||
@ -200,6 +202,7 @@ impl ParseErrorType {
|
|||||||
Self::MalformedCapture(_) => "Invalid capturing",
|
Self::MalformedCapture(_) => "Invalid capturing",
|
||||||
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
|
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
|
||||||
Self::DuplicatedSwitchCase => "Duplicated switch case",
|
Self::DuplicatedSwitchCase => "Duplicated switch case",
|
||||||
|
Self::DuplicatedVariable(_) => "Duplicated variable name",
|
||||||
Self::WrongSwitchDefaultCase => "Default switch case is not the last",
|
Self::WrongSwitchDefaultCase => "Default switch case is not the last",
|
||||||
Self::WrongSwitchCaseCondition => "Default switch case cannot have condition",
|
Self::WrongSwitchCaseCondition => "Default switch case cannot have condition",
|
||||||
Self::PropertyExpected => "Expecting name of a property",
|
Self::PropertyExpected => "Expecting name of a property",
|
||||||
@ -247,6 +250,7 @@ impl fmt::Display for ParseErrorType {
|
|||||||
write!(f, "Duplicated property '{}' for object map literal", s)
|
write!(f, "Duplicated property '{}' for object map literal", s)
|
||||||
}
|
}
|
||||||
Self::DuplicatedSwitchCase => f.write_str(self.desc()),
|
Self::DuplicatedSwitchCase => f.write_str(self.desc()),
|
||||||
|
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s),
|
||||||
|
|
||||||
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
|
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
|
||||||
|
|
||||||
|
@ -2144,6 +2144,21 @@ fn parse_for(
|
|||||||
lib: &mut FunctionsLib,
|
lib: &mut FunctionsLib,
|
||||||
mut settings: ParseSettings,
|
mut settings: ParseSettings,
|
||||||
) -> Result<Stmt, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
|
fn get_name_pos(input: &mut TokenStream) -> Result<(String, Position), ParseError> {
|
||||||
|
match input.next().expect(NEVER_ENDS) {
|
||||||
|
// Variable name
|
||||||
|
(Token::Identifier(s), pos) => Ok((s, pos)),
|
||||||
|
// Reserved keyword
|
||||||
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
|
Err(PERR::Reserved(s).into_err(pos))
|
||||||
|
}
|
||||||
|
// Bad identifier
|
||||||
|
(Token::LexError(err), pos) => Err(err.into_err(pos)),
|
||||||
|
// Not a variable name
|
||||||
|
(_, pos) => Err(PERR::VariableExpected.into_err(pos)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||||
|
|
||||||
@ -2151,17 +2166,36 @@ fn parse_for(
|
|||||||
settings.pos = eat_token(input, Token::For);
|
settings.pos = eat_token(input, Token::For);
|
||||||
|
|
||||||
// for name ...
|
// for name ...
|
||||||
let (name, name_pos) = match input.next().expect(NEVER_ENDS) {
|
let (name, name_pos, counter_name, counter_pos) = if match_token(input, Token::LeftParen).0 {
|
||||||
// Variable name
|
// ( name, counter )
|
||||||
(Token::Identifier(s), pos) => (s, pos),
|
let (name, name_pos) = get_name_pos(input)?;
|
||||||
// Reserved keyword
|
let (has_comma, pos) = match_token(input, Token::Comma);
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
if !has_comma {
|
||||||
return Err(PERR::Reserved(s).into_err(pos));
|
return Err(PERR::MissingToken(
|
||||||
|
Token::Comma.into(),
|
||||||
|
"after the iteration variable name".into(),
|
||||||
|
)
|
||||||
|
.into_err(pos));
|
||||||
}
|
}
|
||||||
// Bad identifier
|
let (counter_name, counter_pos) = get_name_pos(input)?;
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
|
||||||
// Not a variable name
|
if counter_name == name {
|
||||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
return Err(PERR::DuplicatedVariable(counter_name).into_err(counter_pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (has_close_paren, pos) = match_token(input, Token::RightParen);
|
||||||
|
if !has_close_paren {
|
||||||
|
return Err(PERR::MissingToken(
|
||||||
|
Token::RightParen.into(),
|
||||||
|
"to close the iteration variable".into(),
|
||||||
|
)
|
||||||
|
.into_err(pos));
|
||||||
|
}
|
||||||
|
(name, name_pos, Some(counter_name), Some(counter_pos))
|
||||||
|
} else {
|
||||||
|
// name
|
||||||
|
let (name, name_pos) = get_name_pos(input)?;
|
||||||
|
(name, name_pos, None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
// for name in ...
|
// for name in ...
|
||||||
@ -2180,8 +2214,18 @@ fn parse_for(
|
|||||||
ensure_not_statement_expr(input, "a boolean")?;
|
ensure_not_statement_expr(input, "a boolean")?;
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
let loop_var = state.get_identifier(name);
|
|
||||||
let prev_stack_len = state.stack.len();
|
let prev_stack_len = state.stack.len();
|
||||||
|
|
||||||
|
let counter_var = if let Some(name) = counter_name {
|
||||||
|
let counter_var = state.get_identifier(name);
|
||||||
|
state
|
||||||
|
.stack
|
||||||
|
.push((counter_var.clone(), AccessMode::ReadWrite));
|
||||||
|
Some(counter_var)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let loop_var = state.get_identifier(name);
|
||||||
state.stack.push((loop_var.clone(), AccessMode::ReadWrite));
|
state.stack.push((loop_var.clone(), AccessMode::ReadWrite));
|
||||||
|
|
||||||
settings.is_breakable = true;
|
settings.is_breakable = true;
|
||||||
@ -2196,6 +2240,10 @@ fn parse_for(
|
|||||||
name: loop_var,
|
name: loop_var,
|
||||||
pos: name_pos,
|
pos: name_pos,
|
||||||
},
|
},
|
||||||
|
counter_var.map(|name| Ident {
|
||||||
|
name,
|
||||||
|
pos: counter_pos.expect("never fails because `counter_var` is `Some`"),
|
||||||
|
}),
|
||||||
body.into(),
|
body.into(),
|
||||||
)),
|
)),
|
||||||
settings.pos,
|
settings.pos,
|
||||||
@ -2342,10 +2390,19 @@ fn parse_export(
|
|||||||
|
|
||||||
let rename = if match_token(input, Token::As).0 {
|
let rename = if match_token(input, Token::As).0 {
|
||||||
match input.next().expect(NEVER_ENDS) {
|
match input.next().expect(NEVER_ENDS) {
|
||||||
(Token::Identifier(s), pos) => Some(Ident {
|
(Token::Identifier(s), pos) => {
|
||||||
name: state.get_identifier(s),
|
if exports.iter().any(|(_, alias)| match alias {
|
||||||
pos,
|
Some(Ident { name, .. }) if name == &s => true,
|
||||||
}),
|
_ => false,
|
||||||
|
}) {
|
||||||
|
return Err(PERR::DuplicatedVariable(s).into_err(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Ident {
|
||||||
|
name: state.get_identifier(s),
|
||||||
|
pos,
|
||||||
|
})
|
||||||
|
}
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||||
return Err(PERR::Reserved(s).into_err(pos));
|
return Err(PERR::Reserved(s).into_err(pos));
|
||||||
}
|
}
|
||||||
|
16
tests/for.rs
16
tests/for.rs
@ -37,6 +37,22 @@ fn test_for_loop() -> Result<(), Box<EvalAltResult>> {
|
|||||||
35
|
35
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
"
|
||||||
|
let sum = 0;
|
||||||
|
let inputs = [1, 2, 3, 4, 5];
|
||||||
|
|
||||||
|
for (x, i) in inputs {
|
||||||
|
sum += x * (i + 1);
|
||||||
|
}
|
||||||
|
sum
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
55
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
"
|
"
|
||||||
|
Loading…
Reference in New Issue
Block a user