Skip to main content

pax_runtime_api/pax_value/
functions.rs

1use crate::{
2    math::{Transform2, Vector2},
3    Color, ColorChannel, Paint, PaxValue, Rotation,
4};
5use once_cell::sync::Lazy;
6use std::{
7    collections::HashMap,
8    sync::{Arc, RwLock},
9};
10
11use super::{CoercionRules, ToPaxValue};
12
13type FunctionType = Arc<dyn Fn(Vec<PaxValue>) -> Result<PaxValue, String> + Send + Sync>;
14
15static FUNCTIONS: Lazy<Arc<RwLock<HashMap<String, HashMap<String, FunctionType>>>>> =
16    Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));
17
18// Dumps registered PAXEL helper functions for diagnostics.
19pub fn print_all_functions() {
20    let functions = FUNCTIONS.read().unwrap();
21    log::warn!("Total scopes: {}", functions.len());
22    for (scope, funcs) in functions.iter() {
23        log::warn!("Scope: {}", scope);
24        for (name, _) in funcs.iter() {
25            log::warn!("  |{}|", name);
26        }
27    }
28}
29
30// Registers one PAXEL helper function under a scope/name pair.
31pub fn register_function(scope: String, name: String, func: FunctionType) {
32    let mut functions = FUNCTIONS.write().unwrap();
33    functions
34        .entry(scope)
35        .or_insert_with(HashMap::new)
36        .insert(name, func);
37}
38
39// Dispatches a registered PAXEL helper function.
40pub fn call_function(scope: String, name: String, args: Vec<PaxValue>) -> Result<PaxValue, String> {
41    let functions = FUNCTIONS.read().unwrap();
42    let scope_funcs = functions
43        .get(&scope)
44        .ok_or_else(|| format!("Scope {} not found", scope))?;
45    let func = scope_funcs
46        .get(&name)
47        .ok_or_else(|| format!("Function {} not found in scope {}", name, scope))?;
48    func(args)
49}
50
51/// Helper function for adding two PaxValues
52fn add(args: Vec<PaxValue>) -> Result<PaxValue, String> {
53    if args.len() != 2 {
54        return Err("Expected 2 arguments for function add".to_string());
55    }
56    let mut itr = args.into_iter();
57    Ok(itr.next().unwrap() + itr.next().unwrap())
58}
59
60/// Helper function for subtracting two PaxValues
61fn sub_or_neg(args: Vec<PaxValue>) -> Result<PaxValue, String> {
62    if args.len() == 1 {
63        Ok(-args.into_iter().next().unwrap())
64    } else if args.len() == 2 {
65        let mut itr = args.into_iter();
66        Ok(itr.next().unwrap() - itr.next().unwrap())
67    } else {
68        Err("Expected 1 or 2 arguments for function sub_or_neg".to_string())
69    }
70}
71
72/// Helper function for multiplying two PaxValues
73fn mul(args: Vec<PaxValue>) -> Result<PaxValue, String> {
74    if args.len() != 2 {
75        return Err("Expected 2 arguments for function mul".to_string());
76    }
77    let mut itr = args.into_iter();
78    Ok(itr.next().unwrap() * itr.next().unwrap())
79}
80
81/// Helper function for dividing two PaxValues
82fn div(args: Vec<PaxValue>) -> Result<PaxValue, String> {
83    if args.len() != 2 {
84        return Err("Expected 2 arguments for function div".to_string());
85    }
86    let mut itr = args.into_iter();
87    Ok(itr.next().unwrap() / itr.next().unwrap())
88}
89
90/// Helper function for exponentiating one PaxValue by another
91fn exp(args: Vec<PaxValue>) -> Result<PaxValue, String> {
92    if args.len() != 2 {
93        return Err("Expected 2 arguments for function exp".to_string());
94    }
95    let mut itr = args.into_iter();
96    Ok(itr.next().unwrap().pow(itr.next().unwrap()))
97}
98
99/// Helper function for taking the modulus of one PaxValue by another
100fn mod_(args: Vec<PaxValue>) -> Result<PaxValue, String> {
101    if args.len() != 2 {
102        return Err("Expected 2 arguments for function mod_".to_string());
103    }
104    let mut itr = args.into_iter();
105    Ok(itr.next().unwrap() % itr.next().unwrap())
106}
107
108/// Helper function for checking if two PaxValues are equal
109fn rel_eq(args: Vec<PaxValue>) -> Result<PaxValue, String> {
110    if args.len() != 2 {
111        return Err("Expected 2 arguments for function rel_eq".to_string());
112    }
113    Ok(PaxValue::Bool(args[0] == args[1]))
114}
115
116/// Helper function for checking if one PaxValue is greater than another
117fn rel_gt(args: Vec<PaxValue>) -> Result<PaxValue, String> {
118    if args.len() != 2 {
119        return Err("Expected 2 arguments for function rel_gt".to_string());
120    }
121    Ok(PaxValue::Bool(args[0] > args[1]))
122}
123
124/// Helper function for checking if one PaxValue is greater than or equal to another
125fn rel_gte(args: Vec<PaxValue>) -> Result<PaxValue, String> {
126    if args.len() != 2 {
127        return Err("Expected 2 arguments for function rel_gte".to_string());
128    }
129    Ok(PaxValue::Bool(args[0] >= args[1]))
130}
131
132/// Helper function for checking if one PaxValue is less than another
133fn rel_lt(args: Vec<PaxValue>) -> Result<PaxValue, String> {
134    if args.len() != 2 {
135        return Err("Expected 2 arguments for function rel_lt".to_string());
136    }
137    Ok(PaxValue::Bool(args[0] < args[1]))
138}
139
140/// Helper function for checking if one PaxValue is less than or equal to another
141fn rel_lte(args: Vec<PaxValue>) -> Result<PaxValue, String> {
142    if args.len() != 2 {
143        return Err("Expected 2 arguments for function rel_lte".to_string());
144    }
145    Ok(PaxValue::Bool(args[0] <= args[1]))
146}
147
148/// Helper function for checking if two PaxValues are not equal
149fn rel_neq(args: Vec<PaxValue>) -> Result<PaxValue, String> {
150    if args.len() != 2 {
151        return Err("Expected 2 arguments for function rel_neq".to_string());
152    }
153    Ok(PaxValue::Bool(args[0] != args[1]))
154}
155
156/// Helper function for performing a boolean AND operation on two PaxValues
157fn bool_and(args: Vec<PaxValue>) -> Result<PaxValue, String> {
158    if args.len() != 2 {
159        return Err("Expected 2 arguments for function bool_and".to_string());
160    }
161    let mut itr = args.into_iter();
162    Ok(itr.next().unwrap().op_and(itr.next().unwrap()))
163}
164
165/// Helper function for performing a boolean OR operation on two PaxValues
166fn bool_or(args: Vec<PaxValue>) -> Result<PaxValue, String> {
167    if args.len() != 2 {
168        return Err("Expected 2 arguments for function bool_or".to_string());
169    }
170    let mut itr = args.into_iter();
171    Ok(itr.next().unwrap().op_or(itr.next().unwrap()))
172}
173
174/// Helper function for performing a boolean NOT operation on a PaxValue
175fn bool_not(args: Vec<PaxValue>) -> Result<PaxValue, String> {
176    if args.len() != 1 {
177        return Err("Expected 1 argument for function bool_not".to_string());
178    }
179    Ok(args.into_iter().next().unwrap().op_not())
180}
181
182/// Helper function for taking the minimum of two PaxValues
183fn min(args: Vec<PaxValue>) -> Result<PaxValue, String> {
184    if args.len() != 2 {
185        return Err("Expected 2 arguments for function min".to_string());
186    }
187    let mut itr = args.into_iter();
188    Ok(itr.next().unwrap().min(itr.next().unwrap()))
189}
190
191/// Helper function for taking the maximum of two PaxValues
192fn max(args: Vec<PaxValue>) -> Result<PaxValue, String> {
193    if args.len() != 2 {
194        return Err("Expected 2 arguments for function max".to_string());
195    }
196    let mut itr = args.into_iter();
197    Ok(itr.next().unwrap().max(itr.next().unwrap()))
198}
199
200/// Helper function for getting the length of a PaxValue vector
201fn len(args: Vec<PaxValue>) -> Result<PaxValue, String> {
202    if args.len() != 1 {
203        return Err("len function takes a single argument".to_string());
204    }
205    match args.into_iter().next().unwrap() {
206        PaxValue::Vec(vec) => Ok(vec.len().to_pax_value()),
207        e => Err(format!("can't get length of {e:?}")),
208    }
209}
210
211/// Helper function for creating an RGB color from three PaxValues representing the red, green, and blue channels
212fn rgb(args: Vec<PaxValue>) -> Result<PaxValue, String> {
213    if args.len() != 3 {
214        return Err("Expected 3 arguments for function rgb".to_string());
215    }
216    let mut itr = args.into_iter();
217    let r = ColorChannel::try_coerce(itr.next().unwrap())?;
218    let g = ColorChannel::try_coerce(itr.next().unwrap())?;
219    let b = ColorChannel::try_coerce(itr.next().unwrap())?;
220    Ok(Color::rgb(r, g, b).to_pax_value())
221}
222
223/// Helper function for creating an RGBA color from four PaxValues representing the red, green, blue, and alpha channels
224fn rgba(args: Vec<PaxValue>) -> Result<PaxValue, String> {
225    if args.len() != 4 {
226        return Err("Expected 4 arguments for function rgba".to_string());
227    }
228    let mut itr = args.into_iter();
229    let r = ColorChannel::try_coerce(itr.next().unwrap())?;
230    let g = ColorChannel::try_coerce(itr.next().unwrap())?;
231    let b = ColorChannel::try_coerce(itr.next().unwrap())?;
232    let a = ColorChannel::try_coerce(itr.next().unwrap())?;
233    Ok(Color::rgba(r, g, b, a).to_pax_value())
234}
235
236/// Helper function for creating an HSL color from three PaxValues representing the hue, saturation, and lightness channels
237fn hsl(args: Vec<PaxValue>) -> Result<PaxValue, String> {
238    if args.len() != 3 {
239        return Err("Expected 3 arguments for function hsl".to_string());
240    }
241    let mut itr = args.into_iter();
242    let h = Rotation::try_coerce(itr.next().unwrap())?;
243    let s = ColorChannel::try_coerce(itr.next().unwrap())?;
244    let l = ColorChannel::try_coerce(itr.next().unwrap())?;
245    Ok(Color::hsl(h, s, l).to_pax_value())
246}
247
248/// Helper function for creating an HSLA color from four PaxValues representing the hue, saturation, lightness, and alpha channels
249fn hsla(args: Vec<PaxValue>) -> Result<PaxValue, String> {
250    if args.len() != 4 {
251        return Err("Expected 4 arguments for function hsla".to_string());
252    }
253    let mut itr = args.into_iter();
254    let h = Rotation::try_coerce(itr.next().unwrap())?;
255    let s = ColorChannel::try_coerce(itr.next().unwrap())?;
256    let l = ColorChannel::try_coerce(itr.next().unwrap())?;
257    let a = ColorChannel::try_coerce(itr.next().unwrap())?;
258    Ok(Color::hsla(h, s, l, a).to_pax_value())
259}
260
261/// Helper function for creating a color from a hexadecimal string representation
262fn hex(args: Vec<PaxValue>) -> Result<PaxValue, String> {
263    if args.len() != 1 {
264        return Err("Expected 1 argument for function hex".to_string());
265    }
266    let hex = String::try_coerce(args.into_iter().next().unwrap())?;
267    Ok(Color::from_hex(&hex).to_pax_value())
268}
269
270/// Registers helper functions made available to PAXEL scopes.
271pub trait HelperFunctions {
272    /// Registers all helper functions for this type.
273    fn register_all_functions() {}
274}
275
276/// Registry bootstrap for built-in PAXEL helper functions.
277pub struct Functions;
278
279impl Functions {
280    /// Registers built-in math, color, and transform helper functions.
281    pub fn register_all_functions() {
282        // Math
283        register_function("Math".to_string(), "+".to_string(), Arc::new(add));
284        register_function("Math".to_string(), "-".to_string(), Arc::new(sub_or_neg));
285        register_function("Math".to_string(), "*".to_string(), Arc::new(mul));
286        register_function("Math".to_string(), "/".to_string(), Arc::new(div));
287        register_function("Math".to_string(), "^".to_string(), Arc::new(exp));
288        register_function("Math".to_string(), "%%".to_string(), Arc::new(mod_));
289        register_function("Math".to_string(), "==".to_string(), Arc::new(rel_eq));
290        register_function("Math".to_string(), ">".to_string(), Arc::new(rel_gt));
291        register_function("Math".to_string(), ">=".to_string(), Arc::new(rel_gte));
292        register_function("Math".to_string(), "<".to_string(), Arc::new(rel_lt));
293        register_function("Math".to_string(), "<=".to_string(), Arc::new(rel_lte));
294        register_function("Math".to_string(), "!=".to_string(), Arc::new(rel_neq));
295        register_function("Math".to_string(), "&&".to_string(), Arc::new(bool_and));
296        register_function("Math".to_string(), "||".to_string(), Arc::new(bool_or));
297        register_function("Math".to_string(), "!".to_string(), Arc::new(bool_not));
298        register_function("Math".to_string(), "min".to_string(), Arc::new(min));
299        register_function("Math".to_string(), "max".to_string(), Arc::new(max));
300        register_function("Math".to_string(), "len".to_string(), Arc::new(len));
301        // Colors
302        register_function("Color".to_string(), "rgb".to_string(), Arc::new(rgb));
303        register_function("Color".to_string(), "rgba".to_string(), Arc::new(rgba));
304        register_function("Color".to_string(), "hsl".to_string(), Arc::new(hsl));
305        register_function("Color".to_string(), "hsla".to_string(), Arc::new(hsla));
306        register_function("Color".to_string(), "#".to_string(), Arc::new(hex));
307        // Transform2D
308        crate::Transform2D::register_all_functions();
309    }
310
311    /// Returns true if a helper function exists in the named scope.
312    pub fn has_function(scope: &str, name: &str) -> bool {
313        let functions = FUNCTIONS.read().unwrap();
314        if let Some(scope_funcs) = functions.get(scope) {
315            scope_funcs.contains_key(name)
316        } else {
317            false
318        }
319    }
320}
321
322impl HelperFunctions for crate::Size {}
323
324impl HelperFunctions for crate::Color {}
325
326impl HelperFunctions for crate::Rotation {}
327
328impl HelperFunctions for crate::Duration {}
329
330impl HelperFunctions for String {}
331
332impl HelperFunctions for crate::Numeric {}
333
334impl HelperFunctions for crate::UnitValue {}
335
336impl HelperFunctions for bool {}
337
338impl HelperFunctions for Paint {}
339impl HelperFunctions for crate::MeshPoint {}
340impl HelperFunctions for crate::MeshGradient {}
341impl<A, B> HelperFunctions for (A, B) {}
342impl HelperFunctions for crate::Fill {}
343
344impl HelperFunctions for crate::PaxValue {}
345
346impl HelperFunctions for crate::ColorChannel {}
347
348impl HelperFunctions for crate::Opacity {}
349
350impl HelperFunctions for crate::Stroke {}
351
352impl HelperFunctions for crate::PathSmoothing {}
353
354impl HelperFunctions for crate::StrokeCap {}
355
356impl HelperFunctions for crate::StrokeJoin {}
357
358impl HelperFunctions for u8 {}
359impl HelperFunctions for u16 {}
360impl HelperFunctions for u32 {}
361impl HelperFunctions for u64 {}
362impl HelperFunctions for u128 {}
363impl HelperFunctions for usize {}
364
365impl HelperFunctions for i8 {}
366impl HelperFunctions for i16 {}
367impl HelperFunctions for i32 {}
368impl HelperFunctions for i64 {}
369impl HelperFunctions for i128 {}
370impl HelperFunctions for isize {}
371
372impl HelperFunctions for f32 {}
373impl HelperFunctions for f64 {}
374impl<T> HelperFunctions for Vec<T> {}
375
376impl<T: HelperFunctions> HelperFunctions for Option<T> {}
377
378impl HelperFunctions for crate::Transform2D {
379    fn register_all_functions() {
380        register_function(
381            "Transform2D".to_string(),
382            "scale".to_string(),
383            Arc::new(|args| {
384                if args.len() != 2 {
385                    return Err("Expected 2 arguments for function scale".to_string());
386                }
387                let mut itr = args.into_iter();
388                let x = crate::Size::try_coerce(itr.next().unwrap())?;
389                let y = crate::Size::try_coerce(itr.next().unwrap())?;
390                Ok(crate::Transform2D::scale(x, y).to_pax_value())
391            }),
392        );
393        register_function(
394            "Transform2D".to_string(),
395            "rotate".to_string(),
396            Arc::new(|args| {
397                if args.len() != 1 {
398                    return Err("Expected 1 argument for function rotate".to_string());
399                }
400                let z = crate::Rotation::try_coerce(args.into_iter().next().unwrap())?;
401                Ok(crate::Transform2D::rotate(z).to_pax_value())
402            }),
403        );
404        register_function(
405            "Transform2D".to_string(),
406            "translate".to_string(),
407            Arc::new(|args| {
408                if args.len() != 2 {
409                    return Err("Expected 2 arguments for function translate".to_string());
410                }
411                let mut itr = args.into_iter();
412                let x = crate::Size::try_coerce(itr.next().unwrap())?;
413                let y = crate::Size::try_coerce(itr.next().unwrap())?;
414                Ok(crate::Transform2D::translate(x, y).to_pax_value())
415            }),
416        );
417        register_function(
418            "Transform2D".to_string(),
419            "anchor".to_string(),
420            Arc::new(|args| {
421                if args.len() != 2 {
422                    return Err("Expected 2 arguments for function anchor".to_string());
423                }
424                let mut itr = args.into_iter();
425                let x = crate::Size::try_coerce(itr.next().unwrap())?;
426                let y = crate::Size::try_coerce(itr.next().unwrap())?;
427                Ok(crate::Transform2D::anchor(x, y).to_pax_value())
428            }),
429        );
430    }
431}
432
433impl HelperFunctions for Transform2 {
434    fn register_all_functions() {
435        register_function(
436            "Transform2".to_string(),
437            "identity".to_string(),
438            Arc::new(|args| {
439                if args.len() != 0 {
440                    return Err("Expected 0 arguments for function identity".to_string());
441                }
442                Ok(Transform2::identity().to_pax_value())
443            }),
444        );
445        register_function(
446            "Transform2".to_string(),
447            "scale".to_string(),
448            Arc::new(|args| {
449                if args.len() != 1 {
450                    return Err("Expected 1 argument for function scale".to_string());
451                }
452                let s = f64::try_coerce(args.into_iter().next().unwrap())?;
453                Ok(Transform2::scale(s).to_pax_value())
454            }),
455        );
456
457        register_function(
458            "Transform2".to_string(),
459            "translate".to_string(),
460            Arc::new(|args| {
461                if args.len() != 1 {
462                    return Err("Expected 1 argument for function scale".to_string());
463                }
464                let s = Vector2::try_coerce(args.into_iter().next().unwrap())?;
465                Ok(Transform2::translate(s).to_pax_value())
466            }),
467        );
468
469        register_function(
470            "Transform2".to_string(),
471            "rotate".to_string(),
472            Arc::new(|args| {
473                if args.len() != 1 {
474                    return Err("Expected 1 argument for function rotate".to_string());
475                }
476                let s = f64::try_coerce(args.into_iter().next().unwrap())?;
477                Ok(Transform2::rotate(s).to_pax_value())
478            }),
479        );
480        register_function(
481            "Transform2".to_string(),
482            "skew".to_string(),
483            Arc::new(|args| {
484                if args.len() != 1 {
485                    return Err("Expected 1 argument for function skew".to_string());
486                }
487                let s = Vector2::try_coerce(args.into_iter().next().unwrap())?;
488                Ok(Transform2::skew(s).to_pax_value())
489            }),
490        );
491    }
492}