1use crate::ast::*;
4use crate::bytecode::{Chunk, Op};
5use crate::error::IonError;
6use crate::value::{FnChunkCache, Value};
7
8#[derive(Debug, Clone)]
10struct Local {
11 name: String,
12 depth: usize,
13}
14
15fn unmatched_label_msg(keyword: &str, label: Option<&str>) -> String {
16 match label {
17 Some(name) => format!("{keyword} with unknown label '{name}"),
18 None => format!("{keyword} outside of loop"),
19 }
20}
21
22#[derive(Debug)]
25struct LoopFrame {
26 label: Option<String>,
27 break_jumps: Vec<usize>,
29 continue_target: usize,
31 is_for_loop: bool,
34 scope_depth: usize,
36}
37
38pub struct Compiler {
39 chunk: Chunk,
40 pub fn_chunks: FnChunkCache,
42 in_tail_position: bool,
44 locals: Vec<Local>,
46 scope_depth: usize,
48 needs_env_locals: bool,
50 loop_stack: Vec<LoopFrame>,
52}
53
54impl Default for Compiler {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60impl Compiler {
61 pub fn new() -> Self {
62 Self {
63 chunk: Chunk::new(),
64 fn_chunks: FnChunkCache::new(),
65 in_tail_position: false,
66 locals: Vec::new(),
67 scope_depth: 0,
68 needs_env_locals: true, loop_stack: Vec::new(),
70 }
71 }
72
73 fn resolve_loop_target(&self, label: Option<&str>) -> Option<usize> {
77 match label {
78 None => self.loop_stack.len().checked_sub(1),
79 Some(want) => self
80 .loop_stack
81 .iter()
82 .rposition(|f| f.label.as_deref() == Some(want)),
83 }
84 }
85
86 fn stmts_have_closures(stmts: &[Stmt]) -> bool {
88 for stmt in stmts {
89 match &stmt.kind {
90 StmtKind::FnDecl { body: _, .. } => {
91 return true;
93 }
96 StmtKind::ExprStmt { expr, .. } if Self::expr_has_closures(expr) => {
97 return true;
98 }
99 StmtKind::Let { value, .. } if Self::expr_has_closures(value) => {
100 return true;
101 }
102 StmtKind::For { body, iter, .. } => {
103 if Self::expr_has_closures(iter) {
104 return true;
105 }
106 if Self::stmts_have_closures(body) {
107 return true;
108 }
109 }
110 StmtKind::While { cond, body, .. } => {
111 if Self::expr_has_closures(cond) {
112 return true;
113 }
114 if Self::stmts_have_closures(body) {
115 return true;
116 }
117 }
118 StmtKind::Loop { body, .. } if Self::stmts_have_closures(body) => {
119 return true;
120 }
121 StmtKind::Return { value: Some(e) } if Self::expr_has_closures(e) => {
122 return true;
123 }
124 StmtKind::Assign { value, .. } if Self::expr_has_closures(value) => {
125 return true;
126 }
127 StmtKind::WhileLet { expr, body, .. } => {
128 if Self::expr_has_closures(expr) {
129 return true;
130 }
131 if Self::stmts_have_closures(body) {
132 return true;
133 }
134 }
135 _ => {}
136 }
137 }
138 false
139 }
140
141 fn expr_has_closures(expr: &Expr) -> bool {
142 match &expr.kind {
143 ExprKind::Lambda { .. } => true,
144 ExprKind::If {
145 cond,
146 then_body,
147 else_body,
148 } => {
149 Self::expr_has_closures(cond)
150 || Self::stmts_have_closures(then_body)
151 || else_body
152 .as_ref()
153 .is_some_and(|b| Self::stmts_have_closures(b))
154 }
155 ExprKind::Block(stmts) => Self::stmts_have_closures(stmts),
156 ExprKind::Call { func, args } => {
157 Self::expr_has_closures(func)
158 || args.iter().any(|a| Self::expr_has_closures(&a.value))
159 }
160 ExprKind::MethodCall { expr, args, .. } => {
161 Self::expr_has_closures(expr)
162 || args.iter().any(|a| Self::expr_has_closures(&a.value))
163 }
164 ExprKind::BinOp { left, right, .. } => {
165 Self::expr_has_closures(left) || Self::expr_has_closures(right)
166 }
167 ExprKind::UnaryOp { expr, .. } => Self::expr_has_closures(expr),
168 ExprKind::PipeOp { left, right } => {
169 Self::expr_has_closures(left) || Self::expr_has_closures(right)
170 }
171 ExprKind::Match { expr, arms } => {
172 Self::expr_has_closures(expr)
173 || arms.iter().any(|a| Self::expr_has_closures(&a.body))
174 }
175 ExprKind::List(items) => items.iter().any(|e| match e {
176 ListEntry::Elem(expr) | ListEntry::Spread(expr) => Self::expr_has_closures(expr),
177 }),
178 ExprKind::Tuple(items) => items.iter().any(Self::expr_has_closures),
179 ExprKind::ListComp {
180 expr, iter, cond, ..
181 } => {
182 Self::expr_has_closures(expr)
183 || Self::expr_has_closures(iter)
184 || cond.as_ref().is_some_and(|c| Self::expr_has_closures(c))
185 }
186 ExprKind::IfLet {
187 expr,
188 then_body,
189 else_body,
190 ..
191 } => {
192 Self::expr_has_closures(expr)
193 || Self::stmts_have_closures(then_body)
194 || else_body
195 .as_ref()
196 .is_some_and(|b| Self::stmts_have_closures(b))
197 }
198 ExprKind::TryCatch { body, handler, .. } => {
199 Self::stmts_have_closures(body) || Self::stmts_have_closures(handler)
200 }
201 ExprKind::LoopExpr(stmts) => Self::stmts_have_closures(stmts),
202 ExprKind::Range { start, end, .. } => {
203 Self::expr_has_closures(start) || Self::expr_has_closures(end)
204 }
205 ExprKind::Dict(entries) => entries.iter().any(|e| match e {
206 DictEntry::KeyValue(k, v) => {
207 Self::expr_has_closures(k) || Self::expr_has_closures(v)
208 }
209 DictEntry::Spread(expr) => Self::expr_has_closures(expr),
210 }),
211 ExprKind::DictComp {
212 key,
213 value,
214 iter,
215 cond,
216 ..
217 } => {
218 Self::expr_has_closures(key)
219 || Self::expr_has_closures(value)
220 || Self::expr_has_closures(iter)
221 || cond.as_ref().is_some_and(|c| Self::expr_has_closures(c))
222 }
223 ExprKind::FieldAccess { expr, .. }
224 | ExprKind::Try(expr)
225 | ExprKind::SomeExpr(expr)
226 | ExprKind::OkExpr(expr)
227 | ExprKind::ErrExpr(expr) => Self::expr_has_closures(expr),
228 ExprKind::Index { expr, index } => {
229 Self::expr_has_closures(expr) || Self::expr_has_closures(index)
230 }
231 ExprKind::Slice {
232 expr, start, end, ..
233 } => {
234 Self::expr_has_closures(expr)
235 || start.as_ref().is_some_and(|s| Self::expr_has_closures(s))
236 || end.as_ref().is_some_and(|e| Self::expr_has_closures(e))
237 }
238 ExprKind::FStr(parts) => parts.iter().any(|p| match p {
239 FStrPart::Expr(e) => Self::expr_has_closures(e),
240 _ => false,
241 }),
242 ExprKind::StructConstruct { fields, spread, .. } => {
243 fields.iter().any(|(_, e)| Self::expr_has_closures(e))
244 || spread.as_ref().is_some_and(|s| Self::expr_has_closures(s))
245 }
246 _ => false,
247 }
248 }
249
250 #[cfg(feature = "optimize")]
252 fn try_fold_binop(left: &Expr, op: &BinOp, right: &Expr) -> Option<Value> {
253 match (&left.kind, op, &right.kind) {
254 (ExprKind::Int(a), BinOp::Add, ExprKind::Int(b)) => {
256 Some(Value::Int(a.wrapping_add(*b)))
257 }
258 (ExprKind::Int(a), BinOp::Sub, ExprKind::Int(b)) => {
259 Some(Value::Int(a.wrapping_sub(*b)))
260 }
261 (ExprKind::Int(a), BinOp::Mul, ExprKind::Int(b)) => {
262 Some(Value::Int(a.wrapping_mul(*b)))
263 }
264 (ExprKind::Int(a), BinOp::Div, ExprKind::Int(b)) if *b != 0 => Some(Value::Int(a / b)),
265 (ExprKind::Int(a), BinOp::Mod, ExprKind::Int(b)) if *b != 0 => Some(Value::Int(a % b)),
266 (ExprKind::Int(a), BinOp::Eq, ExprKind::Int(b)) => Some(Value::Bool(a == b)),
267 (ExprKind::Int(a), BinOp::Ne, ExprKind::Int(b)) => Some(Value::Bool(a != b)),
268 (ExprKind::Int(a), BinOp::Lt, ExprKind::Int(b)) => Some(Value::Bool(a < b)),
269 (ExprKind::Int(a), BinOp::Gt, ExprKind::Int(b)) => Some(Value::Bool(a > b)),
270 (ExprKind::Int(a), BinOp::Le, ExprKind::Int(b)) => Some(Value::Bool(a <= b)),
271 (ExprKind::Int(a), BinOp::Ge, ExprKind::Int(b)) => Some(Value::Bool(a >= b)),
272 (ExprKind::Int(a), BinOp::BitAnd, ExprKind::Int(b)) => Some(Value::Int(a & b)),
273 (ExprKind::Int(a), BinOp::BitOr, ExprKind::Int(b)) => Some(Value::Int(a | b)),
274 (ExprKind::Int(a), BinOp::BitXor, ExprKind::Int(b)) => Some(Value::Int(a ^ b)),
275 (ExprKind::Int(a), BinOp::Shl, ExprKind::Int(b)) if (0..64).contains(b) => {
276 Some(Value::Int(a << (*b as u32)))
277 }
278 (ExprKind::Int(a), BinOp::Shr, ExprKind::Int(b)) if (0..64).contains(b) => {
279 Some(Value::Int(a >> (*b as u32)))
280 }
281 (ExprKind::Float(a), BinOp::Add, ExprKind::Float(b)) => Some(Value::Float(a + b)),
283 (ExprKind::Float(a), BinOp::Sub, ExprKind::Float(b)) => Some(Value::Float(a - b)),
284 (ExprKind::Float(a), BinOp::Mul, ExprKind::Float(b)) => Some(Value::Float(a * b)),
285 (ExprKind::Float(a), BinOp::Div, ExprKind::Float(b)) => Some(Value::Float(a / b)),
286 (ExprKind::Float(a), BinOp::Mod, ExprKind::Float(b)) => Some(Value::Float(a % b)),
287 (ExprKind::Int(a), BinOp::Add, ExprKind::Float(b)) => Some(Value::Float(*a as f64 + b)),
289 (ExprKind::Float(a), BinOp::Add, ExprKind::Int(b)) => Some(Value::Float(a + *b as f64)),
290 (ExprKind::Int(a), BinOp::Sub, ExprKind::Float(b)) => Some(Value::Float(*a as f64 - b)),
291 (ExprKind::Float(a), BinOp::Sub, ExprKind::Int(b)) => Some(Value::Float(a - *b as f64)),
292 (ExprKind::Int(a), BinOp::Mul, ExprKind::Float(b)) => Some(Value::Float(*a as f64 * b)),
293 (ExprKind::Float(a), BinOp::Mul, ExprKind::Int(b)) => Some(Value::Float(a * *b as f64)),
294 (ExprKind::Int(a), BinOp::Div, ExprKind::Float(b)) => Some(Value::Float(*a as f64 / b)),
295 (ExprKind::Float(a), BinOp::Div, ExprKind::Int(b)) => Some(Value::Float(a / *b as f64)),
296 (ExprKind::Str(a), BinOp::Add, ExprKind::Str(b)) => {
298 let mut s = a.clone();
299 s.push_str(b);
300 Some(Value::Str(s))
301 }
302 (ExprKind::Bool(a), BinOp::And, ExprKind::Bool(b)) => Some(Value::Bool(*a && *b)),
304 (ExprKind::Bool(a), BinOp::Or, ExprKind::Bool(b)) => Some(Value::Bool(*a || *b)),
305 (ExprKind::Bool(a), BinOp::Eq, ExprKind::Bool(b)) => Some(Value::Bool(a == b)),
306 (ExprKind::Bool(a), BinOp::Ne, ExprKind::Bool(b)) => Some(Value::Bool(a != b)),
307 _ => None,
308 }
309 }
310
311 #[cfg(feature = "optimize")]
313 fn try_fold_unary(op: &UnaryOp, inner: &Expr) -> Option<Value> {
314 match (op, &inner.kind) {
315 (UnaryOp::Neg, ExprKind::Int(v)) => Some(Value::Int(-v)),
316 (UnaryOp::Neg, ExprKind::Float(v)) => Some(Value::Float(-v)),
317 (UnaryOp::Not, ExprKind::Bool(v)) => Some(Value::Bool(!v)),
318 _ => None,
319 }
320 }
321
322 #[cfg(feature = "optimize")]
324 fn stmt_is_terminal(stmt: &Stmt) -> bool {
325 matches!(
326 &stmt.kind,
327 StmtKind::Return { .. } | StmtKind::Break { .. } | StmtKind::Continue { .. }
328 )
329 }
330
331 fn resolve_local(&self, name: &str) -> Option<usize> {
333 for (i, local) in self.locals.iter().enumerate().rev() {
334 if local.name == name {
335 return Some(i);
336 }
337 }
338 None
339 }
340
341 fn add_local(&mut self, name: String, _mutable: bool) {
343 self.locals.push(Local {
344 name,
345 depth: self.scope_depth,
346 });
347 }
348
349 fn begin_scope(&mut self, line: usize) {
351 self.scope_depth += 1;
352 self.chunk.emit_op(Op::PushScope, line);
353 }
354
355 fn emit_get_var(&mut self, name: &str, line: usize) {
357 if let Some(slot) = self.resolve_local(name) {
358 self.chunk.emit_op_u16(Op::GetLocalSlot, slot as u16, line);
359 } else {
360 let idx = self.chunk.add_constant(Value::Str(name.to_string()));
361 self.chunk.emit_op_u16(Op::GetGlobal, idx, line);
362 }
363 }
364
365 fn emit_define_local(&mut self, name: &str, mutable: bool, line: usize) {
368 self.add_local(name.to_string(), mutable);
369 if self.needs_env_locals {
370 self.chunk.emit_op(Op::Dup, line);
372 let idx = self.chunk.add_constant(Value::Str(name.to_string()));
373 self.chunk.emit_op_u16(Op::DefineLocal, idx, line);
374 self.chunk.emit(if mutable { 1 } else { 0 }, line);
375 }
376 self.chunk
377 .emit_op_u8(Op::DefineLocalSlot, if mutable { 1 } else { 0 }, line);
378 }
379
380 fn emit_set_var(&mut self, name: &str, line: usize) {
382 if let Some(slot) = self.resolve_local(name) {
383 self.chunk.emit_op_u16(Op::SetLocalSlot, slot as u16, line);
384 } else {
385 let idx = self.chunk.add_constant(Value::Str(name.to_string()));
386 self.chunk.emit_op_u16(Op::SetGlobal, idx, line);
387 }
388 }
389
390 fn end_scope(&mut self, line: usize) {
392 while let Some(local) = self.locals.last() {
393 if local.depth < self.scope_depth {
394 break;
395 }
396 self.locals.pop();
397 }
398 self.scope_depth -= 1;
399 self.chunk.emit_op(Op::PopScope, line);
400 }
401
402 pub fn compile_program(mut self, program: &Program) -> Result<(Chunk, FnChunkCache), IonError> {
403 let len = program.stmts.len();
404 for (i, stmt) in program.stmts.iter().enumerate() {
405 let is_last = i == len - 1;
406 match &stmt.kind {
407 StmtKind::ExprStmt { expr, has_semi } => {
408 self.compile_expr(expr)?;
409 if is_last && !has_semi {
410 } else {
412 self.chunk.emit_op(Op::Pop, stmt.span.line);
413 }
414 }
415 _ => {
416 self.compile_stmt(stmt)?;
417 if is_last {
418 self.chunk.emit_op(Op::Unit, stmt.span.line);
420 }
421 }
422 }
423 #[cfg(feature = "optimize")]
424 if !is_last && Self::stmt_is_terminal(stmt) {
425 break;
426 }
427 }
428 if program.stmts.is_empty() {
429 self.chunk.emit_op(Op::Unit, 0);
430 }
431 self.chunk.emit_op(Op::Return, 0);
432 #[cfg(feature = "optimize")]
433 self.chunk.peephole_optimize();
434 Ok((self.chunk, self.fn_chunks))
435 }
436
437 fn compile_stmt(&mut self, stmt: &Stmt) -> Result<(), IonError> {
438 let line = stmt.span.line;
439 match &stmt.kind {
440 StmtKind::Let {
441 mutable,
442 pattern,
443 type_ann,
444 value,
445 } => {
446 self.compile_expr(value)?;
447 if let Some(ann) = type_ann {
448 let type_name = Self::type_ann_to_string(ann);
449 let idx = self.chunk.add_constant(Value::Str(type_name));
450 self.chunk.emit_op_u16(Op::CheckType, idx, line);
451 }
452 self.compile_let_pattern(pattern, *mutable, line)?;
453 }
454 StmtKind::ExprStmt { expr, .. } => {
455 self.compile_expr(expr)?;
456 self.chunk.emit_op(Op::Pop, line);
457 }
458 StmtKind::FnDecl { name, params, body } => {
459 self.compile_fn_decl(name, params, body, line)?;
460 }
461 StmtKind::For {
462 label,
463 pattern,
464 iter,
465 body,
466 } => {
467 self.compile_for(label.clone(), pattern, iter, body, line)?;
468 }
469 StmtKind::While { label, cond, body } => {
470 self.compile_while(label.clone(), cond, body, line)?;
471 }
472 StmtKind::Loop { label, body } => {
473 self.compile_loop(label.clone(), body, line)?;
474 }
475 StmtKind::Break { label, value } => {
476 let target_idx = match self.resolve_loop_target(label.as_deref()) {
477 Some(idx) => idx,
478 None => {
479 return Err(IonError::runtime(
480 unmatched_label_msg("break", label.as_deref()),
481 line,
482 0,
483 ));
484 }
485 };
486 if let Some(expr) = value {
487 self.compile_expr(expr)?;
488 } else {
489 self.chunk.emit_op(Op::Unit, line);
490 }
491 let target_scope = self.loop_stack[target_idx].scope_depth;
492 for _ in target_scope..self.scope_depth {
493 self.chunk.emit_op(Op::PopScope, line);
494 }
495 for frame in self.loop_stack[target_idx..].iter().rev() {
498 if frame.is_for_loop {
499 self.chunk.emit_op(Op::IterDrop, line);
500 }
501 }
502 let jump = self.chunk.emit_jump(Op::Jump, line);
503 self.loop_stack[target_idx].break_jumps.push(jump);
504 }
505 StmtKind::Continue { label } => {
506 let target_idx = match self.resolve_loop_target(label.as_deref()) {
507 Some(idx) => idx,
508 None => {
509 return Err(IonError::runtime(
510 unmatched_label_msg("continue", label.as_deref()),
511 line,
512 0,
513 ));
514 }
515 };
516 let target_scope = self.loop_stack[target_idx].scope_depth;
517 for _ in target_scope..self.scope_depth {
518 self.chunk.emit_op(Op::PopScope, line);
519 }
520 for frame in self.loop_stack[target_idx + 1..].iter().rev() {
523 if frame.is_for_loop {
524 self.chunk.emit_op(Op::IterDrop, line);
525 }
526 }
527 let target_frame = &self.loop_stack[target_idx];
528 if target_frame.is_for_loop {
529 self.chunk.emit_op(Op::Unit, line);
531 }
532 let offset = self.chunk.len() - target_frame.continue_target + 3;
533 self.chunk.emit_op_u16(Op::Loop, offset as u16, line);
534 }
535 StmtKind::Return { value } => {
536 if let Some(expr) = value {
537 let saved = self.in_tail_position;
538 self.in_tail_position = true;
539 self.compile_expr(expr)?;
540 self.in_tail_position = saved;
541 } else {
542 self.chunk.emit_op(Op::Unit, line);
543 }
544 self.chunk.emit_op(Op::Return, line);
545 }
546 StmtKind::Assign { target, op, value } => {
547 self.compile_assign(target, op, value, line)?;
548 self.chunk.emit_op(Op::Pop, line); }
550 StmtKind::Use { .. } => {
551 return Err(IonError::runtime(
553 ion_str!("use statements not yet supported in VM"),
554 line,
555 0,
556 ));
557 }
558 StmtKind::WhileLet {
559 label,
560 pattern,
561 expr,
562 body,
563 } => {
564 let loop_start = self.chunk.len();
565 self.loop_stack.push(LoopFrame {
566 label: label.clone(),
567 break_jumps: Vec::new(),
568 continue_target: loop_start,
569 is_for_loop: false,
570 scope_depth: self.scope_depth,
571 });
572
573 self.compile_expr(expr)?;
575
576 self.chunk.emit_op(Op::Dup, line); self.compile_pattern_test(pattern, line)?;
579
580 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, line);
581 self.chunk.emit_op(Op::Pop, line); self.begin_scope(line);
585 self.compile_pattern_bind(pattern, line)?;
586 for stmt in body {
587 self.compile_stmt(stmt)?;
588 #[cfg(feature = "optimize")]
589 if Self::stmt_is_terminal(stmt) {
590 break;
591 }
592 }
593 self.end_scope(line);
594
595 let offset = self.chunk.len() - loop_start + 3;
596 self.chunk.emit_op_u16(Op::Loop, offset as u16, line);
597
598 self.chunk.patch_jump(exit_jump);
599 self.chunk.emit_op(Op::Pop, line); self.chunk.emit_op(Op::Pop, line); let frame = self.loop_stack.pop().expect("loop frame");
603 for jump in &frame.break_jumps {
604 self.chunk.patch_jump(*jump);
605 }
606 }
607 }
608 Ok(())
609 }
610
611 fn compile_expr(&mut self, expr: &Expr) -> Result<(), IonError> {
612 let line = expr.span.line;
613 let col = expr.span.col;
614 let was_tail = self.in_tail_position;
616 self.in_tail_position = false;
617 match &expr.kind {
618 ExprKind::Int(n) => {
619 self.chunk.emit_constant(Value::Int(*n), line);
620 }
621 ExprKind::Float(n) => {
622 self.chunk.emit_constant(Value::Float(*n), line);
623 }
624 ExprKind::Bool(b) => {
625 self.chunk
626 .emit_op(if *b { Op::True } else { Op::False }, line);
627 }
628 ExprKind::Str(s) => {
629 self.chunk.emit_constant(Value::Str(s.clone()), line);
630 }
631 ExprKind::Bytes(b) => {
632 self.chunk.emit_constant(Value::Bytes(b.clone()), line);
633 }
634 ExprKind::Unit => {
635 self.chunk.emit_op(Op::Unit, line);
636 }
637 ExprKind::None => {
638 self.chunk.emit_op(Op::None, line);
639 }
640 ExprKind::SomeExpr(inner) => {
641 self.compile_expr(inner)?;
642 self.chunk.emit_op(Op::WrapSome, line);
643 }
644 ExprKind::OkExpr(inner) => {
645 self.compile_expr(inner)?;
646 self.chunk.emit_op(Op::WrapOk, line);
647 }
648 ExprKind::ErrExpr(inner) => {
649 self.compile_expr(inner)?;
650 self.chunk.emit_op(Op::WrapErr, line);
651 }
652
653 ExprKind::Ident(name) => {
654 if let Some(slot) = self.resolve_local(name) {
655 self.chunk.emit_op_u16(Op::GetLocalSlot, slot as u16, line);
656 } else {
657 let idx = self.chunk.add_constant(Value::Str(name.clone()));
658 self.chunk.emit_op_u16(Op::GetGlobal, idx, line);
659 }
660 }
661
662 ExprKind::ModulePath(segments) => {
663 let root_idx = self.chunk.add_constant(Value::Str(segments[0].clone()));
665 self.chunk.emit_op_u16(Op::GetGlobal, root_idx, line);
666 for seg in &segments[1..] {
667 let idx = self.chunk.add_constant(Value::Str(seg.clone()));
668 self.chunk.emit_op_u16_span(Op::GetField, idx, line, col);
669 }
670 }
671
672 ExprKind::BinOp { left, op, right } => {
673 #[cfg(feature = "optimize")]
675 let folded = Self::try_fold_binop(left, op, right);
676 #[cfg(not(feature = "optimize"))]
677 let folded: Option<Value> = None;
678 if let Some(val) = folded {
679 self.chunk.emit_constant(val, line);
680 } else {
681 match op {
682 BinOp::And => {
683 self.compile_expr(left)?;
684 let jump = self.chunk.emit_jump(Op::And, line);
685 self.chunk.emit_op(Op::Pop, line);
686 self.compile_expr(right)?;
687 self.chunk.patch_jump(jump);
688 }
689 BinOp::Or => {
690 self.compile_expr(left)?;
691 let jump = self.chunk.emit_jump(Op::Or, line);
692 self.chunk.emit_op(Op::Pop, line);
693 self.compile_expr(right)?;
694 self.chunk.patch_jump(jump);
695 }
696 _ => {
697 self.compile_expr(left)?;
698 self.compile_expr(right)?;
699 match op {
700 BinOp::Add => self.chunk.emit_op_span(Op::Add, line, col),
701 BinOp::Sub => self.chunk.emit_op_span(Op::Sub, line, col),
702 BinOp::Mul => self.chunk.emit_op_span(Op::Mul, line, col),
703 BinOp::Div => self.chunk.emit_op_span(Op::Div, line, col),
704 BinOp::Mod => self.chunk.emit_op_span(Op::Mod, line, col),
705 BinOp::Eq => self.chunk.emit_op(Op::Eq, line),
706 BinOp::Ne => self.chunk.emit_op(Op::NotEq, line),
707 BinOp::Lt => self.chunk.emit_op(Op::Lt, line),
708 BinOp::Gt => self.chunk.emit_op(Op::Gt, line),
709 BinOp::Le => self.chunk.emit_op(Op::LtEq, line),
710 BinOp::Ge => self.chunk.emit_op(Op::GtEq, line),
711 BinOp::BitAnd => self.chunk.emit_op(Op::BitAnd, line),
712 BinOp::BitOr => self.chunk.emit_op(Op::BitOr, line),
713 BinOp::BitXor => self.chunk.emit_op(Op::BitXor, line),
714 BinOp::Shl => self.chunk.emit_op(Op::Shl, line),
715 BinOp::Shr => self.chunk.emit_op(Op::Shr, line),
716 _ => unreachable!(),
717 }
718 }
719 }
720 }
721 }
722
723 ExprKind::UnaryOp { op, expr: inner } => {
724 #[cfg(feature = "optimize")]
725 let folded = Self::try_fold_unary(op, inner);
726 #[cfg(not(feature = "optimize"))]
727 let folded: Option<Value> = None;
728 if let Some(val) = folded {
729 self.chunk.emit_constant(val, line);
730 } else {
731 self.compile_expr(inner)?;
732 match op {
733 UnaryOp::Neg => self.chunk.emit_op_span(Op::Neg, line, col),
734 UnaryOp::Not => self.chunk.emit_op_span(Op::Not, line, col),
735 }
736 }
737 }
738
739 ExprKind::If {
740 cond,
741 then_body,
742 else_body,
743 } => {
744 self.compile_expr(cond)?;
746 let then_jump = self.chunk.emit_jump(Op::JumpIfFalse, line);
747 self.chunk.emit_op(Op::Pop, line); self.begin_scope(line);
749 self.in_tail_position = was_tail;
751 self.compile_block_expr(then_body, line)?;
752 self.end_scope(line);
753 let else_jump = self.chunk.emit_jump(Op::Jump, line);
754 self.chunk.patch_jump(then_jump);
755 self.chunk.emit_op(Op::Pop, line); if let Some(else_stmts) = else_body {
757 self.begin_scope(line);
758 self.in_tail_position = was_tail;
759 self.compile_block_expr(else_stmts, line)?;
760 self.end_scope(line);
761 } else {
762 self.chunk.emit_op(Op::Unit, line);
763 }
764 self.chunk.patch_jump(else_jump);
765 }
766
767 ExprKind::Block(stmts) => {
768 self.begin_scope(line);
769 self.in_tail_position = was_tail;
770 self.compile_block_expr(stmts, line)?;
771 self.end_scope(line);
772 }
773
774 ExprKind::Call { func, args } => {
775 let has_named = args.iter().any(|a| a.name.is_some());
776 self.compile_expr(func)?;
778 for arg in args {
779 self.compile_expr(&arg.value)?;
780 }
781 if has_named {
782 let named: Vec<(u8, u16)> = args
784 .iter()
785 .enumerate()
786 .filter_map(|(i, a)| {
787 a.name
788 .as_ref()
789 .map(|n| (i as u8, self.chunk.add_constant(Value::Str(n.clone()))))
790 })
791 .collect();
792 self.chunk.emit_op(Op::CallNamed, line);
793 self.chunk.emit(args.len() as u8, line);
794 self.chunk.emit(named.len() as u8, line);
795 for (pos, name_idx) in named {
796 self.chunk.emit(pos, line);
797 self.chunk.emit((name_idx >> 8) as u8, line);
798 self.chunk.emit(name_idx as u8, line);
799 }
800 } else {
801 #[cfg(feature = "optimize")]
802 let op = if was_tail { Op::TailCall } else { Op::Call };
803 #[cfg(not(feature = "optimize"))]
804 let op = Op::Call;
805 self.chunk.emit_op_u8_span(op, args.len() as u8, line, col);
806 }
807 }
808
809 ExprKind::List(items) => {
810 let has_spread = items.iter().any(|e| matches!(e, ListEntry::Spread(_)));
811 if has_spread {
812 self.chunk.emit_op_u16(Op::BuildList, 0, line);
814 for entry in items {
815 match entry {
816 ListEntry::Elem(expr) => {
817 self.compile_expr(expr)?;
818 self.chunk.emit_op(Op::ListAppend, line);
819 }
820 ListEntry::Spread(expr) => {
821 self.compile_expr(expr)?;
822 self.chunk.emit_op(Op::ListExtend, line);
823 }
824 }
825 }
826 } else {
827 for entry in items {
829 if let ListEntry::Elem(expr) = entry {
830 self.compile_expr(expr)?;
831 }
832 }
833 self.chunk
834 .emit_op_u16(Op::BuildList, items.len() as u16, line);
835 }
836 }
837
838 ExprKind::Tuple(items) => {
839 for item in items {
840 self.compile_expr(item)?;
841 }
842 self.chunk
843 .emit_op_u16(Op::BuildTuple, items.len() as u16, line);
844 }
845
846 ExprKind::Dict(entries) => {
847 let has_spread = entries.iter().any(|e| matches!(e, DictEntry::Spread(_)));
848 if has_spread {
849 self.chunk.emit_op_u16(Op::BuildDict, 0, line);
851 for entry in entries {
852 match entry {
853 DictEntry::KeyValue(k, v) => {
854 self.compile_expr(k)?;
855 self.compile_expr(v)?;
856 self.chunk.emit_op(Op::DictInsert, line);
857 }
858 DictEntry::Spread(expr) => {
859 self.compile_expr(expr)?;
860 self.chunk.emit_op(Op::DictMerge, line);
861 }
862 }
863 }
864 } else {
865 let count = entries.len() as u16;
867 for entry in entries {
868 if let DictEntry::KeyValue(k, v) = entry {
869 self.compile_expr(k)?;
870 self.compile_expr(v)?;
871 }
872 }
873 self.chunk.emit_op_u16(Op::BuildDict, count, line);
874 }
875 }
876
877 ExprKind::FieldAccess { expr: inner, field } => {
878 self.compile_expr(inner)?;
879 let idx = self.chunk.add_constant(Value::Str(field.clone()));
880 self.chunk.emit_op_u16_span(Op::GetField, idx, line, col);
881 }
882
883 ExprKind::Index { expr: inner, index } => {
884 self.compile_expr(inner)?;
885 self.compile_expr(index)?;
886 self.chunk.emit_op_span(Op::GetIndex, line, col);
887 }
888
889 ExprKind::Slice {
890 expr: inner,
891 start,
892 end,
893 inclusive,
894 } => {
895 self.compile_expr(inner)?;
896 let mut flags: u8 = 0;
897 if let Some(s) = start {
898 self.compile_expr(s)?;
899 flags |= 1; }
901 if let Some(e) = end {
902 self.compile_expr(e)?;
903 flags |= 2; }
905 if *inclusive {
906 flags |= 4; }
908 self.chunk.emit_op_u8(Op::Slice, flags, line);
909 }
910
911 ExprKind::MethodCall {
912 expr: inner,
913 method,
914 args,
915 } => {
916 self.compile_expr(inner)?;
917 for arg in args {
918 self.compile_expr(&arg.value)?;
919 }
920 let idx = self.chunk.add_constant(Value::Str(method.clone()));
921 self.chunk.emit_op_u16_span(Op::MethodCall, idx, line, col);
922 self.chunk.emit_span(args.len() as u8, line, col);
923 }
924
925 ExprKind::Lambda { params, body } => {
926 let body_stmt = Stmt {
928 kind: StmtKind::ExprStmt {
929 expr: *body.clone(),
930 has_semi: false,
931 },
932 span: expr.span,
933 };
934 let mut fn_compiler = Compiler::new();
936 fn_compiler.in_tail_position = true;
937 fn_compiler.needs_env_locals = Self::expr_has_closures(body);
938 for p in params {
940 fn_compiler.add_local(p.clone(), false);
941 }
942 if fn_compiler.needs_env_locals {
944 for (i, p) in params.iter().enumerate() {
945 fn_compiler
946 .chunk
947 .emit_op_u16(Op::GetLocalSlot, i as u16, line);
948 let idx = fn_compiler.chunk.add_constant(Value::Str(p.clone()));
949 fn_compiler.chunk.emit_op_u16(Op::DefineLocal, idx, line);
950 fn_compiler.chunk.emit(0, line);
951 }
952 }
953 fn_compiler.compile_expr(body)?;
954 fn_compiler.chunk.emit_op(Op::Return, line);
955 #[cfg(feature = "optimize")]
956 fn_compiler.chunk.peephole_optimize();
957 let compiled_chunk = fn_compiler.chunk;
958 self.fn_chunks.extend(fn_compiler.fn_chunks);
959
960 let fn_value = Value::Fn(crate::value::IonFn::new(
961 "<lambda>".to_string(),
962 params
963 .iter()
964 .map(|n| crate::ast::Param {
965 name: n.clone(),
966 default: None,
967 })
968 .collect(),
969 vec![body_stmt],
970 std::collections::HashMap::new(),
971 ));
972 if let Value::Fn(ref ion_fn) = fn_value {
974 self.fn_chunks.insert(ion_fn.fn_id, compiled_chunk);
975 }
976 let fn_idx = self.chunk.add_constant(fn_value);
977 self.chunk.emit_op_u16(Op::Closure, fn_idx, line);
978 }
979
980 ExprKind::FStr(parts) => {
981 for part in parts {
982 match part {
983 FStrPart::Literal(s) => {
984 self.chunk.emit_constant(Value::Str(s.clone()), line);
985 }
986 FStrPart::Expr(expr) => {
987 self.compile_expr(expr)?;
988 }
989 }
990 }
991 self.chunk
992 .emit_op_u16(Op::BuildFString, parts.len() as u16, line);
993 }
994
995 ExprKind::PipeOp { left, right } => {
996 match &right.kind {
999 ExprKind::Call { func, args } => {
1000 self.compile_expr(func)?;
1001 self.compile_expr(left)?; for arg in args {
1003 self.compile_expr(&arg.value)?;
1004 }
1005 self.chunk
1006 .emit_op_u8(Op::Call, (args.len() + 1) as u8, line);
1007 }
1008 _ => {
1009 self.compile_expr(right)?;
1011 self.compile_expr(left)?;
1012 self.chunk.emit_op_u8(Op::Call, 1, line);
1013 }
1014 }
1015 }
1016
1017 ExprKind::Try(inner) => {
1018 self.compile_expr(inner)?;
1019 self.chunk.emit_op(Op::Try, line);
1020 }
1021
1022 ExprKind::Range {
1023 start,
1024 end,
1025 inclusive,
1026 } => {
1027 self.compile_expr(start)?;
1028 self.compile_expr(end)?;
1029 self.chunk
1030 .emit_op_u8(Op::BuildRange, if *inclusive { 1 } else { 0 }, line);
1031 }
1032
1033 ExprKind::LoopExpr(body) => {
1034 self.compile_loop(None, body, line)?;
1035 }
1036
1037 ExprKind::Match {
1038 expr: subject,
1039 arms,
1040 } => {
1041 self.compile_match(subject, arms, line)?;
1042 }
1043
1044 ExprKind::ListComp {
1045 expr: item_expr,
1046 pattern,
1047 iter,
1048 cond,
1049 } => {
1050 self.compile_list_comp(item_expr, pattern, iter, cond.as_deref(), line)?;
1051 }
1052
1053 ExprKind::DictComp {
1054 key,
1055 value,
1056 pattern,
1057 iter,
1058 cond,
1059 } => {
1060 self.compile_dict_comp(key, value, pattern, iter, cond.as_deref(), line)?;
1061 }
1062
1063 ExprKind::IfLet {
1064 pattern,
1065 expr: inner,
1066 then_body,
1067 else_body,
1068 } => {
1069 self.compile_expr(inner)?;
1071
1072 self.chunk.emit_op(Op::Dup, line); self.compile_pattern_test(pattern, line)?;
1075
1076 let else_jump = self.chunk.emit_jump(Op::JumpIfFalse, line);
1077 self.chunk.emit_op(Op::Pop, line); self.begin_scope(line);
1081 self.compile_pattern_bind(pattern, line)?;
1082 self.in_tail_position = was_tail;
1083 self.compile_block_expr(then_body, line)?;
1084 self.end_scope(line);
1085
1086 let end_jump = self.chunk.emit_jump(Op::Jump, line);
1087
1088 self.chunk.patch_jump(else_jump);
1089 self.chunk.emit_op(Op::Pop, line); self.chunk.emit_op(Op::Pop, line); if let Some(else_stmts) = else_body {
1093 self.begin_scope(line);
1094 self.in_tail_position = was_tail;
1095 self.compile_block_expr(else_stmts, line)?;
1096 self.end_scope(line);
1097 } else {
1098 self.chunk.emit_op(Op::Unit, line);
1099 }
1100
1101 self.chunk.patch_jump(end_jump);
1102 }
1103
1104 ExprKind::StructConstruct {
1106 name,
1107 fields,
1108 spread,
1109 } => {
1110 if let Some(spread_expr) = spread {
1111 self.compile_expr(spread_expr)?;
1112 for (fname, fexpr) in fields {
1113 self.chunk.emit_constant(Value::Str(fname.clone()), line);
1114 self.compile_expr(fexpr)?;
1115 }
1116 let type_idx = self.chunk.add_constant(Value::Str(name.clone()));
1117 let field_count = (0x8000 | fields.len()) as u16;
1118 self.chunk.emit_op(Op::ConstructStruct, line);
1119 self.chunk.emit((type_idx >> 8) as u8, line);
1120 self.chunk.emit((type_idx & 0xff) as u8, line);
1121 self.chunk.emit((field_count >> 8) as u8, line);
1122 self.chunk.emit((field_count & 0xff) as u8, line);
1123 } else {
1124 for (fname, fexpr) in fields {
1125 self.chunk.emit_constant(Value::Str(fname.clone()), line);
1126 self.compile_expr(fexpr)?;
1127 }
1128 let type_idx = self.chunk.add_constant(Value::Str(name.clone()));
1129 let count = fields.len() as u16;
1130 self.chunk.emit_op(Op::ConstructStruct, line);
1131 self.chunk.emit((type_idx >> 8) as u8, line);
1132 self.chunk.emit((type_idx & 0xff) as u8, line);
1133 self.chunk.emit((count >> 8) as u8, line);
1134 self.chunk.emit((count & 0xff) as u8, line);
1135 }
1136 }
1137 ExprKind::EnumVariant { enum_name, variant } => {
1138 let enum_idx = self.chunk.add_constant(Value::Str(enum_name.clone()));
1139 let variant_idx = self.chunk.add_constant(Value::Str(variant.clone()));
1140 self.chunk.emit_op(Op::ConstructEnum, line);
1141 self.chunk.emit((enum_idx >> 8) as u8, line);
1142 self.chunk.emit((enum_idx & 0xff) as u8, line);
1143 self.chunk.emit((variant_idx >> 8) as u8, line);
1144 self.chunk.emit((variant_idx & 0xff) as u8, line);
1145 self.chunk.emit(0u8, line);
1146 }
1147 ExprKind::EnumVariantCall {
1148 enum_name,
1149 variant,
1150 args,
1151 } => {
1152 for arg in args {
1153 self.compile_expr(arg)?;
1154 }
1155 let enum_idx = self.chunk.add_constant(Value::Str(enum_name.clone()));
1156 let variant_idx = self.chunk.add_constant(Value::Str(variant.clone()));
1157 self.chunk.emit_op(Op::ConstructEnum, line);
1158 self.chunk.emit((enum_idx >> 8) as u8, line);
1159 self.chunk.emit((enum_idx & 0xff) as u8, line);
1160 self.chunk.emit((variant_idx >> 8) as u8, line);
1161 self.chunk.emit((variant_idx & 0xff) as u8, line);
1162 self.chunk.emit(args.len() as u8, line);
1163 }
1164
1165 #[cfg(feature = "concurrency")]
1166 ExprKind::AsyncBlock(_)
1167 | ExprKind::SpawnExpr(_)
1168 | ExprKind::AwaitExpr(_)
1169 | ExprKind::SelectExpr(_) => {
1170 return Err(IonError::runtime(
1171 ion_str!("concurrency not supported in bytecode VM").to_string(),
1172 line,
1173 col,
1174 ));
1175 }
1176 #[cfg(not(feature = "concurrency"))]
1177 ExprKind::AsyncBlock(_)
1178 | ExprKind::SpawnExpr(_)
1179 | ExprKind::AwaitExpr(_)
1180 | ExprKind::SelectExpr(_) => {
1181 return Err(IonError::runtime(
1182 ion_str!("concurrency not available").to_string(),
1183 line,
1184 col,
1185 ));
1186 }
1187
1188 ExprKind::TryCatch { body, var, handler } => {
1189 let try_begin_patch = self.chunk.emit_jump(Op::TryBegin, line);
1191
1192 self.begin_scope(line);
1194 let old_tail = self.in_tail_position;
1195 self.in_tail_position = false;
1196 for (i, stmt) in body.iter().enumerate() {
1197 if i == body.len() - 1 {
1198 if let crate::ast::StmtKind::ExprStmt { expr, .. } = &stmt.kind {
1199 self.compile_expr(expr)?;
1200 } else {
1201 self.compile_stmt(stmt)?;
1202 self.chunk.emit_op(Op::Unit, line);
1203 }
1204 } else {
1205 self.compile_stmt(stmt)?;
1206 }
1207 }
1208 if body.is_empty() {
1209 self.chunk.emit_op(Op::Unit, line);
1210 }
1211 self.in_tail_position = old_tail;
1212 self.end_scope(line);
1213
1214 let try_end_patch = self.chunk.emit_jump(Op::TryEnd, line);
1216
1217 self.chunk.patch_jump(try_begin_patch);
1219
1220 self.begin_scope(line);
1222 self.emit_define_local(var, false, line);
1223 for (i, stmt) in handler.iter().enumerate() {
1224 if i == handler.len() - 1 {
1225 if let crate::ast::StmtKind::ExprStmt { expr, .. } = &stmt.kind {
1226 self.compile_expr(expr)?;
1227 } else {
1228 self.compile_stmt(stmt)?;
1229 self.chunk.emit_op(Op::Unit, line);
1230 }
1231 } else {
1232 self.compile_stmt(stmt)?;
1233 }
1234 }
1235 if handler.is_empty() {
1236 self.chunk.emit_op(Op::Unit, line);
1237 }
1238 self.end_scope(line);
1239
1240 self.chunk.patch_jump(try_end_patch);
1242 }
1243 }
1244 self.in_tail_position = was_tail;
1245 Ok(())
1246 }
1247
1248 fn compile_block_expr(&mut self, stmts: &[Stmt], line: usize) -> Result<(), IonError> {
1249 if stmts.is_empty() {
1250 self.chunk.emit_op(Op::Unit, line);
1251 return Ok(());
1252 }
1253 let len = stmts.len();
1254 let saved_tail = self.in_tail_position;
1255 for (i, stmt) in stmts.iter().enumerate() {
1256 let is_last = i == len - 1;
1257 if !is_last {
1259 self.in_tail_position = false;
1260 } else {
1261 self.in_tail_position = saved_tail;
1262 }
1263 match &stmt.kind {
1264 StmtKind::ExprStmt { expr, has_semi } => {
1265 if is_last && *has_semi {
1266 self.in_tail_position = false;
1267 }
1268 self.compile_expr(expr)?;
1269 if is_last && !has_semi {
1270 } else {
1272 self.chunk.emit_op(Op::Pop, stmt.span.line);
1273 }
1274 }
1275 _ => {
1276 self.in_tail_position = false;
1277 self.compile_stmt(stmt)?;
1278 if is_last {
1279 self.chunk.emit_op(Op::Unit, stmt.span.line);
1280 }
1281 }
1282 }
1283 #[cfg(feature = "optimize")]
1285 if !is_last && Self::stmt_is_terminal(stmt) {
1286 break;
1287 }
1288 }
1289 self.in_tail_position = saved_tail;
1290 Ok(())
1291 }
1292
1293 fn type_ann_to_string(ann: &TypeAnn) -> String {
1294 match ann {
1295 TypeAnn::Simple(name) => name.clone(),
1296 TypeAnn::Option(inner) => format!("Option<{}>", Self::type_ann_to_string(inner)),
1297 TypeAnn::Result(ok, err) => format!(
1298 "Result<{}, {}>",
1299 Self::type_ann_to_string(ok),
1300 Self::type_ann_to_string(err)
1301 ),
1302 TypeAnn::List(inner) => format!("list<{}>", Self::type_ann_to_string(inner)),
1303 TypeAnn::Dict(k, v) => format!(
1304 "dict<{}, {}>",
1305 Self::type_ann_to_string(k),
1306 Self::type_ann_to_string(v)
1307 ),
1308 }
1309 }
1310
1311 fn compile_let_pattern(
1312 &mut self,
1313 pattern: &Pattern,
1314 mutable: bool,
1315 line: usize,
1316 ) -> Result<(), IonError> {
1317 match pattern {
1318 Pattern::Ident(name) => {
1319 self.emit_define_local(name, mutable, line);
1320 }
1321 Pattern::Tuple(pats) => {
1322 for (i, pat) in pats.iter().enumerate() {
1324 self.chunk.emit_op(Op::Dup, line);
1325 self.chunk.emit_constant(Value::Int(i as i64), line);
1326 self.chunk.emit_op(Op::GetIndex, line);
1327 self.compile_let_pattern(pat, mutable, line)?;
1328 }
1329 self.chunk.emit_op(Op::Pop, line); }
1331 Pattern::List(pats, rest) => {
1332 for (i, pat) in pats.iter().enumerate() {
1333 self.chunk.emit_op(Op::Dup, line);
1334 self.chunk.emit_constant(Value::Int(i as i64), line);
1335 self.chunk.emit_op(Op::GetIndex, line);
1336 self.compile_let_pattern(pat, mutable, line)?;
1337 }
1338 if let Some(rest_pat) = rest {
1339 self.chunk.emit_op(Op::Dup, line);
1340 self.chunk
1341 .emit_constant(Value::Int(pats.len() as i64), line);
1342 self.chunk.emit_op_u8(Op::Slice, 1, line); self.compile_let_pattern(rest_pat, mutable, line)?;
1344 }
1345 self.chunk.emit_op(Op::Pop, line);
1346 }
1347 Pattern::Wildcard => {
1348 self.chunk.emit_op(Op::Pop, line);
1349 }
1350 _ => {
1351 return Err(IonError::runtime(
1352 ion_str!("complex pattern not yet supported in bytecode VM let").to_string(),
1353 line,
1354 0,
1355 ));
1356 }
1357 }
1358 Ok(())
1359 }
1360
1361 fn compile_fn_decl(
1362 &mut self,
1363 name: &str,
1364 params: &[Param],
1365 body: &[Stmt],
1366 line: usize,
1367 ) -> Result<(), IonError> {
1368 let mut fn_compiler = Compiler::new();
1370 fn_compiler.in_tail_position = true;
1371 fn_compiler.needs_env_locals = Self::stmts_have_closures(body);
1373 for param in params {
1375 fn_compiler.add_local(param.name.clone(), false);
1376 }
1377 if fn_compiler.needs_env_locals {
1379 for (i, param) in params.iter().enumerate() {
1380 fn_compiler
1381 .chunk
1382 .emit_op_u16(Op::GetLocalSlot, i as u16, line);
1383 let idx = fn_compiler
1384 .chunk
1385 .add_constant(Value::Str(param.name.clone()));
1386 fn_compiler.chunk.emit_op_u16(Op::DefineLocal, idx, line);
1387 fn_compiler.chunk.emit(0, line); }
1389 }
1390 fn_compiler.compile_block_expr(body, line)?;
1391 fn_compiler.chunk.emit_op(Op::Return, line);
1392 #[cfg(feature = "optimize")]
1393 fn_compiler.chunk.peephole_optimize();
1394 let compiled_chunk = fn_compiler.chunk;
1395 self.fn_chunks.extend(fn_compiler.fn_chunks);
1397
1398 let fn_value = Value::Fn(crate::value::IonFn::new(
1399 name.to_string(),
1400 params.to_vec(),
1401 body.to_vec(), std::collections::HashMap::new(),
1403 ));
1404 if let Value::Fn(ref ion_fn) = fn_value {
1406 self.fn_chunks.insert(ion_fn.fn_id, compiled_chunk);
1407 }
1408
1409 self.chunk.emit_constant(fn_value, line);
1411 self.emit_define_local(name, false, line);
1412 Ok(())
1413 }
1414
1415 fn compile_for(
1416 &mut self,
1417 label: Option<String>,
1418 pattern: &Pattern,
1419 iter: &Expr,
1420 body: &[Stmt],
1421 line: usize,
1422 ) -> Result<(), IonError> {
1423 self.compile_expr(iter)?;
1425
1426 self.chunk.emit_op(Op::IterInit, line);
1428
1429 let loop_start = self.chunk.len();
1430 self.loop_stack.push(LoopFrame {
1431 label,
1432 break_jumps: Vec::new(),
1433 continue_target: loop_start,
1434 is_for_loop: true,
1435 scope_depth: self.scope_depth,
1436 });
1437
1438 let exit_jump = self.chunk.emit_jump(Op::IterNext, line);
1440
1441 self.begin_scope(line);
1443 self.compile_let_pattern(pattern, false, line)?;
1444
1445 for stmt in body {
1447 self.compile_stmt(stmt)?;
1448 #[cfg(feature = "optimize")]
1449 if Self::stmt_is_terminal(stmt) {
1450 break;
1451 }
1452 }
1453 self.end_scope(line);
1454
1455 self.chunk.emit_op(Op::Unit, line);
1457
1458 let offset = self.chunk.len() - loop_start + 3;
1460 self.chunk.emit_op_u16(Op::Loop, offset as u16, line);
1461
1462 self.chunk.patch_jump(exit_jump);
1463 self.chunk.emit_op(Op::Pop, line);
1465
1466 let frame = self.loop_stack.pop().expect("loop frame");
1467 for jump in &frame.break_jumps {
1468 self.chunk.patch_jump(*jump);
1469 }
1470 Ok(())
1471 }
1472
1473 fn compile_while(
1474 &mut self,
1475 label: Option<String>,
1476 cond: &Expr,
1477 body: &[Stmt],
1478 line: usize,
1479 ) -> Result<(), IonError> {
1480 let loop_start = self.chunk.len();
1481 self.loop_stack.push(LoopFrame {
1482 label,
1483 break_jumps: Vec::new(),
1484 continue_target: loop_start,
1485 is_for_loop: false,
1486 scope_depth: self.scope_depth,
1487 });
1488
1489 self.compile_expr(cond)?;
1490 let exit_jump = self.chunk.emit_jump(Op::JumpIfFalse, line);
1491 self.chunk.emit_op(Op::Pop, line); self.begin_scope(line);
1494 for stmt in body {
1495 self.compile_stmt(stmt)?;
1496 #[cfg(feature = "optimize")]
1497 if Self::stmt_is_terminal(stmt) {
1498 break;
1499 }
1500 }
1501 self.end_scope(line);
1502
1503 let offset = self.chunk.len() - loop_start + 3;
1504 self.chunk.emit_op_u16(Op::Loop, offset as u16, line);
1505
1506 self.chunk.patch_jump(exit_jump);
1507 self.chunk.emit_op(Op::Pop, line); let frame = self.loop_stack.pop().expect("loop frame");
1510 for jump in &frame.break_jumps {
1511 self.chunk.patch_jump(*jump);
1512 }
1513 Ok(())
1514 }
1515
1516 fn compile_loop(
1517 &mut self,
1518 label: Option<String>,
1519 body: &[Stmt],
1520 line: usize,
1521 ) -> Result<(), IonError> {
1522 let loop_start = self.chunk.len();
1523 self.loop_stack.push(LoopFrame {
1524 label,
1525 break_jumps: Vec::new(),
1526 continue_target: loop_start,
1527 is_for_loop: false,
1528 scope_depth: self.scope_depth,
1529 });
1530
1531 self.begin_scope(line);
1532 for stmt in body {
1533 self.compile_stmt(stmt)?;
1534 #[cfg(feature = "optimize")]
1535 if Self::stmt_is_terminal(stmt) {
1536 break;
1537 }
1538 }
1539 self.end_scope(line);
1540
1541 let offset = self.chunk.len() - loop_start + 3;
1542 self.chunk.emit_op_u16(Op::Loop, offset as u16, line);
1543
1544 let frame = self.loop_stack.pop().expect("loop frame");
1545 for jump in &frame.break_jumps {
1546 self.chunk.patch_jump(*jump);
1547 }
1548 Ok(())
1549 }
1550
1551 fn compile_assign(
1552 &mut self,
1553 target: &AssignTarget,
1554 op: &AssignOp,
1555 value: &Expr,
1556 line: usize,
1557 ) -> Result<(), IonError> {
1558 match target {
1559 AssignTarget::Ident(name) => {
1560 match op {
1561 AssignOp::Eq => {
1562 self.compile_expr(value)?;
1563 }
1564 AssignOp::PlusEq | AssignOp::MinusEq | AssignOp::StarEq | AssignOp::SlashEq => {
1565 self.emit_get_var(name, line);
1566 self.compile_expr(value)?;
1567 match op {
1568 AssignOp::PlusEq => self.chunk.emit_op(Op::Add, line),
1569 AssignOp::MinusEq => self.chunk.emit_op(Op::Sub, line),
1570 AssignOp::StarEq => self.chunk.emit_op(Op::Mul, line),
1571 AssignOp::SlashEq => self.chunk.emit_op(Op::Div, line),
1572 _ => unreachable!(),
1573 }
1574 }
1575 }
1576 self.emit_set_var(name, line);
1577 }
1578 AssignTarget::Index(obj_expr, index_expr) => {
1579 let var_name = match &obj_expr.kind {
1583 ExprKind::Ident(name) => name.clone(),
1584 _ => {
1585 return Err(IonError::runtime(
1586 ion_str!("index assignment only supported on variables").to_string(),
1587 line,
1588 0,
1589 ))
1590 }
1591 };
1592
1593 self.compile_expr(obj_expr)?;
1595 self.compile_expr(index_expr)?;
1596
1597 match op {
1599 AssignOp::Eq => {
1600 self.compile_expr(value)?;
1601 }
1602 _ => {
1603 self.compile_expr(obj_expr)?;
1605 self.compile_expr(index_expr)?;
1606 self.chunk.emit_op(Op::GetIndex, line);
1607 self.compile_expr(value)?;
1608 match op {
1609 AssignOp::PlusEq => self.chunk.emit_op(Op::Add, line),
1610 AssignOp::MinusEq => self.chunk.emit_op(Op::Sub, line),
1611 AssignOp::StarEq => self.chunk.emit_op(Op::Mul, line),
1612 AssignOp::SlashEq => self.chunk.emit_op(Op::Div, line),
1613 _ => unreachable!(),
1614 }
1615 }
1616 }
1617
1618 self.chunk.emit_op(Op::SetIndex, line);
1620 self.emit_set_var(&var_name, line);
1622 }
1623 AssignTarget::Field(obj_expr, field) => {
1624 let var_name = match &obj_expr.kind {
1625 ExprKind::Ident(name) => name.clone(),
1626 _ => {
1627 return Err(IonError::runtime(
1628 ion_str!("field assignment only supported on variables").to_string(),
1629 line,
1630 0,
1631 ))
1632 }
1633 };
1634
1635 self.compile_expr(obj_expr)?;
1636
1637 match op {
1638 AssignOp::Eq => {
1639 self.compile_expr(value)?;
1640 }
1641 _ => {
1642 self.chunk.emit_op(Op::Dup, line);
1643 let get_idx = self.chunk.add_constant(Value::Str(field.clone()));
1644 self.chunk.emit_op_u16(Op::GetField, get_idx, line);
1645 self.compile_expr(value)?;
1646 match op {
1647 AssignOp::PlusEq => self.chunk.emit_op(Op::Add, line),
1648 AssignOp::MinusEq => self.chunk.emit_op(Op::Sub, line),
1649 AssignOp::StarEq => self.chunk.emit_op(Op::Mul, line),
1650 AssignOp::SlashEq => self.chunk.emit_op(Op::Div, line),
1651 _ => unreachable!(),
1652 }
1653 }
1654 }
1655
1656 let field_idx = self.chunk.add_constant(Value::Str(field.clone()));
1658 self.chunk.emit_op_u16(Op::SetField, field_idx, line);
1659 self.emit_set_var(&var_name, line);
1661 }
1662 }
1663 Ok(())
1664 }
1665
1666 pub fn compile_fn_body(
1668 mut self,
1669 params: &[Param],
1670 body: &[Stmt],
1671 line: usize,
1672 ) -> Result<Chunk, IonError> {
1673 self.in_tail_position = true;
1674 self.needs_env_locals = Self::stmts_have_closures(body);
1675 for param in params {
1677 self.add_local(param.name.clone(), false);
1678 }
1679 if self.needs_env_locals {
1681 for (i, param) in params.iter().enumerate() {
1682 self.chunk.emit_op_u16(Op::GetLocalSlot, i as u16, line);
1683 let idx = self.chunk.add_constant(Value::Str(param.name.clone()));
1684 self.chunk.emit_op_u16(Op::DefineLocal, idx, line);
1685 self.chunk.emit(0, line); }
1687 }
1688 self.compile_block_expr(body, line)?;
1689 self.chunk.emit_op(Op::Return, line);
1690 Ok(self.chunk)
1691 }
1692
1693 fn compile_match(
1694 &mut self,
1695 subject: &Expr,
1696 arms: &[MatchArm],
1697 line: usize,
1698 ) -> Result<(), IonError> {
1699 let was_tail = self.in_tail_position;
1700 self.begin_scope(line);
1702 self.in_tail_position = false;
1703 self.compile_expr(subject)?;
1704 let tmp_name = "__match_subject__";
1705 self.emit_define_local(tmp_name, false, line);
1706 let subject_slot = self.locals.len() - 1;
1707
1708 let mut end_jumps = Vec::new();
1709
1710 for arm in arms {
1711 self.chunk
1713 .emit_op_u16(Op::GetLocalSlot, subject_slot as u16, line);
1714
1715 self.compile_pattern_test(&arm.pattern, line)?;
1717
1718 if let Some(guard) = &arm.guard {
1720 let skip_guard = self.chunk.emit_jump(Op::JumpIfFalse, line);
1721 self.chunk.emit_op(Op::Pop, line); self.compile_expr(guard)?;
1723 let after_guard = self.chunk.emit_jump(Op::Jump, line);
1724 self.chunk.patch_jump(skip_guard);
1725 self.chunk.patch_jump(after_guard);
1727 }
1728
1729 let next_arm = self.chunk.emit_jump(Op::JumpIfFalse, line);
1730 self.chunk.emit_op(Op::Pop, line); self.begin_scope(line);
1734 self.chunk
1735 .emit_op_u16(Op::GetLocalSlot, subject_slot as u16, line);
1736 self.compile_pattern_bind(&arm.pattern, line)?;
1737
1738 self.in_tail_position = was_tail;
1740 self.compile_expr(&arm.body)?;
1741 self.end_scope(line);
1742
1743 end_jumps.push(self.chunk.emit_jump(Op::Jump, line));
1744
1745 self.chunk.patch_jump(next_arm);
1746 self.chunk.emit_op(Op::Pop, line); }
1748
1749 self.chunk.emit_op(Op::MatchEnd, line);
1751
1752 for j in end_jumps {
1753 self.chunk.patch_jump(j);
1754 }
1755
1756 self.end_scope(line); Ok(())
1758 }
1759
1760 fn compile_pattern_test(&mut self, pattern: &Pattern, line: usize) -> Result<(), IonError> {
1762 match pattern {
1763 Pattern::Wildcard | Pattern::Ident(_) => {
1764 self.chunk.emit_op(Op::Pop, line); self.chunk.emit_op(Op::True, line); }
1767 Pattern::Int(n) => {
1768 self.chunk.emit_constant(Value::Int(*n), line);
1769 self.chunk.emit_op(Op::Eq, line);
1770 }
1771 Pattern::Float(n) => {
1772 self.chunk.emit_constant(Value::Float(*n), line);
1773 self.chunk.emit_op(Op::Eq, line);
1774 }
1775 Pattern::Bool(b) => {
1776 self.chunk
1777 .emit_op(if *b { Op::True } else { Op::False }, line);
1778 self.chunk.emit_op(Op::Eq, line);
1779 }
1780 Pattern::Str(s) => {
1781 self.chunk.emit_constant(Value::Str(s.clone()), line);
1782 self.chunk.emit_op(Op::Eq, line);
1783 }
1784 Pattern::Bytes(b) => {
1785 self.chunk.emit_constant(Value::Bytes(b.clone()), line);
1786 self.chunk.emit_op(Op::Eq, line);
1787 }
1788 Pattern::None => {
1789 self.chunk.emit_op(Op::None, line);
1791 self.chunk.emit_op(Op::Eq, line);
1792 }
1793 Pattern::Some(inner) => {
1794 self.chunk.emit_op_u8(Op::MatchBegin, 1, line); let fail_jump = self.chunk.emit_jump(Op::JumpIfFalse, line);
1801 self.chunk.emit_op(Op::Pop, line); self.chunk.emit_op_u8(Op::MatchArm, 1, line); self.compile_pattern_test(inner, line)?;
1805 let end = self.chunk.emit_jump(Op::Jump, line);
1806 self.chunk.patch_jump(fail_jump);
1807 self.chunk.patch_jump(end);
1809 }
1810 Pattern::Ok(inner) => {
1811 self.chunk.emit_op_u8(Op::MatchBegin, 2, line); let fail_jump = self.chunk.emit_jump(Op::JumpIfFalse, line);
1813 self.chunk.emit_op(Op::Pop, line);
1814 self.chunk.emit_op_u8(Op::MatchArm, 2, line); self.compile_pattern_test(inner, line)?;
1816 let end = self.chunk.emit_jump(Op::Jump, line);
1817 self.chunk.patch_jump(fail_jump);
1818 self.chunk.patch_jump(end);
1819 }
1820 Pattern::Err(inner) => {
1821 self.chunk.emit_op_u8(Op::MatchBegin, 3, line); let fail_jump = self.chunk.emit_jump(Op::JumpIfFalse, line);
1823 self.chunk.emit_op(Op::Pop, line);
1824 self.chunk.emit_op_u8(Op::MatchArm, 3, line); self.compile_pattern_test(inner, line)?;
1826 let end = self.chunk.emit_jump(Op::Jump, line);
1827 self.chunk.patch_jump(fail_jump);
1828 self.chunk.patch_jump(end);
1829 }
1830 Pattern::Tuple(pats) => {
1831 self.chunk.emit_op_u8(Op::MatchBegin, 4, line); self.chunk.emit(pats.len() as u8, line); let fail_jump = self.chunk.emit_jump(Op::JumpIfFalse, line);
1835 self.chunk.emit_op(Op::Pop, line); for (i, pat) in pats.iter().enumerate() {
1838 self.chunk.emit_op_u8(Op::MatchArm, 4, line); self.chunk.emit(i as u8, line);
1841 self.compile_pattern_test(pat, line)?;
1842 let sub_fail = self.chunk.emit_jump(Op::JumpIfFalse, line);
1843 self.chunk.emit_op(Op::Pop, line); if i == pats.len() - 1 {
1845 self.chunk.emit_op(Op::True, line);
1847 }
1848 let sub_end = self.chunk.emit_jump(Op::Jump, line);
1850 self.chunk.patch_jump(sub_fail);
1851 self.chunk.patch_jump(sub_end);
1853 }
1854 if pats.is_empty() {
1855 self.chunk.emit_op(Op::True, line);
1856 }
1857 let end = self.chunk.emit_jump(Op::Jump, line);
1858 self.chunk.patch_jump(fail_jump);
1859 self.chunk.patch_jump(end);
1861 }
1862 Pattern::List(pats, rest) => {
1863 let has_rest = rest.is_some();
1865 self.chunk.emit_op_u8(Op::MatchBegin, 5, line); self.chunk.emit(pats.len() as u8, line); self.chunk.emit(if has_rest { 1 } else { 0 }, line); let fail_jump = self.chunk.emit_jump(Op::JumpIfFalse, line);
1869 self.chunk.emit_op(Op::Pop, line); for (i, pat) in pats.iter().enumerate() {
1872 self.chunk.emit_op_u8(Op::MatchArm, 5, line); self.chunk.emit(i as u8, line);
1874 self.compile_pattern_test(pat, line)?;
1875 let sub_fail = self.chunk.emit_jump(Op::JumpIfFalse, line);
1876 self.chunk.emit_op(Op::Pop, line); if i == pats.len() - 1 {
1878 self.chunk.emit_op(Op::True, line);
1879 }
1880 let sub_end = self.chunk.emit_jump(Op::Jump, line);
1881 self.chunk.patch_jump(sub_fail);
1882 self.chunk.patch_jump(sub_end);
1883 }
1884 if pats.is_empty() {
1885 self.chunk.emit_op(Op::True, line);
1886 }
1887 let end = self.chunk.emit_jump(Op::Jump, line);
1888 self.chunk.patch_jump(fail_jump);
1889 self.chunk.patch_jump(end);
1890 }
1891 _ => {
1892 return Err(IonError::runtime(
1894 ion_str!("complex pattern not yet supported in bytecode VM match").to_string(),
1895 line,
1896 0,
1897 ));
1898 }
1899 }
1900 Ok(())
1901 }
1902
1903 fn compile_pattern_bind(&mut self, pattern: &Pattern, line: usize) -> Result<(), IonError> {
1905 match pattern {
1906 Pattern::Wildcard => {
1907 self.chunk.emit_op(Op::Pop, line);
1908 }
1909 Pattern::Ident(name) => {
1910 self.emit_define_local(name, false, line);
1911 }
1912 Pattern::Int(_)
1913 | Pattern::Float(_)
1914 | Pattern::Bool(_)
1915 | Pattern::Str(_)
1916 | Pattern::Bytes(_)
1917 | Pattern::None => {
1918 self.chunk.emit_op(Op::Pop, line); }
1920 Pattern::Some(inner) => {
1921 self.chunk.emit_op_u8(Op::MatchArm, 1, line); self.compile_pattern_bind(inner, line)?;
1924 }
1925 Pattern::Ok(inner) => {
1926 self.chunk.emit_op_u8(Op::MatchArm, 2, line); self.compile_pattern_bind(inner, line)?;
1928 }
1929 Pattern::Err(inner) => {
1930 self.chunk.emit_op_u8(Op::MatchArm, 3, line); self.compile_pattern_bind(inner, line)?;
1932 }
1933 Pattern::Tuple(pats) => {
1934 for (i, pat) in pats.iter().enumerate() {
1935 self.chunk.emit_op(Op::Dup, line); self.chunk.emit_constant(Value::Int(i as i64), line);
1937 self.chunk.emit_op(Op::GetIndex, line);
1938 self.compile_pattern_bind(pat, line)?;
1939 }
1940 self.chunk.emit_op(Op::Pop, line); }
1942 Pattern::List(pats, rest) => {
1943 for (i, pat) in pats.iter().enumerate() {
1945 self.chunk.emit_op(Op::Dup, line); self.chunk.emit_constant(Value::Int(i as i64), line);
1947 self.chunk.emit_op(Op::GetIndex, line);
1948 self.compile_pattern_bind(pat, line)?;
1949 }
1950 if let Some(rest_pat) = rest {
1952 self.chunk.emit_op(Op::Dup, line); self.chunk
1955 .emit_constant(Value::Int(pats.len() as i64), line);
1956 self.chunk.emit_op_u8(Op::Slice, 1, line); self.compile_pattern_bind(rest_pat, line)?;
1959 }
1960 self.chunk.emit_op(Op::Pop, line); }
1962 _ => {
1963 return Err(IonError::runtime(
1964 ion_str!("complex pattern binding not yet supported in bytecode VM")
1965 .to_string(),
1966 line,
1967 0,
1968 ));
1969 }
1970 }
1971 Ok(())
1972 }
1973
1974 fn compile_list_comp(
1975 &mut self,
1976 item_expr: &Expr,
1977 pattern: &Pattern,
1978 iter: &Expr,
1979 cond: Option<&Expr>,
1980 line: usize,
1981 ) -> Result<(), IonError> {
1982 self.chunk.emit_op_u16(Op::BuildList, 0, line); self.compile_expr(iter)?;
1987 self.chunk.emit_op(Op::IterInit, line);
1988
1989 let loop_start = self.chunk.len();
1990 let exit_jump = self.chunk.emit_jump(Op::IterNext, line);
1991
1992 self.begin_scope(line);
1994 self.compile_let_pattern(pattern, false, line)?;
1995
1996 if let Some(cond_expr) = cond {
1998 self.compile_expr(cond_expr)?;
1999 let skip_jump = self.chunk.emit_jump(Op::JumpIfFalse, line);
2000 self.chunk.emit_op(Op::Pop, line); self.compile_expr(item_expr)?;
2004 self.chunk.emit_op(Op::ListAppend, line);
2005
2006 let after = self.chunk.emit_jump(Op::Jump, line);
2007 self.chunk.patch_jump(skip_jump);
2008 self.chunk.emit_op(Op::Pop, line); self.chunk.patch_jump(after);
2010 } else {
2011 self.compile_expr(item_expr)?;
2013 self.chunk.emit_op(Op::ListAppend, line);
2014 }
2015
2016 self.end_scope(line);
2017
2018 self.chunk.emit_op(Op::Unit, line);
2020
2021 let offset = self.chunk.len() - loop_start + 3;
2023 self.chunk.emit_op_u16(Op::Loop, offset as u16, line);
2024
2025 self.chunk.patch_jump(exit_jump);
2026 self.chunk.emit_op(Op::Pop, line); Ok(())
2029 }
2030
2031 fn compile_dict_comp(
2032 &mut self,
2033 key_expr: &Expr,
2034 value_expr: &Expr,
2035 pattern: &Pattern,
2036 iter: &Expr,
2037 cond: Option<&Expr>,
2038 line: usize,
2039 ) -> Result<(), IonError> {
2040 self.chunk.emit_op_u16(Op::BuildDict, 0, line);
2042
2043 self.compile_expr(iter)?;
2044 self.chunk.emit_op(Op::IterInit, line);
2045
2046 let loop_start = self.chunk.len();
2047 let exit_jump = self.chunk.emit_jump(Op::IterNext, line);
2048
2049 self.begin_scope(line);
2050 self.compile_let_pattern(pattern, false, line)?;
2051
2052 if let Some(cond_expr) = cond {
2053 self.compile_expr(cond_expr)?;
2054 let skip_jump = self.chunk.emit_jump(Op::JumpIfFalse, line);
2055 self.chunk.emit_op(Op::Pop, line);
2056
2057 self.compile_expr(key_expr)?;
2058 self.compile_expr(value_expr)?;
2059 self.chunk.emit_op(Op::DictInsert, line);
2060
2061 let after = self.chunk.emit_jump(Op::Jump, line);
2062 self.chunk.patch_jump(skip_jump);
2063 self.chunk.emit_op(Op::Pop, line);
2064 self.chunk.patch_jump(after);
2065 } else {
2066 self.compile_expr(key_expr)?;
2067 self.compile_expr(value_expr)?;
2068 self.chunk.emit_op(Op::DictInsert, line);
2069 }
2070
2071 self.end_scope(line);
2072
2073 self.chunk.emit_op(Op::Unit, line);
2075
2076 let offset = self.chunk.len() - loop_start + 3;
2077 self.chunk.emit_op_u16(Op::Loop, offset as u16, line);
2078
2079 self.chunk.patch_jump(exit_jump);
2080 self.chunk.emit_op(Op::Pop, line);
2081 Ok(())
2082 }
2083}