use js_sys::{Array, Function, Object, Reflect};
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use web_sys::console;
#[wasm_bindgen]
pub struct Pipeline {
operations: Vec<Operation>,
}
#[derive(Clone)]
enum Operation {
Map(Rc<dyn Fn(JsValue) -> JsValue>),
Filter(Rc<dyn Fn(&JsValue) -> bool>),
MapFilter {
map: Rc<dyn Fn(JsValue) -> JsValue>,
filter: Rc<dyn Fn(&JsValue) -> bool>,
},
FlatMap(Rc<dyn Fn(JsValue) -> Vec<JsValue>>),
Take(usize),
TakeWhile(Rc<dyn Fn(&JsValue) -> bool>),
Drop(usize),
DropWhile(Rc<dyn Fn(&JsValue) -> bool>),
Tap(Rc<dyn Fn(&JsValue)>),
}
#[wasm_bindgen]
impl Pipeline {
#[wasm_bindgen(constructor)]
pub fn new() -> Pipeline {
Pipeline {
operations: Vec::new(),
}
}
#[wasm_bindgen]
pub fn map(&self, f: &Function) -> Pipeline {
let f = f.clone();
let mut ops = self.operations.clone();
let map_fn = Rc::new(move |val: JsValue| -> JsValue {
let this = JsValue::null();
f.call1(&this, &val).unwrap_or(JsValue::undefined())
}) as Rc<dyn Fn(JsValue) -> JsValue>;
ops.push(Operation::Map(map_fn));
Pipeline { operations: ops }
}
#[wasm_bindgen]
pub fn filter(&self, pred: &Function) -> Pipeline {
let pred = pred.clone();
let mut ops = self.operations.clone();
let filter_fn = Rc::new(move |val: &JsValue| -> bool {
let this = JsValue::null();
match pred.call1(&this, val) {
Ok(result) => result.as_bool().unwrap_or(false),
Err(_) => false,
}
}) as Rc<dyn Fn(&JsValue) -> bool>;
if let Some(Operation::Map(map_fn)) = ops.pop() {
ops.push(Operation::MapFilter {
map: map_fn,
filter: filter_fn,
});
} else {
ops.push(Operation::Filter(filter_fn));
}
Pipeline { operations: ops }
}
#[wasm_bindgen(js_name = flatMap)]
pub fn flat_map(&self, f: &Function) -> Pipeline {
let f = f.clone();
let mut ops = self.operations.clone();
let flatmap_fn = Rc::new(move |val: JsValue| -> Vec<JsValue> {
let this = JsValue::null();
match f.call1(&this, &val) {
Ok(result) => {
if let Ok(array) = result.dyn_into::<Array>() {
(0..array.length()).map(|i| array.get(i)).collect()
} else {
vec![]
}
}
Err(_) => vec![],
}
}) as Rc<dyn Fn(JsValue) -> Vec<JsValue>>;
ops.push(Operation::FlatMap(flatmap_fn));
Pipeline { operations: ops }
}
#[wasm_bindgen]
pub fn take(&self, n: usize) -> Pipeline {
let mut ops = self.operations.clone();
ops.push(Operation::Take(n));
Pipeline { operations: ops }
}
#[wasm_bindgen(js_name = takeWhile)]
pub fn take_while(&self, pred: &Function) -> Pipeline {
let pred = pred.clone();
let mut ops = self.operations.clone();
ops.push(Operation::TakeWhile(Rc::new(move |val| {
let this = JsValue::null();
match pred.call1(&this, val) {
Ok(result) => result.as_bool().unwrap_or(false),
Err(_) => false,
}
})));
Pipeline { operations: ops }
}
#[wasm_bindgen]
pub fn drop(&self, n: usize) -> Pipeline {
let mut ops = self.operations.clone();
ops.push(Operation::Drop(n));
Pipeline { operations: ops }
}
#[wasm_bindgen(js_name = dropWhile)]
pub fn drop_while(&self, pred: &Function) -> Pipeline {
let pred = pred.clone();
let mut ops = self.operations.clone();
ops.push(Operation::DropWhile(Rc::new(move |val| {
let this = JsValue::null();
match pred.call1(&this, val) {
Ok(result) => result.as_bool().unwrap_or(false),
Err(_) => false,
}
})));
Pipeline { operations: ops }
}
#[wasm_bindgen]
pub fn tap(&self, f: &Function) -> Pipeline {
let f = f.clone();
let mut ops = self.operations.clone();
ops.push(Operation::Tap(Rc::new(move |val| {
let this = JsValue::null();
let _ = f.call1(&this, val);
})));
Pipeline { operations: ops }
}
#[wasm_bindgen]
pub fn pluck(&self, property_name: &str) -> Pipeline {
let prop_key = JsValue::from_str(property_name);
let mut ops = self.operations.clone();
let map_fn = Rc::new(move |val: JsValue| -> JsValue {
Reflect::get(&val, &prop_key).unwrap_or(JsValue::undefined())
}) as Rc<dyn Fn(JsValue) -> JsValue>;
ops.push(Operation::Map(map_fn));
Pipeline { operations: ops }
}
#[wasm_bindgen]
pub fn project(&self, keys: &JsValue) -> Pipeline {
let keys_array: Array = keys.clone().dyn_into().unwrap_or_else(|_| Array::new());
let key_strings: Vec<JsValue> = (0..keys_array.length())
.map(|i| keys_array.get(i))
.collect();
let mut ops = self.operations.clone();
let map_fn = Rc::new(move |val: JsValue| -> JsValue {
let result = Object::new();
for key in &key_strings {
if let Ok(prop_val) = Reflect::get(&val, key) {
let _ = Reflect::set(&result, key, &prop_val);
}
}
result.into()
}) as Rc<dyn Fn(JsValue) -> JsValue>;
ops.push(Operation::Map(map_fn));
Pipeline { operations: ops }
}
#[wasm_bindgen]
pub fn compact(&self) -> Pipeline {
let mut ops = self.operations.clone();
let filter_fn = Rc::new(move |val: &JsValue| -> bool {
val.as_bool() != Some(false)
&& !val.is_null()
&& !val.is_undefined()
&& val.as_f64() != Some(0.0)
&& val.as_string().is_none_or(|s| !s.is_empty())
&& !is_nan(val)
}) as Rc<dyn Fn(&JsValue) -> bool>;
ops.push(Operation::Filter(filter_fn));
Pipeline { operations: ops }
}
#[wasm_bindgen]
pub fn flatten(&self, depth: usize) -> Pipeline {
let mut ops = self.operations.clone();
let flatmap_fn = Rc::new(move |val: JsValue| -> Vec<JsValue> { flatten_value(val, depth) })
as Rc<dyn Fn(JsValue) -> Vec<JsValue>>;
ops.push(Operation::FlatMap(flatmap_fn));
Pipeline { operations: ops }
}
#[wasm_bindgen(js_name = whereMatches)]
pub fn where_matches(&self, spec: &JsValue) -> Pipeline {
let spec_obj = spec.clone();
let spec_keys = Object::keys(&Object::from(spec_obj.clone()));
let mut spec_entries: Vec<(JsValue, JsValue)> = Vec::new();
for i in 0..spec_keys.length() {
let key = spec_keys.get(i);
if let Ok(val) = Reflect::get(&spec_obj, &key) {
spec_entries.push((key, val));
}
}
let mut ops = self.operations.clone();
let filter_fn = Rc::new(move |val: &JsValue| -> bool {
for (key, expected) in &spec_entries {
match Reflect::get(val, key) {
Ok(actual) => {
if !js_strict_eq(&actual, expected) {
return false;
}
}
Err(_) => return false,
}
}
true
}) as Rc<dyn Fn(&JsValue) -> bool>;
ops.push(Operation::Filter(filter_fn));
Pipeline { operations: ops }
}
#[wasm_bindgen(js_name = viewLens)]
pub fn view_lens(&self, optic: &crate::optics_wasm::JsLens) -> Pipeline {
let get_fn = optic.get_fn.clone();
let mut ops = self.operations.clone();
let map_fn = Rc::new(move |val: JsValue| -> JsValue { get_fn(&val) })
as Rc<dyn Fn(JsValue) -> JsValue>;
ops.push(Operation::Map(map_fn));
Pipeline { operations: ops }
}
#[wasm_bindgen(js_name = overLens)]
pub fn over_lens(&self, optic: &crate::optics_wasm::JsLens, f: &Function) -> Pipeline {
let get_fn = optic.get_fn.clone();
let set_fn = optic.set_fn.clone();
let f = f.clone();
let mut ops = self.operations.clone();
let map_fn = Rc::new(move |val: JsValue| -> JsValue {
let current = get_fn(&val);
let this = JsValue::null();
let updated = f.call1(&this, ¤t).unwrap_or_else(|_| current.clone());
set_fn(&val, updated)
}) as Rc<dyn Fn(JsValue) -> JsValue>;
ops.push(Operation::Map(map_fn));
Pipeline { operations: ops }
}
#[wasm_bindgen(js_name = filterLens)]
pub fn filter_lens(&self, optic: &crate::optics_wasm::JsLens, pred: &Function) -> Pipeline {
let get_fn = optic.get_fn.clone();
let pred = pred.clone();
let mut ops = self.operations.clone();
let filter_fn = Rc::new(move |val: &JsValue| -> bool {
let focused = get_fn(val);
let this = JsValue::null();
match pred.call1(&this, &focused) {
Ok(result) => result.as_bool().unwrap_or(false),
Err(_) => false,
}
}) as Rc<dyn Fn(&JsValue) -> bool>;
ops.push(Operation::Filter(filter_fn));
Pipeline { operations: ops }
}
#[wasm_bindgen(js_name = setLens)]
pub fn set_lens(&self, optic: &crate::optics_wasm::JsLens, value: JsValue) -> Pipeline {
let set_fn = optic.set_fn.clone();
let mut ops = self.operations.clone();
let map_fn = Rc::new(move |val: JsValue| -> JsValue { set_fn(&val, value.clone()) })
as Rc<dyn Fn(JsValue) -> JsValue>;
ops.push(Operation::Map(map_fn));
Pipeline { operations: ops }
}
#[wasm_bindgen(js_name = toArray)]
pub fn to_array(&self, source: &Array) -> Array {
let result = Array::new();
let mut should_stop = false;
let mut state = ProcessState::new();
for i in 0..source.length() {
if should_stop {
break;
}
let val = source.get(i);
let results = self.process_value_with_state(val, &mut state);
for res in results {
match res {
ProcessResult::Continue(v) => {
result.push(&v);
}
ProcessResult::Skip => {
}
ProcessResult::Stop(v) => {
if let Some(val) = v {
result.push(&val);
}
should_stop = true;
break;
}
}
}
}
result
}
#[wasm_bindgen]
pub fn reduce(&self, source: &Array, reducer: &Function, initial: JsValue) -> JsValue {
let mut acc = initial;
let mut should_stop = false;
let mut state = ProcessState::new();
for i in 0..source.length() {
if should_stop {
break;
}
let val = source.get(i);
let results = self.process_value_with_state(val, &mut state);
for res in results {
match res {
ProcessResult::Continue(v) => {
let this = JsValue::null();
acc = reducer.call2(&this, &acc, &v).unwrap_or(acc);
}
ProcessResult::Skip => {}
ProcessResult::Stop(v) => {
if let Some(val) = v {
let this = JsValue::null();
acc = reducer.call2(&this, &acc, &val).unwrap_or(acc);
}
should_stop = true;
break;
}
}
}
}
acc
}
#[wasm_bindgen(js_name = logExecution)]
pub fn log_execution(&self, source: &Array) -> Array {
console::log_1(&"Pipeline execution:".into());
let pipeline = self.tap(&Function::new_with_args("x", "console.log('Value:', x)"));
pipeline.to_array(source)
}
fn process_value_with_state(
&self,
val: JsValue,
state: &mut ProcessState,
) -> Vec<ProcessResult> {
self.process_value_from(val, 0, state)
}
#[allow(unused_assignments)]
fn process_value_from(
&self,
mut val: JsValue,
start_idx: usize,
state: &mut ProcessState,
) -> Vec<ProcessResult> {
for (idx, op) in self.operations.iter().enumerate().skip(start_idx) {
match op {
Operation::Map(f) => {
val = f(val);
}
Operation::Filter(pred) => {
if !pred(&val) {
return vec![ProcessResult::Skip];
}
}
Operation::MapFilter { map, filter } => {
val = map(val);
if !filter(&val) {
return vec![ProcessResult::Skip];
}
}
Operation::FlatMap(f) => {
let expanded = f(val);
let mut results = Vec::new();
for expanded_val in expanded {
let sub_results = self.process_value_from(expanded_val, idx + 1, state);
let should_stop = sub_results
.iter()
.any(|r| matches!(r, ProcessResult::Stop(_)));
results.extend(sub_results);
if should_stop {
break;
}
}
return results;
}
Operation::Take(n) => {
state.take_count += 1;
if state.take_count > *n {
return vec![ProcessResult::Stop(None)];
}
}
Operation::TakeWhile(pred) => {
if !pred(&val) {
return vec![ProcessResult::Stop(None)];
}
}
Operation::Drop(n) => {
if state.drop_count < *n {
state.drop_count += 1;
return vec![ProcessResult::Skip];
}
}
Operation::DropWhile(pred) => {
if !state.dropping && pred(&val) {
return vec![ProcessResult::Skip];
} else {
state.dropping = false;
}
}
Operation::Tap(f) => {
f(&val);
}
}
}
vec![ProcessResult::Continue(val)]
}
}
impl Default for Pipeline {
fn default() -> Self {
Self::new()
}
}
enum ProcessResult {
Continue(JsValue),
Skip,
Stop(Option<JsValue>),
}
struct ProcessState {
take_count: usize,
drop_count: usize,
dropping: bool,
}
impl ProcessState {
fn new() -> Self {
ProcessState {
take_count: 0,
drop_count: 0,
dropping: false,
}
}
}
#[wasm_bindgen(js_name = pipeline)]
pub fn create_pipeline() -> Pipeline {
Pipeline::new()
}
#[wasm_bindgen]
pub fn merge(arrays: Array) -> Array {
let result = Array::new();
let mut iters: Vec<_> = (0..arrays.length())
.map(|i| {
let arr = arrays
.get(i)
.dyn_into::<Array>()
.unwrap_or_else(|_| Array::new());
(arr, 0)
})
.collect();
let mut active = true;
while active {
active = false;
for (arr, idx) in &mut iters {
if *idx < arr.length() {
result.push(&arr.get(*idx));
*idx += 1;
active = true;
}
}
}
result
}
#[wasm_bindgen]
pub fn intersection(array_a: &Array, array_b: &Array) -> Array {
use std::collections::HashSet;
let mut set_b = HashSet::new();
for i in 0..array_b.length() {
let val = array_b.get(i);
if let Ok(json) = js_sys::JSON::stringify(&val) {
set_b.insert(json.as_string().unwrap_or_default());
}
}
let result = Array::new();
for i in 0..array_a.length() {
let val = array_a.get(i);
if let Ok(json) = js_sys::JSON::stringify(&val) {
if set_b.contains(&json.as_string().unwrap_or_default()) {
result.push(&val);
}
}
}
result
}
#[wasm_bindgen]
pub fn difference(array_a: &Array, array_b: &Array) -> Array {
use std::collections::HashSet;
let mut set_b = HashSet::new();
for i in 0..array_b.length() {
let val = array_b.get(i);
if let Ok(json) = js_sys::JSON::stringify(&val) {
set_b.insert(json.as_string().unwrap_or_default());
}
}
let result = Array::new();
for i in 0..array_a.length() {
let val = array_a.get(i);
if let Ok(json) = js_sys::JSON::stringify(&val) {
if !set_b.contains(&json.as_string().unwrap_or_default()) {
result.push(&val);
}
}
}
result
}
#[wasm_bindgen]
pub fn union(array_a: &Array, array_b: &Array) -> Array {
use std::collections::HashSet;
let mut seen = HashSet::new();
let result = Array::new();
for i in 0..array_a.length() {
let val = array_a.get(i);
if let Ok(json) = js_sys::JSON::stringify(&val) {
if seen.insert(json.as_string().unwrap_or_default()) {
result.push(&val);
}
}
}
for i in 0..array_b.length() {
let val = array_b.get(i);
if let Ok(json) = js_sys::JSON::stringify(&val) {
if seen.insert(json.as_string().unwrap_or_default()) {
result.push(&val);
}
}
}
result
}
#[wasm_bindgen(js_name = symmetricDifference)]
pub fn symmetric_difference(array_a: &Array, array_b: &Array) -> Array {
use std::collections::HashSet;
let mut set_a = HashSet::new();
for i in 0..array_a.length() {
let val = array_a.get(i);
if let Ok(json) = js_sys::JSON::stringify(&val) {
set_a.insert(json.as_string().unwrap_or_default());
}
}
let mut set_b = HashSet::new();
for i in 0..array_b.length() {
let val = array_b.get(i);
if let Ok(json) = js_sys::JSON::stringify(&val) {
set_b.insert(json.as_string().unwrap_or_default());
}
}
let result = Array::new();
let mut seen = HashSet::new();
for i in 0..array_a.length() {
let val = array_a.get(i);
if let Ok(json) = js_sys::JSON::stringify(&val) {
let json_str = json.as_string().unwrap_or_default();
if !set_b.contains(&json_str) && seen.insert(json_str) {
result.push(&val);
}
}
}
for i in 0..array_b.length() {
let val = array_b.get(i);
if let Ok(json) = js_sys::JSON::stringify(&val) {
let json_str = json.as_string().unwrap_or_default();
if !set_a.contains(&json_str) && seen.insert(json_str) {
result.push(&val);
}
}
}
result
}
#[wasm_bindgen(js_name = takeLast)]
pub fn take_last(source: &Array, n: u32) -> Array {
let len = source.length();
let result = Array::new();
if n >= len {
for i in 0..len {
result.push(&source.get(i));
}
} else {
let start = len - n;
for i in start..len {
result.push(&source.get(i));
}
}
result
}
#[wasm_bindgen(js_name = dropLast)]
pub fn drop_last(source: &Array, n: u32) -> Array {
let len = source.length();
let result = Array::new();
if n >= len {
return result;
}
let end = len - n;
for i in 0..end {
result.push(&source.get(i));
}
result
}
#[wasm_bindgen]
pub fn aperture(source: &Array, size: u32) -> Array {
let len = source.length();
let result = Array::new();
if size == 0 || size > len {
return result;
}
for i in 0..=(len - size) {
let window = Array::new();
for j in 0..size {
window.push(&source.get(i + j));
}
result.push(&window);
}
result
}
#[wasm_bindgen]
pub fn product(source: &Array) -> f64 {
let mut result = 1.0;
for i in 0..source.length() {
let val = source.get(i);
if let Some(num) = val.as_f64() {
result *= num;
}
}
result
}
#[wasm_bindgen]
pub fn mean(source: &Array) -> JsValue {
let len = source.length();
if len == 0 {
return JsValue::undefined();
}
let mut sum = 0.0;
for i in 0..len {
let val = source.get(i);
if let Some(num) = val.as_f64() {
sum += num;
}
}
JsValue::from_f64(sum / (len as f64))
}
#[wasm_bindgen]
pub fn median(source: &Array) -> JsValue {
let len = source.length();
if len == 0 {
return JsValue::undefined();
}
let mut values: Vec<f64> = Vec::new();
for i in 0..len {
let val = source.get(i);
if let Some(num) = val.as_f64() {
values.push(num);
}
}
if values.is_empty() {
return JsValue::undefined();
}
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let mid = values.len() / 2;
if values.len() % 2 == 1 {
JsValue::from_f64(values[mid])
} else {
JsValue::from_f64((values[mid - 1] + values[mid]) / 2.0)
}
}
#[wasm_bindgen]
pub fn min(source: &Array) -> JsValue {
let len = source.length();
if len == 0 {
return JsValue::undefined();
}
let mut min_val = f64::INFINITY;
for i in 0..len {
let val = source.get(i);
if let Some(num) = val.as_f64() {
if num < min_val {
min_val = num;
}
}
}
if min_val == f64::INFINITY {
JsValue::undefined()
} else {
JsValue::from_f64(min_val)
}
}
#[wasm_bindgen]
pub fn max(source: &Array) -> JsValue {
let len = source.length();
if len == 0 {
return JsValue::undefined();
}
let mut max_val = f64::NEG_INFINITY;
for i in 0..len {
let val = source.get(i);
if let Some(num) = val.as_f64() {
if num > max_val {
max_val = num;
}
}
}
if max_val == f64::NEG_INFINITY {
JsValue::undefined()
} else {
JsValue::from_f64(max_val)
}
}
#[wasm_bindgen(js_name = minBy)]
pub fn min_by(source: &Array, key_fn: &Function) -> JsValue {
let len = source.length();
if len == 0 {
return JsValue::undefined();
}
let mut min_element = JsValue::undefined();
let mut min_key = f64::INFINITY;
let this = JsValue::null();
for i in 0..len {
let element = source.get(i);
if let Ok(key_val) = key_fn.call1(&this, &element) {
if let Some(key) = key_val.as_f64() {
if key < min_key {
min_key = key;
min_element = element;
}
}
}
}
min_element
}
#[wasm_bindgen(js_name = maxBy)]
pub fn max_by(source: &Array, key_fn: &Function) -> JsValue {
let len = source.length();
if len == 0 {
return JsValue::undefined();
}
let mut max_element = JsValue::undefined();
let mut max_key = f64::NEG_INFINITY;
let this = JsValue::null();
for i in 0..len {
let element = source.get(i);
if let Ok(key_val) = key_fn.call1(&this, &element) {
if let Some(key) = key_val.as_f64() {
if key > max_key {
max_key = key;
max_element = element;
}
}
}
}
max_element
}
#[wasm_bindgen]
pub fn variance(source: &Array) -> JsValue {
let len = source.length();
if len < 2 {
return JsValue::undefined();
}
let mut values: Vec<f64> = Vec::new();
for i in 0..len {
let val = source.get(i);
if let Some(num) = val.as_f64() {
values.push(num);
}
}
if values.len() < 2 {
return JsValue::undefined();
}
let n = values.len() as f64;
let mean_val: f64 = values.iter().sum::<f64>() / n;
let sum_squared_diff: f64 = values
.iter()
.map(|x| {
let diff = x - mean_val;
diff * diff
})
.sum();
JsValue::from_f64(sum_squared_diff / (n - 1.0))
}
#[wasm_bindgen(js_name = stdDev)]
pub fn std_dev(source: &Array) -> JsValue {
match variance(source) {
v if v.is_undefined() => JsValue::undefined(),
v => {
let var_val = v.as_f64().unwrap();
JsValue::from_f64(var_val.sqrt())
}
}
}
#[wasm_bindgen]
pub fn quantile(source: &Array, p: f64) -> JsValue {
if !(0.0..=1.0).contains(&p) {
return JsValue::undefined();
}
let len = source.length();
if len == 0 {
return JsValue::undefined();
}
let mut values: Vec<f64> = Vec::new();
for i in 0..len {
let val = source.get(i);
if let Some(num) = val.as_f64() {
values.push(num);
}
}
if values.is_empty() {
return JsValue::undefined();
}
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = values.len();
if n == 1 {
return JsValue::from_f64(values[0]);
}
let index = p * (n - 1) as f64;
let lower = index.floor() as usize;
let upper = index.ceil() as usize;
if lower == upper {
JsValue::from_f64(values[lower])
} else {
let weight = index - lower as f64;
JsValue::from_f64(values[lower] * (1.0 - weight) + values[upper] * weight)
}
}
#[wasm_bindgen]
pub fn mode(source: &Array) -> JsValue {
use std::collections::HashMap;
let len = source.length();
if len == 0 {
return JsValue::undefined();
}
let mut freq_map: HashMap<String, (JsValue, usize)> = HashMap::new();
for i in 0..len {
let element = source.get(i);
let key = format!("{:?}", element);
freq_map
.entry(key)
.and_modify(|(_, count)| *count += 1)
.or_insert((element.clone(), 1));
}
freq_map
.into_iter()
.max_by_key(|(_, (_, count))| *count)
.map(|(_, (value, _))| value)
.unwrap_or(JsValue::undefined())
}
#[wasm_bindgen]
pub fn path(obj: &JsValue, path_array: &Array) -> JsValue {
let mut current = obj.clone();
for i in 0..path_array.length() {
let key = path_array.get(i);
if let Some(key_str) = key.as_string() {
match Reflect::get(¤t, &JsValue::from_str(&key_str)) {
Ok(value) => {
if value.is_undefined() {
return JsValue::undefined();
}
current = value;
}
Err(_) => return JsValue::undefined(),
}
} else {
return JsValue::undefined();
}
}
current
}
#[wasm_bindgen(js_name = pathOr)]
pub fn path_or(obj: &JsValue, path_array: &Array, default: &JsValue) -> JsValue {
let result = path(obj, path_array);
if result.is_undefined() {
default.clone()
} else {
result
}
}
#[wasm_bindgen]
pub fn evolve(obj: &JsValue, transformations: &JsValue) -> Result<JsValue, JsValue> {
let json_string = js_sys::JSON::stringify(obj)?;
let result = js_sys::JSON::parse(&json_string.as_string().unwrap_or_default())?;
let keys = js_sys::Object::keys(&js_sys::Object::from(transformations.clone()));
for i in 0..keys.length() {
let key_str = keys.get(i).as_string().unwrap();
if let Ok(transform_fn) = Reflect::get(transformations, &JsValue::from_str(&key_str)) {
if !transform_fn.is_function() {
continue;
}
let func = js_sys::Function::from(transform_fn);
if key_str.contains('.') {
let path_parts: Vec<&str> = key_str.split('.').collect();
let path_array = Array::new();
for part in &path_parts {
path_array.push(&JsValue::from_str(part));
}
let current_value = path(&result, &path_array);
if !current_value.is_undefined() {
let this = JsValue::null();
if let Ok(new_value) = func.call1(&this, ¤t_value) {
let mut target = result.clone();
for (idx, part) in path_parts.iter().enumerate() {
if idx == path_parts.len() - 1 {
let _ = Reflect::set(&target, &JsValue::from_str(part), &new_value);
} else {
if let Ok(next) = Reflect::get(&target, &JsValue::from_str(part)) {
target = next;
}
}
}
}
}
} else {
if let Ok(current_value) = Reflect::get(&result, &JsValue::from_str(&key_str)) {
let this = JsValue::null();
if let Ok(new_value) = func.call1(&this, ¤t_value) {
let _ = Reflect::set(&result, &JsValue::from_str(&key_str), &new_value);
}
}
}
}
}
Ok(result)
}
#[wasm_bindgen(js_name = sortBy)]
pub fn sort_by(source: &Array, key_fn: &Function) -> Array {
let len = source.length();
let mut items: Vec<(JsValue, f64)> = Vec::new();
let this = JsValue::null();
for i in 0..len {
let item = source.get(i);
if let Ok(key) = key_fn.call1(&this, &item) {
if let Some(key_num) = key.as_f64() {
items.push((item, key_num));
}
}
}
items.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
let result = Array::new();
for (item, _) in items {
result.push(&item);
}
result
}
#[wasm_bindgen(js_name = sortWith)]
pub fn sort_with(source: &Array, comparator: &Function) -> Array {
let len = source.length();
let mut items: Vec<JsValue> = Vec::new();
for i in 0..len {
items.push(source.get(i));
}
let this = JsValue::null();
items.sort_by(|a, b| {
if let Ok(result) = comparator.call2(&this, a, b) {
if let Some(cmp) = result.as_f64() {
if cmp < 0.0 {
return std::cmp::Ordering::Less;
} else if cmp > 0.0 {
return std::cmp::Ordering::Greater;
}
}
}
std::cmp::Ordering::Equal
});
let result = Array::new();
for item in items {
result.push(&item);
}
result
}
#[wasm_bindgen]
pub fn reverse(source: &Array) -> Array {
let len = source.length();
let result = Array::new();
for i in (0..len).rev() {
result.push(&source.get(i));
}
result
}
#[wasm_bindgen]
pub fn range(start: i32, end: i32, step: i32) -> Array {
let result = Array::new();
if step == 0 {
return result;
}
let mut current = start;
if step > 0 {
while current < end {
result.push(&JsValue::from_f64(current as f64));
current += step;
}
} else {
while current > end {
result.push(&JsValue::from_f64(current as f64));
current += step;
}
}
result
}
#[wasm_bindgen]
pub fn repeat(value: &JsValue, n: u32) -> Array {
let result = Array::new();
for _ in 0..n {
result.push(value);
}
result
}
#[wasm_bindgen]
pub fn cycle(source: &Array, n: u32) -> Array {
let len = source.length();
let result = Array::new();
for _ in 0..n {
for i in 0..len {
result.push(&source.get(i));
}
}
result
}
#[wasm_bindgen]
pub fn unfold(seed: &JsValue, f: &Function, limit: u32) -> Array {
let result = Array::new();
let mut current = seed.clone();
let this = JsValue::null();
for _ in 0..limit {
match f.call1(&this, ¤t) {
Ok(next) => {
if next.is_null() || next.is_undefined() {
break;
}
result.push(&next);
current = next;
}
Err(_) => break,
}
}
result
}
fn flatten_value(val: JsValue, depth: usize) -> Vec<JsValue> {
if depth == 0 {
return vec![val];
}
match val.dyn_into::<Array>() {
Ok(array) => {
let mut result = Vec::new();
for i in 0..array.length() {
let item = array.get(i);
result.extend(flatten_value(item, depth - 1));
}
result
}
Err(original) => vec![original],
}
}
fn is_nan(val: &JsValue) -> bool {
js_sys::Number::is_nan(val)
}
fn js_strict_eq(a: &JsValue, b: &JsValue) -> bool {
a == b
}