Use chars() to iterate strings.

This commit is contained in:
Stephen Chung 2021-06-07 09:47:49 +08:00
parent 859a18c6fd
commit 989cb702c0
5 changed files with 87 additions and 13 deletions

View File

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

View File

@ -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, 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<char>, 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<Self::Item> {
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<Item=Decimal>"]);
}
// Register string iterator
lib.set_iterator::<CharsStream>();
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<Item=char>"]);
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<Item=char>"]);
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<Item=char>"]);
// Register bit-field iterator
lib.set_iterator::<BitRange>();
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<Item=bool>"]);
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator<Item=bool>"]);
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<Item=bool>"]);
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator<Item=bool>"]);
let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX));
#[cfg(feature = "metadata")]

View File

@ -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::<ImmutableString>(),
|string| Box::new(string.cast::<ImmutableString>().chars().collect::<Vec<_>>().into_iter().map(Into::into))
);
});
#[export_module]

View File

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

View File

@ -235,7 +235,7 @@ fn test_for_string() -> Result<(), Box<EvalAltResult>> {
let s = "hello";
let sum = 0;
for ch in s {
for ch in s.chars() {
sum += to_int(ch);
}