polydat_nodes/
param_helpers.rs1use regex::Regex;
23
24#[polydat::polydat_node(category = Arithmetic)]
36fn required(
37 input: Option<u64>,
38 #[poly_default("value")] name: polydat::derive_support::Const<&str>,
39) -> u64 {
40 input.unwrap_or_else(|| panic!("required({}): value was not defined", name.0))
41}
42
43#[polydat::polydat_node(category = Arithmetic)]
55fn this_or(primary: Option<u64>, default: u64) -> u64 {
56 primary.unwrap_or(default)
57}
58
59#[polydat::polydat_node(category = Arithmetic)]
69fn is_positive(
70 input: u64,
71 #[poly_default("value")] name: polydat::derive_support::Const<&str>,
72) -> u64 {
73 if input == 0 {
74 panic!("is_positive({}): value must be > 0, got 0", name.0);
75 }
76 input
77}
78
79#[polydat::polydat_node(category = Arithmetic)]
89fn in_range(
90 input: u64,
91 #[poly_default(0u64)] lo: polydat::derive_support::Const<u64>,
92 #[poly_default(u64::MAX)] hi: polydat::derive_support::Const<u64>,
93) -> u64 {
94 if input < *lo || input > *hi {
95 panic!("in_range: value {input} outside [{}, {}]", *lo, *hi);
96 }
97 input
98}
99
100#[polydat::polydat_node(category = Arithmetic)]
111fn is_one_of(input: u64, allowed: polydat::derive_support::Const<Vec<u64>>) -> u64 {
112 if !allowed.contains(&input) {
113 panic!(
114 "is_one_of: value {input} not in allowed set {:?}",
115 allowed.0
116 );
117 }
118 input
119}
120
121fn compile_matches_regex(pattern: &str) -> Regex {
127 Regex::new(pattern).unwrap_or_else(|e| panic!("matches: invalid regex {pattern:?}: {e}"))
128}
129
130#[polydat::polydat_node(category = Arithmetic)]
133fn matches(
134 input: &str,
135 pattern: polydat::derive_support::Const<&str>,
136 #[poly_const(compile_matches_regex, from = pattern)] re: &Regex,
137) -> String {
138 if !re.is_match(input) {
139 panic!(
140 "matches: value {input:?} does not match pattern {:?}",
141 pattern.0
142 );
143 }
144 input.to_string()
145}
146
147use polydat::dsl::registry::FuncSig;
152
153pub fn signatures() -> &'static [FuncSig] {
155 &[
156 ]
162}
163
164pub(crate) fn build_node(
165 name: &str,
166 _wires: &[polydat::compile::assembly::WireRef],
167 _wire_types: &[polydat::ast::PortType],
168 consts: &[polydat::dsl::factory::ConstArg],
169) -> Option<Result<Box<dyn polydat::ast::PolydatNode>, String>> {
170 let _ = name;
171 let _ = consts;
172 None
174}
175
176pub(crate) fn validate_node(
179 name: &str,
180 consts: &[polydat::dsl::factory::ConstArg],
181) -> Result<(), String> {
182 match name {
183 "in_range" => {
184 let lo = consts.first().map(|c| c.as_u64()).unwrap_or(0);
185 let hi = consts.get(1).map(|c| c.as_u64()).unwrap_or(u64::MAX);
186 if lo > hi {
187 Err(format!("lo ({lo}) must be <= hi ({hi})"))
188 } else {
189 Ok(())
190 }
191 }
192 "is_one_of" => {
193 if consts.is_empty() {
194 Err("at least one allowed value required".into())
195 } else {
196 Ok(())
197 }
198 }
199 _ => Ok(()),
200 }
201}
202
203polydat::register_nodes!(signatures, build_node, validate_node);
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use polydat::ast::{PolydatNode, Value};
209
210 #[test]
211 fn required_passes_defined_value() {
212 let n = Required::new("x".to_string());
213 let mut out = [Value::None];
214 n.eval(&[Value::U64(42)], &mut out);
215 assert_eq!(out[0].as_u64(), 42);
216 }
217
218 #[test]
219 #[should_panic(expected = "required(x): value was not defined")]
220 fn required_panics_on_none() {
221 let n = Required::new("x".to_string());
222 let mut out = [Value::None];
223 n.eval(&[Value::None], &mut out);
224 }
225
226 #[test]
227 fn this_or_prefers_primary_when_defined() {
228 let n = ThisOr::new();
229 let mut out = [Value::None];
230 n.eval(&[Value::U64(7), Value::U64(99)], &mut out);
231 assert_eq!(out[0].as_u64(), 7);
232 }
233
234 #[test]
235 fn this_or_falls_back_to_default_on_none() {
236 let n = ThisOr::new();
237 let mut out = [Value::None];
238 n.eval(&[Value::None, Value::U64(99)], &mut out);
239 assert_eq!(out[0].as_u64(), 99);
240 }
241
242 #[test]
243 fn is_positive_passes_positive() {
244 let n = IsPositive::new("rate".to_string());
245 let mut out = [Value::None];
246 n.eval(&[Value::U64(1)], &mut out);
247 assert_eq!(out[0].as_u64(), 1);
248 }
249
250 #[test]
251 #[should_panic(expected = "is_positive(rate)")]
252 fn is_positive_panics_on_zero() {
253 let n = IsPositive::new("rate".to_string());
254 let mut out = [Value::None];
255 n.eval(&[Value::U64(0)], &mut out);
256 }
257
258 #[test]
259 fn in_range_passes_interior() {
260 let n = InRange::new(10, 100);
261 let mut out = [Value::None];
262 n.eval(&[Value::U64(50)], &mut out);
263 assert_eq!(out[0].as_u64(), 50);
264 n.eval(&[Value::U64(10)], &mut out);
265 assert_eq!(out[0].as_u64(), 10);
266 n.eval(&[Value::U64(100)], &mut out);
267 assert_eq!(out[0].as_u64(), 100);
268 }
269
270 #[test]
271 #[should_panic(expected = "outside [10, 100]")]
272 fn in_range_panics_below() {
273 let n = InRange::new(10, 100);
274 let mut out = [Value::None];
275 n.eval(&[Value::U64(5)], &mut out);
276 }
277
278 #[test]
279 #[should_panic(expected = "outside [10, 100]")]
280 fn in_range_panics_above() {
281 let n = InRange::new(10, 100);
282 let mut out = [Value::None];
283 n.eval(&[Value::U64(101)], &mut out);
284 }
285
286 #[test]
287 fn is_one_of_passes_allowed() {
288 let n = IsOneOf::new(vec![1, 2, 3, 5, 8]);
289 let mut out = [Value::None];
290 n.eval(&[Value::U64(5)], &mut out);
291 assert_eq!(out[0].as_u64(), 5);
292 }
293
294 #[test]
295 #[should_panic(expected = "not in allowed set")]
296 fn is_one_of_panics_on_disallowed() {
297 let n = IsOneOf::new(vec![1, 2, 3]);
298 let mut out = [Value::None];
299 n.eval(&[Value::U64(4)], &mut out);
300 }
301
302 #[test]
303 fn matches_passes_matching_string() {
304 let n = Matches::new(r"^\w+@\w+\.\w+$".to_string());
305 let mut out = [Value::None];
306 n.eval(&[Value::Str("jshook@example.com".into())], &mut out);
307 assert_eq!(out[0].as_str(), "jshook@example.com");
308 }
309
310 #[test]
311 #[should_panic(expected = "does not match pattern")]
312 fn matches_panics_on_mismatch() {
313 let n = Matches::new(r"^\d+$".to_string());
314 let mut out = [Value::None];
315 n.eval(&[Value::Str("abc".into())], &mut out);
316 }
317}