1use crate::ast::{CompareOp, Expr};
7use crate::errors::{Result, RunjucksError};
8use crate::value::is_undefined_value;
9use serde_json::{json, Map, Value};
10use std::borrow::Cow;
11use std::cmp::Ordering;
12use std::sync::Arc;
13
14use crate::globals::{
15 builtin_range, cycler_handle_value, is_builtin_marker_value, joiner_handle_value,
16 CyclerState, JoinerState,
17};
18
19use ahash::AHashMap;
20
21pub type ExtendsLayout = (Expr, std::collections::HashMap<String, Vec<crate::ast::Node>>);
23
24pub fn is_truthy(v: &Value) -> bool {
26 if is_undefined_value(v) {
27 return false;
28 }
29 match v {
30 Value::Null | Value::Bool(false) => false,
31 Value::Bool(true) => true,
32 Value::Number(n) => n.as_f64().map(|x| x != 0.0 && !x.is_nan()).unwrap_or(true),
33 Value::String(s) => !s.is_empty(),
34 Value::Array(_) | Value::Object(_) => true,
35 }
36}
37
38pub fn compare_values(left: &Value, op: CompareOp, right: &Value) -> bool {
40 match op {
41 CompareOp::Eq | CompareOp::StrictEq => left == right,
42 CompareOp::Ne | CompareOp::StrictNe => left != right,
43 CompareOp::Lt => json_partial_cmp(left, right) == Some(Ordering::Less),
44 CompareOp::Gt => json_partial_cmp(left, right) == Some(Ordering::Greater),
45 CompareOp::Le => matches!(
46 json_partial_cmp(left, right),
47 Some(Ordering::Less | Ordering::Equal)
48 ),
49 CompareOp::Ge => matches!(
50 json_partial_cmp(left, right),
51 Some(Ordering::Greater | Ordering::Equal)
52 ),
53 }
54}
55
56pub fn json_partial_cmp(a: &Value, b: &Value) -> Option<Ordering> {
58 match (a, b) {
59 (Value::Number(x), Value::Number(y)) => {
60 let xf = x.as_f64()?;
61 let yf = y.as_f64()?;
62 xf.partial_cmp(&yf)
63 }
64 (Value::String(x), Value::String(y)) => Some(x.cmp(y)),
65 _ => None,
66 }
67}
68
69pub fn as_number(v: &Value) -> Option<f64> {
71 match v {
72 Value::Number(n) => n.as_f64(),
73 Value::String(s) => s.parse().ok(),
74 Value::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
75 _ => None,
76 }
77}
78
79pub fn add_like_js(a: &Value, b: &Value) -> Value {
81 if let (Some(x), Some(y)) = (as_number(a), as_number(b)) {
82 json_num(x + y)
83 } else {
84 Value::String(format!(
85 "{}{}",
86 crate::value::value_to_string(a),
87 crate::value::value_to_string(b)
88 ))
89 }
90}
91
92pub fn json_num(x: f64) -> Value {
94 if x.fract() == 0.0 && x >= i64::MIN as f64 && x <= i64::MAX as f64 {
95 json!(x as i64)
96 } else {
97 json!(x)
98 }
99}
100
101pub fn eval_in(key: &Value, container: &Value) -> Result<bool> {
103 match container {
104 Value::Array(a) => Ok(a.iter().any(|v| v == key)),
105 Value::String(s) => {
106 let frag = match key {
107 Value::String(k) => k.as_str(),
108 _ => return Ok(false),
109 };
110 Ok(s.contains(frag))
111 }
112 Value::Object(o) => match key {
113 Value::String(k) => Ok(o.contains_key(k)),
114 _ => Ok(false),
115 },
116 _ => Err(RunjucksError::new(
117 "Cannot use \"in\" operator to search in unexpected type",
118 )),
119 }
120}
121
122pub fn is_test_parts(e: &Expr) -> Option<(&str, &[Expr])> {
124 match e {
125 Expr::Variable(n) => Some((n.as_str(), &[])),
126 Expr::Literal(Value::String(s)) => Some((s.as_str(), &[])),
127 Expr::Literal(Value::Null) => Some(("null", &[])),
128 Expr::Call {
129 callee,
130 args,
131 kwargs,
132 } => {
133 if !kwargs.is_empty() {
134 return None;
135 }
136 if let Expr::Variable(n) = callee.as_ref() {
137 Some((n.as_str(), args.as_slice()))
138 } else {
139 None
140 }
141 }
142 _ => None,
143 }
144}
145
146pub fn jinja_slice_array(
148 obj: &[Value],
149 start: Option<i64>,
150 stop: Option<i64>,
151 step: Option<i64>,
152) -> Vec<Value> {
153 let len = obj.len() as i64;
154 let step = step.unwrap_or(1);
155 if step == 0 {
156 return vec![];
157 }
158 let mut start = start;
159 let mut stop = stop;
160 if start.is_none() {
161 start = Some(if step < 0 { (len - 1).max(0) } else { 0 });
162 }
163 if stop.is_none() {
164 stop = Some(if step < 0 { -1 } else { len });
165 } else if let Some(s) = stop {
166 if s < 0 {
167 stop = Some(s + len);
168 }
169 }
170 if let Some(s) = start {
171 if s < 0 {
172 start = Some(s + len);
173 }
174 }
175 let start = start.unwrap_or(0);
176 let stop = stop.unwrap_or(len);
177 let mut results = Vec::new();
178 let mut i = start;
179 loop {
180 if i < 0 || i > len {
181 break;
182 }
183 if step > 0 && i >= stop {
184 break;
185 }
186 if step < 0 && i <= stop {
187 break;
188 }
189 if let Some(item) = obj.get(i as usize) {
190 results.push(item.clone());
191 }
192 i += step;
193 }
194 results
195}
196
197pub enum Iterable {
199 Rows(Vec<Value>),
200 Pairs(Vec<(String, Value)>),
201}
202
203pub fn iterable_from_value(v: Value) -> Iterable {
205 match v {
206 Value::Null => Iterable::Rows(vec![]),
207 Value::Array(a) => Iterable::Rows(a),
208 Value::Object(o) => {
209 let mut keys: Vec<String> = o.keys().cloned().collect();
210 keys.sort();
211 let pairs: Vec<(String, Value)> = keys
212 .into_iter()
213 .map(|k| {
214 let val = o.get(&k).cloned().unwrap_or(Value::Null);
215 (k, val)
216 })
217 .collect();
218 Iterable::Pairs(pairs)
219 }
220 _ => Iterable::Rows(vec![]),
221 }
222}
223
224pub fn iterable_empty(it: &Iterable) -> bool {
226 match it {
227 Iterable::Rows(a) => a.is_empty(),
228 Iterable::Pairs(p) => p.is_empty(),
229 }
230}
231
232pub fn fill_loop_object(m: &mut Map<String, Value>, i: usize, len: usize) {
234 m.insert("index".to_string(), Value::Number(((i + 1) as u64).into()));
235 m.insert("index0".to_string(), Value::Number((i as u64).into()));
236 m.insert("first".to_string(), Value::Bool(i == 0));
237 m.insert("last".to_string(), Value::Bool(len > 0 && i + 1 == len));
238 m.insert("length".to_string(), Value::Number((len as u64).into()));
239 m.insert(
240 "revindex".to_string(),
241 Value::Number((len.saturating_sub(i) as u64).into()),
242 );
243 m.insert(
244 "revindex0".to_string(),
245 Value::Number((len.saturating_sub(1).saturating_sub(i) as u64).into()),
246 );
247}
248
249pub fn inject_loop(frames: &mut Vec<AHashMap<String, Arc<Value>>>, i: usize, len: usize) {
251 let inner = frames
252 .last_mut()
253 .expect("inject_loop requires an active frame");
254 match inner.get_mut("loop") {
255 Some(arc) => match Arc::make_mut(arc) {
256 Value::Object(m) => fill_loop_object(m, i, len),
257 _ => {
258 let mut m = Map::with_capacity(7);
259 fill_loop_object(&mut m, i, len);
260 *arc = Arc::new(Value::Object(m));
261 }
262 },
263 None => {
264 let mut m = Map::with_capacity(7);
265 fill_loop_object(&mut m, i, len);
266 inner.insert("loop".to_string(), Arc::new(Value::Object(m)));
267 }
268 }
269 }
271
272pub fn can_dispatch_builtin_check(is_defined: bool, binding: Option<&Value>, name: &str) -> bool {
274 matches!(name, "range" | "cycler" | "joiner")
275 && (!is_defined
276 || binding
277 .map(|v| is_builtin_marker_value(v, name))
278 .unwrap_or(false))
279}
280
281pub fn try_dispatch_builtin(
283 cyclers: &mut Vec<CyclerState>,
284 joiners: &mut Vec<JoinerState>,
285 is_defined: bool,
286 binding: Option<&Value>,
287 name: &str,
288 arg_vals: &[Value],
289) -> Option<Result<Value>> {
290 if !can_dispatch_builtin_check(is_defined, binding, name) {
291 return None;
292 }
293 match name {
294 "range" => Some(builtin_range(arg_vals)),
295 "cycler" => {
296 let id = cyclers.len();
297 cyclers.push(CyclerState::new(arg_vals.to_vec()));
298 Some(Ok(cycler_handle_value(id)))
299 }
300 "joiner" => {
301 let sep = match arg_vals.len() {
302 0 => ",".to_string(),
303 1 => {
304 let s = crate::value::value_to_string(&arg_vals[0]);
305 if s.is_empty() {
306 ",".to_string()
307 } else {
308 s
309 }
310 }
311 _ => return Some(Err(RunjucksError::new("`joiner` expects 0 or 1 arguments"))),
312 };
313 let id = joiners.len();
314 joiners.push(JoinerState::new(sep));
315 Some(Ok(joiner_handle_value(id)))
316 }
317 _ => None,
318 }
319}
320
321pub fn collect_attr_chain_from_getattr<'a>(mut e: &'a Expr) -> Option<(&'a str, Vec<&'a str>)> {
324 let mut attrs: Vec<&'a str> = Vec::new();
325 loop {
326 match e {
327 Expr::GetAttr { base, attr } => {
328 attrs.push(attr.as_str());
329 e = base.as_ref();
330 }
331 Expr::Variable(name) => {
332 attrs.reverse();
333 return Some((name.as_str(), attrs));
334 }
335 _ => return None,
336 }
337 }
338}
339
340pub fn peel_builtin_upper_lower_length_chain<'a>(
343 mut e: &'a Expr,
344 custom_filters: &std::collections::HashMap<String, crate::environment::CustomFilter>,
345) -> Option<(Vec<&'a str>, &'a Expr)> {
346 let mut names: Vec<&'a str> = Vec::new();
347 loop {
348 match e {
349 Expr::Filter { name, input, args }
350 if args.is_empty() && !custom_filters.contains_key(name) =>
351 {
352 let n = name.as_str();
353 if !matches!(n, "upper" | "lower" | "length" | "trim" | "capitalize") {
354 return None;
355 }
356 names.push(n);
357 e = input.as_ref();
358 }
359 _ => break,
360 }
361 }
362 if names.is_empty() {
363 return None;
364 }
365 names.reverse();
366 if !builtin_filter_chain_application_order_valid(&names) {
367 return None;
368 }
369 Some((names, e))
370}
371
372pub fn builtin_filter_chain_application_order_valid(rev_names: &[&str]) -> bool {
374 if rev_names.is_empty() {
375 return false;
376 }
377 let last = rev_names.len() - 1;
378 for (i, &name) in rev_names.iter().enumerate() {
379 match name {
380 "upper" | "lower" | "trim" | "capitalize" => {}
381 "length" => {
382 if i != last {
383 return false;
384 }
385 }
386 _ => return false,
387 }
388 }
389 true
390}
391
392pub fn apply_builtin_filter_chain_on_cow_value(
394 mut current: Cow<'_, Value>,
395 rev_names: &[&str],
396) -> Result<Value> {
397 for n in rev_names {
398 match *n {
399 "upper" => {
400 let t = crate::value::value_to_string(current.as_ref()).to_uppercase();
401 current = Cow::Owned(Value::String(t));
402 }
403 "lower" => {
404 let t = crate::value::value_to_string(current.as_ref()).to_lowercase();
405 current = Cow::Owned(Value::String(t));
406 }
407 "trim" => {
408 let t = crate::filters::chain_trim_like_builtin(current.as_ref());
409 current = Cow::Owned(t);
410 }
411 "capitalize" => {
412 let t = crate::filters::chain_capitalize_like_builtin(current.as_ref());
413 current = Cow::Owned(t);
414 }
415 "length" => {
416 return Ok(match current.as_ref() {
417 Value::String(s) => json!(s.chars().count()),
418 Value::Array(a) => json!(a.len()),
419 Value::Object(o) => json!(o.len()),
420 x if is_undefined_value(x) => json!(0),
421 _ => json!(0),
422 });
423 }
424 _ => unreachable!(),
425 }
426 }
427 Ok(current.into_owned())
428}