use std::fs::File;
use std::io::{BufReader, Read};
use std::sync::{Arc, Mutex};
use mumu::parser::interpreter::{DynamicFnInfo, Interpreter};
use mumu::parser::types::{FunctionValue, Value};
use crate::common::is_placeholder;
pub fn register_flow_read(interp: &mut Interpreter) {
let func = Arc::new(Mutex::new(flow_read_bridge));
let info = DynamicFnInfo::new(func, true);
interp.register_dynamic_function_ex("flow:read", info);
interp.set_variable(
"flow:read",
Value::Function(Box::new(FunctionValue::Named("flow:read".to_string()))),
);
}
fn flow_read_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
match args.len() {
0 => Ok(make_partial(None, None)),
1 => {
let a = &args[0];
if is_placeholder(a) {
Ok(make_partial(None, None))
} else if let Some(sz) = take_size_maybe(a)? {
Ok(make_partial(Some(sz), None))
} else {
Ok(make_partial(None, Some(a.clone())))
}
}
2 => {
let a_size = take_size_maybe(&args[0])?;
let a_path = if is_placeholder(&args[1]) {
None
} else {
Some(args[1].clone())
};
match (a_size, a_path) {
(Some(sz), Some(pv)) => finalize_read(sz, pv),
(sz, pv) => Ok(make_partial(sz, pv)),
}
}
n => Err(format!(
"flow:read => expected up to 2 arguments (size, path), got {}",
n
)),
}
}
fn make_partial(size_opt: Option<i32>, path_opt: Option<Value>) -> Value {
use mumu::parser::types::FunctionValue::RustClosure;
Value::Function(Box::new(RustClosure(
"flow:read-partial".to_string(),
Arc::new(Mutex::new(move |_interp: &mut Interpreter, new_args: Vec<Value>| {
let mut s = size_opt;
let mut p = path_opt.clone();
for a in new_args {
if s.is_none() {
if is_placeholder(&a) {
} else if let Some(sz) = take_size_maybe(&a)? {
s = Some(sz);
} else {
if p.is_none() {
p = Some(a);
} else {
return Err("flow:read => unexpected extra argument".to_string());
}
}
continue;
}
if p.is_none() {
if is_placeholder(&a) {
} else {
p = Some(a);
}
continue;
}
return Err("flow:read => partial => too many arguments".to_string());
}
match (s, p.clone()) {
(Some(sz), Some(pv)) => finalize_read(sz, pv),
_ => Ok(make_partial(s, p)),
}
})),
0,
)))
}
fn take_size_maybe(v: &Value) -> Result<Option<i32>, String> {
let size = match v {
Value::Int(i) => *i,
Value::Long(l) => {
if *l < i64::from(i32::MIN) || *l > i64::from(i32::MAX) {
return Err("flow:read => chunk size out of range for i32".to_string());
}
*l as i32
}
Value::Float(f) => *f as i32,
_ => return Ok(None),
};
if size <= 0 {
return Err(format!("flow:read => chunk size must be > 0, got {}", size));
}
Ok(Some(size))
}
fn take_path_from_value(v: Value) -> Result<String, String> {
match v {
Value::SingleString(s) => Ok(s),
Value::StrArray(arr) if arr.len() == 1 => Ok(arr[0].clone()),
other => Err(format!(
"flow:read => filename must be a string (or single-element StrArray), got {:?}",
other
)),
}
}
fn finalize_read(chunk_size: i32, v: Value) -> Result<Value, String> {
let path = take_path_from_value(v)?;
let file =
File::open(&path).map_err(|e| format!("flow:read => cannot open '{}': {}", path, e))?;
let reader = BufReader::new(file);
Ok(build_read_transform(path, chunk_size as usize, reader))
}
struct ReadEnv {
path: String,
reader: BufReader<File>,
done: bool,
chunk_size: usize,
buf: Vec<u8>,
}
fn build_read_transform(path: String, chunk_size: usize, reader: BufReader<File>) -> Value {
use mumu::parser::types::FunctionValue::RustClosure;
let env = Arc::new(Mutex::new(ReadEnv {
path,
reader,
done: false,
chunk_size,
buf: vec![0u8; chunk_size.max(1)],
}));
let clos = RustClosure(
"flow:read-transform(bytes)".to_string(),
Arc::new(Mutex::new(move |interp: &mut Interpreter, _args: Vec<Value>| {
let mut guard = env
.lock()
.map_err(|_| "flow:read => lock error".to_string())?;
if guard.done {
if interp.is_verbose() {
eprintln!("[flow:read] '{}' => EOF (already done)", guard.path);
}
return Err("NO_MORE_DATA".to_string());
}
let mut buf = std::mem::take(&mut guard.buf);
if buf.len() < guard.chunk_size {
buf.resize(guard.chunk_size, 0u8);
}
let n = match guard.reader.read(&mut buf) {
Ok(0) => {
guard.done = true;
guard.buf = buf;
if interp.is_verbose() {
eprintln!("[flow:read] '{}' => EOF", guard.path);
}
return Err("NO_MORE_DATA".to_string());
}
Ok(nread) => nread,
Err(e) => {
guard.buf = buf;
return Err(format!("flow:read => I/O error: {}", e));
}
};
let s = String::from_utf8_lossy(&buf[..n]).to_string();
guard.buf = buf;
if interp.is_verbose() {
eprintln!(
"[flow:read] '{}' => chunk {} bytes (requested {})",
guard.path, n, guard.chunk_size
);
}
Ok(Value::SingleString(s))
})),
0,
);
Value::Function(Box::new(clos))
}