1use crate::ast::{
4 BinOp, Expr, ForVars, MacroDef, MacroParam, Node, SwitchCase, UnaryOp,
5};
6use crate::environment::Environment;
7use crate::errors::{Result, RunjucksError};
8use crate::globals::{
9 parse_cycler_id, parse_joiner_id, CyclerState, JoinerState, RJ_CALLABLE,
10};
11use crate::loader::TemplateLoader;
12use crate::render_common::{
13 add_like_js, apply_builtin_filter_chain_on_cow_value, as_number, collect_attr_chain_from_getattr,
14 compare_values, eval_in, is_test_parts, is_truthy, iterable_empty, iterable_from_value,
15 jinja_slice_array, json_num, peel_builtin_upper_lower_length_chain, ExtendsLayout, Iterable,
16};
17use crate::value::{is_undefined_value, mark_safe, undefined_value};
18use ahash::AHashMap;
19use rand::rngs::SmallRng;
20use rand::SeedableRng;
21use serde_json::{json, Map, Value};
22use std::borrow::Cow;
23use std::collections::{HashMap, HashSet};
24use std::sync::Arc;
25
26#[derive(Debug, Clone)]
33pub struct CtxStack {
34 pub(crate) frames: Vec<AHashMap<String, Arc<Value>>>,
35 revision: u64,
38}
39
40impl CtxStack {
41 pub fn from_root(root: Map<String, Value>) -> Self {
42 let mapped: AHashMap<String, Arc<Value>> =
43 root.into_iter().map(|(k, v)| (k, Arc::new(v))).collect();
44 Self {
45 frames: vec![mapped],
46 revision: 0,
47 }
48 }
49
50 #[inline]
51 pub(crate) fn bump_revision(&mut self) {
52 self.revision = self.revision.wrapping_add(1);
53 }
54
55 #[inline]
57 pub fn revision(&self) -> u64 {
58 self.revision
59 }
60
61 pub fn push_frame(&mut self) {
62 self.frames.push(AHashMap::new());
63 self.bump_revision();
64 }
65
66 pub fn pop_frame(&mut self) {
67 if self.frames.len() > 1 {
68 self.frames.pop();
69 self.bump_revision();
70 }
71 }
72
73 pub fn get_ref(&self, name: &str) -> Option<&Value> {
75 for f in self.frames.iter().rev() {
76 if let Some(v) = f.get(name) {
77 return Some(v.as_ref());
78 }
79 }
80 None
81 }
82
83 pub fn get(&self, name: &str) -> Value {
84 self.get_ref(name).cloned().unwrap_or(Value::Null)
85 }
86
87 pub fn defined(&self, name: &str) -> bool {
88 self.frames.iter().rev().any(|f| f.contains_key(name))
89 }
90
91 pub fn set(&mut self, name: &str, value: Value) {
92 let arc = Arc::new(value);
93 for f in self.frames.iter_mut().rev() {
94 if f.contains_key(name) {
95 f.insert(name.to_string(), arc);
96 self.bump_revision();
97 return;
98 }
99 }
100 if let Some(inner) = self.frames.last_mut() {
101 inner.insert(name.to_string(), arc);
102 self.bump_revision();
103 }
104 }
105
106 pub fn set_local(&mut self, name: &str, value: Value) {
108 if let Some(inner) = self.frames.last_mut() {
109 inner.insert(name.to_string(), Arc::new(value));
110 self.bump_revision();
111 }
112 }
113
114 pub fn flatten(&self) -> Map<String, Value> {
116 let cap: usize = self.frames.iter().map(|f| f.len()).sum();
117 let mut m = Map::with_capacity(cap);
118 for f in &self.frames {
119 for (k, v) in f {
120 m.insert(k.clone(), v.as_ref().clone());
121 }
122 }
123 m
124 }
125}
126
127#[derive(Clone)]
129pub struct CallerFrame {
130 pub body: Vec<Node>,
131 pub params: Vec<MacroParam>,
132}
133
134pub struct RenderState<'a> {
136 pub loader: Option<&'a (dyn TemplateLoader + Send + Sync)>,
137 pub stack: Vec<String>,
138 pub macro_scopes: Vec<HashMap<String, MacroDef>>,
139 pub macro_namespaces: HashMap<String, HashMap<String, MacroDef>>,
141 pub macro_namespace_values: HashMap<String, HashMap<String, Value>>,
145 pub block_chains: Option<HashMap<String, Vec<Vec<Node>>>>,
147 pub super_context: Option<(String, usize)>,
149 pub caller_stack: Vec<CallerFrame>,
151 pub cyclers: Vec<CyclerState>,
153 pub joiners: Vec<JoinerState>,
155 pub rng: SmallRng,
157 extension_context_cache: Option<(u64, Value)>,
159}
160
161impl<'a> RenderState<'a> {
162 pub fn new(
163 loader: Option<&'a (dyn TemplateLoader + Send + Sync)>,
164 rng_seed: Option<u64>,
165 ) -> Self {
166 let rng = match rng_seed {
167 Some(s) => SmallRng::seed_from_u64(s),
168 None => SmallRng::from_entropy(),
169 };
170 Self {
171 loader,
172 stack: Vec::new(),
173 macro_scopes: Vec::new(),
174 macro_namespaces: HashMap::new(),
175 macro_namespace_values: HashMap::new(),
176 block_chains: None,
177 super_context: None,
178 caller_stack: Vec::new(),
179 cyclers: Vec::new(),
180 joiners: Vec::new(),
181 rng,
182 extension_context_cache: None,
183 }
184 }
185
186 pub fn push_template(&mut self, name: &str) -> Result<()> {
187 if self.stack.iter().any(|s| s == name) {
188 return Err(RunjucksError::new(format!(
189 "circular template reference: {name}"
190 )));
191 }
192 self.stack.push(name.to_string());
193 Ok(())
194 }
195
196 pub fn pop_template(&mut self) {
197 self.stack.pop();
198 }
199
200 pub fn push_macros(&mut self, defs: HashMap<String, MacroDef>) {
201 self.macro_scopes.push(defs);
202 }
203
204 pub fn pop_macros(&mut self) {
205 self.macro_scopes.pop();
206 }
207
208 pub fn lookup_macro(&self, name: &str) -> Option<&MacroDef> {
209 for scope in self.macro_scopes.iter().rev() {
210 if let Some(m) = scope.get(name) {
211 return Some(m);
212 }
213 }
214 None
215 }
216
217 pub fn lookup_namespaced_macro(&self, ns: &str, macro_name: &str) -> Option<&MacroDef> {
218 self.macro_namespaces
219 .get(ns)
220 .and_then(|m| m.get(macro_name))
221 }
222
223 pub fn lookup_namespaced_value(&self, ns: &str, name: &str) -> Option<&Value> {
224 self.macro_namespace_values
225 .get(ns)
226 .and_then(|m| m.get(name))
227 }
228}
229
230pub fn render(
232 env: &Environment,
233 loader: Option<&(dyn TemplateLoader + Send + Sync)>,
234 root: &Node,
235 ctx_stack: &mut CtxStack,
236) -> Result<String> {
237 let mut state = RenderState::new(loader, env.random_seed);
238 render_entry(env, &mut state, root, ctx_stack)
239}
240
241pub fn render_entry(
243 env: &Environment,
244 state: &mut RenderState<'_>,
245 root: &Node,
246 ctx_stack: &mut CtxStack,
247) -> Result<String> {
248 if let Some((parent_expr, blocks)) = extract_layout_if_any(root)? {
249 let parent_name =
250 crate::value::value_to_string(&eval_to_value(env, state, &parent_expr, ctx_stack)?);
251 render_extends(env, state, &parent_name, blocks, ctx_stack)
252 } else {
253 render_with_state(env, state, root, ctx_stack)
254 }
255}
256
257pub(crate) fn extract_layout_if_any(root: &Node) -> Result<Option<ExtendsLayout>> {
258 let Node::Root(children) = root else {
259 return Ok(None);
260 };
261 let mut idx = 0usize;
262 while idx < children.len() {
263 match &children[idx] {
264 Node::Text(s) if s.trim().is_empty() => idx += 1,
265 Node::Extends { parent } => {
266 let parent = parent.clone();
267 let mut blocks = HashMap::new();
268 for n in children.iter().skip(idx + 1) {
269 match n {
270 Node::Block { name, body } => {
271 blocks.insert(name.clone(), body.clone());
272 }
273 Node::Text(s) if s.chars().all(|c| c.is_whitespace()) => {}
274 Node::MacroDef(_) => {}
275 _ => {
276 return Err(RunjucksError::new(
277 "invalid content in template with `extends` (only `block` allowed)",
278 ));
279 }
280 }
281 }
282 return Ok(Some((parent, blocks)));
283 }
284 _ => return Ok(None),
285 }
286 }
287 Ok(None)
288}
289
290pub(crate) fn collect_blocks_in_root(root: &Node) -> HashMap<String, Vec<Node>> {
291 let Node::Root(children) = root else {
292 return HashMap::new();
293 };
294 let mut m = HashMap::new();
295 for n in children {
296 if let Node::Block { name, body } = n {
297 m.insert(name.clone(), body.clone());
298 }
299 }
300 m
301}
302
303pub(crate) fn extends_parent_expr(root: &Node) -> Option<&Expr> {
304 let Node::Root(children) = root else {
305 return None;
306 };
307 for n in children {
308 if let Node::Extends { parent } = n {
309 return Some(parent);
310 }
311 }
312 None
313}
314
315#[allow(clippy::too_many_arguments)]
317fn build_block_chains(
318 parent_name: &str,
319 parent_ast: &Node,
320 immediate_child_overrides: &HashMap<String, Vec<Node>>,
321 loader: &(dyn TemplateLoader + Send + Sync),
322 visited: &mut HashSet<String>,
323 env: &Environment,
324 state: &mut RenderState<'_>,
325 ctx_stack: &mut CtxStack,
326) -> Result<HashMap<String, Vec<Vec<Node>>>> {
327 if !visited.insert(parent_name.to_string()) {
328 return Err(RunjucksError::new(format!(
329 "circular `{{% extends %}}` involving `{parent_name}`"
330 )));
331 }
332
333 let result = (|| {
334 let local_blocks = collect_blocks_in_root(parent_ast);
335 let inherited: HashMap<String, Vec<Vec<Node>>> =
336 if let Some(gp_expr) = extends_parent_expr(parent_ast) {
337 let gp_name =
338 crate::value::value_to_string(&eval_to_value(env, state, gp_expr, ctx_stack)?);
339 let gp_ast = env.load_and_parse_named(&gp_name, loader)?;
340 build_block_chains(
341 &gp_name,
342 gp_ast.as_ref(),
343 &local_blocks,
344 loader,
345 visited,
346 env,
347 state,
348 ctx_stack,
349 )?
350 } else {
351 HashMap::new()
352 };
353
354 let mut all_names: HashSet<String> = immediate_child_overrides.keys().cloned().collect();
355 all_names.extend(local_blocks.keys().cloned());
356 all_names.extend(inherited.keys().cloned());
357
358 let mut out = HashMap::new();
359 for name in all_names {
360 let mut chain: Vec<Vec<Node>> = Vec::new();
361 if let Some(c) = immediate_child_overrides.get(&name) {
362 chain.push(c.clone());
363 }
364 if let Some(rest) = inherited.get(&name) {
365 chain.extend(rest.iter().cloned());
366 } else if let Some(l) = local_blocks.get(&name) {
367 chain.push(l.clone());
368 }
369 if !chain.is_empty() {
370 out.insert(name, chain);
371 }
372 }
373 Ok(out)
374 })();
375
376 visited.remove(parent_name);
377 result
378}
379
380fn render_extends(
381 env: &Environment,
382 state: &mut RenderState<'_>,
383 parent_name: &str,
384 blocks: HashMap<String, Vec<Node>>,
385 ctx_stack: &mut CtxStack,
386) -> Result<String> {
387 let loader = state
388 .loader
389 .ok_or_else(|| RunjucksError::new("`extends` requires a template loader"))?;
390 let parent_ast = env.load_and_parse_named(parent_name, loader)?;
391 state.push_template(parent_name)?;
392 let mut visited = HashSet::new();
393 let chains = build_block_chains(
394 parent_name,
395 parent_ast.as_ref(),
396 &blocks,
397 loader,
398 &mut visited,
399 env,
400 state,
401 ctx_stack,
402 )?;
403 let prev_chains = state.block_chains.take();
404 state.block_chains = Some(chains);
405 let out = render_with_state(env, state, parent_ast.as_ref(), ctx_stack)?;
406 state.block_chains = prev_chains;
407 state.pop_template();
408 Ok(out)
409}
410
411fn render_with_state(
412 env: &Environment,
413 state: &mut RenderState<'_>,
414 root: &Node,
415 ctx_stack: &mut CtxStack,
416) -> Result<String> {
417 render_node(env, state, root, ctx_stack)
418}
419
420pub(crate) fn collect_top_level_macros(root: &Node) -> HashMap<String, MacroDef> {
422 let mut m = HashMap::new();
423 let Node::Root(children) = root else {
424 return m;
425 };
426 for n in children {
427 if let Node::MacroDef(def) = n {
428 m.insert(def.name.clone(), def.clone());
429 }
430 }
431 m
432}
433
434pub(crate) enum TopLevelSetExport {
437 FromExpr { targets: Vec<String>, expr: Expr },
439 FromBlock { target: String, body: Vec<Node> },
441}
442
443pub(crate) fn collect_top_level_set_exports(root: &Node) -> Vec<TopLevelSetExport> {
444 let mut out = Vec::new();
445 let Node::Root(children) = root else {
446 return out;
447 };
448 for n in children {
449 match n {
450 Node::Set {
451 targets,
452 value: Some(expr),
453 body: None,
454 } if !targets.is_empty() => {
455 out.push(TopLevelSetExport::FromExpr {
456 targets: targets.clone(),
457 expr: expr.clone(),
458 });
459 }
460 Node::Set {
461 targets,
462 value: None,
463 body: Some(body),
464 } if targets.len() == 1 => {
465 out.push(TopLevelSetExport::FromBlock {
466 target: targets[0].clone(),
467 body: body.clone(),
468 });
469 }
470 _ => {}
471 }
472 }
473 out
474}
475
476fn eval_exported_top_level_sets(
479 env: &Environment,
480 state: &mut RenderState<'_>,
481 root: &Node,
482 ctx_stack: &mut CtxStack,
483 with_context: Option<bool>,
484) -> Result<HashMap<String, Value>> {
485 let mut out = HashMap::new();
486 let exports = collect_top_level_set_exports(root);
487 let mut import_stack = if matches!(with_context, Some(true)) {
488 CtxStack::from_root(ctx_stack.flatten())
489 } else {
490 CtxStack::from_root(Map::new())
491 };
492 for ex in exports {
493 match ex {
494 TopLevelSetExport::FromExpr { targets, expr } => {
495 let v = eval_to_value(env, state, &expr, &mut import_stack)?;
496 for t in &targets {
497 import_stack.set(t, v.clone());
498 }
499 for t in &targets {
500 out.insert(t.clone(), v.clone());
501 }
502 }
503 TopLevelSetExport::FromBlock { target, body } => {
504 let s = render_children(env, state, &body, &mut import_stack)?;
505 let val = Value::String(s);
506 import_stack.set(&target, val.clone());
507 out.insert(target, val);
508 }
509 }
510 }
511 Ok(out)
512}
513
514pub(crate) fn scan_literal_extends_graph(
519 env: &Environment,
520 state: &mut RenderState<'_>,
521 root: &Node,
522 loader: &(dyn TemplateLoader + Send + Sync),
523) -> Result<()> {
524 let Some(expr) = extends_parent_expr(root) else {
525 return Ok(());
526 };
527 let Expr::Literal(Value::String(path)) = expr else {
528 return Ok(());
529 };
530 state.push_template(path)?;
531 let nested = env.load_and_parse_named(path, loader)?;
532 let r = scan_literal_extends_graph(env, state, nested.as_ref(), loader);
533 state.pop_template();
534 r
535}
536
537pub(crate) fn scan_literal_import_graph(
538 env: &Environment,
539 state: &mut RenderState<'_>,
540 root: &Node,
541 loader: &(dyn TemplateLoader + Send + Sync),
542) -> Result<()> {
543 let Node::Root(children) = root else {
544 return Ok(());
545 };
546 for n in children {
547 let template_expr = match n {
548 Node::Import { template, .. } | Node::FromImport { template, .. } => template,
549 _ => continue,
550 };
551 let Expr::Literal(Value::String(path)) = template_expr else {
552 continue;
553 };
554 state.push_template(path)?;
555 let nested = env.load_and_parse_named(path, loader)?;
556 scan_literal_import_graph(env, state, nested.as_ref(), loader)?;
557 state.pop_template();
558 }
559 Ok(())
560}
561
562fn render_node(
563 env: &Environment,
564 state: &mut RenderState<'_>,
565 n: &Node,
566 stack: &mut CtxStack,
567) -> Result<String> {
568 match n {
569 Node::Root(nodes) => {
570 let mut defs = HashMap::new();
571 for n in nodes.iter() {
572 if let Node::MacroDef(m) = n {
573 defs.insert(m.name.clone(), m.clone());
574 }
575 }
576 let had_macros = !defs.is_empty();
577 let scope_base = state.macro_scopes.len();
578 if had_macros {
579 state.push_macros(defs);
580 }
581 let mut out = String::new();
582 out.reserve(nodes.len().saturating_mul(32));
583 for child in nodes.iter() {
584 if matches!(child, Node::MacroDef(_) | Node::Extends { .. }) {
585 continue;
586 }
587 out.push_str(&render_node(env, state, child, stack)?);
588 }
589 while state.macro_scopes.len() > scope_base {
590 state.pop_macros();
591 }
592 Ok(out)
593 }
594 Node::Text(s) => Ok(s.to_string()),
595 Node::Output(exprs) => render_output(env, state, exprs, stack),
596 Node::If { branches } => {
597 for br in branches {
598 if let Some(cond) = &br.cond {
599 if !is_truthy(&eval_to_value(env, state, cond, stack)?) {
600 continue;
601 }
602 }
603 return render_children(env, state, &br.body, stack);
604 }
605 Ok(String::new())
606 }
607 Node::Switch {
608 expr,
609 cases,
610 default_body,
611 } => render_switch(env, state, expr, cases, default_body.as_deref(), stack),
612 Node::For {
613 vars,
614 iter,
615 body,
616 else_body,
617 } => render_for(env, state, vars, iter, body, else_body.as_deref(), stack),
618 Node::Set {
619 targets,
620 value,
621 body,
622 } => {
623 if let Some(expr) = value {
624 let v = eval_to_value(env, state, expr, stack)?;
625 for t in targets {
626 stack.set(t, v.clone());
627 }
628 } else if let Some(nodes) = body {
629 let s = render_children(env, state, nodes, stack)?;
630 if let Some(t) = targets.first() {
631 stack.set(t, Value::String(s));
632 }
633 }
634 Ok(String::new())
635 }
636 Node::Include {
637 template,
638 ignore_missing,
639 with_context,
640 } => {
641 let loader = state
642 .loader
643 .ok_or_else(|| RunjucksError::new("`include` requires a template loader"))?;
644 let name = crate::value::value_to_string(&eval_to_value(env, state, template, stack)?);
645 let included = match env.load_and_parse_named(&name, loader) {
646 Ok(ast) => ast,
647 Err(_) if *ignore_missing => return Ok(String::new()),
648 Err(e) => return Err(e),
649 };
650 state.push_template(&name)?;
651 let out = if matches!(with_context, Some(false)) {
652 let mut isolated = CtxStack::from_root(Map::new());
653 render_entry(env, state, included.as_ref(), &mut isolated)?
654 } else {
655 render_entry(env, state, included.as_ref(), stack)?
656 };
657 state.pop_template();
658 Ok(out)
659 }
660 Node::Import {
661 template,
662 alias,
663 with_context,
664 } => {
665 let loader = state
666 .loader
667 .ok_or_else(|| RunjucksError::new("`import` requires a template loader"))?;
668 let name = crate::value::value_to_string(&eval_to_value(env, state, template, stack)?);
669 let imported = env.load_and_parse_named(&name, loader)?;
670 state.push_template(&name)?;
671 scan_literal_import_graph(env, state, imported.as_ref(), loader)?;
672 let defs = collect_top_level_macros(imported.as_ref());
673 let exported_sets =
674 eval_exported_top_level_sets(env, state, imported.as_ref(), stack, *with_context)?;
675 state.pop_template();
676 state.macro_namespaces.insert(alias.clone(), defs);
677 state
678 .macro_namespace_values
679 .insert(alias.clone(), exported_sets);
680 Ok(String::new())
681 }
682 Node::FromImport {
683 template,
684 names,
685 with_context,
686 } => {
687 let loader = state
688 .loader
689 .ok_or_else(|| RunjucksError::new("`from` requires a template loader"))?;
690 let name = crate::value::value_to_string(&eval_to_value(env, state, template, stack)?);
691 let imported = env.load_and_parse_named(&name, loader)?;
692 state.push_template(&name)?;
693 scan_literal_import_graph(env, state, imported.as_ref(), loader)?;
694 let defs = collect_top_level_macros(imported.as_ref());
695 let exported_sets =
696 eval_exported_top_level_sets(env, state, imported.as_ref(), stack, *with_context)?;
697 state.pop_template();
698 let mut scope = HashMap::new();
699 for (export_name, alias_opt) in names {
700 let local = alias_opt.as_ref().unwrap_or(export_name);
701 if let Some(mdef) = defs.get(export_name) {
702 scope.insert(local.clone(), mdef.clone());
703 } else if let Some(v) = exported_sets.get(export_name) {
704 stack.set(local, v.clone());
705 } else {
706 return Err(RunjucksError::new(format!("cannot import '{export_name}'")));
707 }
708 }
709 state.push_macros(scope);
710 Ok(String::new())
711 }
712 Node::Extends { .. } => Err(RunjucksError::new(
713 "`extends` is only valid at the top level of a loaded template",
714 )),
715 Node::Block { name, body } => {
716 let to_render: Vec<Node> = if let Some(ref chains) = state.block_chains {
717 chains
718 .get(name)
719 .and_then(|ch| ch.first().cloned())
720 .unwrap_or_else(|| body.clone())
721 } else {
722 body.clone()
723 };
724 let prev_super = state.super_context.take();
725 state.super_context = Some((name.clone(), 0));
726 let out = render_children(env, state, &to_render, stack);
727 state.super_context = prev_super;
728 out
729 }
730 Node::FilterBlock { name, args, body } => {
731 #[cfg(feature = "async")]
732 if env.async_custom_filters.contains_key(name) {
733 return Err(RunjucksError::new(format!(
734 "`{name}` is an async filter and can only be used with `renderStringAsync()` or `renderTemplateAsync()`"
735 )));
736 }
737 let s = render_children(env, state, body, stack)?;
738 let arg_vals: Vec<Value> = args
739 .iter()
740 .map(|a| eval_to_value(env, state, a, stack))
741 .collect::<Result<_>>()?;
742 let v = crate::filters::apply_builtin(
743 env,
744 &mut state.rng,
745 name,
746 &Value::String(s),
747 &arg_vals,
748 )?;
749 let out = crate::value::value_to_string(&v);
750 if env.autoescape && !crate::value::is_marked_safe(&v) {
751 Ok(crate::filters::escape_html(&out))
752 } else {
753 Ok(out)
754 }
755 }
756 Node::CallBlock {
757 caller_params,
758 callee,
759 body,
760 } => {
761 let Expr::Call {
762 callee: macro_target,
763 args,
764 kwargs,
765 } = callee
766 else {
767 return Err(RunjucksError::new(
768 "`{% call %}` expects a macro call expression such as `wrap()` or `ns.wrap()`",
769 ));
770 };
771 let arg_vals: Vec<Value> = args
772 .iter()
773 .map(|a| eval_to_value(env, state, a, stack))
774 .collect::<Result<_>>()?;
775 let kw_vals: Vec<(String, Value)> = kwargs
776 .iter()
777 .map(|(k, e)| Ok((k.clone(), eval_to_value(env, state, e, stack)?)))
778 .collect::<Result<_>>()?;
779 let mdef = if let Expr::Variable(name) = macro_target.as_ref() {
780 state
781 .lookup_macro(name)
782 .cloned()
783 .ok_or_else(|| RunjucksError::new(format!("unknown macro `{name}`")))?
784 } else if let Expr::GetAttr { base, attr } = macro_target.as_ref() {
785 if let Expr::Variable(ns) = base.as_ref() {
786 state
787 .lookup_namespaced_macro(ns, attr)
788 .cloned()
789 .ok_or_else(|| RunjucksError::new(format!("unknown macro `{ns}.{attr}`")))?
790 } else {
791 return Err(RunjucksError::new(
792 "`{% call %}` only supports simple macro or `namespace.macro()` calls",
793 ));
794 }
795 } else {
796 return Err(RunjucksError::new(
797 "`{% call %}` only supports simple macro or `namespace.macro()` calls",
798 ));
799 };
800 let frame = CallerFrame {
801 body: body.clone(),
802 params: caller_params.clone(),
803 };
804 state.caller_stack.push(frame);
805 let module_closure_owned =
806 if let Expr::GetAttr { base, attr: _ } = macro_target.as_ref() {
807 if let Expr::Variable(ns) = base.as_ref() {
808 state.macro_namespace_values.get(ns).cloned()
809 } else {
810 None
811 }
812 } else {
813 None
814 };
815 let res = render_macro_body(
816 env,
817 state,
818 &mdef,
819 &arg_vals,
820 &kw_vals,
821 stack,
822 module_closure_owned.as_ref(),
823 );
824 state.caller_stack.pop();
825 res
826 }
827 Node::ExtensionTag {
828 extension_name,
829 args,
830 body,
831 ..
832 } => {
833 let handler = env.custom_extensions.get(extension_name).ok_or_else(|| {
834 RunjucksError::new(format!("unknown extension `{extension_name}`"))
835 })?;
836 let rev = stack.revision();
837 let ctx_for_handler = match state.extension_context_cache.take() {
838 Some((r, v)) if r == rev => v,
839 _ => Value::Object(stack.flatten()),
840 };
841 let body_s = if let Some(nodes) = body {
842 Some(render_children(env, state, nodes, stack)?)
843 } else {
844 None
845 };
846 let out = handler(&ctx_for_handler, args.as_str(), body_s)?;
847 state.extension_context_cache = Some((rev, ctx_for_handler));
848 Ok(if env.autoescape {
849 crate::filters::escape_html(&out)
850 } else {
851 out
852 })
853 }
854 Node::AsyncEach { .. } => Err(RunjucksError::new(
855 "`{% asyncEach %}` requires async render mode; use `renderStringAsync()` or `renderTemplateAsync()`",
856 )),
857 Node::AsyncAll { .. } => Err(RunjucksError::new(
858 "`{% asyncAll %}` requires async render mode; use `renderStringAsync()` or `renderTemplateAsync()`",
859 )),
860 Node::IfAsync { .. } => Err(RunjucksError::new(
861 "`{% ifAsync %}` requires async render mode; use `renderStringAsync()` or `renderTemplateAsync()`",
862 )),
863 Node::MacroDef(_) => Ok(String::new()),
864 }
865}
866
867fn render_switch(
868 env: &Environment,
869 state: &mut RenderState<'_>,
870 disc_expr: &Expr,
871 cases: &[SwitchCase],
872 default_body: Option<&[Node]>,
873 stack: &mut CtxStack,
874) -> Result<String> {
875 let disc = eval_to_value(env, state, disc_expr, stack)?;
876 let mut start = None;
877 for (i, c) in cases.iter().enumerate() {
878 if eval_to_value(env, state, &c.cond, stack)? == disc {
879 start = Some(i);
880 break;
881 }
882 }
883 let mut acc = String::new();
884 if let Some(mut idx) = start {
885 loop {
886 let body = &cases[idx].body;
887 acc.push_str(&render_children(env, state, body, stack)?);
888 if !body.is_empty() {
889 return Ok(acc);
890 }
891 idx += 1;
892 if idx >= cases.len() {
893 break;
894 }
895 }
896 }
897 if let Some(db) = default_body {
898 acc.push_str(&render_children(env, state, db, stack)?);
899 }
900 Ok(acc)
901}
902
903fn inject_loop(stack: &mut CtxStack, i: usize, len: usize) {
904 crate::render_common::inject_loop(&mut stack.frames, i, len);
905 stack.bump_revision();
906}
907
908fn render_for(
909 env: &Environment,
910 state: &mut RenderState<'_>,
911 vars: &ForVars,
912 iter_expr: &Expr,
913 body: &[Node],
914 else_body: Option<&[Node]>,
915 stack: &mut CtxStack,
916) -> Result<String> {
917 let v = eval_to_value(env, state, iter_expr, stack)?;
918 let it = iterable_from_value(v);
919 if iterable_empty(&it) {
920 return if let Some(eb) = else_body {
921 render_children(env, state, eb, stack)
922 } else {
923 Ok(String::new())
924 };
925 }
926
927 stack.push_frame();
928 let mut acc = String::new();
929
930 match (vars, it) {
931 (ForVars::Single(x), Iterable::Rows(items)) => {
932 let len = items.len();
933 acc.reserve(len.saturating_mul(16));
934 for (i, item) in items.into_iter().enumerate() {
935 inject_loop(stack, i, len);
936 stack.set_local(x, item);
937 acc.push_str(&render_children(env, state, body, stack)?);
938 }
939 }
940 (ForVars::Multi(names), Iterable::Rows(rows)) if names.len() >= 2 => {
941 let len = rows.len();
942 acc.reserve(len.saturating_mul(16));
943 for (i, row) in rows.into_iter().enumerate() {
944 inject_loop(stack, i, len);
945 if let Value::Array(cols) = row {
946 for (u, name) in names.iter().enumerate() {
947 let cell = cols.get(u).cloned().unwrap_or(Value::Null);
948 stack.set_local(name, cell);
949 }
950 } else {
951 for name in names {
952 stack.set_local(name, Value::Null);
953 }
954 }
955 acc.push_str(&render_children(env, state, body, stack)?);
956 }
957 }
958 (ForVars::Multi(names), Iterable::Pairs(pairs)) if names.len() == 2 => {
959 let len = pairs.len();
960 acc.reserve(len.saturating_mul(16));
961 for (i, (k, v)) in pairs.into_iter().enumerate() {
962 inject_loop(stack, i, len);
963 stack.set_local(&names[0], Value::String(k));
964 stack.set_local(&names[1], v);
965 acc.push_str(&render_children(env, state, body, stack)?);
966 }
967 }
968 (ForVars::Single(_), _) | (ForVars::Multi(_), _) => {
969 stack.pop_frame();
970 return if let Some(eb) = else_body {
971 render_children(env, state, eb, stack)
972 } else {
973 Ok(String::new())
974 };
975 }
976 }
977
978 stack.pop_frame();
979 Ok(acc)
980}
981
982fn render_children(
983 env: &Environment,
984 state: &mut RenderState<'_>,
985 nodes: &[Node],
986 stack: &mut CtxStack,
987) -> Result<String> {
988 let mut out = String::new();
989 out.reserve(nodes.len().saturating_mul(32));
990 for child in nodes {
991 out.push_str(&render_node(env, state, child, stack)?);
992 }
993 Ok(out)
994}
995
996fn render_output(
997 env: &Environment,
998 state: &mut RenderState<'_>,
999 exprs: &[Expr],
1000 stack: &mut CtxStack,
1001) -> Result<String> {
1002 let mut out = String::new();
1003 out.reserve(exprs.len().saturating_mul(24));
1004 for e in exprs {
1005 out.push_str(&eval_for_output(env, state, e, stack)?);
1006 }
1007 Ok(out)
1008}
1009
1010#[cfg(feature = "async")]
1011fn filter_chain_has_async_override(env: &Environment, e: &Expr) -> bool {
1012 let mut cur = e;
1013 loop {
1014 match cur {
1015 Expr::Filter { name, input, .. } => {
1016 if env.async_custom_filters.contains_key(name) {
1017 return true;
1018 }
1019 cur = input.as_ref();
1020 }
1021 _ => return false,
1022 }
1023 }
1024}
1025
1026fn try_apply_peeled_builtin_filter_chain_value(
1027 env: &Environment,
1028 stack: &mut CtxStack,
1029 e: &Expr,
1030) -> Option<Result<Value>> {
1031 #[cfg(feature = "async")]
1032 if filter_chain_has_async_override(env, e) {
1033 return None;
1034 }
1035 let (rev_names, leaf) = peel_builtin_upper_lower_length_chain(e, &env.custom_filters)?;
1036 match leaf {
1037 Expr::Variable(var_name) => {
1038 let v = match env.resolve_variable_ref(stack, var_name) {
1039 Ok(v) => v,
1040 Err(e) => return Some(Err(e)),
1041 };
1042 Some(apply_builtin_filter_chain_on_cow_value(v, &rev_names))
1043 }
1044 Expr::Literal(Value::String(s)) => {
1045 let mut current = s.clone();
1046 for n in &rev_names {
1047 match *n {
1048 "upper" => current = current.to_uppercase(),
1049 "lower" => current = current.to_lowercase(),
1050 "trim" => {
1051 current = current
1052 .trim_matches(|c: char| c.is_whitespace())
1053 .to_string();
1054 }
1055 "capitalize" => {
1056 current = crate::filters::capitalize_string_slice(¤t);
1057 }
1058 "length" => return Some(Ok(json!(current.chars().count()))),
1059 _ => unreachable!(),
1060 }
1061 }
1062 Some(Ok(Value::String(current)))
1063 }
1064 Expr::Literal(Value::Array(a)) if rev_names == ["length"] => Some(Ok(json!(a.len()))),
1065 _ => None,
1066 }
1067}
1068
1069fn eval_for_output(
1070 env: &Environment,
1071 state: &mut RenderState<'_>,
1072 e: &Expr,
1073 stack: &mut CtxStack,
1074) -> Result<String> {
1075 match e {
1076 Expr::Literal(v) => Ok(crate::value::value_to_string(v)),
1077 Expr::Variable(name) => {
1078 let v = env.resolve_variable_ref(stack, name)?;
1079 let s = crate::value::value_to_string(v.as_ref());
1080 if env.autoescape && !crate::value::is_marked_safe(v.as_ref()) {
1081 Ok(crate::filters::escape_html(&s))
1082 } else {
1083 Ok(s)
1084 }
1085 }
1086 Expr::Filter { name, input, args } => {
1087 #[cfg(feature = "async")]
1088 if env.async_custom_filters.contains_key(name) {
1089 return Err(RunjucksError::new(format!(
1090 "`{name}` is an async filter and can only be used with `renderStringAsync()` or `renderTemplateAsync()`"
1091 )));
1092 }
1093 if args.is_empty() {
1094 if let Some((rev_names, leaf)) = peel_builtin_upper_lower_length_chain(e, &env.custom_filters) {
1095 match leaf {
1096 Expr::Variable(var_name) => {
1097 let v = env.resolve_variable_ref(stack, var_name)?;
1098 let input_safe = crate::value::is_marked_safe(v.as_ref());
1099 match apply_builtin_filter_chain_on_cow_value(v, &rev_names) {
1100 Ok(val) => {
1101 let s = crate::value::value_to_string(&val);
1102 let escape = env.autoescape
1103 && match &val {
1104 Value::String(_) => !input_safe,
1105 _ => true,
1106 };
1107 if escape {
1108 return Ok(crate::filters::escape_html(&s));
1109 }
1110 return Ok(s);
1111 }
1112 Err(e) => return Err(e),
1113 }
1114 }
1115 Expr::Literal(Value::String(s)) => {
1116 let mut current = s.clone();
1117 for n in &rev_names {
1118 match *n {
1119 "upper" => current = current.to_uppercase(),
1120 "lower" => current = current.to_lowercase(),
1121 "trim" => {
1122 current = current
1123 .trim_matches(|c: char| c.is_whitespace())
1124 .to_string();
1125 }
1126 "capitalize" => {
1127 current = crate::filters::capitalize_string_slice(¤t);
1128 }
1129 "length" => {
1130 let s = current.chars().count().to_string();
1131 let escape = env.autoescape;
1132 return Ok(if escape {
1133 crate::filters::escape_html(&s)
1134 } else {
1135 s
1136 });
1137 }
1138 _ => unreachable!(),
1139 }
1140 }
1141 let escape = env.autoescape;
1142 return Ok(if escape {
1143 crate::filters::escape_html(¤t)
1144 } else {
1145 current
1146 });
1147 }
1148 Expr::Literal(Value::Array(a)) if rev_names == ["length"] => {
1149 let s = a.len().to_string();
1150 let escape = env.autoescape;
1151 return Ok(if escape {
1152 crate::filters::escape_html(&s)
1153 } else {
1154 s
1155 });
1156 }
1157 _ => {}
1158 }
1159 }
1160 }
1161 if args.is_empty() && !env.custom_filters.contains_key(name) {
1162 if let Expr::Variable(var_name) = input.as_ref() {
1163 let input_v = env.resolve_variable_ref(stack, var_name)?;
1164 match name.as_str() {
1165 "upper" => {
1166 let out =
1167 crate::value::value_to_string(input_v.as_ref()).to_uppercase();
1168 return if env.autoescape
1169 && !crate::value::is_marked_safe(input_v.as_ref())
1170 {
1171 Ok(crate::filters::escape_html(&out))
1172 } else {
1173 Ok(out)
1174 };
1175 }
1176 "lower" => {
1177 let out =
1178 crate::value::value_to_string(input_v.as_ref()).to_lowercase();
1179 return if env.autoescape
1180 && !crate::value::is_marked_safe(input_v.as_ref())
1181 {
1182 Ok(crate::filters::escape_html(&out))
1183 } else {
1184 Ok(out)
1185 };
1186 }
1187 "length" => {
1188 let n = match input_v.as_ref() {
1189 Value::String(s) => s.chars().count(),
1190 Value::Array(a) => a.len(),
1191 Value::Object(o) => o.len(),
1192 v if is_undefined_value(v) => 0,
1193 _ => 0,
1194 };
1195 return Ok(n.to_string());
1196 }
1197 "capitalize" => {
1198 let out =
1199 crate::filters::chain_capitalize_like_builtin(input_v.as_ref());
1200 let s = crate::value::value_to_string(&out);
1201 return if env.autoescape
1202 && !crate::value::is_marked_safe(input_v.as_ref())
1203 {
1204 Ok(crate::filters::escape_html(&s))
1205 } else {
1206 Ok(s)
1207 };
1208 }
1209 _ => {}
1210 }
1211 }
1212 if let Expr::Literal(Value::String(s)) = input.as_ref() {
1213 match name.as_str() {
1214 "upper" => {
1215 let out = s.to_uppercase();
1216 return if env.autoescape {
1217 Ok(crate::filters::escape_html(&out))
1218 } else {
1219 Ok(out)
1220 };
1221 }
1222 "lower" => {
1223 let out = s.to_lowercase();
1224 return if env.autoescape {
1225 Ok(crate::filters::escape_html(&out))
1226 } else {
1227 Ok(out)
1228 };
1229 }
1230 "length" => {
1231 return Ok(s.chars().count().to_string());
1232 }
1233 "capitalize" => {
1234 let out = crate::filters::capitalize_string_slice(s);
1235 return if env.autoescape {
1236 Ok(crate::filters::escape_html(&out))
1237 } else {
1238 Ok(out)
1239 };
1240 }
1241 _ => {}
1242 }
1243 }
1244 if let Expr::Literal(Value::Array(a)) = input.as_ref() {
1245 if name == "length" {
1246 return Ok(a.len().to_string());
1247 }
1248 }
1249 }
1250 let v = eval_to_value(env, state, e, stack)?;
1251 let s = crate::value::value_to_string(&v);
1252 if env.autoescape && !crate::value::is_marked_safe(&v) {
1253 Ok(crate::filters::escape_html(&s))
1254 } else {
1255 Ok(s)
1256 }
1257 }
1258 _ => {
1259 let v = eval_to_value(env, state, e, stack)?;
1260 let s = crate::value::value_to_string(&v);
1261 if env.autoescape && !crate::value::is_marked_safe(&v) {
1262 Ok(crate::filters::escape_html(&s))
1263 } else {
1264 Ok(s)
1265 }
1266 }
1267 }
1268}
1269
1270fn eval_slice_bound(
1271 env: &Environment,
1272 state: &mut RenderState<'_>,
1273 e: Option<&Expr>,
1274 stack: &mut CtxStack,
1275) -> Result<Option<i64>> {
1276 let Some(e) = e else {
1277 return Ok(None);
1278 };
1279 let v = eval_to_value(env, state, e, stack)?;
1280 if v.is_null() || crate::value::is_undefined_value(&v) {
1281 return Ok(None);
1282 }
1283 let n = v
1284 .as_i64()
1285 .or_else(|| v.as_f64().map(|x| x as i64))
1286 .or_else(|| crate::value::value_to_string(&v).parse().ok());
1287 match n {
1288 Some(x) => Ok(Some(x)),
1289 None => Err(RunjucksError::new("slice bound must be a number")),
1290 }
1291}
1292
1293fn try_dispatch_builtin(
1294 state: &mut RenderState<'_>,
1295 stack: &CtxStack,
1296 name: &str,
1297 arg_vals: &[Value],
1298) -> Option<Result<Value>> {
1299 crate::render_common::try_dispatch_builtin(
1300 &mut state.cyclers,
1301 &mut state.joiners,
1302 stack.defined(name),
1303 stack.get_ref(name),
1304 name,
1305 arg_vals,
1306 )
1307}
1308
1309fn render_macro_body(
1310 env: &Environment,
1311 state: &mut RenderState<'_>,
1312 m: &MacroDef,
1313 positional: &[Value],
1314 kwargs: &[(String, Value)],
1315 outer: &mut CtxStack,
1316 module_closure: Option<&HashMap<String, Value>>,
1317) -> Result<String> {
1318 let mut inner = outer.flatten();
1319 if let Some(mc) = module_closure {
1320 for (k, v) in mc {
1321 inner.insert(k.clone(), v.clone());
1322 }
1323 }
1324 for p in &m.params {
1325 let val = if let Some(ref d) = p.default {
1326 eval_to_value(env, state, d, outer)?
1327 } else {
1328 Value::Null
1329 };
1330 inner.insert(p.name.clone(), val);
1331 }
1332 for (i, p) in m.params.iter().enumerate() {
1333 if let Some(v) = positional.get(i) {
1334 inner.insert(p.name.clone(), v.clone());
1335 }
1336 }
1337 for (k, v) in kwargs {
1338 if m.params.iter().any(|p| p.name == *k) {
1339 inner.insert(k.clone(), v.clone());
1340 }
1341 }
1342 let mut stack = CtxStack::from_root(inner);
1343 render_children(env, state, &m.body, &mut stack)
1344}
1345
1346fn render_caller_invocation(
1348 env: &Environment,
1349 state: &mut RenderState<'_>,
1350 frame: &CallerFrame,
1351 positional: &[Value],
1352 kwargs: &[(String, Value)],
1353 stack: &mut CtxStack,
1354) -> Result<String> {
1355 if frame.params.is_empty() {
1356 if !positional.is_empty() || !kwargs.is_empty() {
1357 return Err(RunjucksError::new("`caller()` takes no arguments"));
1358 }
1359 return render_children(env, state, &frame.body, stack);
1360 }
1361 stack.push_frame();
1362 for p in &frame.params {
1363 let val = if let Some(ref d) = p.default {
1364 eval_to_value(env, state, d, stack)?
1365 } else {
1366 Value::Null
1367 };
1368 stack.set_local(&p.name, val);
1369 }
1370 for (i, p) in frame.params.iter().enumerate() {
1371 if let Some(v) = positional.get(i) {
1372 stack.set_local(&p.name, v.clone());
1373 }
1374 }
1375 for (k, v) in kwargs {
1376 if frame.params.iter().any(|p| p.name == *k) {
1377 stack.set_local(k, v.clone());
1378 }
1379 }
1380 let out = render_children(env, state, &frame.body, stack)?;
1381 stack.pop_frame();
1382 Ok(out)
1383}
1384
1385fn eval_to_value(
1386 env: &Environment,
1387 state: &mut RenderState<'_>,
1388 e: &Expr,
1389 stack: &mut CtxStack,
1390) -> Result<Value> {
1391 match e {
1392 Expr::Literal(v) => Ok(v.clone()),
1393 Expr::Variable(name) => env.resolve_variable(stack, name),
1394 Expr::Unary { op, expr } => match op {
1395 UnaryOp::Not => {
1396 if let Expr::Variable(name) = expr.as_ref() {
1397 let v = env.resolve_variable_ref(stack, name)?;
1398 return Ok(Value::Bool(!is_truthy(v.as_ref())));
1399 }
1400 let v = eval_to_value(env, state, expr, stack)?;
1401 Ok(Value::Bool(!is_truthy(&v)))
1402 }
1403 UnaryOp::Neg => {
1404 if let Expr::Variable(name) = expr.as_ref() {
1405 let v = env.resolve_variable_ref(stack, name)?;
1406 let n = as_number(v.as_ref())
1407 .ok_or_else(|| RunjucksError::new("unary '-' expects a numeric value"))?;
1408 return Ok(json_num(-n));
1409 }
1410 let v = eval_to_value(env, state, expr, stack)?;
1411 let n = as_number(&v)
1412 .ok_or_else(|| RunjucksError::new("unary '-' expects a numeric value"))?;
1413 Ok(json_num(-n))
1414 }
1415 UnaryOp::Pos => {
1416 if let Expr::Variable(name) = expr.as_ref() {
1417 let v = env.resolve_variable_ref(stack, name)?;
1418 if let Some(n) = as_number(v.as_ref()) {
1419 return Ok(json_num(n));
1420 }
1421 return Ok(v.into_owned());
1422 }
1423 let v = eval_to_value(env, state, expr, stack)?;
1424 Ok(v)
1425 }
1426 },
1427 Expr::Binary { op, left, right } => match op {
1428 BinOp::Add => Ok(add_like_js(
1429 &eval_to_value(env, state, left, stack)?,
1430 &eval_to_value(env, state, right, stack)?,
1431 )),
1432 BinOp::Concat => Ok(Value::String(format!(
1433 "{}{}",
1434 eval_for_output(env, state, left, stack)?,
1435 eval_for_output(env, state, right, stack)?
1436 ))),
1437 BinOp::Sub => {
1438 let a = eval_to_value(env, state, left, stack)?;
1439 let b = eval_to_value(env, state, right, stack)?;
1440 let x = as_number(&a).ok_or_else(|| RunjucksError::new("`-` expects numbers"))?;
1441 let y = as_number(&b).ok_or_else(|| RunjucksError::new("`-` expects numbers"))?;
1442 Ok(json_num(x - y))
1443 }
1444 BinOp::Mul => {
1445 let a = eval_to_value(env, state, left, stack)?;
1446 let b = eval_to_value(env, state, right, stack)?;
1447 let x = as_number(&a).ok_or_else(|| RunjucksError::new("`*` expects numbers"))?;
1448 let y = as_number(&b).ok_or_else(|| RunjucksError::new("`*` expects numbers"))?;
1449 Ok(json_num(x * y))
1450 }
1451 BinOp::Div => {
1452 let a = eval_to_value(env, state, left, stack)?;
1453 let b = eval_to_value(env, state, right, stack)?;
1454 let x = as_number(&a).ok_or_else(|| RunjucksError::new("`/` expects numbers"))?;
1455 let y = as_number(&b).ok_or_else(|| RunjucksError::new("`/` expects numbers"))?;
1456 Ok(json!(x / y))
1457 }
1458 BinOp::FloorDiv => {
1459 let a = eval_to_value(env, state, left, stack)?;
1460 let b = eval_to_value(env, state, right, stack)?;
1461 let x = as_number(&a).ok_or_else(|| RunjucksError::new("`//` expects numbers"))?;
1462 let y = as_number(&b).ok_or_else(|| RunjucksError::new("`//` expects numbers"))?;
1463 if y == 0.0 {
1464 return Err(RunjucksError::new("division by zero"));
1465 }
1466 Ok(json_num((x / y).floor()))
1467 }
1468 BinOp::Mod => {
1469 let a = eval_to_value(env, state, left, stack)?;
1470 let b = eval_to_value(env, state, right, stack)?;
1471 let x = as_number(&a).ok_or_else(|| RunjucksError::new("`%` expects numbers"))?;
1472 let y = as_number(&b).ok_or_else(|| RunjucksError::new("`%` expects numbers"))?;
1473 Ok(json_num(x % y))
1474 }
1475 BinOp::Pow => {
1476 let a = eval_to_value(env, state, left, stack)?;
1477 let b = eval_to_value(env, state, right, stack)?;
1478 let x = as_number(&a).ok_or_else(|| RunjucksError::new("`**` expects numbers"))?;
1479 let y = as_number(&b).ok_or_else(|| RunjucksError::new("`**` expects numbers"))?;
1480 Ok(json!(x.powf(y)))
1481 }
1482 BinOp::And => {
1483 let l = eval_to_value(env, state, left, stack)?;
1484 if !is_truthy(&l) {
1485 return Ok(l);
1486 }
1487 eval_to_value(env, state, right, stack)
1488 }
1489 BinOp::Or => {
1490 let l = eval_to_value(env, state, left, stack)?;
1491 if is_truthy(&l) {
1492 return Ok(l);
1493 }
1494 eval_to_value(env, state, right, stack)
1495 }
1496 BinOp::In => {
1497 let key = eval_to_value(env, state, left, stack)?;
1498 let container = eval_to_value(env, state, right, stack)?;
1499 Ok(Value::Bool(eval_in(&key, &container)?))
1500 }
1501 BinOp::Is => {
1502 let (test_name, arg_exprs) = is_test_parts(right).ok_or_else(|| {
1503 RunjucksError::new("`is` test must be an identifier, call, string, or null")
1504 })?;
1505 if test_name == "defined" {
1506 if let Expr::Variable(n) = &**left {
1507 return Ok(Value::Bool(stack.defined(n)));
1508 }
1509 }
1510 if test_name == "callable" {
1511 if let Expr::Variable(n) = &**left {
1512 if state.lookup_macro(n).is_some() {
1513 return Ok(Value::Bool(true));
1514 }
1515 }
1516 }
1517 if arg_exprs.is_empty() {
1518 let v = match &**left {
1519 Expr::Variable(n) => env.resolve_variable_ref(stack, n)?,
1520 _ => Cow::Owned(eval_to_value(env, state, left, stack)?),
1521 };
1522 return Ok(Value::Bool(env.apply_is_test(
1523 test_name,
1524 v.as_ref(),
1525 &[],
1526 )?));
1527 }
1528 let arg_vals: Vec<Value> = arg_exprs
1529 .iter()
1530 .map(|e| eval_to_value(env, state, e, stack))
1531 .collect::<Result<_>>()?;
1532 let v = match &**left {
1533 Expr::Variable(n) => env.resolve_variable_ref(stack, n)?,
1534 _ => Cow::Owned(eval_to_value(env, state, left, stack)?),
1535 };
1536 if matches!(test_name, "equalto" | "eq" | "sameas") && arg_exprs.len() == 1 {
1537 if let Expr::Variable(lhs) = &**left {
1538 if let Expr::Variable(rhs) = &arg_exprs[0] {
1539 if lhs == rhs {
1540 return Ok(Value::Bool(true));
1541 }
1542 }
1543 }
1544 }
1545 Ok(Value::Bool(env.apply_is_test(
1546 test_name,
1547 v.as_ref(),
1548 &arg_vals,
1549 )?))
1550 }
1551 },
1552 Expr::Compare { head, rest } => {
1553 if rest.len() == 1 {
1554 let (op, rhs_e) = &rest[0];
1555 match head.as_ref() {
1556 Expr::Variable(n) => {
1557 let r = eval_to_value(env, state, rhs_e, stack)?;
1561 let left = env.resolve_variable_ref(stack, n)?;
1562 return Ok(Value::Bool(compare_values(left.as_ref(), *op, &r)));
1563 }
1564 _ => {
1565 let left = eval_to_value(env, state, head, stack)?;
1566 let r = eval_to_value(env, state, rhs_e, stack)?;
1567 return Ok(Value::Bool(compare_values(&left, *op, &r)));
1568 }
1569 }
1570 }
1571 let mut acc = eval_to_value(env, state, head, stack)?;
1572 for (op, rhs_e) in rest.iter() {
1573 let r = eval_to_value(env, state, rhs_e, stack)?;
1574 let ok = compare_values(&acc, *op, &r);
1575 acc = Value::Bool(ok);
1576 }
1577 Ok(acc)
1578 }
1579 Expr::InlineIf {
1580 cond,
1581 then_expr,
1582 else_expr,
1583 } => {
1584 let c = eval_to_value(env, state, cond, stack)?;
1585 if is_truthy(&c) {
1586 eval_to_value(env, state, then_expr, stack)
1587 } else if let Some(els) = else_expr {
1588 eval_to_value(env, state, els, stack)
1589 } else {
1590 Ok(Value::Null)
1591 }
1592 }
1593 Expr::GetAttr { base, attr } => {
1594 if let Expr::Variable(ns) = base.as_ref() {
1595 if state.macro_namespaces.contains_key(ns)
1596 || state.macro_namespace_values.contains_key(ns)
1597 {
1598 if let Some(v) = state.lookup_namespaced_value(ns, attr) {
1599 return Ok(v.clone());
1600 }
1601 if state.lookup_namespaced_macro(ns, attr).is_some() {
1602 let mut m = Map::new();
1603 m.insert(RJ_CALLABLE.to_string(), Value::Bool(true));
1604 return Ok(Value::Object(m));
1605 }
1606 return Ok(undefined_value());
1607 }
1608 }
1609 if let Some((root_name, attrs)) = collect_attr_chain_from_getattr(e) {
1610 if !state.macro_namespaces.contains_key(root_name)
1611 && !state.macro_namespace_values.contains_key(root_name)
1612 {
1613 let mut cur = env.resolve_variable_ref(stack, root_name)?;
1614 for a in &attrs {
1615 if is_undefined_value(cur.as_ref()) || cur.as_ref().is_null() {
1616 return Ok(undefined_value());
1617 }
1618 match cur.as_ref() {
1619 Value::Object(o) => {
1620 cur =
1621 Cow::Owned(o.get(*a).cloned().unwrap_or_else(undefined_value));
1622 }
1623 _ => return Ok(undefined_value()),
1624 }
1625 }
1626 return Ok(cur.into_owned());
1627 }
1628 }
1629 let b = eval_to_value(env, state, base, stack)?;
1630 if is_undefined_value(&b) || b.is_null() {
1631 return Ok(undefined_value());
1632 }
1633 match b {
1634 Value::Object(o) => Ok(o.get(attr).cloned().unwrap_or_else(undefined_value)),
1635 _ => Ok(undefined_value()),
1636 }
1637 }
1638 Expr::GetItem { base, index } => match index.as_ref() {
1639 Expr::Slice {
1640 start: s,
1641 stop: st,
1642 step: step_e,
1643 } => {
1644 let start_v = eval_slice_bound(env, state, s.as_deref(), stack)?;
1645 let stop_v = eval_slice_bound(env, state, st.as_deref(), stack)?;
1646 let step_v = eval_slice_bound(env, state, step_e.as_deref(), stack)?;
1647 if let Expr::Variable(name) = base.as_ref() {
1648 let base_val = env.resolve_variable_ref(stack, name)?;
1649 if is_undefined_value(base_val.as_ref()) || base_val.as_ref().is_null() {
1650 return Ok(undefined_value());
1651 }
1652 if let Value::Array(a) = base_val.as_ref() {
1653 return Ok(Value::Array(jinja_slice_array(a, start_v, stop_v, step_v)));
1654 }
1655 return Ok(Value::Null);
1656 }
1657 let b = eval_to_value(env, state, base, stack)?;
1658 if is_undefined_value(&b) || b.is_null() {
1659 return Ok(undefined_value());
1660 }
1661 let Value::Array(a) = &b else {
1662 return Ok(Value::Null);
1663 };
1664 Ok(Value::Array(jinja_slice_array(a, start_v, stop_v, step_v)))
1665 }
1666 idx_e => {
1667 if let Expr::Variable(name) = base.as_ref() {
1668 let base_val = env.resolve_variable_ref(stack, name)?;
1669 if is_undefined_value(base_val.as_ref()) || base_val.as_ref().is_null() {
1670 return Ok(undefined_value());
1671 }
1672 match idx_e {
1673 Expr::Literal(Value::Number(n)) => {
1674 let idx = n
1675 .as_u64()
1676 .or_else(|| n.as_f64().map(|x| x as u64))
1677 .unwrap_or(0) as usize;
1678 match base_val.as_ref() {
1679 Value::Array(a) => {
1680 return Ok(a.get(idx).cloned().unwrap_or_else(undefined_value));
1681 }
1682 _ => return Ok(undefined_value()),
1683 }
1684 }
1685 Expr::Literal(Value::String(k)) => match base_val.as_ref() {
1686 Value::Object(o) => {
1687 return Ok(o.get(k).cloned().unwrap_or_else(undefined_value));
1688 }
1689 _ => return Ok(undefined_value()),
1690 },
1691 _ => {}
1692 }
1693 }
1694 let b = eval_to_value(env, state, base, stack)?;
1695 if is_undefined_value(&b) || b.is_null() {
1696 return Ok(undefined_value());
1697 }
1698 let i = eval_to_value(env, state, idx_e, stack)?;
1699 match (&b, &i) {
1700 (Value::Array(a), Value::Number(n)) => {
1701 let idx = n
1702 .as_u64()
1703 .or_else(|| n.as_f64().map(|x| x as u64))
1704 .unwrap_or(0) as usize;
1705 Ok(a.get(idx).cloned().unwrap_or_else(undefined_value))
1706 }
1707 (Value::Object(o), Value::String(k)) => {
1708 Ok(o.get(k).cloned().unwrap_or_else(undefined_value))
1709 }
1710 _ => Ok(undefined_value()),
1711 }
1712 }
1713 },
1714 Expr::Slice { .. } => Err(RunjucksError::new(
1715 "slice expression is only valid inside `[ ]`",
1716 )),
1717 Expr::Call {
1718 callee,
1719 args,
1720 kwargs,
1721 } => {
1722 let arg_vals: Vec<Value> = args
1723 .iter()
1724 .map(|a| eval_to_value(env, state, a, stack))
1725 .collect::<Result<_>>()?;
1726 let kw_vals: Vec<(String, Value)> = kwargs
1727 .iter()
1728 .map(|(k, e)| Ok((k.clone(), eval_to_value(env, state, e, stack)?)))
1729 .collect::<Result<_>>()?;
1730 if let Expr::GetAttr { base, attr } = callee.as_ref() {
1731 if attr == "test" {
1732 let base_v = eval_to_value(env, state, base, stack)?;
1733 if crate::value::is_regexp_value(&base_v) {
1734 if !kw_vals.is_empty() {
1735 return Err(RunjucksError::new(
1736 "regex `.test` does not accept keyword arguments",
1737 ));
1738 }
1739 if arg_vals.len() != 1 {
1740 return Err(RunjucksError::new(
1741 "regex `.test` expects exactly one argument",
1742 ));
1743 }
1744 let Some((pat, fl)) = crate::value::regexp_pattern_flags(&base_v) else {
1745 return Err(RunjucksError::new("invalid regex value"));
1746 };
1747 let s = crate::value::value_to_string(&arg_vals[0]);
1748 return Ok(Value::Bool(crate::js_regex::regexp_test(&pat, &fl, &s)?));
1749 }
1750 }
1751 }
1752 if let Expr::GetAttr { base, attr } = callee.as_ref() {
1753 if attr == "next" && arg_vals.is_empty() && kw_vals.is_empty() {
1754 let b = eval_to_value(env, state, base, stack)?;
1755 if let Some(id) = parse_cycler_id(&b) {
1756 if let Some(c) = state.cyclers.get_mut(id) {
1757 return Ok(c.next());
1758 }
1759 return Ok(Value::Null);
1760 }
1761 }
1762 }
1763 if let Expr::Variable(name) = callee.as_ref() {
1764 if name == "super" {
1765 if !args.is_empty() || !kw_vals.is_empty() {
1766 return Err(RunjucksError::new("`super()` takes no arguments"));
1767 }
1768 let (block_name, layer) = state.super_context.clone().ok_or_else(|| {
1769 RunjucksError::new("`super()` is only valid inside a `{% block %}`")
1770 })?;
1771 let (body_to_render, next) = {
1772 let chains = state.block_chains.as_ref().ok_or_else(|| {
1773 RunjucksError::new(
1774 "`super()` requires template inheritance (`{% extends %}`)",
1775 )
1776 })?;
1777 let chain = chains.get(&block_name).ok_or_else(|| {
1778 RunjucksError::new(format!(
1779 "no super block available for `{block_name}`"
1780 ))
1781 })?;
1782 let next = layer + 1;
1783 if next >= chain.len() {
1784 return Err(RunjucksError::new(
1785 "no parent block available for `super()`",
1786 ));
1787 }
1788 (chain[next].clone(), next)
1789 };
1790 let prev = state.super_context.replace((block_name.clone(), next));
1791 let s = render_children(env, state, &body_to_render, stack)?;
1792 state.super_context = prev;
1793 return Ok(mark_safe(s));
1794 }
1795 if name == "caller" {
1796 let frame = state.caller_stack.last().cloned().ok_or_else(|| {
1797 RunjucksError::new(
1798 "`caller()` is only valid inside a macro invoked from `{% call %}`",
1799 )
1800 })?;
1801 let s =
1802 render_caller_invocation(env, state, &frame, &arg_vals, &kw_vals, stack)?;
1803 return Ok(mark_safe(s));
1804 }
1805 if let Some(mdef) = state.lookup_macro(name).cloned() {
1806 let s = render_macro_body(env, state, &mdef, &arg_vals, &kw_vals, stack, None)?;
1807 return Ok(mark_safe(s));
1808 }
1809 if arg_vals.is_empty() {
1810 let v = env.resolve_variable(stack, name)?;
1811 if let Some(id) = parse_joiner_id(&v) {
1812 if let Some(j) = state.joiners.get_mut(id) {
1813 return Ok(Value::String(j.invoke()));
1814 }
1815 }
1816 }
1817 if let Some(r) = try_dispatch_builtin(state, stack, name, &arg_vals) {
1818 return r;
1819 }
1820 if let Some(f) = env.custom_globals.get(name) {
1821 return f(&arg_vals, &kw_vals);
1822 }
1823 #[cfg(feature = "async")]
1824 if env.async_custom_globals.contains_key(name) {
1825 return Err(RunjucksError::new(format!(
1826 "`{name}` is an async global and can only be used with `renderStringAsync()` or `renderTemplateAsync()`"
1827 )));
1828 }
1829 }
1830 if let Expr::GetAttr { base, attr } = callee.as_ref() {
1831 if let Expr::Variable(ns) = base.as_ref() {
1832 if let Some(mdef) = state.lookup_namespaced_macro(ns, attr).cloned() {
1833 let mc = state.macro_namespace_values.get(ns).cloned();
1834 let s = render_macro_body(
1835 env,
1836 state,
1837 &mdef,
1838 &arg_vals,
1839 &kw_vals,
1840 stack,
1841 mc.as_ref(),
1842 )?;
1843 return Ok(mark_safe(s));
1844 }
1845 }
1846 }
1847 Err(RunjucksError::new(
1848 "only template macros, built-in globals (`range`, `cycler`, `joiner`), registered global callables, or `super`/`caller` are supported for `()` expressions",
1849 ))
1850 }
1851 Expr::Filter { name, input, args } => {
1852 if args.is_empty() {
1853 if let Some(r) = try_apply_peeled_builtin_filter_chain_value(env, stack, e) {
1854 return r;
1855 }
1856 }
1857 #[cfg(feature = "async")]
1858 let async_override = env.async_custom_filters.contains_key(name);
1859 #[cfg(not(feature = "async"))]
1860 let async_override = false;
1861 if args.is_empty() && !env.custom_filters.contains_key(name) && !async_override {
1862 if let Expr::Variable(var_name) = input.as_ref() {
1863 let input_v = env.resolve_variable_ref(stack, var_name)?;
1864 match name.as_str() {
1865 "upper" => {
1866 return Ok(Value::String(
1867 crate::value::value_to_string(input_v.as_ref()).to_uppercase(),
1868 ));
1869 }
1870 "lower" => {
1871 return Ok(Value::String(
1872 crate::value::value_to_string(input_v.as_ref()).to_lowercase(),
1873 ));
1874 }
1875 "length" => {
1876 return Ok(match input_v.as_ref() {
1877 Value::String(s) => json!(s.chars().count()),
1878 Value::Array(a) => json!(a.len()),
1879 Value::Object(o) => json!(o.len()),
1880 v if is_undefined_value(v) => json!(0),
1881 _ => json!(0),
1882 });
1883 }
1884 "capitalize" => {
1885 return Ok(crate::filters::chain_capitalize_like_builtin(
1886 input_v.as_ref(),
1887 ));
1888 }
1889 _ => {}
1890 }
1891 }
1892 if let Expr::Literal(Value::String(s)) = input.as_ref() {
1893 match name.as_str() {
1894 "upper" => return Ok(Value::String(s.to_uppercase())),
1895 "lower" => return Ok(Value::String(s.to_lowercase())),
1896 "length" => return Ok(json!(s.chars().count())),
1897 "capitalize" => {
1898 return Ok(Value::String(crate::filters::capitalize_string_slice(s)));
1899 }
1900 _ => {}
1901 }
1902 }
1903 if let Expr::Literal(Value::Array(a)) = input.as_ref() {
1904 if name == "length" {
1905 return Ok(json!(a.len()));
1906 }
1907 }
1908 }
1909 #[cfg(feature = "async")]
1910 if env.async_custom_filters.contains_key(name) {
1911 return Err(RunjucksError::new(format!(
1912 "`{name}` is an async filter and can only be used with `renderStringAsync()` or `renderTemplateAsync()`"
1913 )));
1914 }
1915 let input_v = eval_to_value(env, state, input, stack)?;
1916 let arg_vals: Vec<Value> = args
1917 .iter()
1918 .map(|a| eval_to_value(env, state, a, stack))
1919 .collect::<Result<_>>()?;
1920 crate::filters::apply_builtin(env, &mut state.rng, name, &input_v, &arg_vals)
1921 }
1922 Expr::List(items) => {
1923 let mut out = Vec::with_capacity(items.len());
1924 for it in items {
1925 out.push(eval_to_value(env, state, it, stack)?);
1926 }
1927 Ok(Value::Array(out))
1928 }
1929 Expr::Dict(pairs) => {
1930 use serde_json::Map;
1931 let mut m = Map::new();
1932 for (k, v) in pairs {
1933 let key_v = eval_to_value(env, state, k, stack)?;
1934 let key = match key_v {
1935 Value::String(s) => s,
1936 _ => crate::value::value_to_string(&key_v),
1937 };
1938 m.insert(key, eval_to_value(env, state, v, stack)?);
1939 }
1940 Ok(Value::Object(m))
1941 }
1942 Expr::RegexLiteral { pattern, flags } => {
1943 let mut m = Map::new();
1944 m.insert(crate::value::RJ_REGEXP.to_string(), Value::Bool(true));
1945 m.insert("pattern".to_string(), Value::String(pattern.clone()));
1946 m.insert("flags".to_string(), Value::String(flags.clone()));
1947 Ok(Value::Object(m))
1948 }
1949 }
1950}