use mumu::parser::interpreter::Interpreter;
use mumu::parser::types::{FunctionValue, IteratorKind, Value};
use crate::apply::apply_n_ary_function_value;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicU64, Ordering};
static GENSYM: AtomicU64 = AtomicU64::new(1);
fn gensym(prefix: &str) -> String {
let n = GENSYM.fetch_add(1, Ordering::Relaxed);
format!("__array_reduce_{}_{}", prefix, n)
}
fn is_placeholder(v: &Value) -> bool {
matches!(v, Value::Placeholder)
|| matches!(v, Value::SingleString(s) if s == "_")
|| matches!(v, Value::StrArray(ss) if ss.len() == 1 && ss[0] == "_")
}
fn looks_like_array(v: &Value) -> bool {
matches!(
v,
Value::IntArray(_)
| Value::FloatArray(_)
| Value::BoolArray(_)
| Value::StrArray(_)
| Value::MixedArray(_)
| Value::Iterator(_)
)
}
fn looks_like_function(v: &Value) -> bool {
matches!(v, Value::Function(_))
}
fn make_named_partial(
interp: &mut Interpreter,
f_opt: Option<Value>,
init_opt: Option<Value>,
data_opt: Option<Value>,
) -> Result<Value, String> {
let name = gensym("partial");
let closure = move |interp: &mut Interpreter, new_args: Vec<Value>| {
let mut f = f_opt.clone();
let mut init = init_opt.clone();
let mut data = data_opt.clone();
for arg in new_args {
if f.is_none() {
if !is_placeholder(&arg) {
f = Some(arg);
}
continue;
}
if init.is_none() {
if looks_like_array(&arg) {
if data.is_none() {
data = Some(arg);
} else {
init = Some(arg);
}
} else if !is_placeholder(&arg) {
init = Some(arg);
}
continue;
}
if data.is_none() {
if !is_placeholder(&arg) {
data = Some(arg);
}
continue;
}
return Err("array:reduce partial => too many arguments".to_string());
}
if let (Some(fv), Some(dv)) = (f.clone(), data.clone()) {
return do_reduce(interp, fv, init.clone(), dv);
}
make_named_partial(interp, f, init, data)
};
let f: mumu::parser::interpreter::DynamicFn = Arc::new(Mutex::new(closure));
interp.register_dynamic_function(&name, f);
Ok(Value::Function(Box::new(FunctionValue::Named(name))))
}
pub fn array_reduce_bridge(interp: &mut Interpreter, mut args: Vec<Value>) -> Result<Value, String> {
match args.len() {
0 => make_named_partial(interp, None, None, None),
1 => {
let a = args.remove(0);
if is_placeholder(&a) {
make_named_partial(interp, None, None, None)
} else if looks_like_function(&a) {
make_named_partial(interp, Some(a), None, None)
} else if looks_like_array(&a) {
make_named_partial(interp, None, None, Some(a))
} else {
make_named_partial(interp, None, Some(a), None)
}
}
2 => {
let a = args.remove(0);
let b = args.remove(0);
if is_placeholder(&a) && is_placeholder(&b) {
return make_named_partial(interp, None, None, None);
}
if is_placeholder(&a) {
if looks_like_array(&b) {
return make_named_partial(interp, None, None, Some(b));
} else {
return make_named_partial(interp, None, Some(b), None);
}
}
if is_placeholder(&b) {
if looks_like_function(&a) {
return make_named_partial(interp, Some(a), None, None);
} else if looks_like_array(&a) {
return make_named_partial(interp, None, None, Some(a));
} else {
return make_named_partial(interp, None, Some(a), None);
}
}
if looks_like_function(&a) && looks_like_array(&b) {
return do_reduce(interp, a, None, b);
}
if looks_like_function(&a) && !looks_like_array(&b) {
return make_named_partial(interp, Some(a), Some(b), None);
}
if !looks_like_function(&a) && looks_like_array(&b) {
return make_named_partial(interp, None, Some(a), Some(b));
}
make_named_partial(interp, Some(a), Some(b), None)
}
3 => {
let f = args.remove(0);
let i = args.remove(0);
let d = args.remove(0);
if is_placeholder(&f) || is_placeholder(&i) || is_placeholder(&d) {
return make_named_partial(
interp,
if is_placeholder(&f) { None } else { Some(f) },
if is_placeholder(&i) { None } else { Some(i) },
if is_placeholder(&d) { None } else { Some(d) },
);
}
do_reduce(interp, f, Some(i), d)
}
n => Err(format!("array:reduce expects up to 3 arguments (fn, init?, array?), got {}", n)),
}
}
fn do_reduce(
interp: &mut Interpreter,
func_val: Value,
init_opt: Option<Value>,
data_val: Value,
) -> Result<Value, String> {
let func_box = match func_val {
Value::Function(fb) => fb,
other => return Err(format!("array:reduce => first argument must be a function, got {:?}", other)),
};
match data_val {
Value::IntArray(xs) => {
let iter = xs.into_iter().map(Value::Int);
reduce_values(interp, func_box, init_opt, iter)
}
Value::FloatArray(xs) => {
let iter = xs.into_iter().map(Value::Float);
reduce_values(interp, func_box, init_opt, iter)
}
Value::BoolArray(xs) => {
let iter = xs.into_iter().map(Value::Bool);
reduce_values(interp, func_box, init_opt, iter)
}
Value::StrArray(xs) => {
let iter = xs.into_iter().map(Value::SingleString);
reduce_values(interp, func_box, init_opt, iter)
}
Value::MixedArray(xs) => {
reduce_values(interp, func_box, init_opt, xs.into_iter())
}
Value::Iterator(h) => {
match &h.kind {
IteratorKind::Core(state_arc) => {
let mut guard = state_arc.lock().map_err(|_| "IteratorState lock error".to_string())?;
let mut items: Vec<Value> = Vec::new();
while !guard.done && guard.current < guard.end {
let next_val = guard.current;
guard.current += 1;
if guard.current >= guard.end {
guard.done = true;
}
items.push(Value::Int(next_val));
}
drop(guard);
reduce_values(interp, func_box, init_opt, items.into_iter())
}
IteratorKind::Plugin(plugin_arc) => {
let mut plugin = plugin_arc.lock().map_err(|_| "Plugin Iterator lock error".to_string())?;
let mut items: Vec<Value> = Vec::new();
loop {
match plugin.next_value() {
Ok(v) => items.push(v),
Err(e) if e == "NO_MORE_DATA" => break,
Err(e) => return Err(format!("array:reduce => plugin iterator error: {}", e)),
}
}
reduce_values(interp, func_box, init_opt, items.into_iter())
}
}
}
other => Err(format!(
"array:reduce => third argument must be an array or iterator, got {:?}",
other
)),
}
}
fn reduce_values<I>(
interp: &mut Interpreter,
func_box: Box<FunctionValue>,
init: Option<Value>,
iter: I,
) -> Result<Value, String>
where
I: IntoIterator<Item = Value>,
{
let mut it = iter.into_iter();
let mut acc = match init {
Some(v) => v,
None => it
.next()
.ok_or_else(|| "array:reduce => empty input with no initial value".to_string())?,
};
for x in it {
acc = apply_n_ary_function_value(interp, func_box.clone(), vec![acc, x])?;
}
Ok(acc)
}