Skip to main content

minijinja/compiler/
codegen.rs

1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::mem;
4
5use crate::compiler::ast;
6use crate::compiler::instructions::{
7    Instruction, Instructions, LocalId, LOOP_FLAG_RECURSIVE, LOOP_FLAG_WITH_LOOP_VAR, MAX_LOCALS,
8};
9use crate::compiler::tokens::Span;
10use crate::output::CaptureMode;
11use crate::value::ops::neg;
12use crate::value::{Kwargs, UndefinedType, Value, ValueMap, ValueRepr};
13
14#[cfg(test)]
15use similar_asserts::assert_eq;
16
17#[cfg(feature = "macros")]
18type Caller<'source> = ast::Spanned<ast::Macro<'source>>;
19
20#[cfg(not(feature = "macros"))]
21type Caller<'source> = std::marker::PhantomData<&'source ()>;
22
23/// For the first `MAX_LOCALS` filters/tests, an ID is returned for faster lookups from the stack.
24fn get_local_id<'source>(ids: &mut BTreeMap<&'source str, LocalId>, name: &'source str) -> LocalId {
25    if let Some(id) = ids.get(name) {
26        *id
27    } else if ids.len() >= MAX_LOCALS {
28        !0
29    } else {
30        let next_id = ids.len() as LocalId;
31        ids.insert(name, next_id);
32        next_id
33    }
34}
35
36/// Represents an open block of code that does not yet have updated
37/// jump targets.
38enum PendingBlock {
39    Branch {
40        jump_instr: u32,
41    },
42    Loop {
43        iter_instr: u32,
44        jump_instrs: Vec<u32>,
45    },
46    ScBool {
47        jump_instrs: Vec<u32>,
48    },
49}
50
51const CODEGEN_POOL_MAX_ITEMS: usize = 64;
52const CODEGEN_POOLED_MAX_CAPACITY: usize = 64;
53
54thread_local! {
55    static PENDING_BLOCK_POOL: RefCell<Vec<Vec<PendingBlock>>> = const { RefCell::new(Vec::new()) };
56    static SPAN_STACK_POOL: RefCell<Vec<Vec<Span>>> = const { RefCell::new(Vec::new()) };
57}
58
59#[inline(always)]
60fn take_pending_block_buffer() -> Vec<PendingBlock> {
61    let mut buf = PENDING_BLOCK_POOL
62        .with(|pool| pool.borrow_mut().pop())
63        .unwrap_or_else(|| Vec::with_capacity(8));
64    buf.clear();
65    buf
66}
67
68#[inline(always)]
69fn take_span_stack_buffer() -> Vec<Span> {
70    let mut buf = SPAN_STACK_POOL
71        .with(|pool| pool.borrow_mut().pop())
72        .unwrap_or_else(|| Vec::with_capacity(8));
73    buf.clear();
74    buf
75}
76
77#[inline(always)]
78fn recycle_pending_block_buffer(mut buf: Vec<PendingBlock>) {
79    if buf.capacity() <= CODEGEN_POOLED_MAX_CAPACITY {
80        buf.clear();
81        PENDING_BLOCK_POOL.with(|pool| {
82            let mut pool = pool.borrow_mut();
83            if pool.len() < CODEGEN_POOL_MAX_ITEMS {
84                pool.push(buf);
85            }
86        });
87    }
88}
89
90#[inline(always)]
91fn recycle_span_stack_buffer(mut buf: Vec<Span>) {
92    if buf.capacity() <= CODEGEN_POOLED_MAX_CAPACITY {
93        buf.clear();
94        SPAN_STACK_POOL.with(|pool| {
95            let mut pool = pool.borrow_mut();
96            if pool.len() < CODEGEN_POOL_MAX_ITEMS {
97                pool.push(buf);
98            }
99        });
100    }
101}
102
103/// Provides a convenient interface to creating instructions for the VM.
104pub struct CodeGenerator<'source> {
105    instructions: Instructions<'source>,
106    blocks: BTreeMap<&'source str, Instructions<'source>>,
107    pending_block: Vec<PendingBlock>,
108    current_line: u16,
109    span_stack: Vec<Span>,
110    filter_local_ids: BTreeMap<&'source str, LocalId>,
111    test_local_ids: BTreeMap<&'source str, LocalId>,
112    raw_template_bytes: usize,
113}
114
115impl<'source> CodeGenerator<'source> {
116    /// Creates a new code generator.
117    pub fn new(file: &'source str, source: &'source str) -> CodeGenerator<'source> {
118        CodeGenerator {
119            instructions: Instructions::new(file, source),
120            blocks: BTreeMap::new(),
121            pending_block: take_pending_block_buffer(),
122            current_line: 0,
123            span_stack: take_span_stack_buffer(),
124            filter_local_ids: BTreeMap::new(),
125            test_local_ids: BTreeMap::new(),
126            raw_template_bytes: 0,
127        }
128    }
129
130    /// Sets the current location's line.
131    pub fn set_line(&mut self, lineno: u16) {
132        self.current_line = lineno;
133    }
134
135    /// Sets line from span.
136    pub fn set_line_from_span(&mut self, span: Span) {
137        self.set_line(span.start_line);
138    }
139
140    /// Pushes a span to the stack
141    pub fn push_span(&mut self, span: Span) {
142        self.span_stack.push(span);
143        self.set_line_from_span(span);
144    }
145
146    /// Pops a span from the stack.
147    pub fn pop_span(&mut self) {
148        self.span_stack.pop();
149    }
150
151    /// Add a simple instruction with the current location.
152    pub fn add(&mut self, instr: Instruction<'source>) -> u32 {
153        if let Some(span) = self.span_stack.last() {
154            if span.start_line == self.current_line {
155                return self.instructions.add_with_span(instr, *span);
156            }
157        }
158        self.instructions.add_with_line(instr, self.current_line)
159    }
160
161    /// Add a simple instruction with other location.
162    pub fn add_with_span(&mut self, instr: Instruction<'source>, span: Span) -> u32 {
163        self.instructions.add_with_span(instr, span)
164    }
165
166    /// Returns the next instruction index.
167    pub fn next_instruction(&self) -> u32 {
168        self.instructions.len() as u32
169    }
170
171    /// Creates a sub generator.
172    #[cfg(feature = "multi_template")]
173    fn new_subgenerator(&self) -> CodeGenerator<'source> {
174        let mut sub = CodeGenerator::new(self.instructions.name(), self.instructions.source());
175        sub.current_line = self.current_line;
176        sub.span_stack = self.span_stack.last().copied().into_iter().collect();
177        sub
178    }
179
180    /// Finishes a sub generator and syncs it back.
181    #[cfg(feature = "multi_template")]
182    fn finish_subgenerator(&mut self, sub: CodeGenerator<'source>) -> Instructions<'source> {
183        self.current_line = sub.current_line;
184        let (instructions, blocks) = sub.finish();
185        self.blocks.extend(blocks);
186        instructions
187    }
188
189    /// Starts a for loop
190    pub fn start_for_loop(&mut self, with_loop_var: bool, recursive: bool) {
191        let mut flags = 0;
192        if with_loop_var {
193            flags |= LOOP_FLAG_WITH_LOOP_VAR;
194        }
195        if recursive {
196            flags |= LOOP_FLAG_RECURSIVE;
197        }
198        self.add(Instruction::PushLoop(flags));
199        let instr = self.add(Instruction::Iterate(!0));
200        self.pending_block.push(PendingBlock::Loop {
201            iter_instr: instr,
202            jump_instrs: Vec::new(),
203        });
204    }
205
206    /// Ends the open for loop
207    pub fn end_for_loop(&mut self, push_did_not_iterate: bool) {
208        if let Some(PendingBlock::Loop {
209            iter_instr,
210            jump_instrs,
211        }) = self.pending_block.pop()
212        {
213            self.add(Instruction::Jump(iter_instr));
214            let loop_end = self.next_instruction();
215            if push_did_not_iterate {
216                self.add(Instruction::PushDidNotIterate);
217            };
218            self.add(Instruction::PopLoopFrame);
219            for instr in jump_instrs.into_iter().chain(Some(iter_instr)) {
220                match self.instructions.get_mut(instr) {
221                    Some(&mut Instruction::Iterate(ref mut jump_target))
222                    | Some(&mut Instruction::Jump(ref mut jump_target)) => {
223                        *jump_target = loop_end;
224                    }
225                    _ => unreachable!(),
226                }
227            }
228        } else {
229            unreachable!()
230        }
231    }
232
233    /// Begins an if conditional
234    pub fn start_if(&mut self) {
235        let jump_instr = self.add(Instruction::JumpIfFalse(!0));
236        self.pending_block.push(PendingBlock::Branch { jump_instr });
237    }
238
239    /// Begins an else conditional
240    pub fn start_else(&mut self) {
241        let jump_instr = self.add(Instruction::Jump(!0));
242        self.end_condition(jump_instr + 1);
243        self.pending_block.push(PendingBlock::Branch { jump_instr });
244    }
245
246    /// Closes the current if block.
247    pub fn end_if(&mut self) {
248        self.end_condition(self.next_instruction());
249    }
250
251    /// Starts a short-circuited bool block.
252    pub fn start_sc_bool(&mut self) {
253        self.pending_block.push(PendingBlock::ScBool {
254            jump_instrs: Vec::new(),
255        });
256    }
257
258    /// Emits a short-circuited bool operator.
259    pub fn sc_bool(&mut self, and: bool) {
260        if let Some(&mut PendingBlock::ScBool {
261            ref mut jump_instrs,
262        }) = self.pending_block.last_mut()
263        {
264            jump_instrs.push(self.instructions.add(if and {
265                Instruction::JumpIfFalseOrPop(!0)
266            } else {
267                Instruction::JumpIfTrueOrPop(!0)
268            }));
269        } else {
270            unreachable!();
271        }
272    }
273
274    /// Ends a short-circuited bool block.
275    pub fn end_sc_bool(&mut self) {
276        let end = self.next_instruction();
277        if let Some(PendingBlock::ScBool { jump_instrs }) = self.pending_block.pop() {
278            for instr in jump_instrs {
279                match self.instructions.get_mut(instr) {
280                    Some(&mut Instruction::JumpIfFalseOrPop(ref mut target))
281                    | Some(&mut Instruction::JumpIfTrueOrPop(ref mut target)) => {
282                        *target = end;
283                    }
284                    _ => unreachable!(),
285                }
286            }
287        }
288    }
289
290    fn end_condition(&mut self, new_jump_instr: u32) {
291        match self.pending_block.pop() {
292            Some(PendingBlock::Branch { jump_instr }) => {
293                match self.instructions.get_mut(jump_instr) {
294                    Some(&mut Instruction::JumpIfFalse(ref mut target))
295                    | Some(&mut Instruction::Jump(ref mut target)) => {
296                        *target = new_jump_instr;
297                    }
298                    _ => {}
299                }
300            }
301            _ => unreachable!(),
302        }
303    }
304
305    /// Compiles a statement.
306    pub fn compile_stmt(&mut self, stmt: &ast::Stmt<'source>) {
307        match stmt {
308            ast::Stmt::Template(t) => {
309                self.set_line_from_span(t.span());
310                for node in &t.children {
311                    self.compile_stmt(node);
312                }
313            }
314            ast::Stmt::EmitExpr(expr) => {
315                self.compile_emit_expr(expr);
316            }
317            ast::Stmt::EmitRaw(raw) => {
318                self.set_line_from_span(raw.span());
319                self.add(Instruction::EmitRaw(raw.raw));
320                self.raw_template_bytes += raw.raw.len();
321            }
322            ast::Stmt::ForLoop(for_loop) => {
323                self.compile_for_loop(for_loop);
324            }
325            ast::Stmt::IfCond(if_cond) => {
326                self.compile_if_stmt(if_cond);
327            }
328            ast::Stmt::WithBlock(with_block) => {
329                self.set_line_from_span(with_block.span());
330                self.add(Instruction::PushWith);
331                for (target, expr) in &with_block.assignments {
332                    self.compile_expr(expr);
333                    self.compile_assignment(target);
334                }
335                for node in &with_block.body {
336                    self.compile_stmt(node);
337                }
338                self.add(Instruction::PopFrame);
339            }
340            ast::Stmt::Set(set) => {
341                self.set_line_from_span(set.span());
342                self.compile_expr(&set.expr);
343                self.compile_assignment(&set.target);
344            }
345            ast::Stmt::SetBlock(set_block) => {
346                self.set_line_from_span(set_block.span());
347                self.add(Instruction::BeginCapture(CaptureMode::Capture));
348                for node in &set_block.body {
349                    self.compile_stmt(node);
350                }
351                self.add(Instruction::EndCapture);
352                if let Some(ref filter) = set_block.filter {
353                    self.compile_expr(filter);
354                }
355                self.compile_assignment(&set_block.target);
356            }
357            ast::Stmt::AutoEscape(auto_escape) => {
358                self.set_line_from_span(auto_escape.span());
359                self.compile_expr(&auto_escape.enabled);
360                self.add(Instruction::PushAutoEscape);
361                for node in &auto_escape.body {
362                    self.compile_stmt(node);
363                }
364                self.add(Instruction::PopAutoEscape);
365            }
366            ast::Stmt::FilterBlock(filter_block) => {
367                self.set_line_from_span(filter_block.span());
368                self.add(Instruction::BeginCapture(CaptureMode::Capture));
369                for node in &filter_block.body {
370                    self.compile_stmt(node);
371                }
372                self.add(Instruction::EndCapture);
373                self.compile_expr(&filter_block.filter);
374                self.add(Instruction::Emit);
375            }
376            #[cfg(feature = "multi_template")]
377            ast::Stmt::Block(block) => {
378                self.compile_block(block);
379            }
380            #[cfg(feature = "multi_template")]
381            ast::Stmt::Import(import) => {
382                self.add(Instruction::BeginCapture(CaptureMode::Capture));
383                self.add(Instruction::PushWith);
384                self.compile_expr(&import.expr);
385                self.add_with_span(Instruction::Include(false), import.span());
386                self.add(Instruction::EndCapture);
387                self.add(Instruction::ExportLocals);
388                self.add(Instruction::PopFrame);
389                self.compile_assignment(&import.name);
390            }
391            #[cfg(feature = "multi_template")]
392            ast::Stmt::FromImport(from_import) => {
393                self.add(Instruction::BeginCapture(CaptureMode::Discard));
394                self.add(Instruction::PushWith);
395                self.compile_expr(&from_import.expr);
396                self.add_with_span(Instruction::Include(false), from_import.span());
397                for (name, _) in &from_import.names {
398                    self.compile_expr(name);
399                }
400                self.add(Instruction::PopFrame);
401                for (name, alias) in from_import.names.iter().rev() {
402                    self.compile_assignment(alias.as_ref().unwrap_or(name));
403                }
404                self.add(Instruction::EndCapture);
405            }
406            #[cfg(feature = "multi_template")]
407            ast::Stmt::Extends(extends) => {
408                self.set_line_from_span(extends.span());
409                self.compile_expr(&extends.name);
410                self.add_with_span(Instruction::LoadBlocks, extends.span());
411            }
412            #[cfg(feature = "multi_template")]
413            ast::Stmt::Include(include) => {
414                self.set_line_from_span(include.span());
415                self.compile_expr(&include.name);
416                self.add_with_span(Instruction::Include(include.ignore_missing), include.span());
417            }
418            #[cfg(feature = "macros")]
419            ast::Stmt::Macro(macro_decl) => {
420                self.compile_macro(macro_decl);
421            }
422            #[cfg(feature = "macros")]
423            ast::Stmt::CallBlock(call_block) => {
424                self.compile_call_block(call_block);
425            }
426            #[cfg(feature = "loop_controls")]
427            ast::Stmt::Continue(cont) => {
428                self.set_line_from_span(cont.span());
429                for pending_block in self.pending_block.iter().rev() {
430                    if let PendingBlock::Loop { iter_instr, .. } = pending_block {
431                        self.add(Instruction::Jump(*iter_instr));
432                        break;
433                    }
434                }
435            }
436            #[cfg(feature = "loop_controls")]
437            ast::Stmt::Break(brk) => {
438                self.set_line_from_span(brk.span());
439                let instr = self.add(Instruction::Jump(0));
440                for pending_block in self.pending_block.iter_mut().rev() {
441                    if let &mut PendingBlock::Loop {
442                        ref mut jump_instrs,
443                        ..
444                    } = pending_block
445                    {
446                        jump_instrs.push(instr);
447                        break;
448                    }
449                }
450            }
451            ast::Stmt::Do(do_tag) => {
452                self.compile_do(do_tag);
453            }
454        }
455    }
456
457    #[cfg(feature = "multi_template")]
458    fn compile_block(&mut self, block: &ast::Spanned<ast::Block<'source>>) {
459        self.set_line_from_span(block.span());
460        let mut sub = self.new_subgenerator();
461        for node in &block.body {
462            sub.compile_stmt(node);
463        }
464        let instructions = self.finish_subgenerator(sub);
465        self.blocks.insert(block.name, instructions);
466        self.add(Instruction::CallBlock(block.name));
467    }
468
469    #[cfg(feature = "macros")]
470    fn compile_macro_expression(&mut self, macro_decl: &ast::Spanned<ast::Macro<'source>>) {
471        use crate::compiler::instructions::MACRO_CALLER;
472        self.set_line_from_span(macro_decl.span());
473        let instr = self.add(Instruction::Jump(!0));
474        let mut defaults_iter = macro_decl.defaults.iter().rev();
475        for arg in macro_decl.args.iter().rev() {
476            if let Some(default) = defaults_iter.next() {
477                self.add(Instruction::DupTop);
478                self.add(Instruction::IsUndefined);
479                self.start_if();
480                self.add(Instruction::DiscardTop);
481                self.compile_expr(default);
482                self.end_if();
483            }
484            self.compile_assignment(arg);
485        }
486        for node in &macro_decl.body {
487            self.compile_stmt(node);
488        }
489        self.add(Instruction::Return);
490        let mut undeclared = crate::compiler::meta::find_macro_closure(macro_decl);
491        let caller_reference = undeclared.remove("caller");
492        let macro_instr = self.next_instruction();
493        for name in &undeclared {
494            self.add(Instruction::Enclose(name));
495        }
496        self.add(Instruction::GetClosure);
497        self.add(Instruction::LoadConst(Value::from_object(
498            macro_decl
499                .args
500                .iter()
501                .map(|x| match x {
502                    ast::Expr::Var(var) => Value::from(var.id),
503                    _ => unreachable!(),
504                })
505                .collect::<Vec<Value>>(),
506        )));
507        let mut flags = 0;
508        if caller_reference {
509            flags |= MACRO_CALLER;
510        }
511        self.add(Instruction::BuildMacro(macro_decl.name, instr + 1, flags));
512        if let Some(&mut Instruction::Jump(ref mut target)) = self.instructions.get_mut(instr) {
513            *target = macro_instr;
514        } else {
515            unreachable!();
516        }
517    }
518
519    #[cfg(feature = "macros")]
520    fn compile_macro(&mut self, macro_decl: &ast::Spanned<ast::Macro<'source>>) {
521        self.compile_macro_expression(macro_decl);
522        self.add(Instruction::StoreLocal(macro_decl.name));
523    }
524
525    #[cfg(feature = "macros")]
526    fn compile_call_block(&mut self, call_block: &ast::Spanned<ast::CallBlock<'source>>) {
527        self.compile_call(&call_block.call, Some(&call_block.macro_decl));
528        self.add(Instruction::Emit);
529    }
530
531    fn compile_do(&mut self, do_tag: &ast::Spanned<ast::Do<'source>>) {
532        self.compile_call(&do_tag.call, None);
533    }
534
535    fn compile_if_stmt(&mut self, if_cond: &ast::Spanned<ast::IfCond<'source>>) {
536        self.set_line_from_span(if_cond.span());
537        self.push_span(if_cond.expr.span());
538        self.compile_expr(&if_cond.expr);
539        self.start_if();
540        self.pop_span();
541        for node in &if_cond.true_body {
542            self.compile_stmt(node);
543        }
544        if !if_cond.false_body.is_empty() {
545            self.start_else();
546            for node in &if_cond.false_body {
547                self.compile_stmt(node);
548            }
549        }
550        self.end_if();
551    }
552
553    fn compile_emit_expr(&mut self, expr: &ast::Spanned<ast::EmitExpr<'source>>) {
554        if let ast::Expr::Call(call) = &expr.expr {
555            self.set_line_from_span(expr.expr.span());
556            match call.identify_call() {
557                ast::CallType::Function(name) => {
558                    if name == "super" && call.args.is_empty() {
559                        self.add_with_span(Instruction::FastSuper, call.span());
560                        return;
561                    } else if name == "loop" && call.args.len() == 1 {
562                        self.compile_call_args(std::slice::from_ref(&call.args[0]), 0, None);
563                        self.add_with_span(Instruction::FastRecurse, call.span());
564                        return;
565                    }
566                }
567                #[cfg(feature = "multi_template")]
568                ast::CallType::Block(name) => {
569                    self.add(Instruction::CallBlock(name));
570                    return;
571                }
572                _ => {}
573            }
574        }
575        self.push_span(expr.expr.span());
576        self.compile_expr(&expr.expr);
577        self.add(Instruction::Emit);
578        self.pop_span();
579    }
580
581    fn compile_for_loop(&mut self, for_loop: &ast::Spanned<ast::ForLoop<'source>>) {
582        self.set_line_from_span(for_loop.span());
583
584        // filter expressions work like a nested for loop without
585        // the special loop variable. in one loop, the condition is checked and
586        // passing items accumulated into a list. in the second, that list is
587        // iterated over normally
588        if let Some(ref filter_expr) = for_loop.filter_expr {
589            self.add(Instruction::LoadConst(Value::from(0usize)));
590            self.push_span(filter_expr.span());
591            self.compile_expr(&for_loop.iter);
592            self.start_for_loop(false, false);
593            self.add(Instruction::DupTop);
594            self.compile_assignment(&for_loop.target);
595            self.compile_expr(filter_expr);
596            self.start_if();
597            self.add(Instruction::Swap);
598            self.add(Instruction::LoadConst(Value::from(1usize)));
599            self.add(Instruction::Add);
600            self.start_else();
601            self.add(Instruction::DiscardTop);
602            self.end_if();
603            self.pop_span();
604            self.end_for_loop(false);
605            self.add(Instruction::BuildList(None));
606            self.start_for_loop(true, for_loop.recursive);
607        } else {
608            self.push_span(for_loop.iter.span());
609            self.compile_expr(&for_loop.iter);
610            self.start_for_loop(true, for_loop.recursive);
611            self.pop_span();
612        }
613
614        self.compile_assignment(&for_loop.target);
615        for node in &for_loop.body {
616            self.compile_stmt(node);
617        }
618        self.end_for_loop(!for_loop.else_body.is_empty());
619        if !for_loop.else_body.is_empty() {
620            self.start_if();
621            for node in &for_loop.else_body {
622                self.compile_stmt(node);
623            }
624            self.end_if();
625        };
626    }
627
628    /// Compiles an assignment expression.
629    pub fn compile_assignment(&mut self, expr: &ast::Expr<'source>) {
630        match expr {
631            ast::Expr::Var(var) => {
632                self.add(Instruction::StoreLocal(var.id));
633            }
634            ast::Expr::List(list) => {
635                self.push_span(list.span());
636                self.add(Instruction::UnpackList(list.items.len()));
637                for expr in &list.items {
638                    self.compile_assignment(expr);
639                }
640                self.pop_span();
641            }
642            ast::Expr::GetAttr(attr) => {
643                self.push_span(attr.span());
644                self.compile_expr(&attr.expr);
645                self.add(Instruction::SetAttr(attr.name));
646            }
647            _ => unreachable!(),
648        }
649    }
650
651    /// Compiles an expression.
652    pub fn compile_expr(&mut self, expr: &ast::Expr<'source>) {
653        // try to do constant folding
654        if let Some(v) = expr.as_const() {
655            self.set_line_from_span(expr.span());
656            self.add(Instruction::LoadConst(v.clone()));
657            return;
658        }
659
660        match expr {
661            ast::Expr::Var(v) => {
662                self.set_line_from_span(v.span());
663                self.add(Instruction::Lookup(v.id));
664            }
665            ast::Expr::Const(_) => unreachable!(), // handled by constant folding
666            ast::Expr::Slice(s) => {
667                self.push_span(s.span());
668                self.compile_expr(&s.expr);
669                if let Some(ref start) = s.start {
670                    self.compile_expr(start);
671                } else {
672                    self.add(Instruction::LoadConst(Value::from(())));
673                }
674                if let Some(ref stop) = s.stop {
675                    self.compile_expr(stop);
676                } else {
677                    self.add(Instruction::LoadConst(Value::from(())));
678                }
679                if let Some(ref step) = s.step {
680                    self.compile_expr(step);
681                } else {
682                    self.add(Instruction::LoadConst(Value::from(())));
683                }
684                self.add(Instruction::Slice);
685                self.pop_span();
686            }
687            ast::Expr::UnaryOp(c) => {
688                self.set_line_from_span(c.span());
689                match c.op {
690                    ast::UnaryOpKind::Not => {
691                        self.compile_expr(&c.expr);
692                        self.add(Instruction::Not);
693                    }
694                    ast::UnaryOpKind::Neg => {
695                        // common case: negative numbers.  In that case we
696                        // directly negate them if this is possible without
697                        // an error.
698                        if let ast::Expr::Const(ref c) = c.expr {
699                            if let Ok(negated) = neg(&c.value) {
700                                self.add(Instruction::LoadConst(negated));
701                                return;
702                            }
703                        }
704                        self.compile_expr(&c.expr);
705                        self.add_with_span(Instruction::Neg, c.span());
706                    }
707                }
708            }
709            ast::Expr::BinOp(c) => {
710                self.compile_bin_op(c);
711            }
712            ast::Expr::IfExpr(i) => {
713                self.set_line_from_span(i.span());
714                self.compile_expr(&i.test_expr);
715                self.start_if();
716                self.compile_expr(&i.true_expr);
717                self.start_else();
718                if let Some(ref false_expr) = i.false_expr {
719                    self.compile_expr(false_expr);
720                } else {
721                    // special behavior: missing false block have a silent undefined
722                    // to permit special casing.  This is for compatibility also with
723                    // what Jinja2 does.
724                    self.add(Instruction::LoadConst(
725                        ValueRepr::Undefined(UndefinedType::Silent).into(),
726                    ));
727                }
728                self.end_if();
729            }
730            ast::Expr::Filter(f) => {
731                self.push_span(f.span());
732                if let Some(ref expr) = f.expr {
733                    self.compile_expr(expr);
734                }
735                let arg_count = self.compile_call_args(&f.args, 1, None);
736                let local_id = get_local_id(&mut self.filter_local_ids, f.name);
737                self.add(Instruction::ApplyFilter(f.name, arg_count, local_id));
738                self.pop_span();
739            }
740            ast::Expr::Test(f) => {
741                self.push_span(f.span());
742                self.compile_expr(&f.expr);
743                let arg_count = self.compile_call_args(&f.args, 1, None);
744                let local_id = get_local_id(&mut self.test_local_ids, f.name);
745                self.add(Instruction::PerformTest(f.name, arg_count, local_id));
746                self.pop_span();
747            }
748            ast::Expr::GetAttr(g) => {
749                self.push_span(g.span());
750                self.compile_expr(&g.expr);
751                self.add(Instruction::GetAttr(g.name));
752                self.pop_span();
753            }
754            ast::Expr::GetItem(g) => {
755                self.push_span(g.span());
756                self.compile_expr(&g.expr);
757                self.compile_expr(&g.subscript_expr);
758                self.add(Instruction::GetItem);
759                self.pop_span();
760            }
761            ast::Expr::Call(c) => {
762                self.compile_call(c, None);
763            }
764            ast::Expr::List(l) => {
765                self.set_line_from_span(l.span());
766                for item in &l.items {
767                    self.compile_expr(item);
768                }
769                self.add(Instruction::BuildList(Some(l.items.len())));
770            }
771            ast::Expr::Map(m) => {
772                self.set_line_from_span(m.span());
773                assert_eq!(m.keys.len(), m.values.len());
774                for (key, value) in m.keys.iter().zip(m.values.iter()) {
775                    self.compile_expr(key);
776                    self.compile_expr(value);
777                }
778                self.add(Instruction::BuildMap(m.keys.len()));
779            }
780        }
781    }
782
783    fn compile_call(
784        &mut self,
785        c: &ast::Spanned<ast::Call<'source>>,
786        caller: Option<&Caller<'source>>,
787    ) {
788        self.push_span(c.span());
789        match c.identify_call() {
790            ast::CallType::Function(name) => {
791                let arg_count = self.compile_call_args(&c.args, 0, caller);
792                self.add(Instruction::CallFunction(name, arg_count));
793            }
794            #[cfg(feature = "multi_template")]
795            ast::CallType::Block(name) => {
796                self.add(Instruction::BeginCapture(CaptureMode::Capture));
797                self.add(Instruction::CallBlock(name));
798                self.add(Instruction::EndCapture);
799            }
800            ast::CallType::Method(expr, name) => {
801                self.compile_expr(expr);
802                let arg_count = self.compile_call_args(&c.args, 1, caller);
803                self.add(Instruction::CallMethod(name, arg_count));
804            }
805            ast::CallType::Object(expr) => {
806                self.compile_expr(expr);
807                let arg_count = self.compile_call_args(&c.args, 1, caller);
808                self.add(Instruction::CallObject(arg_count));
809            }
810        };
811        self.pop_span();
812    }
813
814    fn compile_call_args(
815        &mut self,
816        args: &[ast::CallArg<'source>],
817        extra_args: usize,
818        caller: Option<&Caller<'source>>,
819    ) -> Option<u16> {
820        let mut pending_args = extra_args;
821        let mut num_args_batches = 0;
822        let mut has_kwargs = caller.is_some();
823        let mut static_kwargs = caller.is_none();
824
825        for arg in args {
826            match arg {
827                ast::CallArg::Pos(expr) => {
828                    self.compile_expr(expr);
829                    pending_args += 1;
830                }
831                ast::CallArg::PosSplat(expr) => {
832                    if pending_args > 0 {
833                        self.add(Instruction::BuildList(Some(pending_args)));
834                        pending_args = 0;
835                        num_args_batches += 1;
836                    }
837                    self.compile_expr(expr);
838                    num_args_batches += 1;
839                }
840                ast::CallArg::Kwarg(_, expr) => {
841                    if !matches!(expr, ast::Expr::Const(_)) {
842                        static_kwargs = false;
843                    }
844                    has_kwargs = true;
845                }
846                ast::CallArg::KwargSplat(_) => {
847                    static_kwargs = false;
848                    has_kwargs = true;
849                }
850            }
851        }
852
853        if has_kwargs {
854            let mut pending_kwargs = 0;
855            let mut num_kwargs_batches = 0;
856            let mut collected_kwargs = ValueMap::new();
857            for arg in args {
858                match arg {
859                    ast::CallArg::Kwarg(key, value) => {
860                        if static_kwargs {
861                            if let ast::Expr::Const(c) = value {
862                                collected_kwargs.insert(Value::from(*key), c.value.clone());
863                            } else {
864                                unreachable!();
865                            }
866                        } else {
867                            self.add(Instruction::LoadConst(Value::from(*key)));
868                            self.compile_expr(value);
869                            pending_kwargs += 1;
870                        }
871                    }
872                    ast::CallArg::KwargSplat(expr) => {
873                        if pending_kwargs > 0 {
874                            self.add(Instruction::BuildKwargs(pending_kwargs));
875                            num_kwargs_batches += 1;
876                            pending_kwargs = 0;
877                        }
878                        self.compile_expr(expr);
879                        num_kwargs_batches += 1;
880                    }
881                    ast::CallArg::Pos(_) | ast::CallArg::PosSplat(_) => {}
882                }
883            }
884
885            if !collected_kwargs.is_empty() {
886                self.add(Instruction::LoadConst(Kwargs::wrap(collected_kwargs)));
887            } else {
888                // The conditions above guarantee that if we collect static kwargs
889                // we cannot enter this block (single kwargs batch, no caller).
890
891                #[cfg(feature = "macros")]
892                {
893                    if let Some(caller) = caller {
894                        self.add(Instruction::LoadConst(Value::from("caller")));
895                        self.compile_macro_expression(caller);
896                        pending_kwargs += 1
897                    }
898                }
899                if num_kwargs_batches > 0 {
900                    if pending_kwargs > 0 {
901                        self.add(Instruction::BuildKwargs(pending_kwargs));
902                        num_kwargs_batches += 1;
903                    }
904                    self.add(Instruction::MergeKwargs(num_kwargs_batches));
905                } else {
906                    self.add(Instruction::BuildKwargs(pending_kwargs));
907                }
908            }
909            pending_args += 1;
910        }
911
912        if num_args_batches > 0 {
913            if pending_args > 0 {
914                self.add(Instruction::BuildList(Some(pending_args)));
915                num_args_batches += 1;
916            }
917            self.add(Instruction::UnpackLists(num_args_batches));
918            None
919        } else {
920            assert!(pending_args as u16 as usize == pending_args);
921            Some(pending_args as u16)
922        }
923    }
924
925    fn compile_bin_op(&mut self, c: &ast::Spanned<ast::BinOp<'source>>) {
926        self.push_span(c.span());
927        let instr = match c.op {
928            ast::BinOpKind::Eq => Instruction::Eq,
929            ast::BinOpKind::Ne => Instruction::Ne,
930            ast::BinOpKind::Lt => Instruction::Lt,
931            ast::BinOpKind::Lte => Instruction::Lte,
932            ast::BinOpKind::Gt => Instruction::Gt,
933            ast::BinOpKind::Gte => Instruction::Gte,
934            ast::BinOpKind::ScAnd | ast::BinOpKind::ScOr => {
935                self.start_sc_bool();
936                self.compile_expr(&c.left);
937                self.sc_bool(matches!(c.op, ast::BinOpKind::ScAnd));
938                self.compile_expr(&c.right);
939                self.end_sc_bool();
940                self.pop_span();
941                return;
942            }
943            ast::BinOpKind::Add => Instruction::Add,
944            ast::BinOpKind::Sub => Instruction::Sub,
945            ast::BinOpKind::Mul => Instruction::Mul,
946            ast::BinOpKind::Div => Instruction::Div,
947            ast::BinOpKind::FloorDiv => Instruction::IntDiv,
948            ast::BinOpKind::Rem => Instruction::Rem,
949            ast::BinOpKind::Pow => Instruction::Pow,
950            ast::BinOpKind::Concat => Instruction::StringConcat,
951            ast::BinOpKind::In => Instruction::In,
952        };
953        self.compile_expr(&c.left);
954        self.compile_expr(&c.right);
955        self.add(instr);
956        self.pop_span();
957    }
958
959    /// Returns the size hint for buffers.
960    ///
961    /// This is a proposal for the initial buffer size when rendering directly to a string.
962    pub fn buffer_size_hint(&self) -> usize {
963        // for now the assumption is made that twice the bytes of template code without
964        // control structures, rounded up to the next power of two is a good default.  The
965        // round to the next power of two is chosen because the underlying vector backing
966        // strings prefers powers of two.
967        (self.raw_template_bytes * 2).next_power_of_two()
968    }
969
970    /// Converts the compiler into the instructions.
971    pub fn finish(
972        mut self,
973    ) -> (
974        Instructions<'source>,
975        BTreeMap<&'source str, Instructions<'source>>,
976    ) {
977        assert!(self.pending_block.is_empty());
978        recycle_pending_block_buffer(mem::take(&mut self.pending_block));
979        recycle_span_stack_buffer(mem::take(&mut self.span_stack));
980        (self.instructions, self.blocks)
981    }
982}