diff --git a/CHANGELOG.md b/CHANGELOG.md index 990ceab6..e616dc13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,16 @@ Bug fixes * Fixed incorrect optimization regarding chain-indexing with non-numeric index. +Breaking changes +---------------- + +* To keep the API consistent, strings are no longer iterable by default. Use the `chars` method to iterator the characters in a string. + New features ------------ * 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. Version 0.20.2 diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index f5e7cc27..b88f8a10 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -10,7 +10,7 @@ use num_traits::{CheckedAdd as Add, CheckedSub as Sub}; #[cfg(feature = "unchecked")] use std::ops::{Add, Sub}; -// Register range function with step +// Range iterator with step #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] struct StepRange(T, T, T) where @@ -126,7 +126,7 @@ where } } -// Register range function with step +// Bit-field iterator with step #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] struct BitRange(INT, INT, usize); @@ -191,6 +191,64 @@ impl Iterator for BitRange { } } +// String iterator over characters +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +struct CharsStream(Vec, usize); + +impl CharsStream { + pub fn new(string: &str, from: INT, len: INT) -> Self { + if len <= 0 { + return Self(Default::default(), 0); + } + if from >= 0 { + return Self( + string + .chars() + .skip(from as usize) + .take(len as usize) + .collect(), + 0, + ); + } + #[cfg(not(feature = "unchecked"))] + return if let Some(abs_from) = from.checked_abs() { + let num_chars = string.chars().count(); + let offset = if num_chars < (abs_from as usize) { + 0 + } else { + num_chars - (abs_from as usize) + }; + Self(string.chars().skip(offset).take(len as usize).collect(), 0) + } else { + Self(string.chars().skip(0).take(len as usize).collect(), 0) + }; + + #[cfg(feature = "unchecked")] + return Self( + string + .chars() + .skip(from as usize) + .take(len as usize) + .collect, + 0, + ); + } +} + +impl Iterator for CharsStream { + type Item = char; + + fn next(&mut self) -> Option { + if self.1 >= self.0.len() { + None + } else { + let ch = self.0[self.1]; + self.1 += 1; + Some(ch) + } + } +} + macro_rules! reg_range { ($lib:ident | $x:expr => $( $y:ty ),*) => { $( @@ -370,15 +428,31 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator"]); } + // Register string iterator + lib.set_iterator::(); + + let _hash = lib.set_native_fn("chars", |string, from,len| Ok(CharsStream::new(string, from, len))); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "len: INT", "Iterator"]); + + let _hash = lib.set_native_fn("chars", |string, from| Ok(CharsStream::new(string, from, INT::MAX))); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "Iterator"]); + + let _hash = lib.set_native_fn("chars", |string| Ok(CharsStream::new(string, 0, INT::MAX))); + #[cfg(feature = "metadata")] + lib.update_fn_metadata(_hash, &["string: &str", "Iterator"]); + + // Register bit-field iterator lib.set_iterator::(); let _hash = lib.set_native_fn("bits", |value, from, len| BitRange::new(value, from, len)); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "from: Decimal", "len: Decimal", "Iterator"]); + lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator"]); let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX)); #[cfg(feature = "metadata")] - lib.update_fn_metadata(_hash, &["value: INT", "from: Decimal", "Iterator"]); + lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator"]); let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX)); #[cfg(feature = "metadata")] diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index a88ddf84..86694e84 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::plugin::*; -use crate::{def_package, Dynamic, ImmutableString, StaticVec, INT}; +use crate::{def_package, Dynamic, StaticVec, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, mem}; @@ -10,12 +10,6 @@ use super::string_basic::{print_with_func, FUNC_TO_STRING}; def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { combine_with_exported_module!(lib, "string", string_functions); - - // Register string iterator - lib.set_iter( - TypeId::of::(), - |string| Box::new(string.cast::().chars().collect::>().into_iter().map(Into::into)) - ); }); #[export_module] diff --git a/src/result.rs b/src/result.rs index 354f5083..3b3f08f7 100644 --- a/src/result.rs +++ b/src/result.rs @@ -113,7 +113,7 @@ impl EvalAltResult { Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index", Self::ErrorStringBounds(_, _, _) => "String index out of bounds", Self::ErrorBitFieldBounds(_, _, _) => "Bit-field index out of bounds", - Self::ErrorFor(_) => "For loop expects an array, object map, or range", + Self::ErrorFor(_) => "For loop expects a type with an iterator defined", Self::ErrorVariableNotFound(_, _) => "Variable not found", Self::ErrorModuleNotFound(_, _) => "Module not found", Self::ErrorDataRace(_, _) => "Data race detected when accessing variable", diff --git a/tests/for.rs b/tests/for.rs index 0a7cb840..59986344 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -235,7 +235,7 @@ fn test_for_string() -> Result<(), Box> { let s = "hello"; let sum = 0; - for ch in s { + for ch in s.chars() { sum += to_int(ch); }