Skip to main content

openjd_expr/functions/
misc.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// Copyright by contributors to this project.
3// SPDX-License-Identifier: (Apache-2.0 OR MIT)
4
5//! Miscellaneous function implementations (fail).
6
7use crate::error::ExpressionError;
8use crate::function_library::EvalContext;
9use crate::value::ExprValue;
10
11type R = Result<ExprValue, ExpressionError>;
12type Ctx<'a> = &'a mut dyn EvalContext;
13
14pub fn fail_fn(_: Ctx, a: &[ExprValue]) -> R {
15    let msg = if a.is_empty() {
16        "fail() called".to_string()
17    } else {
18        a[0].to_display_string()
19    };
20    Err(ExpressionError::explicit_fail(msg))
21}
22
23pub fn zfill_fn(ctx: Ctx, a: &[ExprValue]) -> R {
24    if a.len() != 2 {
25        return Err(ExpressionError::new("zfill() takes exactly 2 arguments"));
26    }
27    let s = match &a[0] {
28        ExprValue::String(s) => s.clone(),
29        ExprValue::Int(i) => i.to_string(),
30        _ => a[0].to_display_string(),
31    };
32    ctx.count_string_ops(s.len())?;
33    let width = match &a[1] {
34        ExprValue::Int(w) => *w as usize,
35        _ => return Err(ExpressionError::new("zfill() width must be int")),
36    };
37    let clen = s.chars().count();
38    let result = if clen >= width {
39        s
40    } else {
41        let (sign, num) = if s.starts_with('-') || s.starts_with('+') {
42            (&s[..1], &s[1..])
43        } else {
44            ("", s.as_str())
45        };
46        let zeros = width - clen;
47        format!("{}{}{}", sign, "0".repeat(zeros), num)
48    };
49    Ok(ExprValue::String(result))
50}
51
52pub fn any_fn(ctx: Ctx, a: &[ExprValue]) -> R {
53    if a.len() != 1 {
54        return Err(ExpressionError::new("any() takes 1 argument"));
55    }
56    let iter = a[0]
57        .list_iter()
58        .ok_or_else(|| ExpressionError::new("any() argument must be a list"))?;
59    for e in iter {
60        ctx.count_op()?;
61        // Signature restricts to list[bool]; match directly (no truthy semantics).
62        if let ExprValue::Bool(true) = e {
63            return Ok(ExprValue::Bool(true));
64        }
65    }
66    Ok(ExprValue::Bool(false))
67}
68
69pub fn all_fn(ctx: Ctx, a: &[ExprValue]) -> R {
70    if a.len() != 1 {
71        return Err(ExpressionError::new("all() takes 1 argument"));
72    }
73    let iter = a[0]
74        .list_iter()
75        .ok_or_else(|| ExpressionError::new("all() argument must be a list"))?;
76    for e in iter {
77        ctx.count_op()?;
78        // Signature restricts to list[bool]; match directly (no truthy semantics).
79        if let ExprValue::Bool(false) = e {
80            return Ok(ExprValue::Bool(false));
81        }
82    }
83    Ok(ExprValue::Bool(true))
84}
85
86pub fn abs_int(_: Ctx, a: &[ExprValue]) -> R {
87    match &a[0] {
88        ExprValue::Int(i) => Ok(ExprValue::Int(
89            i.checked_abs()
90                .ok_or_else(ExpressionError::integer_overflow)?,
91        )),
92        _ => Err(ExpressionError::type_error("type error")),
93    }
94}
95
96pub fn abs_float(_: Ctx, a: &[ExprValue]) -> R {
97    match &a[0] {
98        ExprValue::Float(f) => Ok(ExprValue::Float(crate::value::Float64::new(
99            f.value().abs(),
100        )?)),
101        _ => Err(ExpressionError::type_error("type error")),
102    }
103}
104
105pub fn len_string(_: Ctx, a: &[ExprValue]) -> R {
106    match &a[0] {
107        ExprValue::String(s) => Ok(ExprValue::Int(s.chars().count() as i64)),
108        _ => Err(ExpressionError::type_error("type error")),
109    }
110}
111
112pub fn len_path(_: Ctx, a: &[ExprValue]) -> R {
113    match &a[0] {
114        ExprValue::Path { value, .. } => Ok(ExprValue::Int(value.chars().count() as i64)),
115        _ => Err(ExpressionError::type_error("type error")),
116    }
117}
118
119pub fn len_list(_: Ctx, a: &[ExprValue]) -> R {
120    match &a[0] {
121        val if val.is_list() => Ok(ExprValue::Int(val.list_len().unwrap() as i64)),
122        _ => Err(ExpressionError::type_error("type error")),
123    }
124}
125
126pub fn len_range(_: Ctx, a: &[ExprValue]) -> R {
127    match &a[0] {
128        ExprValue::RangeExpr(r) => Ok(ExprValue::Int(r.len() as i64)),
129        _ => Err(ExpressionError::type_error("type error")),
130    }
131}
132
133pub fn path_fn(ctx: Ctx, a: &[ExprValue]) -> R {
134    let s = match &a[0] {
135        ExprValue::String(s) => s.clone(),
136        ExprValue::Path { value, .. } => value.clone(),
137        val if val.is_list() => {
138            let parts: Vec<String> = val
139                .list_iter()
140                .expect("guard ensures list")
141                .map(|e| match &e {
142                    ExprValue::String(s) | ExprValue::Path { value: s, .. } => s.clone(),
143                    _ => e.to_display_string(),
144                })
145                .collect();
146            if parts.is_empty() {
147                String::new()
148            } else if parts[0].contains("://") {
149                // URI: join with "/" preserving empty components (no normalization)
150                if parts.len() == 1 {
151                    parts[0].clone()
152                } else {
153                    format!("{}/{}", parts[0], parts[1..].join("/"))
154                }
155            } else {
156                super::path_parse::join_pathlib(&parts, ctx.path_format())
157            }
158        }
159        _ => {
160            return Err(ExpressionError::new(format!(
161                "path() not supported for {}",
162                a[0].expr_type()
163            )))
164        }
165    };
166    ctx.count_string_ops(s.len())?;
167    // Pathlib normalizes filesystem paths on construction:
168    // strips leading `./`, internal `/./`, trailing separators,
169    // collapses runs of separators (POSIX `//` is a special
170    // double-slash root), and on Windows converts `/` to `\`.
171    // URI inputs (`scheme://...`) are NOT normalized — the spec
172    // explicitly preserves URI path components verbatim because
173    // `a//b` and `a/b` may refer to different resources for
174    // schemes like S3. See specifications wiki §1.2.1.
175    let normalized = if crate::uri_path::is_uri(&s) {
176        s
177    } else {
178        super::path_parse::pathlib_normalize(&s, ctx.path_format())
179    };
180    Ok(ExprValue::new_path(normalized, ctx.path_format()))
181}
182
183pub fn path_join_fn(ctx: Ctx, a: &[ExprValue]) -> R {
184    let parts: Vec<String> = a
185        .iter()
186        .map(|v| match v {
187            ExprValue::String(s) | ExprValue::Path { value: s, .. } => s.clone(),
188            _ => v.to_display_string(),
189        })
190        .collect();
191    let sep = super::path_parse::sep(ctx.path_format());
192    let mut result = parts[0].clone();
193    for part in &parts[1..] {
194        result.push(sep);
195        result.push_str(part);
196    }
197    ctx.count_string_ops(result.len())?;
198    Ok(ExprValue::new_path(result, ctx.path_format()))
199}
200
201// ── Getitem operators ──
202
203pub fn getitem_list(_: Ctx, a: &[ExprValue]) -> R {
204    let len = a[0]
205        .list_len()
206        .ok_or_else(|| ExpressionError::new("indexing requires list"))?;
207    let idx = match &a[1] {
208        ExprValue::Int(i) => *i,
209        _ => return Err(ExpressionError::new("index must be int")),
210    };
211    a[0].list_get(idx).ok_or_else(|| {
212        ExpressionError::index_out_of_bounds(format!(
213            "Index {} out of bounds for list of length {}",
214            a[1].to_display_string(),
215            len
216        ))
217    })
218}
219
220pub fn getitem_string(_: Ctx, a: &[ExprValue]) -> R {
221    let s = match &a[0] {
222        ExprValue::String(s) => s.as_str(),
223        _ => return Err(ExpressionError::new("indexing requires string")),
224    };
225    let idx = match &a[1] {
226        ExprValue::Int(i) => *i,
227        _ => return Err(ExpressionError::new("index must be int")),
228    };
229    let chars: Vec<char> = s.chars().collect();
230    let idx = if idx < 0 {
231        chars.len() as i64 + idx
232    } else {
233        idx
234    } as usize;
235    if idx >= chars.len() {
236        return Err(ExpressionError::index_out_of_bounds(format!(
237            "Index {} out of bounds for string of length {}",
238            a[1].to_display_string(),
239            chars.len()
240        )));
241    }
242    Ok(ExprValue::String(chars[idx].to_string()))
243}
244
245pub fn getitem_range(_: Ctx, a: &[ExprValue]) -> R {
246    let r = match &a[0] {
247        ExprValue::RangeExpr(r) => r,
248        _ => return Err(ExpressionError::new("indexing requires range_expr")),
249    };
250    let idx = match &a[1] {
251        ExprValue::Int(i) => *i,
252        _ => return Err(ExpressionError::new("index must be int")),
253    };
254    r.get(idx).map(ExprValue::Int).ok_or_else(|| {
255        ExpressionError::index_out_of_bounds(format!(
256            "Index {} out of bounds for range_expr of length {}",
257            idx,
258            r.len()
259        ))
260    })
261}