use mumu::parser::ast::Expr;
use mumu::parser::interpreter::Interpreter;
use mumu::parser::types::{FunctionValue, IteratorKind, Value};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
static GENSYM: AtomicU64 = AtomicU64::new(1);
fn gensym(prefix: &str) -> String {
let n = GENSYM.fetch_add(1, Ordering::Relaxed);
format!("__mu_apply_{}_{}", 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::MixedArray(_)
| Value::IntArray(_)
| Value::FloatArray(_)
| Value::BoolArray(_)
| Value::StrArray(_)
| Value::Iterator(_)
)
}
fn looks_like_function(v: &Value) -> bool {
matches!(v, Value::Function(_))
}
pub fn apply_n_ary_function_value(
interp: &mut Interpreter,
func: Box<FunctionValue>,
args: Vec<Value>,
) -> Result<Value, String> {
let fn_sym = gensym("f");
interp.set_variable(&fn_sym, Value::Function(func));
let mut arg_idents = Vec::with_capacity(args.len());
for (i, arg) in args.into_iter().enumerate() {
let sym = gensym(&format!("a{}", i));
interp.set_variable(&sym, arg);
arg_idents.push(Expr::Ident { name: sym, line: 0, col: 0 });
}
let call = Expr::FunctionCall {
callee: Box::new(Expr::Ident { name: fn_sym, line: 0, col: 0 }),
args: arg_idents,
line: 0,
col: 0,
};
interp.eval_expression(&call)
}
pub fn apply_one_function_value(
interp: &mut Interpreter,
func: Box<FunctionValue>,
arg: Value,
) -> Result<Value, String> {
apply_n_ary_function_value(interp, func, vec![arg])
}
pub fn do_map(
interp: &mut Interpreter,
map_fn: &FunctionValue,
data_val: Value,
) -> Result<Value, String> {
match data_val {
Value::IntArray(xs) => {
let mut out = Vec::with_capacity(xs.len());
for x in xs {
match apply_one_function_value(interp, Box::new(map_fn.clone()), Value::Int(x))? {
Value::Int(i2) => out.push(i2),
other => {
return Err(format!(
"array:map => user function returned non-int: {:?}",
other
))
}
}
}
Ok(Value::IntArray(out))
}
Value::FloatArray(ff) => {
let mut out = Vec::with_capacity(ff.len());
for x in ff {
match apply_one_function_value(interp, Box::new(map_fn.clone()), Value::Float(x))? {
Value::Float(y) => out.push(y),
other => {
return Err(format!(
"array:map => user function returned non-float: {:?}",
other
))
}
}
}
Ok(Value::FloatArray(out))
}
Value::StrArray(ss) => {
let mut results = Vec::with_capacity(ss.len());
let mut out_kind: Option<&'static str> = None;
for s in ss {
let ret = apply_one_function_value(
interp,
Box::new(map_fn.clone()),
Value::SingleString(s),
)?;
match &ret {
Value::Int(_) => {
if out_kind.get_or_insert("int") != &"int" {
return Err("array:map => inconsistent return types".into());
}
}
Value::SingleString(_) => {
if out_kind.get_or_insert("string") != &"string" {
return Err("array:map => inconsistent return types".into());
}
}
_ => return Err("array:map => unsupported return type from user function".into()),
}
results.push(ret);
}
match out_kind {
None => Ok(Value::StrArray(vec![])),
Some("int") => Ok(Value::IntArray(
results
.into_iter()
.map(|v| if let Value::Int(n) = v { n } else { unreachable!() })
.collect(),
)),
Some("string") => Ok(Value::StrArray(
results
.into_iter()
.map(|v| if let Value::SingleString(s) = v { s } else { unreachable!() })
.collect(),
)),
_ => Err("array:map => unrecognized type".into()),
}
}
Value::Iterator(h) => match &h.kind {
IteratorKind::Core(state_arc) => {
let mut out = Vec::new();
let mut guard = state_arc
.lock()
.map_err(|_| "IteratorState lock error".to_string())?;
while !guard.done && guard.current < guard.end {
let next_val = guard.current;
guard.current += 1;
if guard.current >= guard.end {
guard.done = true;
}
drop(guard);
match apply_one_function_value(
interp,
Box::new(map_fn.clone()),
Value::Int(next_val),
)? {
Value::Int(i2) => out.push(i2),
other => {
return Err(format!(
"array:map => user function returned non-int: {:?}",
other
))
}
}
guard = state_arc
.lock()
.map_err(|_| "IteratorState lock error".to_string())?;
}
Ok(Value::IntArray(out))
}
IteratorKind::Plugin(plugin_arc) => {
let mut out = Vec::new();
let mut plugin = plugin_arc
.lock()
.map_err(|_| "Plugin Iterator lock error".to_string())?;
loop {
match plugin.next_value() {
Ok(val) => match apply_one_function_value(
interp,
Box::new(map_fn.clone()),
val,
)? {
Value::Int(i2) => out.push(i2),
_ => {
return Err(
"array:map => user function returned non-int for plugin iterator"
.into()
)
}
},
Err(e) if e == "NO_MORE_DATA" => break,
Err(e) => return Err(e),
}
}
Ok(Value::IntArray(out))
}
},
other => Err(format!("array:map => unsupported input: {:?}", other)),
}
}
fn flatten_args(arg_values: Value) -> Result<Vec<Value>, String> {
match arg_values {
Value::MixedArray(xs) => Ok(xs),
Value::IntArray(xs) => Ok(xs.into_iter().map(Value::Int).collect()),
Value::FloatArray(xs) => Ok(xs.into_iter().map(Value::Float).collect()),
Value::BoolArray(xs) => Ok(xs.into_iter().map(Value::Bool).collect()),
Value::StrArray(xs) => Ok(xs.into_iter().map(Value::SingleString).collect()),
Value::Iterator(h) => match &h.kind {
IteratorKind::Core(state_arc) => {
let mut out = Vec::new();
let mut guard = state_arc
.lock()
.map_err(|_| "IteratorState lock error".to_string())?;
while !guard.done && guard.current < guard.end {
let next_val = guard.current;
guard.current += 1;
if guard.current >= guard.end {
guard.done = true;
}
out.push(Value::Int(next_val));
}
Ok(out)
}
IteratorKind::Plugin(plugin_arc) => {
let mut out = Vec::new();
let mut plugin = plugin_arc
.lock()
.map_err(|_| "Plugin Iterator lock error".to_string())?;
loop {
match plugin.next_value() {
Ok(val) => out.push(val),
Err(e) if e == "NO_MORE_DATA" => break,
Err(e) => return Err(e),
}
}
Ok(out)
}
},
Value::Placeholder => Err("array:apply => missing args-array".to_string()),
other => Ok(vec![other]),
}
}
fn do_apply_call(interp: &mut Interpreter, func: Value, arg_values: Value) -> Result<Value, String> {
let func_box = match func {
Value::Function(fb) => fb,
other => return Err(format!("array:apply => first argument must be a function, got {:?}", other)),
};
let call_args = flatten_args(arg_values)?;
apply_n_ary_function_value(interp, func_box, call_args)
}
fn make_named_partial_apply(
interp: &mut Interpreter,
f_opt: Option<Value>,
args_opt: Option<Value>,
) -> Result<Value, String> {
let name = gensym("apply_partial");
let c_f = f_opt.clone();
let c_args = args_opt.clone();
let closure = move |interp: &mut Interpreter, new_args: Vec<Value>| {
let mut f = c_f.clone();
let mut arr = c_args.clone();
for arg in new_args {
if is_placeholder(&arg) {
continue;
}
if f.is_none() && looks_like_function(&arg) {
f = Some(arg);
continue;
}
if arr.is_none() {
arr = Some(arg);
continue;
}
return Err("array:apply partial => too many arguments".to_string());
}
if let (Some(ff), Some(av)) = (f.clone(), arr.clone()) {
return do_apply_call(interp, ff, av);
}
make_named_partial_apply(interp, f, arr)
};
interp.register_dynamic_function(&name, Arc::new(Mutex::new(closure)));
Ok(Value::Function(Box::new(FunctionValue::Named(name))))
}
pub fn array_apply_bridge(interp: &mut Interpreter, mut args: Vec<Value>) -> Result<Value, String> {
match args.len() {
0 => {
make_named_partial_apply(interp, None, None)
}
1 => {
let a = args.remove(0);
if is_placeholder(&a) {
make_named_partial_apply(interp, None, None)
} else if looks_like_function(&a) {
make_named_partial_apply(interp, Some(a), None)
} else {
make_named_partial_apply(interp, None, Some(a))
}
}
2 => {
let a = args.remove(0);
let b = args.remove(0);
let a_ph = is_placeholder(&a);
let b_ph = is_placeholder(&b);
match (a_ph, b_ph) {
(true, true) => return make_named_partial_apply(interp, None, None),
(true, false) => return make_named_partial_apply(interp, None, Some(b)),
(false, true) => return make_named_partial_apply(interp, Some(a), None),
(false, false) => { }
}
if looks_like_function(&a) {
return do_apply_call(interp, a, b);
}
if looks_like_function(&b) {
return do_apply_call(interp, b, a);
}
make_named_partial_apply(interp, None, Some(a))
}
n => Err(format!(
"array:apply => expected at most 2 arguments (fn?, args-array?), got {}",
n
)),
}
}