Add string iteration

This commit is contained in:
Andrew Morris
2023-05-29 18:40:02 +10:00
parent e0690f58e0
commit 4570aac6e8
5 changed files with 202 additions and 26 deletions

View File

@@ -1,2 +1,3 @@
pub mod array_iterator;
pub mod iteration_result;
pub mod string_iterator;

View File

@@ -0,0 +1,166 @@
use std::{fmt, rc::Rc};
use num_bigint::BigInt;
use crate::{
builtins::{error_builtin::ToError, type_error_builtin::ToTypeError},
native_function::{native_fn, NativeFunction},
vs_array::VsArray,
vs_class::VsClass,
vs_value::{dynamic_make_mut, ToDynamicVal, ToVal, Val, VsType},
LoadFunctionResult, ValTrait,
};
use super::iteration_result::IterationResult;
#[derive(Clone)]
pub struct StringIterator {
pub string: Rc<String>,
pub index: usize,
}
impl StringIterator {
pub fn new(string: Rc<String>) -> StringIterator {
StringIterator { string, index: 0 }
}
fn next(&mut self) -> Option<char> {
let bytes = self.string.as_bytes();
if self.index >= bytes.len() {
return None;
}
let byte = bytes[self.index];
self.index += 1;
let leading_ones = byte.leading_ones() as usize;
if leading_ones == 0 {
return Some(std::char::from_u32(byte as u32).expect("Invalid code point"));
}
if leading_ones == 1 || leading_ones > 4 || (self.index - 1) + leading_ones > bytes.len() {
panic!("Invalid unicode");
}
let mut value = (byte & (0x7F >> leading_ones)) as u32;
for _ in 1..leading_ones {
let next_byte = bytes[self.index];
self.index += 1;
if next_byte.leading_ones() != 1 {
return None;
}
value = (value << 6) | (next_byte & 0x3F) as u32;
}
Some(std::char::from_u32(value as u32).expect("Invalid code point"))
}
}
impl ValTrait for StringIterator {
fn typeof_(&self) -> VsType {
VsType::Object
}
fn to_number(&self) -> f64 {
core::f64::NAN
}
fn to_index(&self) -> Option<usize> {
None
}
fn is_primitive(&self) -> bool {
false
}
fn is_truthy(&self) -> bool {
true
}
fn is_nullish(&self) -> bool {
false
}
fn bind(&self, _params: Vec<Val>) -> Option<Val> {
None
}
fn as_bigint_data(&self) -> Option<BigInt> {
None
}
fn as_array_data(&self) -> Option<Rc<VsArray>> {
None
}
fn as_class_data(&self) -> Option<Rc<VsClass>> {
None
}
fn load_function(&self) -> LoadFunctionResult {
LoadFunctionResult::NotAFunction
}
fn sub(&self, key: Val) -> Result<Val, Val> {
if key.to_string() == "next" {
return Ok(NEXT.to_val());
}
Ok(Val::Undefined)
}
fn submov(&mut self, _key: Val, _value: Val) -> Result<(), Val> {
Err("Cannot assign to subscript of string iterator".to_type_error())
}
fn pretty_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\x1b[36m[StringIterator]\x1b[39m")
}
fn codify(&self) -> String {
format!(
"StringIterator({{ string: {}, index: {} }})",
Val::String(self.string.clone()).codify(),
self.index
)
}
}
impl fmt::Display for StringIterator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[object String Iterator]")
}
}
static NEXT: NativeFunction = native_fn(|mut this, _| {
let dynamic = match this.get_mut()? {
Val::Dynamic(dynamic) => dynamic,
_ => return Err("TODO: indirection".to_error()),
};
let iter = dynamic_make_mut(dynamic)
.as_any_mut()
.downcast_mut::<StringIterator>()
.ok_or_else(|| "StringIterator.next called on different object".to_type_error())?;
let char = iter.next();
Ok(
match char {
Some(c) => IterationResult {
value: c.to_val(),
done: false,
},
None => IterationResult {
value: Val::Undefined,
done: true,
},
}
.to_dynamic_val(),
)
});

View File

@@ -3,35 +3,40 @@ use std::{rc::Rc, str::Chars};
use crate::{
builtins::error_builtin::ToError,
helpers::{to_wrapping_index, to_wrapping_index_clamped},
iteration::string_iterator::StringIterator,
native_function::{native_fn, NativeFunction},
vs_value::{ToVal, Val},
vs_symbol::VsSymbol,
vs_value::{ToDynamicVal, ToVal, Val},
ValTrait,
};
pub fn op_sub_string(string_data: &Rc<String>, subscript: &Val) -> Val {
let right_index = match subscript.to_index() {
None => {
let method = subscript.to_string();
let method_str = method.as_str();
if let Some(subscript) = subscript.to_index() {
let string_bytes = string_data.as_bytes();
return match method_str {
"length" => Val::Number(string_data.as_bytes().len() as f64),
_ => get_string_method(method_str),
};
if subscript >= string_bytes.len() {
return Val::Undefined;
}
Some(i) => i,
return match unicode_at(string_bytes, string_bytes.len(), subscript) {
Some(char) => char.to_string().to_val(),
None => "".to_val(),
};
}
if let Val::Symbol(subscript) = subscript {
match subscript {
VsSymbol::ITERATOR => return VALUES.to_val(),
}
}
let method = subscript.to_string();
let method_str = method.as_str();
return match method_str {
"length" => Val::Number(string_data.as_bytes().len() as f64),
_ => get_string_method(method_str),
};
let string_bytes = string_data.as_bytes();
if right_index >= string_bytes.len() {
return Val::Undefined;
}
match unicode_at(string_bytes, string_bytes.len(), right_index) {
Some(char) => char.to_string().to_val(),
None => "".to_val(),
}
}
pub fn get_string_method(method: &str) -> Val {
@@ -656,6 +661,13 @@ static VALUE_OF: NativeFunction = native_fn(|this, _params| {
})
});
static VALUES: NativeFunction = native_fn(|this, _params| {
Ok(match this.get() {
Val::String(string_data) => StringIterator::new(string_data.clone()).to_dynamic_val(),
_ => return Err("string indirection".to_error()),
})
});
/**
* Tries to match str_chars_param against matcher.
* - Successful match: Advances str_chars_param and returns true.

View File

@@ -646,7 +646,7 @@ static UNSHIFT: NativeFunction = native_fn(|mut this, params| {
})
});
pub static VALUES: NativeFunction = native_fn(|this, _params| {
static VALUES: NativeFunction = native_fn(|this, _params| {
Ok(match this.get() {
Val::Array(array_data) => ArrayIterator::new(array_data.clone()).to_dynamic_val(),
_ => return Err("array indirection".to_error()),