Emits for/if comprehension scaffolding; reinjcts body with loop-bound SSA slots. */
(&mut self, elem_bodies: &[(usize, Vec<Instruction>)], append_op: OpCode, versions_before: &HashMap<String, u32>)
| 151 | |
| 152 | /* Emits for/if comprehension scaffolding; reinjcts body with loop-bound SSA slots. */ |
| 153 | pub(super) fn comprehension_loop(&mut self, elem_bodies: &[(usize, Vec<Instruction>)], append_op: OpCode, versions_before: &HashMap<String, u32>) { |
| 154 | let mut loop_starts: Vec<u16> = Vec::new(); |
| 155 | let mut for_iters: Vec<usize> = Vec::new(); |
| 156 | let mut all_vars: Vec<String> = Vec::new(); |
| 157 | |
| 158 | while self.eat_if(TokenType::For) { |
| 159 | let mut vars: Vec<String> = Vec::new(); |
| 160 | loop { |
| 161 | vars.push(self.advance_text()); |
| 162 | if !self.eat_if(TokenType::Comma) { break; } |
| 163 | if matches!(self.peek(), Some(TokenType::In)) { break; } |
| 164 | } |
| 165 | |
| 166 | self.eat(TokenType::In); |
| 167 | self.expr_bp(1); |
| 168 | self.chunk.emit(OpCode::GetIter, 0); |
| 169 | |
| 170 | let ls = self.chunk.instructions.len() as u16; |
| 171 | let fi = self.emit_jump(OpCode::ForIter); |
| 172 | |
| 173 | if vars.len() == 1 { |
| 174 | self.store_name(vars[0].clone()); |
| 175 | } else { |
| 176 | self.chunk.emit(OpCode::UnpackSequence, vars.len() as u16); |
| 177 | for var in &vars { |
| 178 | self.store_name(var.clone()); |
| 179 | } |
| 180 | } |
| 181 | for v in &vars { all_vars.push(v.clone()); } |
| 182 | |
| 183 | while self.eat_if(TokenType::If) { |
| 184 | self.expr_bp(1); |
| 185 | self.chunk.emit(OpCode::JumpIfFalse, ls); |
| 186 | } |
| 187 | |
| 188 | loop_starts.push(ls); |
| 189 | for_iters.push(fi); |
| 190 | } |
| 191 | |
| 192 | // Linear scan: size 1-5 beats HashMap and avoids monomorphizing for u16 keys. |
| 193 | let mut var_map: Vec<(u16, u16)> = Vec::new(); |
| 194 | for var in &all_vars { |
| 195 | let old_ver = versions_before.get(var).copied().unwrap_or(0); |
| 196 | let new_ver = self.current_version(var); |
| 197 | if old_ver == new_ver { continue; } |
| 198 | let mut ob = [0u8; 128]; |
| 199 | let old_name = Self::ssa_name(var, old_ver, &mut ob); |
| 200 | let Some(&old_slot) = self.chunk.name_index.get(old_name) else { continue }; |
| 201 | let mut nb = [0u8; 128]; |
| 202 | let new_slot = self.chunk.push_name(Self::ssa_name(var, new_ver, &mut nb)); |
| 203 | var_map.push((old_slot, new_slot)); |
| 204 | } |
| 205 | |
| 206 | for (orig_base, body) in elem_bodies { |
| 207 | // Body is relocated by this delta; internal jump targets (from `or`/`and`/membership) must shift with it. |
| 208 | let delta = self.chunk.instructions.len() as i64 - *orig_base as i64; |
| 209 | for ins in body { |
| 210 | let operand = if matches!(ins.opcode, OpCode::LoadName | OpCode::StoreName) { |
no test coverage detected