cas_compiler/expr/
loops.rs

1use cas_compute::{numerical::value::Value, primitive::int};
2use cas_error::Error;
3use cas_parser::parser::{
4    ast::{for_expr::For, loop_expr::Loop, while_expr::While, RangeKind},
5    token::op::BinOpKind,
6};
7use crate::{item::Symbol, Compile, Compiler, InstructionKind};
8
9impl Compile for Loop {
10    fn compile(&self, compiler: &mut Compiler) -> Result<(), Error> {
11        let loop_start = compiler.new_end_label();
12        let loop_end = compiler.new_unassociated_label();
13
14        compiler.with_state(|state| {
15            // in case `continue` and `break` expressions are inside, we need the loop start and
16            // end labels for their jumps
17            state.loop_start = Some(loop_start);
18            state.loop_end = Some(loop_end);
19        }, |compiler| {
20            compiler.new_scope(|compiler| self.body.compile(compiler))?;
21            compiler.add_instr(InstructionKind::Drop);
22            Ok(())
23        })?;
24
25        compiler.add_instr(InstructionKind::Jump(loop_start));
26
27        // NOTE: we don't have to do the same "hack" as in `while` loops, since `loop`s cannot
28        // terminate without a `break` expression
29
30        compiler.set_end_label(loop_end);
31        Ok(())
32    }
33}
34
35impl Compile for While {
36    fn compile(&self, compiler: &mut Compiler) -> Result<(), Error> {
37        let condition_start = compiler.new_end_label();
38        self.condition.compile(compiler)?;
39
40        let end_with_no_break = compiler.new_unassociated_label();
41        let loop_end = compiler.new_unassociated_label();
42        compiler.add_instr_with_spans(
43            InstructionKind::JumpIfFalse(end_with_no_break),
44            // for error if condition doesn't evaluate to boolean
45            vec![self.condition.span(), self.body.span()],
46        );
47        compiler.with_state(|state| {
48            // in case `continue` and `break` expressions are inside, we need the loop start and
49            // end labels for their jumps
50            state.loop_start = Some(condition_start);
51            state.loop_end = Some(loop_end);
52        }, |compiler| {
53            compiler.new_scope(|compiler| self.body.compile(compiler))?;
54            compiler.add_instr(InstructionKind::Drop);
55            Ok(())
56        })?;
57
58        compiler.add_instr(InstructionKind::Jump(condition_start));
59
60        // if the loop doesn't terminate through a `break` expression, we need to load something to
61        // the stack so that the automatically generated `Drop` instruction has something to drop
62        //
63        // TODO: this can be optimized out at some point
64        compiler.set_end_label(end_with_no_break);
65        compiler.add_instr(InstructionKind::LoadConst(Value::Unit));
66
67        compiler.set_end_label(loop_end);
68        Ok(())
69    }
70}
71
72impl Compile for For {
73    fn compile(&self, compiler: &mut Compiler) -> Result<(), Error> {
74        // ```
75        // for i in 0..10 then print(i)
76        // ```
77        //
78        // equivalent to:
79        //
80        // ```
81        // i = 0
82        // while i < 10 {
83        //     print(i)
84        //     i += 1
85        // }
86        // ```
87        //
88        // but with control flow; specifically, `continue` also increments `i`
89        //
90        // TODO: this will one day be generalized to work on any iterator
91
92        compiler.new_scope(|compiler| {
93            // compile range end up here so that the index variable isn't in scope, then insert it
94            // down at the condition
95            let chunk = compiler.new_chunk_get(|compiler| {
96                self.range.end.compile(compiler)
97            })?;
98
99            // assign: initialize index in range, jump past initial increment
100            self.range.start.compile(compiler)?;
101            let symbol_id = compiler.add_symbol(&self.variable)?;
102            compiler.add_instr(InstructionKind::AssignVar(symbol_id));
103
104            // condition: continue summing while the variable is in the range:
105            // `symbol_id < self.range.end`
106            let condition_start = compiler.new_end_label();
107            compiler.add_instr(InstructionKind::LoadVar(Symbol::User(symbol_id)));
108            compiler.add_chunk_instrs(chunk);
109            match self.range.kind {
110                RangeKind::HalfOpen => compiler.add_instr(InstructionKind::Binary(BinOpKind::Less)),
111                RangeKind::Closed => compiler.add_instr(InstructionKind::Binary(BinOpKind::LessEq)),
112            }
113
114            let end_with_no_break = compiler.new_unassociated_label();
115            let loop_end = compiler.new_unassociated_label();
116            compiler.add_instr(InstructionKind::JumpIfFalse(end_with_no_break));
117
118            // body: run body
119            let index_start = compiler.new_unassociated_label();
120            compiler.with_state(|state| {
121                // in case `continue` and `break` expressions are inside, we need the loop start and
122                // end labels for their jumps
123                state.loop_start = Some(index_start);
124                state.loop_end = Some(loop_end);
125            }, |compiler| {
126                self.body.compile(compiler)?;
127                compiler.add_instr(InstructionKind::Drop);
128                Ok(())
129            })?;
130
131            // increment index
132            compiler.set_end_label(index_start);
133            compiler.add_instr(InstructionKind::LoadVar(Symbol::User(symbol_id)));
134            compiler.add_instr(InstructionKind::LoadConst(Value::Integer(int(1))));
135            compiler.add_instr(InstructionKind::Binary(BinOpKind::Add));
136            compiler.add_instr(InstructionKind::AssignVar(symbol_id));
137
138            // jump back to condition
139            compiler.add_instr(InstructionKind::Jump(condition_start));
140
141            // if the loop doesn't terminate through a `break` expression, we need to load
142            // something to the stack so that the automatically generated `Drop` instruction has
143            // something to drop
144            //
145            // TODO: same potential optimization as in `while` loops
146            compiler.set_end_label(end_with_no_break);
147            compiler.add_instr(InstructionKind::LoadConst(Value::Unit));
148
149            compiler.set_end_label(loop_end);
150            Ok(())
151        })?;
152        Ok(())
153    }
154}