///|
/// For the first `MAX_LOCALS` filters/tests, an ID is returned for faster
/// lookups from the stack.
fn get_local_id(ids : Map[String, Int], name : String) -> Int {
match ids.get(name) {
Some(id) => id
None =>
if ids.length() >= MAX_LOCALS {
-1
} else {
let next_id = ids.length()
ids[name] = next_id
next_id
}
}
}
///|
fn compare_op(op : CompareOpKind) -> CompareOp {
match op {
Eq => Eq
Ne => Ne
Lt => Lt
Lte => Lte
Gt => Gt
Gte => Gte
In => In
NotIn => NotIn
}
}
///|
/// Represents an open block of code that does not yet have updated jump
/// targets.
priv enum PendingBlock {
Branch(Int)
Loop(Int, Array[Int])
ScBool(Array[Int])
}
///|
/// Provides a convenient interface to creating instructions for the VM.
priv struct CodeGenerator {
instructions : Instructions
blocks : Map[String, Instructions]
pending_block : Array[PendingBlock]
mut current_line : Int
span_stack : Array[Span]
filter_local_ids : Map[String, Int]
test_local_ids : Map[String, Int]
mut raw_template_bytes : Int
}
///|
fn CodeGenerator::new(file : String, source : String) -> CodeGenerator {
{
instructions: Instructions::new(file, source),
blocks: Map([]),
pending_block: [],
current_line: 0,
span_stack: [],
filter_local_ids: Map([]),
test_local_ids: Map([]),
raw_template_bytes: 0,
}
}
///|
fn CodeGenerator::set_line(self : CodeGenerator, lineno : Int) -> Unit {
self.current_line = lineno
}
///|
fn CodeGenerator::set_line_from_span(self : CodeGenerator, span : Span) -> Unit {
self.set_line(span.start_line)
}
///|
fn CodeGenerator::push_span(self : CodeGenerator, span : Span) -> Unit {
self.span_stack.push(span)
self.set_line_from_span(span)
}
///|
fn CodeGenerator::pop_span(self : CodeGenerator) -> Unit {
self.span_stack.pop() |> ignore
}
///|
/// Add a simple instruction with the current location.
fn CodeGenerator::add(self : CodeGenerator, instr : Instruction) -> Int {
if self.span_stack.last() is Some(span) &&
span.start_line == self.current_line {
return self.instructions.add_with_span(instr, span)
}
self.instructions.add_with_line(instr, self.current_line)
}
///|
/// Add a simple instruction with other location.
fn CodeGenerator::add_with_span(
self : CodeGenerator,
instr : Instruction,
span : Span,
) -> Int {
self.instructions.add_with_span(instr, span)
}
///|
fn CodeGenerator::next_instruction(self : CodeGenerator) -> Int {
self.instructions.len()
}
///|
fn CodeGenerator::patch(self : CodeGenerator, idx : Int, target : Int) -> Unit {
let instr = self.instructions.instructions[idx]
self.instructions.instructions[idx] = match instr {
Iterate(_) => Iterate(target)
Jump(_) => Jump(target)
JumpIfFalse(_) => JumpIfFalse(target)
JumpIfFalseOrPop(_) => JumpIfFalseOrPop(target)
JumpIfTrueOrPop(_) => JumpIfTrueOrPop(target)
other => other
}
}
///|
/// Creates a sub generator.
fn CodeGenerator::new_subgenerator(self : CodeGenerator) -> CodeGenerator {
let sub = CodeGenerator::new(self.instructions.name, self.instructions.source)
sub.current_line = self.current_line
if self.span_stack.last() is Some(span) {
sub.span_stack.push(span)
}
sub
}
///|
/// Finishes a sub generator and syncs it back.
fn CodeGenerator::finish_subgenerator(
self : CodeGenerator,
sub : CodeGenerator,
) -> Instructions {
self.current_line = sub.current_line
let (instructions, blocks) = sub.finish()
for name, instr in blocks {
self.blocks[name] = instr
}
instructions
}
///|
/// Starts a for loop
fn CodeGenerator::start_for_loop(
self : CodeGenerator,
with_loop_var : Bool,
recursive : Bool,
) -> Unit {
let mut flags = 0
if with_loop_var {
flags = flags | LOOP_FLAG_WITH_LOOP_VAR
}
if recursive {
flags = flags | LOOP_FLAG_RECURSIVE
}
self.add(PushLoop(flags)) |> ignore
let instr = self.add(Iterate(-1))
self.pending_block.push(Loop(instr, []))
}
///|
/// Ends the open for loop
fn CodeGenerator::end_for_loop(
self : CodeGenerator,
push_did_not_iterate : Bool,
) -> Unit {
guard self.pending_block.pop() is Some(Loop(iter_instr, jump_instrs)) else {
abort("unreachable")
}
self.add(Jump(iter_instr)) |> ignore
let loop_end = self.next_instruction()
if push_did_not_iterate {
self.add(PushDidNotIterate) |> ignore
}
self.add(PopLoopFrame) |> ignore
for instr in jump_instrs {
self.patch(instr, loop_end)
}
self.patch(iter_instr, loop_end)
}
///|
/// Begins an if conditional
fn CodeGenerator::start_if(self : CodeGenerator) -> Unit {
let jump_instr = self.add(JumpIfFalse(-1))
self.pending_block.push(Branch(jump_instr))
}
///|
/// Begins an else conditional
fn CodeGenerator::start_else(self : CodeGenerator) -> Unit {
let jump_instr = self.add(Jump(-1))
self.end_condition(jump_instr + 1)
self.pending_block.push(Branch(jump_instr))
}
///|
/// Closes the current if block.
fn CodeGenerator::end_if(self : CodeGenerator) -> Unit {
self.end_condition(self.next_instruction())
}
///|
/// Starts a short-circuited bool block.
fn CodeGenerator::start_sc_bool(self : CodeGenerator) -> Unit {
self.pending_block.push(ScBool([]))
}
///|
/// Emits a short-circuited bool operator.
fn CodeGenerator::sc_bool(self : CodeGenerator, is_and : Bool) -> Unit {
guard self.pending_block.last() is Some(ScBool(jump_instrs)) else {
abort("unreachable")
}
jump_instrs.push(
self.instructions.add(
if is_and {
JumpIfFalseOrPop(-1)
} else {
JumpIfTrueOrPop(-1)
},
),
)
}
///|
/// Ends a short-circuited bool block.
fn CodeGenerator::end_sc_bool(self : CodeGenerator) -> Unit {
let end = self.next_instruction()
if self.pending_block.pop() is Some(ScBool(jump_instrs)) {
for instr in jump_instrs {
self.patch(instr, end)
}
}
}
///|
fn CodeGenerator::end_condition(
self : CodeGenerator,
new_jump_instr : Int,
) -> Unit {
match self.pending_block.pop() {
Some(Branch(jump_instr)) => self.patch(jump_instr, new_jump_instr)
_ => abort("unreachable")
}
}
///|
/// Compiles a statement.
fn CodeGenerator::compile_stmt(self : CodeGenerator, stmt : Stmt) -> Unit {
match stmt {
Template(t) => {
self.set_line_from_span(t.span)
for node in t.children {
self.compile_stmt(node)
}
}
EmitExpr(expr) => self.compile_emit_expr(expr)
EmitRaw(raw) => {
self.set_line_from_span(raw.span)
self.add(EmitRaw(raw.raw)) |> ignore
self.raw_template_bytes += raw.raw.length()
}
ForLoop(for_loop) => self.compile_for_loop(for_loop)
IfCond(if_cond) => self.compile_if_stmt(if_cond)
WithBlock(with_block) => {
self.set_line_from_span(with_block.span)
self.add(PushWith) |> ignore
for pair in with_block.assignments {
self.compile_expr(pair.1)
self.compile_assignment(pair.0)
}
for node in with_block.body {
self.compile_stmt(node)
}
self.add(PopFrame) |> ignore
}
Set(set) => {
self.set_line_from_span(set.span)
self.compile_expr(set.expr)
self.compile_assignment(set.target)
}
SetBlock(set_block) => {
self.set_line_from_span(set_block.span)
self.add(BeginCapture(Capture)) |> ignore
for node in set_block.body {
self.compile_stmt(node)
}
self.add(EndCapture) |> ignore
if set_block.filter is Some(filter) {
self.compile_expr(filter)
}
self.compile_assignment(set_block.target)
}
AutoEscape(auto_escape) => {
self.set_line_from_span(auto_escape.span)
self.compile_expr(auto_escape.enabled)
self.add(PushAutoEscape) |> ignore
for node in auto_escape.body {
self.compile_stmt(node)
}
self.add(PopAutoEscape) |> ignore
}
FilterBlock(filter_block) => {
self.set_line_from_span(filter_block.span)
self.add(BeginCapture(Capture)) |> ignore
for node in filter_block.body {
self.compile_stmt(node)
}
self.add(EndCapture) |> ignore
self.compile_expr(filter_block.filter)
self.add(Emit) |> ignore
}
Block(block) => self.compile_block(block)
Import(import_) => {
self.add(BeginCapture(Capture)) |> ignore
self.add(PushWith) |> ignore
self.compile_expr(import_.expr)
self.add_with_span(Include(false), import_.span) |> ignore
self.add(EndCapture) |> ignore
self.add(ExportLocals) |> ignore
self.add(PopFrame) |> ignore
self.compile_assignment(import_.name)
}
FromImport(from_import) => {
self.add(BeginCapture(Discard)) |> ignore
self.add(PushWith) |> ignore
self.compile_expr(from_import.expr)
self.add_with_span(Include(false), from_import.span) |> ignore
for pair in from_import.names {
self.compile_expr(pair.0)
}
self.add(PopFrame) |> ignore
for i = from_import.names.length() - 1; i >= 0; i = i - 1 {
let (name, alias_name) = from_import.names[i]
self.compile_assignment(alias_name.unwrap_or(name))
}
self.add(EndCapture) |> ignore
}
Extends(extends) => {
self.set_line_from_span(extends.span)
self.compile_expr(extends.name)
self.add_with_span(LoadBlocks, extends.span) |> ignore
}
Include(incl) => {
self.set_line_from_span(incl.span)
self.compile_expr(incl.name)
self.add_with_span(Include(incl.ignore_missing), incl.span) |> ignore
}
Macro(macro_decl) => self.compile_macro(macro_decl)
CallBlock(call_block) => self.compile_call_block(call_block)
Continue(span) => {
self.set_line_from_span(span)
for i = self.pending_block.length() - 1; i >= 0; i = i - 1 {
if self.pending_block[i] is Loop(iter_instr, _) {
self.add(Jump(iter_instr)) |> ignore
break
}
}
}
Break(span) => {
self.set_line_from_span(span)
let instr = self.add(Jump(0))
for i = self.pending_block.length() - 1; i >= 0; i = i - 1 {
if self.pending_block[i] is Loop(_, jump_instrs) {
jump_instrs.push(instr)
break
}
}
}
Do(do_tag) => self.compile_call(do_tag.call, None)
}
}
///|
fn CodeGenerator::compile_block(
self : CodeGenerator,
block : BlockNode,
) -> Unit {
self.set_line_from_span(block.span)
let sub = self.new_subgenerator()
for node in block.body {
sub.compile_stmt(node)
}
sub.instructions.required_block = block.required
let instructions = self.finish_subgenerator(sub)
self.blocks[block.name] = instructions
self.add(CallBlock(block.name)) |> ignore
}
///|
fn CodeGenerator::compile_macro_expression(
self : CodeGenerator,
macro_decl : MacroNode,
) -> Unit {
self.set_line_from_span(macro_decl.span)
let instr = self.add(Jump(-1))
let defaults = macro_decl.defaults
let mut default_idx = defaults.length() - 1
for i = macro_decl.args.length() - 1; i >= 0; i = i - 1 {
let arg = macro_decl.args[i]
if default_idx >= 0 {
let default = defaults[default_idx]
default_idx -= 1
self.add(DupTop) |> ignore
self.add(IsUndefined) |> ignore
self.start_if()
self.add(DiscardTop) |> ignore
self.compile_expr(default)
self.end_if()
}
self.compile_assignment(arg)
}
for node in macro_decl.body {
self.compile_stmt(node)
}
self.add(Return) |> ignore
let undeclared = find_macro_closure(macro_decl)
let caller_reference = undeclared.contains("caller")
undeclared.remove("caller")
let macro_instr = self.next_instruction()
for name in undeclared {
self.add(Enclose(name)) |> ignore
}
self.add(GetClosure) |> ignore
self.add(
LoadConst(
Value::from_array(
macro_decl.args.map(x => {
match x {
Var(v) => Value::from_string(v.id)
_ => abort("unreachable")
}
}),
),
),
)
|> ignore
let mut flags = 0
if caller_reference {
flags = flags | MACRO_CALLER
}
self.add(BuildMacro(macro_decl.name, instr + 1, flags)) |> ignore
self.patch(instr, macro_instr)
}
///|
fn CodeGenerator::compile_macro(
self : CodeGenerator,
macro_decl : MacroNode,
) -> Unit {
self.compile_macro_expression(macro_decl)
self.add(StoreLocal(macro_decl.name)) |> ignore
}
///|
fn CodeGenerator::compile_call_block(
self : CodeGenerator,
call_block : CallBlockNode,
) -> Unit {
self.compile_call(call_block.call, Some(call_block.macro_decl))
self.add(Emit) |> ignore
}
///|
fn CodeGenerator::compile_if_stmt(
self : CodeGenerator,
if_cond : IfCondNode,
) -> Unit {
self.set_line_from_span(if_cond.span)
self.push_span(if_cond.expr.span())
self.compile_expr(if_cond.expr)
self.start_if()
self.pop_span()
for node in if_cond.true_body {
self.compile_stmt(node)
}
if !if_cond.false_body.is_empty() {
self.start_else()
for node in if_cond.false_body {
self.compile_stmt(node)
}
}
self.end_if()
}
///|
fn CodeGenerator::compile_emit_expr(
self : CodeGenerator,
expr : EmitExprNode,
) -> Unit {
if expr.expr is Call(call) {
self.set_line_from_span(expr.expr.span())
match call.identify_call() {
Function("super") if call.args.is_empty() => {
self.add_with_span(FastSuper, call.span) |> ignore
return
}
Function("loop") if call.args.length() == 1 => {
self.compile_call_args([call.args[0]], 0, None) |> ignore
self.add_with_span(FastRecurse, call.span) |> ignore
return
}
Block(name) => {
self.add(CallBlock(name)) |> ignore
return
}
_ => ()
}
}
self.push_span(expr.expr.span())
self.compile_expr(expr.expr)
self.add(Emit) |> ignore
self.pop_span()
}
///|
fn CodeGenerator::compile_for_loop(
self : CodeGenerator,
for_loop : ForLoopNode,
) -> Unit {
self.set_line_from_span(for_loop.span)
// filter expressions work like a nested for loop without the special loop
// variable. in one loop, the condition is checked and passing items
// accumulated into a list. in the second, that list is iterated over
// normally
match for_loop.filter_expr {
Some(filter_expr) => {
self.add(LoadConst(Value::from_int(0))) |> ignore
self.push_span(filter_expr.span())
self.compile_expr(for_loop.iter)
self.start_for_loop(false, false)
self.add(DupTop) |> ignore
self.compile_assignment(for_loop.target)
self.compile_expr(filter_expr)
self.start_if()
self.add(Swap) |> ignore
self.add(LoadConst(Value::from_int(1))) |> ignore
self.add(Add) |> ignore
self.start_else()
self.add(DiscardTop) |> ignore
self.end_if()
self.pop_span()
self.end_for_loop(false)
self.add(BuildList(None)) |> ignore
self.start_for_loop(true, for_loop.recursive)
}
None => {
self.push_span(for_loop.iter.span())
self.compile_expr(for_loop.iter)
self.start_for_loop(true, for_loop.recursive)
self.pop_span()
}
}
self.compile_assignment(for_loop.target)
for node in for_loop.body {
self.compile_stmt(node)
}
self.end_for_loop(!for_loop.else_body.is_empty())
if !for_loop.else_body.is_empty() {
self.start_if()
for node in for_loop.else_body {
self.compile_stmt(node)
}
self.end_if()
}
}
///|
/// Compiles an assignment expression.
fn CodeGenerator::compile_assignment(self : CodeGenerator, expr : Expr) -> Unit {
match expr {
Var(v) => self.add(StoreLocal(v.id)) |> ignore
List(list) => {
self.push_span(list.span)
self.add(UnpackList(list.items.length())) |> ignore
for item in list.items {
self.compile_assignment(item)
}
self.pop_span()
}
GetAttr(attr) => {
self.push_span(attr.span)
self.compile_expr(attr.expr)
self.add(SetAttr(attr.name)) |> ignore
}
_ => abort("unreachable")
}
}
///|
/// Compiles an expression.
fn CodeGenerator::compile_expr(self : CodeGenerator, expr : Expr) -> Unit {
// try to do constant folding
if expr.as_const() is Some(v) {
self.set_line_from_span(expr.span())
self.add(LoadConst(v)) |> ignore
return
}
match expr {
Var(v) => {
self.set_line_from_span(v.span)
self.add(Lookup(v.id)) |> ignore
}
Const(_) => abort("unreachable") // handled by constant folding
Slice(s) => {
self.push_span(s.span)
self.compile_expr(s.expr)
for part in [s.start, s.stop, s.step] {
match part {
Some(e) => self.compile_expr(e)
None => self.add(LoadConst(Value::none())) |> ignore
}
}
self.add(Slice) |> ignore
self.pop_span()
}
UnaryOp(c) => {
self.set_line_from_span(c.span)
match c.op {
Not => {
self.compile_expr(c.expr)
self.add(Not) |> ignore
}
Neg => {
// common case: negative numbers. In that case we directly negate
// them if this is possible without an error.
if c.expr is Const(cnst) {
let negated = value_neg(cnst.value) catch {
_ => Value::undefined()
}
if !negated.is_undefined() {
self.add(LoadConst(negated)) |> ignore
return
}
}
self.compile_expr(c.expr)
self.add_with_span(Neg, c.span) |> ignore
}
}
}
BinOp(c) => self.compile_bin_op(c)
Compare(c) => self.compile_compare(c)
IfExpr(i) => {
self.set_line_from_span(i.span)
self.compile_expr(i.test_expr)
self.start_if()
self.compile_expr(i.true_expr)
self.start_else()
match i.false_expr {
Some(false_expr) => self.compile_expr(false_expr)
// special behavior: missing false block have a silent undefined to
// permit special casing. This is for compatibility also with what
// Jinja2 does.
None => self.add(LoadConst(Value::silent_undefined())) |> ignore
}
self.end_if()
}
Filter(f) => {
self.push_span(f.span)
if f.expr is Some(e) {
self.compile_expr(e)
}
let arg_count = self.compile_call_args(f.args, 1, None)
let local_id = get_local_id(self.filter_local_ids, f.name)
self.add(ApplyFilter(f.name, arg_count, local_id)) |> ignore
self.pop_span()
}
Test(f) => {
self.push_span(f.span)
self.compile_expr(f.expr)
let arg_count = self.compile_call_args(f.args, 1, None)
let local_id = get_local_id(self.test_local_ids, f.name)
self.add(PerformTest(f.name, arg_count, local_id)) |> ignore
self.pop_span()
}
GetAttr(g) => {
self.push_span(g.span)
self.compile_expr(g.expr)
self.add(GetAttr(g.name)) |> ignore
self.pop_span()
}
GetItem(g) => {
self.push_span(g.span)
self.compile_expr(g.expr)
self.compile_expr(g.subscript_expr)
self.add(GetItem) |> ignore
self.pop_span()
}
Call(c) => self.compile_call(c, None)
List(l) => {
self.set_line_from_span(l.span)
for item in l.items {
self.compile_expr(item)
}
self.add(BuildList(Some(l.items.length()))) |> ignore
}
Tuple(t) => {
self.set_line_from_span(t.span)
for item in t.items {
self.compile_expr(item)
}
self.add(BuildTuple(Some(t.items.length()))) |> ignore
}
Map(m) => {
self.set_line_from_span(m.span)
for i, key in m.keys {
self.compile_expr(key)
self.compile_expr(m.values[i])
}
self.add(BuildMap(m.keys.length())) |> ignore
}
}
}
///|
fn CodeGenerator::compile_call(
self : CodeGenerator,
c : CallNode,
caller : MacroNode?,
) -> Unit {
self.push_span(c.span)
match c.identify_call() {
Function(name) => {
let arg_count = self.compile_call_args(c.args, 0, caller)
self.add(CallFunction(name, arg_count)) |> ignore
}
Block(name) => {
self.add(BeginCapture(Capture)) |> ignore
self.add(CallBlock(name)) |> ignore
self.add(EndCapture) |> ignore
}
Method(expr, name) => {
self.compile_expr(expr)
let arg_count = self.compile_call_args(c.args, 1, caller)
self.add(CallMethod(name, arg_count)) |> ignore
}
Object(expr) => {
self.compile_expr(expr)
let arg_count = self.compile_call_args(c.args, 1, caller)
self.add(CallObject(arg_count)) |> ignore
}
}
self.pop_span()
}
///|
fn CodeGenerator::compile_call_args(
self : CodeGenerator,
args : Array[CallArg],
extra_args : Int,
caller : MacroNode?,
) -> Int? {
let mut pending_args = extra_args
let mut num_args_batches = 0
let mut has_kwargs = caller is Some(_)
let mut static_kwargs = caller is None
for arg in args {
match arg {
Pos(expr) => {
self.compile_expr(expr)
pending_args += 1
}
PosSplat(expr) => {
if pending_args > 0 {
self.add(BuildList(Some(pending_args))) |> ignore
pending_args = 0
num_args_batches += 1
}
self.compile_expr(expr)
num_args_batches += 1
}
Kwarg(_, expr) => {
if !(expr is Const(_)) {
static_kwargs = false
}
has_kwargs = true
}
KwargSplat(_) => {
static_kwargs = false
has_kwargs = true
}
}
}
if has_kwargs {
let mut pending_kwargs = 0
let mut num_kwargs_batches = 0
let collected_kwargs : Map[Value, Value] = Map([])
for arg in args {
match arg {
Kwarg(key, value) =>
if static_kwargs {
match value {
Const(c) => collected_kwargs[Value::from_string(key)] = c.value
_ => abort("unreachable")
}
} else {
self.add(LoadConst(Value::from_string(key))) |> ignore
self.compile_expr(value)
pending_kwargs += 1
}
KwargSplat(expr) => {
if pending_kwargs > 0 {
self.add(BuildKwargs(pending_kwargs)) |> ignore
num_kwargs_batches += 1
pending_kwargs = 0
}
self.compile_expr(expr)
num_kwargs_batches += 1
}
Pos(_) | PosSplat(_) => ()
}
}
if !collected_kwargs.is_empty() {
self.add(LoadConst(Value::from_kwargs_map(collected_kwargs))) |> ignore
} else {
// The conditions above guarantee that if we collect static kwargs we
// cannot enter this block (single kwargs batch, no caller).
if caller is Some(caller) {
self.add(LoadConst(Value::from_string("caller"))) |> ignore
self.compile_macro_expression(caller)
pending_kwargs += 1
}
if num_kwargs_batches > 0 {
if pending_kwargs > 0 {
self.add(BuildKwargs(pending_kwargs)) |> ignore
num_kwargs_batches += 1
}
self.add(MergeKwargs(num_kwargs_batches)) |> ignore
} else {
self.add(BuildKwargs(pending_kwargs)) |> ignore
}
}
pending_args += 1
}
if num_args_batches > 0 {
if pending_args > 0 {
self.add(BuildList(Some(pending_args))) |> ignore
num_args_batches += 1
}
self.add(UnpackLists(num_args_batches)) |> ignore
None
} else {
Some(pending_args)
}
}
///|
fn CodeGenerator::compile_compare(
self : CodeGenerator,
c : CompareNode,
) -> Unit {
self.push_span(c.span)
self.compile_expr(c.expr)
let cleanup_jumps = []
for idx, op in c.ops {
self.compile_expr(op.expr)
if idx + 1 == c.ops.length() {
self.emit_compare(op.op)
} else {
self.add(CompareAndPreserve(compare_op(op.op))) |> ignore
cleanup_jumps.push(self.add(JumpIfFalseOrPop(-1)))
}
}
if !cleanup_jumps.is_empty() {
let jump_end = self.add(Jump(-1))
let cleanup_start = self.next_instruction()
self.add(Swap) |> ignore
self.add(DiscardTop) |> ignore
let end = self.next_instruction()
for instr in cleanup_jumps {
self.patch(instr, cleanup_start)
}
self.patch(jump_end, end)
}
self.pop_span()
}
///|
fn CodeGenerator::emit_compare(
self : CodeGenerator,
op : CompareOpKind,
) -> Unit {
self.add(
match op {
Eq => Eq
Ne => Ne
Lt => Lt
Lte => Lte
Gt => Gt
Gte => Gte
In | NotIn => In
},
)
|> ignore
if op is NotIn {
self.add(Not) |> ignore
}
}
///|
fn CodeGenerator::compile_bin_op(self : CodeGenerator, c : BinOpNode) -> Unit {
self.push_span(c.span)
let instr : Instruction = match c.op {
Eq => Eq
Ne => Ne
Lt => Lt
Lte => Lte
Gt => Gt
Gte => Gte
ScAnd | ScOr => {
self.start_sc_bool()
self.compile_expr(c.left)
self.sc_bool(c.op is ScAnd)
self.compile_expr(c.right)
self.end_sc_bool()
self.pop_span()
return
}
Add => Add
Sub => Sub
Mul => Mul
Div => Div
FloorDiv => IntDiv
Rem => Rem
Pow => Pow
Concat => StringConcat
In => In
}
self.compile_expr(c.left)
self.compile_expr(c.right)
self.add(instr) |> ignore
self.pop_span()
}
///|
/// Returns the size hint for buffers.
fn CodeGenerator::buffer_size_hint(self : CodeGenerator) -> Int {
self.raw_template_bytes * 2
}
///|
/// Converts the compiler into the instructions.
fn CodeGenerator::finish(
self : CodeGenerator,
) -> (Instructions, Map[String, Instructions]) {
(self.instructions, self.blocks)
}