openjd_expr/functions/
misc.rs1use 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 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 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 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 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
201pub 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}