shape_runtime/intrinsics/
array_transforms.rs1use crate::context::ExecutionContext;
15use crate::marshal::{
16 register_typed_fn_1, register_typed_fn_2, register_typed_fn_2_full, register_typed_fn_3,
17};
18use crate::module_exports::{ModuleExports, ModuleParam};
19use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
20use shape_ast::error::{Result, ShapeError};
21use shape_value::KindedSlot;
22use std::sync::Arc;
23
24pub fn create_array_transforms_module() -> ModuleExports {
30 let mut module = ModuleExports::new("std::core::intrinsics::array_transforms");
31 module.description =
32 "Array-transform intrinsics (typed entries; polymorphic-shape intrinsics stay as legacy bodies pending M1-split sub-decision)"
33 .to_string();
34
35 register_typed_fn_1::<_, Arc<Vec<f64>>>(
36 &mut module,
37 "__intrinsic_series",
38 "Identity column projection of a Vec<number>",
39 "input",
40 "Array<number>",
41 ConcreteType::ArrayNumber,
42 |input, _ctx| {
43 Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(
44 input.as_slice().to_vec(),
45 )))
46 },
47 );
48
49 register_typed_fn_2::<_, Arc<Vec<f64>>, i64>(
50 &mut module,
51 "__intrinsic_shift",
52 "Shift a Vec<number> by N positions, padding with NaN",
53 [("series", "Array<number>"), ("shift", "int")],
54 ConcreteType::ArrayNumber,
55 |series, shift, _ctx| {
56 let data = series.as_slice();
57 let mut result = Vec::with_capacity(data.len());
58 if shift > 0 {
59 let s = (shift as usize).min(data.len());
60 for _ in 0..s {
61 result.push(f64::NAN);
62 }
63 result.extend_from_slice(&data[..data.len().saturating_sub(s)]);
64 } else if shift < 0 {
65 let s = ((-shift) as usize).min(data.len());
66 result.extend_from_slice(&data[s..]);
67 for _ in 0..s {
68 result.push(f64::NAN);
69 }
70 } else {
71 result.extend_from_slice(data);
72 }
73 Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(result)))
74 },
75 );
76
77 register_typed_fn_2_full::<_, Arc<Vec<f64>>, i64>(
78 &mut module,
79 "__intrinsic_pct_change",
80 "Percent change between consecutive (or period-spaced) Vec<number> elements",
81 [
82 ModuleParam {
83 name: "series".to_string(),
84 type_name: "Array<number>".to_string(),
85 required: true,
86 description: "Input series".to_string(),
87 ..Default::default()
88 },
89 ModuleParam {
90 name: "period".to_string(),
91 type_name: "int".to_string(),
92 required: false,
93 description: "Period for change comparison (default 1)".to_string(),
94 default_snippet: Some("1".to_string()),
95 ..Default::default()
96 },
97 ],
98 ConcreteType::ArrayNumber,
99 |series, period, _ctx| {
100 let data = series.as_slice();
101 let period = period.max(0) as usize;
102 let mut result = Vec::with_capacity(data.len());
103 for i in 0..data.len() {
104 if i < period {
105 result.push(f64::NAN);
106 } else {
107 let prev = data[i - period];
108 if prev == 0.0 {
109 result.push(f64::NAN);
110 } else {
111 result.push((data[i] - prev) / prev);
112 }
113 }
114 }
115 Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(result)))
116 },
117 );
118
119 register_typed_fn_2::<_, Arc<Vec<f64>>, f64>(
120 &mut module,
121 "__intrinsic_fillna",
122 "Replace NaN entries in a Vec<number> with a fill value",
123 [("series", "Array<number>"), ("value", "number")],
124 ConcreteType::ArrayNumber,
125 |series, value, _ctx| {
126 let result: Vec<f64> = series
127 .as_slice()
128 .iter()
129 .map(|&v| if v.is_nan() { value } else { v })
130 .collect();
131 Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(result)))
132 },
133 );
134
135 register_typed_fn_1::<_, Arc<Vec<f64>>>(
136 &mut module,
137 "__intrinsic_cumprod",
138 "Cumulative product of a Vec<number>",
139 "input",
140 "Array<number>",
141 ConcreteType::ArrayNumber,
142 |input, _ctx| {
143 let data = input.as_slice();
144 let mut result = Vec::with_capacity(data.len());
145 let mut acc = 1.0;
146 for &v in data {
147 acc *= v;
148 result.push(acc);
149 }
150 Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(result)))
151 },
152 );
153
154 register_typed_fn_3::<_, Arc<Vec<f64>>, f64, f64>(
155 &mut module,
156 "__intrinsic_clip",
157 "Clip Vec<number> elements to the [min, max] interval",
158 [
159 ("series", "Array<number>"),
160 ("min", "number"),
161 ("max", "number"),
162 ],
163 ConcreteType::ArrayNumber,
164 |series, min, max, _ctx| {
165 let result: Vec<f64> = series
166 .as_slice()
167 .iter()
168 .map(|&v| v.max(min).min(max))
169 .collect();
170 Ok(TypedReturn::Concrete(ConcreteReturn::ArrayF64(result)))
171 },
172 );
173
174 module
175}
176
177pub fn intrinsic_diff(_args: &[KindedSlot], _ctx: &mut ExecutionContext) -> Result<KindedSlot> {
190 Err(ShapeError::RuntimeError {
191 message: "intrinsic_diff: pending Phase 2c intrinsic kind threading + M1-split — see ADR-006 §2.7.4".to_string(),
192 location: None,
193 })
194}
195
196pub fn intrinsic_cumsum(_args: &[KindedSlot], _ctx: &mut ExecutionContext) -> Result<KindedSlot> {
202 Err(ShapeError::RuntimeError {
203 message: "intrinsic_cumsum: pending Phase 2c intrinsic kind threading + M1-split — see ADR-006 §2.7.4".to_string(),
204 location: None,
205 })
206}