use crate::context::ExecutionContext;
use crate::marshal::{
register_typed_fn_1, register_typed_fn_2, register_typed_fn_2_full, register_typed_fn_3,
};
use crate::module_exports::{ModuleExports, ModuleParam};
use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
use shape_ast::error::{Result, ShapeError};
use shape_value::KindedSlot;
use std::sync::Arc;
pub fn create_array_transforms_module() -> ModuleExports {
let mut module = ModuleExports::new("std::core::intrinsics::array_transforms");
module.description =
"Array-transform intrinsics (typed entries; polymorphic-shape intrinsics stay as legacy bodies pending M1-split sub-decision)"
.to_string();
register_typed_fn_1::<_, Arc<Vec<f64>>>(
&mut module,
"__intrinsic_series",
"Identity column projection of a Vec<number>",
"input",
"Array<number>",
ConcreteType::ArrayNumber,
|input, _ctx| {
Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(
input.as_slice().to_vec(),
)))
},
);
register_typed_fn_2::<_, Arc<Vec<f64>>, i64>(
&mut module,
"__intrinsic_shift",
"Shift a Vec<number> by N positions, padding with NaN",
[("series", "Array<number>"), ("shift", "int")],
ConcreteType::ArrayNumber,
|series, shift, _ctx| {
let data = series.as_slice();
let mut result = Vec::with_capacity(data.len());
if shift > 0 {
let s = (shift as usize).min(data.len());
for _ in 0..s {
result.push(f64::NAN);
}
result.extend_from_slice(&data[..data.len().saturating_sub(s)]);
} else if shift < 0 {
let s = ((-shift) as usize).min(data.len());
result.extend_from_slice(&data[s..]);
for _ in 0..s {
result.push(f64::NAN);
}
} else {
result.extend_from_slice(data);
}
Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(result)))
},
);
register_typed_fn_2_full::<_, Arc<Vec<f64>>, i64>(
&mut module,
"__intrinsic_pct_change",
"Percent change between consecutive (or period-spaced) Vec<number> elements",
[
ModuleParam {
name: "series".to_string(),
type_name: "Array<number>".to_string(),
required: true,
description: "Input series".to_string(),
..Default::default()
},
ModuleParam {
name: "period".to_string(),
type_name: "int".to_string(),
required: false,
description: "Period for change comparison (default 1)".to_string(),
default_snippet: Some("1".to_string()),
..Default::default()
},
],
ConcreteType::ArrayNumber,
|series, period, _ctx| {
let data = series.as_slice();
let period = period.max(0) as usize;
let mut result = Vec::with_capacity(data.len());
for i in 0..data.len() {
if i < period {
result.push(f64::NAN);
} else {
let prev = data[i - period];
if prev == 0.0 {
result.push(f64::NAN);
} else {
result.push((data[i] - prev) / prev);
}
}
}
Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(result)))
},
);
register_typed_fn_2::<_, Arc<Vec<f64>>, f64>(
&mut module,
"__intrinsic_fillna",
"Replace NaN entries in a Vec<number> with a fill value",
[("series", "Array<number>"), ("value", "number")],
ConcreteType::ArrayNumber,
|series, value, _ctx| {
let result: Vec<f64> = series
.as_slice()
.iter()
.map(|&v| if v.is_nan() { value } else { v })
.collect();
Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(result)))
},
);
register_typed_fn_1::<_, Arc<Vec<f64>>>(
&mut module,
"__intrinsic_cumprod",
"Cumulative product of a Vec<number>",
"input",
"Array<number>",
ConcreteType::ArrayNumber,
|input, _ctx| {
let data = input.as_slice();
let mut result = Vec::with_capacity(data.len());
let mut acc = 1.0;
for &v in data {
acc *= v;
result.push(acc);
}
Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(result)))
},
);
register_typed_fn_3::<_, Arc<Vec<f64>>, f64, f64>(
&mut module,
"__intrinsic_clip",
"Clip Vec<number> elements to the [min, max] interval",
[
("series", "Array<number>"),
("min", "number"),
("max", "number"),
],
ConcreteType::ArrayNumber,
|series, min, max, _ctx| {
let result: Vec<f64> = series
.as_slice()
.iter()
.map(|&v| v.max(min).min(max))
.collect();
Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(result)))
},
);
module
}
pub fn intrinsic_diff(_args: &[KindedSlot], _ctx: &mut ExecutionContext) -> Result<KindedSlot> {
Err(ShapeError::RuntimeError {
message: "intrinsic_diff: pending Phase 2c intrinsic kind threading + M1-split — see ADR-006 §2.7.4".to_string(),
location: None,
})
}
pub fn intrinsic_cumsum(_args: &[KindedSlot], _ctx: &mut ExecutionContext) -> Result<KindedSlot> {
Err(ShapeError::RuntimeError {
message: "intrinsic_cumsum: pending Phase 2c intrinsic kind threading + M1-split — see ADR-006 §2.7.4".to_string(),
location: None,
})
}