1use crate::ast::{
20 ArgSep, AssignmentSpan, CallableSpan, CaseGuardSpan, CaseRelOp, DoGuard, DoSpan, Expr,
21 ExprType, ForSpan, IfSpan, OnErrorSpan, SelectSpan, Statement, VarRef, WhileSpan,
22};
23use crate::bytecode::{self, ErrorHandlerMode, PackedArrayType, Register};
24use crate::callable::{ArgSepSyntax, CallableMetadata, RequiredValueSyntax, SingularArgSyntax};
25use crate::compiler::args::{compile_args, define_new_args};
26use crate::compiler::codegen::{Codegen, Fixup};
27use crate::compiler::exprs::{compile_expr, compile_expr_as_type, compile_integer_exprs};
28use crate::compiler::syms::{
29 self, GlobalSymtable, LocalSymtable, SymbolKey, SymbolPrototype, TempSymtable,
30};
31use crate::compiler::{Error, Result};
32use crate::image::{GlobalVarInfo, Image, ImageDelta};
33use crate::mem::ConstantDatum;
34use crate::reader::LineCol;
35use crate::{Callable, CallableMetadataBuilder, parser};
36use std::borrow::Cow;
37use std::cmp::max;
38use std::collections::{HashMap, HashSet};
39use std::io;
40use std::iter::Iterator;
41use std::rc::Rc;
42
43use super::syms::LocalSymtableSnapshot;
44
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47enum CallableKind {
48 Function,
50
51 Sub,
53}
54
55#[derive(Clone, Default)]
60pub(super) struct Context {
61 codegen: Codegen,
63
64 data: Vec<Option<ConstantDatum>>,
66
67 do_exit_stack: Vec<Vec<(usize, LineCol)>>,
69
70 for_exit_stack: Vec<Vec<(usize, LineCol)>>,
72
73 current_callable: Option<CallableKind>,
75
76 defined_callables: HashSet<SymbolKey>,
78
79 callable_exit_jumps: Vec<(usize, LineCol)>,
81}
82
83fn data_expr_to_constant(expr: Expr) -> ConstantDatum {
85 match expr {
86 Expr::Boolean(span) => ConstantDatum::Boolean(span.value),
87 Expr::Double(span) => ConstantDatum::Double(span.value),
88 Expr::Integer(span) => ConstantDatum::Integer(span.value),
89 Expr::Text(span) => ConstantDatum::Text(span.value),
90 _ => unreachable!("Parser guarantees DATA only contains literal values"),
91 }
92}
93
94fn static_end_code(expr: &Expr) -> Option<(i32, LineCol)> {
96 match expr {
97 Expr::Integer(span) => Some((span.value, span.pos)),
98 Expr::Negate(span) => {
99 let Expr::Integer(inner) = &span.expr else {
100 return None;
101 };
102 inner.value.checked_neg().map(|value| (value, span.pos))
103 }
104 _ => None,
105 }
106}
107
108fn expr_references_symbol(expr: &Expr, target_key: &SymbolKey) -> bool {
110 let mut stack = vec![expr];
111 while let Some(expr) = stack.pop() {
112 match expr {
113 Expr::Boolean(..) | Expr::Double(..) | Expr::Integer(..) | Expr::Text(..) => {}
114
115 Expr::Symbol(span) => {
116 if SymbolKey::from(&span.vref.name) == *target_key {
117 return true;
118 }
119 }
120
121 Expr::Negate(span) | Expr::Not(span) => {
122 stack.push(&span.expr);
123 }
124
125 Expr::Call(span) => {
126 for arg in &span.args {
127 if let Some(arg_expr) = &arg.expr {
128 stack.push(arg_expr);
129 }
130 }
131 }
132
133 Expr::Add(span)
134 | Expr::And(span)
135 | Expr::Divide(span)
136 | Expr::Equal(span)
137 | Expr::Greater(span)
138 | Expr::GreaterEqual(span)
139 | Expr::Less(span)
140 | Expr::LessEqual(span)
141 | Expr::Modulo(span)
142 | Expr::Multiply(span)
143 | Expr::NotEqual(span)
144 | Expr::Or(span)
145 | Expr::Power(span)
146 | Expr::ShiftLeft(span)
147 | Expr::ShiftRight(span)
148 | Expr::Subtract(span)
149 | Expr::Xor(span) => {
150 stack.push(&span.lhs);
151 stack.push(&span.rhs);
152 }
153 }
154 }
155 false
156}
157
158fn compile_assignment(
160 codegen: &mut Codegen,
161 symtable: &mut LocalSymtable<'_>,
162 span: AssignmentSpan,
163) -> Result<()> {
164 let AssignmentSpan { vref, vref_pos, expr } = span;
165 let key = SymbolKey::from(&vref.name);
166
167 let (reg, etype, was_defined) = match symtable.get_local_or_global(&vref) {
168 Ok((_, SymbolPrototype::Array(_))) => {
169 return Err(Error::ArrayUsedAsScalar(vref_pos, vref));
170 }
171
172 Ok((reg, SymbolPrototype::Scalar(etype))) => (reg, Some(etype), true),
173
174 Err(syms::Error::UndefinedSymbol(..)) => {
175 if symtable.get_callable(&key).is_some() {
176 return Err(Error::from_syms(syms::Error::AlreadyDefined(vref.clone()), vref_pos));
177 }
178 let reg = symtable
179 .put_local(
180 key.clone(),
181 SymbolPrototype::Scalar(vref.ref_type.unwrap_or(ExprType::Integer)),
182 )
183 .map_err(|e| Error::from_syms(e, vref_pos))?;
184 match vref.ref_type {
185 Some(etype) => (reg, Some(etype), false),
186 None => (reg, None, false),
187 }
188 }
189
190 Err(e) => return Err(Error::from_syms(e, vref_pos)),
191 };
192
193 let has_self_reference = was_defined && expr_references_symbol(&expr, &key);
194
195 if let Some(etype) = etype {
196 let result = if has_self_reference {
199 let mut frozen = symtable.frozen();
200 let mut scope = frozen.temp_scope();
201 let temp = scope.alloc().map_err(|e| Error::from_syms(e, vref_pos))?;
202 let r = compile_expr_as_type(codegen, &mut frozen, temp, expr, etype);
203 if r.is_ok() {
204 codegen.emit(bytecode::make_move(reg, temp), vref_pos);
205 }
206 r
207 } else {
208 compile_expr_as_type(codegen, &mut symtable.frozen(), reg, expr, etype)
209 };
210
211 match result {
212 Err(Error::TypeMismatch(pos, actual, expected)) => {
213 return Err(Error::IncompatibleTypesInAssignment(pos, actual, expected));
214 }
215 r => return r,
216 }
217 }
218
219 let etype = compile_expr(codegen, &mut symtable.frozen(), reg, expr)?;
223 symtable.fixup_local_type(&vref, etype).map_err(|e| Error::from_syms(e, vref_pos))
224}
225
226fn case_relop_name(relop: &CaseRelOp) -> &'static str {
228 match relop {
229 CaseRelOp::Equal => "=",
230 CaseRelOp::NotEqual => "<>",
231 CaseRelOp::Less => "<",
232 CaseRelOp::LessEqual => "<=",
233 CaseRelOp::Greater => ">",
234 CaseRelOp::GreaterEqual => ">=",
235 }
236}
237
238fn case_relop_instr(
240 relop: &CaseRelOp,
241 etype: ExprType,
242) -> Option<fn(Register, Register, Register) -> u32> {
243 match etype {
244 ExprType::Boolean => match relop {
245 CaseRelOp::Equal => Some(bytecode::make_equal_boolean),
246 CaseRelOp::NotEqual => Some(bytecode::make_not_equal_boolean),
247 CaseRelOp::Less
248 | CaseRelOp::LessEqual
249 | CaseRelOp::Greater
250 | CaseRelOp::GreaterEqual => None,
251 },
252
253 ExprType::Double => match relop {
254 CaseRelOp::Equal => Some(bytecode::make_equal_double),
255 CaseRelOp::NotEqual => Some(bytecode::make_not_equal_double),
256 CaseRelOp::Less => Some(bytecode::make_less_double),
257 CaseRelOp::LessEqual => Some(bytecode::make_less_equal_double),
258 CaseRelOp::Greater => Some(bytecode::make_greater_double),
259 CaseRelOp::GreaterEqual => Some(bytecode::make_greater_equal_double),
260 },
261
262 ExprType::Integer => match relop {
263 CaseRelOp::Equal => Some(bytecode::make_equal_integer),
264 CaseRelOp::NotEqual => Some(bytecode::make_not_equal_integer),
265 CaseRelOp::Less => Some(bytecode::make_less_integer),
266 CaseRelOp::LessEqual => Some(bytecode::make_less_equal_integer),
267 CaseRelOp::Greater => Some(bytecode::make_greater_integer),
268 CaseRelOp::GreaterEqual => Some(bytecode::make_greater_equal_integer),
269 },
270
271 ExprType::Text => match relop {
272 CaseRelOp::Equal => Some(bytecode::make_equal_text),
273 CaseRelOp::NotEqual => Some(bytecode::make_not_equal_text),
274 CaseRelOp::Less => Some(bytecode::make_less_text),
275 CaseRelOp::LessEqual => Some(bytecode::make_less_equal_text),
276 CaseRelOp::Greater => Some(bytecode::make_greater_text),
277 CaseRelOp::GreaterEqual => Some(bytecode::make_greater_equal_text),
278 },
279 }
280}
281
282fn compile_case_relop(
284 ctx: &mut Context,
285 pos: LineCol,
286 test: (Register, ExprType),
287 rhs: (Register, ExprType),
288 relop: CaseRelOp,
289 dest: Register,
290) -> Result<()> {
291 let (test_reg, test_type) = test;
292 let (rhs_reg, rhs_type) = rhs;
293 let op_name = case_relop_name(&relop);
294 let opcode = match (test_type, rhs_type) {
295 (ExprType::Double, ExprType::Integer) => {
296 ctx.codegen.emit(bytecode::make_integer_to_double(rhs_reg), pos);
297 case_relop_instr(&relop, ExprType::Double)
298 }
299
300 (ExprType::Integer, ExprType::Double) => {
301 ctx.codegen.emit(bytecode::make_integer_to_double(test_reg), pos);
302 case_relop_instr(&relop, ExprType::Double)
303 }
304
305 (lhs, rhs) if lhs == rhs => case_relop_instr(&relop, lhs),
306 _ => None,
307 };
308
309 match opcode {
310 Some(opcode) => {
311 ctx.codegen.emit(opcode(dest, test_reg, rhs_reg), pos);
312 Ok(())
313 }
314 None => Err(Error::BinaryOpType(pos, op_name, test_type, rhs_type)),
315 }
316}
317
318fn compile_case_guard(
320 ctx: &mut Context,
321 symtable: &mut TempSymtable<'_, '_>,
322 test_reg: Register,
323 test_type: ExprType,
324 guard: CaseGuardSpan,
325) -> Result<(Register, LineCol)> {
326 match guard {
327 CaseGuardSpan::Is(relop, expr) => {
328 let pos = expr.start_pos();
329
330 let mut scope = symtable.temp_scope();
331 let lhs_reg = scope.alloc().map_err(|e| Error::from_syms(e, pos))?;
332 let rhs_reg = scope.alloc().map_err(|e| Error::from_syms(e, pos))?;
333 let cond_reg = scope.alloc().map_err(|e| Error::from_syms(e, pos))?;
334
335 ctx.codegen.emit(bytecode::make_move(lhs_reg, test_reg), pos);
336 let rhs_type = compile_expr(&mut ctx.codegen, symtable, rhs_reg, expr)?;
337 compile_case_relop(
338 ctx,
339 pos,
340 (lhs_reg, test_type),
341 (rhs_reg, rhs_type),
342 relop,
343 cond_reg,
344 )?;
345
346 Ok((cond_reg, pos))
347 }
348
349 CaseGuardSpan::To(from_expr, to_expr) => {
350 let pos = from_expr.start_pos();
351
352 let mut scope = symtable.temp_scope();
353 let lhs_from_reg = scope.alloc().map_err(|e| Error::from_syms(e, pos))?;
354 let rhs_from_reg = scope.alloc().map_err(|e| Error::from_syms(e, pos))?;
355 let cond_from_reg = scope.alloc().map_err(|e| Error::from_syms(e, pos))?;
356
357 let lhs_to_reg = scope.alloc().map_err(|e| Error::from_syms(e, pos))?;
358 let rhs_to_reg = scope.alloc().map_err(|e| Error::from_syms(e, pos))?;
359 let cond_to_reg = scope.alloc().map_err(|e| Error::from_syms(e, pos))?;
360
361 let cond_reg = scope.alloc().map_err(|e| Error::from_syms(e, pos))?;
362
363 ctx.codegen.emit(bytecode::make_move(lhs_from_reg, test_reg), pos);
364 let rhs_from_type = compile_expr(&mut ctx.codegen, symtable, rhs_from_reg, from_expr)?;
365 compile_case_relop(
366 ctx,
367 pos,
368 (lhs_from_reg, test_type),
369 (rhs_from_reg, rhs_from_type),
370 CaseRelOp::GreaterEqual,
371 cond_from_reg,
372 )?;
373
374 ctx.codegen.emit(bytecode::make_move(lhs_to_reg, test_reg), pos);
375 let rhs_to_type = compile_expr(&mut ctx.codegen, symtable, rhs_to_reg, to_expr)?;
376 compile_case_relop(
377 ctx,
378 pos,
379 (lhs_to_reg, test_type),
380 (rhs_to_reg, rhs_to_type),
381 CaseRelOp::LessEqual,
382 cond_to_reg,
383 )?;
384
385 ctx.codegen.emit(bytecode::make_bitwise_and(cond_reg, cond_from_reg, cond_to_reg), pos);
386 Ok((cond_reg, pos))
387 }
388 }
389}
390
391fn compile_select(
393 ctx: &mut Context,
394 symtable: &mut LocalSymtable<'_>,
395 span: SelectSpan,
396) -> Result<()> {
397 let end_pos = span.end_pos;
398 let ncases = span.cases.len();
399 let select_cases = span.cases;
400 let select_expr = span.expr;
401 let select_expr_pos = select_expr.start_pos();
402
403 struct PendingCase {
405 body: Vec<Statement>,
406 body_jump_pcs: Vec<(usize, LineCol)>,
407 has_next_case: bool,
408 }
409
410 let mut pending_cases = Vec::with_capacity(ncases);
411 let mut pending_next_case_jump: Option<(usize, LineCol)> = None;
412
413 symtable.with_reserved_temp(
414 |e| Error::from_syms(e, select_expr_pos),
415 |test_reg, frozen| {
416 let test_type = compile_expr(&mut ctx.codegen, frozen, test_reg, select_expr)?;
417
418 for (i, case) in select_cases.into_iter().enumerate() {
419 let has_next_case = i < ncases - 1;
420 let mut body_jump_pcs = vec![];
421 let case_dispatch_addr = ctx.codegen.next_pc();
422 if let Some((jump_pc, pos)) = pending_next_case_jump.take() {
423 let target = u16::try_from(case_dispatch_addr)
424 .map_err(|_| Error::TargetTooFar(pos, case_dispatch_addr))?;
425 ctx.codegen.patch(jump_pc, bytecode::make_jump(target));
426 }
427
428 if case.guards.is_empty() {
429 let jump_body_pc = ctx.codegen.emit(bytecode::make_nop(), end_pos);
430 body_jump_pcs.push((jump_body_pc, end_pos));
431 } else {
432 for guard in case.guards {
433 let (cond_reg, pos) =
434 compile_case_guard(ctx, frozen, test_reg, test_type, guard)?;
435 let jump_next_guard_pc = ctx.codegen.emit(bytecode::make_nop(), pos);
436 let jump_body_pc = ctx.codegen.emit(bytecode::make_nop(), pos);
437 body_jump_pcs.push((jump_body_pc, pos));
438
439 let next_addr = ctx.codegen.next_pc();
440 let target = u16::try_from(next_addr)
441 .map_err(|_| Error::TargetTooFar(pos, next_addr))?;
442 ctx.codegen.patch(
443 jump_next_guard_pc,
444 bytecode::make_jump_if_false(cond_reg, target),
445 );
446 }
447
448 let jump_next_case_pc = ctx.codegen.emit(bytecode::make_nop(), end_pos);
449 pending_next_case_jump = Some((jump_next_case_pc, end_pos));
450 }
451
452 pending_cases.push(PendingCase { body: case.body, body_jump_pcs, has_next_case });
453 }
454
455 Ok(())
456 },
457 )?;
458
459 let dispatch_end_jump_pc = ctx.codegen.emit(bytecode::make_nop(), end_pos);
460 if let Some((jump_pc, pos)) = pending_next_case_jump {
461 let target = u16::try_from(dispatch_end_jump_pc)
462 .map_err(|_| Error::TargetTooFar(pos, dispatch_end_jump_pc))?;
463 ctx.codegen.patch(jump_pc, bytecode::make_jump(target));
464 }
465
466 let mut end_jumps = vec![];
467 for case in pending_cases {
468 let body_addr = ctx.codegen.next_pc();
469 for (jump_body_pc, pos) in case.body_jump_pcs {
470 let target =
471 u16::try_from(body_addr).map_err(|_| Error::TargetTooFar(pos, body_addr))?;
472 ctx.codegen.patch(jump_body_pc, bytecode::make_jump(target));
473 }
474
475 for stmt in case.body {
476 compile_stmt(ctx, symtable, stmt)?;
477 }
478
479 if case.has_next_case {
480 end_jumps.push(ctx.codegen.emit(bytecode::make_nop(), end_pos));
481 }
482 }
483
484 let end_addr = ctx.codegen.next_pc();
485 let end_target = u16::try_from(end_addr).map_err(|_| Error::TargetTooFar(end_pos, end_addr))?;
486 ctx.codegen.patch(dispatch_end_jump_pc, bytecode::make_jump(end_target));
487 for end_jump in end_jumps {
488 ctx.codegen.patch(end_jump, bytecode::make_jump(end_target));
489 }
490
491 Ok(())
492}
493
494fn compile_do(ctx: &mut Context, symtable: &mut LocalSymtable<'_>, span: DoSpan) -> Result<()> {
496 fn compile_guard(
498 ctx: &mut Context,
499 symtable: &mut LocalSymtable<'_>,
500 guard: Expr,
501 ) -> Result<(Register, LineCol)> {
502 let guard_pos = guard.start_pos();
503 let mut frozen = symtable.frozen();
504 let mut scope = frozen.temp_scope();
505 let reg = scope.alloc().map_err(|e| Error::from_syms(e, guard_pos))?;
506 compile_expr_as_type(&mut ctx.codegen, &mut frozen, reg, guard, ExprType::Boolean)?;
507 Ok((reg, guard_pos))
508 }
509
510 ctx.do_exit_stack.push(vec![]);
511
512 let end_addr = match span.guard {
513 DoGuard::Infinite => {
514 let start_pc = ctx.codegen.next_pc();
515 for stmt in span.body {
516 compile_stmt(ctx, symtable, stmt)?;
517 }
518 let end_pos = LineCol { line: 0, col: 0 };
519 let target =
520 u16::try_from(start_pc).map_err(|_| Error::TargetTooFar(end_pos, start_pc))?;
521 ctx.codegen.emit(bytecode::make_jump(target), end_pos);
522 ctx.codegen.next_pc()
523 }
524
525 DoGuard::PreUntil(guard) => {
526 let start_pc = ctx.codegen.next_pc();
527 let (cond_reg, guard_pos) = compile_guard(ctx, symtable, guard)?;
528 let jump_body_pc = ctx.codegen.emit(bytecode::make_nop(), guard_pos);
529 let jump_end_pc = ctx.codegen.emit(bytecode::make_nop(), guard_pos);
530 let body_addr = ctx.codegen.next_pc();
531 let body_target =
532 u16::try_from(body_addr).map_err(|_| Error::TargetTooFar(guard_pos, body_addr))?;
533 ctx.codegen.patch(jump_body_pc, bytecode::make_jump_if_false(cond_reg, body_target));
534
535 for stmt in span.body {
536 compile_stmt(ctx, symtable, stmt)?;
537 }
538 let start_target =
539 u16::try_from(start_pc).map_err(|_| Error::TargetTooFar(guard_pos, start_pc))?;
540 ctx.codegen.emit(bytecode::make_jump(start_target), guard_pos);
541 let end_addr = ctx.codegen.next_pc();
542 let end_target =
543 u16::try_from(end_addr).map_err(|_| Error::TargetTooFar(guard_pos, end_addr))?;
544 ctx.codegen.patch(jump_end_pc, bytecode::make_jump(end_target));
545 end_addr
546 }
547
548 DoGuard::PreWhile(guard) => {
549 let start_pc = ctx.codegen.next_pc();
550 let (cond_reg, guard_pos) = compile_guard(ctx, symtable, guard)?;
551 let jump_end_pc = ctx.codegen.emit(bytecode::make_nop(), guard_pos);
552
553 for stmt in span.body {
554 compile_stmt(ctx, symtable, stmt)?;
555 }
556 let start_target =
557 u16::try_from(start_pc).map_err(|_| Error::TargetTooFar(guard_pos, start_pc))?;
558 ctx.codegen.emit(bytecode::make_jump(start_target), guard_pos);
559 let end_addr = ctx.codegen.next_pc();
560 let end_target =
561 u16::try_from(end_addr).map_err(|_| Error::TargetTooFar(guard_pos, end_addr))?;
562 ctx.codegen.patch(jump_end_pc, bytecode::make_jump_if_false(cond_reg, end_target));
563 end_addr
564 }
565
566 DoGuard::PostUntil(guard) => {
567 let start_pc = ctx.codegen.next_pc();
568 for stmt in span.body {
569 compile_stmt(ctx, symtable, stmt)?;
570 }
571 let (cond_reg, guard_pos) = compile_guard(ctx, symtable, guard)?;
572 let start_target =
573 u16::try_from(start_pc).map_err(|_| Error::TargetTooFar(guard_pos, start_pc))?;
574 ctx.codegen.emit(bytecode::make_jump_if_false(cond_reg, start_target), guard_pos);
575 ctx.codegen.next_pc()
576 }
577
578 DoGuard::PostWhile(guard) => {
579 let start_pc = ctx.codegen.next_pc();
580 for stmt in span.body {
581 compile_stmt(ctx, symtable, stmt)?;
582 }
583 let (cond_reg, guard_pos) = compile_guard(ctx, symtable, guard)?;
584 let jump_end_pc = ctx.codegen.emit(bytecode::make_nop(), guard_pos);
585 let start_target =
586 u16::try_from(start_pc).map_err(|_| Error::TargetTooFar(guard_pos, start_pc))?;
587 ctx.codegen.emit(bytecode::make_jump(start_target), guard_pos);
588 let end_addr = ctx.codegen.next_pc();
589 let end_target =
590 u16::try_from(end_addr).map_err(|_| Error::TargetTooFar(guard_pos, end_addr))?;
591 ctx.codegen.patch(jump_end_pc, bytecode::make_jump_if_false(cond_reg, end_target));
592 end_addr
593 }
594 };
595
596 let exit_jumps = ctx.do_exit_stack.pop().expect("Must have a matching DO scope");
597 for (addr, pos) in exit_jumps {
598 let end_target = u16::try_from(end_addr).map_err(|_| Error::TargetTooFar(pos, end_addr))?;
599 ctx.codegen.patch(addr, bytecode::make_jump(end_target));
600 }
601
602 Ok(())
603}
604
605fn compile_for(ctx: &mut Context, symtable: &mut LocalSymtable<'_>, span: ForSpan) -> Result<()> {
607 if span.iter_double && span.iter.ref_type.is_none() {
608 match symtable.get_local_or_global(&span.iter) {
609 Ok(..) => {
610 }
613
614 Err(syms::Error::UndefinedSymbol(..)) => {
615 let key = SymbolKey::from(&span.iter.name);
616 if symtable.get_callable(&key).is_some() {
617 return Err(Error::from_syms(
618 syms::Error::AlreadyDefined(span.iter.clone()),
619 span.iter_pos,
620 ));
621 }
622 symtable
623 .put_local(key, SymbolPrototype::Scalar(ExprType::Double))
624 .map_err(|e| Error::from_syms(e, span.iter_pos))?;
625 }
626
627 Err(e) => return Err(Error::from_syms(e, span.iter_pos)),
628 }
629 }
630
631 compile_assignment(
632 &mut ctx.codegen,
633 symtable,
634 AssignmentSpan { vref: span.iter.clone(), vref_pos: span.iter_pos, expr: span.start },
635 )?;
636
637 let start_pc = ctx.codegen.next_pc();
638 let (jump_end_pc, cond_reg, cond_pos) = {
639 let cond_pos = span.end.start_pos();
640 let mut frozen = symtable.frozen();
641 let mut scope = frozen.temp_scope();
642 let reg = scope.alloc().map_err(|e| Error::from_syms(e, cond_pos))?;
643 compile_expr_as_type(&mut ctx.codegen, &mut frozen, reg, span.end, ExprType::Boolean)?;
644 (ctx.codegen.emit(bytecode::make_nop(), cond_pos), reg, cond_pos)
645 };
646 ctx.codegen.mark_statement_start(start_pc);
647
648 ctx.for_exit_stack.push(vec![]);
649
650 for stmt in span.body {
651 compile_stmt(ctx, symtable, stmt)?;
652 }
653
654 compile_assignment(
655 &mut ctx.codegen,
656 symtable,
657 AssignmentSpan { vref: span.iter, vref_pos: span.iter_pos, expr: span.next },
658 )?;
659
660 let start_target =
661 u16::try_from(start_pc).map_err(|_| Error::TargetTooFar(cond_pos, start_pc))?;
662 ctx.codegen.emit(bytecode::make_jump(start_target), cond_pos);
663
664 let end_addr = ctx.codegen.next_pc();
665 let end_target =
666 u16::try_from(end_addr).map_err(|_| Error::TargetTooFar(cond_pos, end_addr))?;
667 ctx.codegen.patch(jump_end_pc, bytecode::make_jump_if_false(cond_reg, end_target));
668
669 let exit_jumps = ctx.for_exit_stack.pop().expect("Must have a matching FOR scope");
670 for (addr, pos) in exit_jumps {
671 let end_target = u16::try_from(end_addr).map_err(|_| Error::TargetTooFar(pos, end_addr))?;
672 ctx.codegen.patch(addr, bytecode::make_jump(end_target));
673 }
674
675 Ok(())
676}
677
678fn compile_if(ctx: &mut Context, symtable: &mut LocalSymtable<'_>, span: IfSpan) -> Result<()> {
680 let mut end_pcs: Vec<usize> = vec![];
681 let nbranches = span.branches.len();
682
683 for (i, branch) in span.branches.into_iter().enumerate() {
684 let is_last = i == nbranches - 1;
685 let guard_pos = branch.guard.start_pos();
686
687 let (jump_pc, cond_reg) = {
688 let mut frozen = symtable.frozen();
689 let mut scope = frozen.temp_scope();
690 let reg = scope.alloc().map_err(|e| Error::from_syms(e, guard_pos))?;
691 compile_expr_as_type(
692 &mut ctx.codegen,
693 &mut frozen,
694 reg,
695 branch.guard,
696 ExprType::Boolean,
697 )?;
698 (ctx.codegen.emit(bytecode::make_nop(), guard_pos), reg)
699 };
700
701 for stmt in branch.body {
702 compile_stmt(ctx, symtable, stmt)?;
703 }
704
705 if !is_last {
706 let end_pc = ctx.codegen.emit(bytecode::make_nop(), guard_pos);
707 end_pcs.push(end_pc);
708 }
709
710 let next_addr = ctx.codegen.next_pc();
711 let target =
712 u16::try_from(next_addr).map_err(|_| Error::TargetTooFar(guard_pos, next_addr))?;
713 ctx.codegen.patch(jump_pc, bytecode::make_jump_if_false(cond_reg, target));
714 }
715
716 let end_addr = ctx.codegen.next_pc();
717 for end_pc in end_pcs {
718 let end_target = u16::try_from(end_addr)
719 .map_err(|_| Error::TargetTooFar(LineCol { line: 0, col: 0 }, end_addr))?;
720 ctx.codegen.patch(end_pc, bytecode::make_jump(end_target));
721 }
722
723 Ok(())
724}
725
726fn compile_while(
728 ctx: &mut Context,
729 symtable: &mut LocalSymtable<'_>,
730 span: WhileSpan,
731) -> Result<()> {
732 let start_pc = ctx.codegen.next_pc();
733
734 let (jump_end_pc, cond_reg, guard_pos) = {
735 let guard_pos = span.expr.start_pos();
736 let mut frozen = symtable.frozen();
737 let mut scope = frozen.temp_scope();
738 let reg = scope.alloc().map_err(|e| Error::from_syms(e, guard_pos))?;
739 compile_expr_as_type(&mut ctx.codegen, &mut frozen, reg, span.expr, ExprType::Boolean)?;
740 (ctx.codegen.emit(bytecode::make_nop(), guard_pos), reg, guard_pos)
741 };
742
743 for stmt in span.body {
744 compile_stmt(ctx, symtable, stmt)?;
745 }
746
747 let start_target =
748 u16::try_from(start_pc).map_err(|_| Error::TargetTooFar(guard_pos, start_pc))?;
749 ctx.codegen.emit(bytecode::make_jump(start_target), guard_pos);
750
751 let end_addr = ctx.codegen.next_pc();
752 let end_target =
753 u16::try_from(end_addr).map_err(|_| Error::TargetTooFar(guard_pos, end_addr))?;
754 ctx.codegen.patch(jump_end_pc, bytecode::make_jump_if_false(cond_reg, end_target));
755
756 Ok(())
757}
758
759fn compile_stmt(
761 ctx: &mut Context,
762 symtable: &mut LocalSymtable<'_>,
763 stmt: Statement,
764) -> Result<()> {
765 let start_pc = ctx.codegen.next_pc();
766 let mut mark_start = true;
767 match stmt {
768 Statement::ArrayAssignment(span) => {
769 let key_pos = span.vref_pos;
770
771 let (arr_reg, info) = match symtable.get_local_or_global(&span.vref) {
772 Ok((reg, SymbolPrototype::Array(info))) => (reg, info),
773
774 Ok((_, SymbolPrototype::Scalar(_))) | Err(syms::Error::UndefinedSymbol(..)) => {
775 return Err(Error::NotAnArray(key_pos, span.vref));
776 }
777
778 Err(e) => return Err(Error::from_syms(e, key_pos)),
779 };
780
781 if span.subscripts.len() != info.ndims {
782 return Err(Error::WrongNumberOfSubscripts(
783 key_pos,
784 info.ndims,
785 span.subscripts.len(),
786 ));
787 }
788
789 let mut symtable = symtable.frozen();
790 let mut outer_scope = symtable.temp_scope();
791
792 let val_reg = outer_scope.alloc().map_err(|e| Error::from_syms(e, key_pos))?;
793 compile_expr_as_type(
794 &mut ctx.codegen,
795 &mut symtable,
796 val_reg,
797 span.expr,
798 info.subtype,
799 )?;
800
801 let first_sub_reg = compile_integer_exprs(
802 &mut ctx.codegen,
803 &mut symtable,
804 &mut outer_scope,
805 key_pos,
806 span.subscripts.into_iter(),
807 )?;
808 ctx.codegen.emit(bytecode::make_store_array(arr_reg, val_reg, first_sub_reg), key_pos);
809 }
810
811 Statement::Assignment(span) => {
812 compile_assignment(&mut ctx.codegen, symtable, span)?;
813 }
814
815 Statement::Call(span) => {
816 let key = SymbolKey::from(&span.vref.name);
817 let key_pos = span.vref_pos;
818
819 let Some(md) = symtable.get_callable(&key) else {
820 return Err(Error::UndefinedSymbol(key_pos, span.vref.clone()));
821 };
822 if md.return_type().is_some() {
823 return Err(Error::NotAFunction(span.vref_pos, span.vref));
824 }
825 let is_user_defined = md.is_user_defined();
826 let md = md.clone();
827
828 define_new_args(&span, &md, symtable, &mut ctx.codegen)?;
829 let (first_temp, arg_linecols) = {
830 let mut symtable = symtable.frozen();
831 compile_args(span, md.clone(), &mut symtable, &mut ctx.codegen)?
832 };
833
834 if is_user_defined {
835 let addr = ctx.codegen.emit(bytecode::make_nop(), key_pos);
836 ctx.codegen.set_arg_linecols(addr, arg_linecols);
837 ctx.codegen.add_fixup(addr, Fixup::Call(first_temp, key));
838 } else {
839 let upcall = ctx.codegen.get_upcall(key, None, key_pos)?;
840 let op = if md.is_async() {
841 bytecode::make_upcall_async(upcall, first_temp)
842 } else {
843 bytecode::make_upcall(upcall, first_temp)
844 };
845 let addr = ctx.codegen.emit(op, key_pos);
846 ctx.codegen.set_arg_linecols(addr, arg_linecols);
847 }
848 }
849
850 Statement::Callable(span) => {
851 mark_start = false;
852 declare_callable(symtable, &span.name, span.name_pos, &span.params)?;
853
854 let key = SymbolKey::from(&span.name.name);
855 if ctx.defined_callables.contains(&key) {
858 return Err(Error::AlreadyDefined(span.name_pos, span.name));
859 }
860 compile_user_callable(ctx, symtable.global(), span)?;
861 ctx.defined_callables.insert(key);
862 }
863
864 Statement::Data(span) => {
865 ctx.data.extend(span.values.into_iter().map(|expr| expr.map(data_expr_to_constant)));
866 }
867
868 Statement::Declare(span) => {
869 mark_start = false;
870 if ctx.current_callable.is_some() {
871 return Err(Error::CannotNestUserCallables(span.name_pos));
872 }
873 declare_callable(symtable, &span.name, span.name_pos, &span.params)?;
874 }
875
876 Statement::Dim(span) => {
877 let name_pos = span.name_pos;
878 let key = SymbolKey::from(&span.name);
879
880 if symtable.get_callable(&key).is_some() {
881 return Err(Error::from_syms(
882 syms::Error::AlreadyDefined(VarRef::new(&span.name, None)),
883 name_pos,
884 ));
885 }
886
887 let reg = if span.shared {
888 if symtable.contains_global(&key) {
889 return Err(Error::from_syms(
890 syms::Error::AlreadyDefined(VarRef::new(&span.name, None)),
891 name_pos,
892 ));
893 }
894 symtable.put_global(key, SymbolPrototype::Scalar(span.vtype))
895 } else {
896 if symtable.contains_local(&key) {
897 return Err(Error::from_syms(
898 syms::Error::AlreadyDefined(VarRef::new(&span.name, None)),
899 name_pos,
900 ));
901 }
902 symtable.put_local(key, SymbolPrototype::Scalar(span.vtype))
903 }
904 .map_err(|e| Error::from_syms(e, name_pos))?;
905 ctx.codegen.emit_default(reg, span.vtype, name_pos);
906 }
907
908 Statement::DimArray(span) => {
909 let name_pos = span.name_pos;
910 let key = SymbolKey::from(&span.name);
911 let ndims = span.dimensions.len();
912
913 if symtable.get_callable(&key).is_some() {
914 return Err(Error::from_syms(
915 syms::Error::AlreadyDefined(VarRef::new(&span.name, None)),
916 name_pos,
917 ));
918 }
919
920 let info = syms::ArrayInfo { subtype: span.subtype, ndims };
921 let reg = if span.shared {
922 if symtable.contains_global(&key) {
923 return Err(Error::from_syms(
924 syms::Error::AlreadyDefined(VarRef::new(&span.name, None)),
925 name_pos,
926 ));
927 }
928 symtable.put_global(key, SymbolPrototype::Array(info))
929 } else {
930 if symtable.contains_local(&key) {
931 return Err(Error::from_syms(
932 syms::Error::AlreadyDefined(VarRef::new(&span.name, None)),
933 name_pos,
934 ));
935 }
936 symtable.put_local(key, SymbolPrototype::Array(info))
937 }
938 .map_err(|e| Error::from_syms(e, name_pos))?;
939
940 let mut symtable = symtable.frozen();
941 let mut outer_scope = symtable.temp_scope();
942
943 let first_dim_reg = compile_integer_exprs(
944 &mut ctx.codegen,
945 &mut symtable,
946 &mut outer_scope,
947 name_pos,
948 span.dimensions.into_iter(),
949 )?;
950 let packed = PackedArrayType::new(span.subtype, ndims)
951 .map_err(|_| Error::TooManyArrayDimensions(span.name_pos, ndims))?;
952 ctx.codegen.emit(bytecode::make_alloc_array(reg, packed, first_dim_reg), name_pos);
953 }
954
955 Statement::Do(span) => {
956 compile_do(ctx, symtable, span)?;
957 }
958
959 Statement::End(span) => {
960 if let Some(expr) = span.code.as_ref()
961 && let Some((code, code_pos)) = static_end_code(expr)
962 && let Err(e) = bytecode::ExitCode::try_from(code)
963 {
964 return Err(Error::from_bytecode_invalid_exit_code(e, code_pos));
965 }
966
967 let mut symtable = symtable.frozen();
968 let mut scope = symtable.temp_scope();
969 let reg = scope.alloc().map_err(|e| Error::from_syms(e, span.pos))?;
970 match span.code {
971 Some(expr) => {
972 compile_expr_as_type(
973 &mut ctx.codegen,
974 &mut symtable,
975 reg,
976 expr,
977 ExprType::Integer,
978 )?;
979 }
980 None => {
981 ctx.codegen.emit(bytecode::make_load_integer(reg, 0), span.pos);
982 }
983 }
984 ctx.codegen.emit(bytecode::make_end(reg), span.pos);
985 }
986
987 Statement::ExitDo(span) => {
988 let Some(exit_stack) = ctx.do_exit_stack.last_mut() else {
989 return Err(Error::MisplacedExit(span.pos, "DO"));
990 };
991 let addr = ctx.codegen.emit(bytecode::make_nop(), span.pos);
992 exit_stack.push((addr, span.pos));
993 }
994
995 Statement::ExitFor(span) => {
996 let Some(exit_stack) = ctx.for_exit_stack.last_mut() else {
997 return Err(Error::MisplacedExit(span.pos, "FOR"));
998 };
999 let addr = ctx.codegen.emit(bytecode::make_nop(), span.pos);
1000 exit_stack.push((addr, span.pos));
1001 }
1002
1003 Statement::ExitFunction(span) => {
1004 if ctx.current_callable != Some(CallableKind::Function) {
1005 return Err(Error::MisplacedExit(span.pos, "FUNCTION"));
1006 }
1007 let addr = ctx.codegen.emit(bytecode::make_nop(), span.pos);
1008 ctx.callable_exit_jumps.push((addr, span.pos));
1009 }
1010
1011 Statement::ExitSub(span) => {
1012 if ctx.current_callable != Some(CallableKind::Sub) {
1013 return Err(Error::MisplacedExit(span.pos, "SUB"));
1014 }
1015 let addr = ctx.codegen.emit(bytecode::make_nop(), span.pos);
1016 ctx.callable_exit_jumps.push((addr, span.pos));
1017 }
1018
1019 Statement::For(span) => {
1020 compile_for(ctx, symtable, span)?;
1021 }
1022
1023 Statement::Gosub(span) => {
1024 let addr = ctx.codegen.emit(bytecode::make_nop(), span.target_pos);
1025 ctx.codegen.add_fixup(addr, Fixup::Gosub(span.target));
1026 }
1027
1028 Statement::Goto(span) => {
1029 let addr = ctx.codegen.emit(bytecode::make_nop(), span.target_pos);
1030 ctx.codegen.add_fixup(addr, Fixup::Goto(span.target));
1031 }
1032
1033 Statement::Label(span) => {
1034 mark_start = false;
1035 if !ctx.codegen.define_label(SymbolKey::from(&span.name), ctx.codegen.next_pc()) {
1036 return Err(Error::DuplicateLabel(span.name_pos, span.name));
1037 }
1038 }
1039
1040 Statement::Return(span) => {
1041 ctx.codegen.emit(bytecode::make_return(), span.pos);
1042 }
1043
1044 Statement::If(span) => {
1045 compile_if(ctx, symtable, span)?;
1046 }
1047
1048 Statement::OnError(span) => {
1049 match span {
1050 OnErrorSpan::Goto(span, pos) => {
1051 let addr = ctx.codegen.emit(bytecode::make_nop(), pos);
1052 ctx.codegen.add_fixup(addr, Fixup::OnErrorGoto(span.target));
1053 }
1054 OnErrorSpan::Reset(pos) => {
1055 ctx.codegen
1056 .emit(bytecode::make_set_error_handler(ErrorHandlerMode::None, 0), pos);
1057 }
1058 OnErrorSpan::ResumeNext(pos) => {
1059 ctx.codegen.emit(
1060 bytecode::make_set_error_handler(ErrorHandlerMode::ResumeNext, 0),
1061 pos,
1062 );
1063 }
1064 };
1065 }
1066
1067 Statement::Select(span) => {
1068 compile_select(ctx, symtable, span)?;
1069 }
1070
1071 Statement::While(span) => {
1072 compile_while(ctx, symtable, span)?;
1073 }
1074 }
1075 if mark_start && start_pc != ctx.codegen.next_pc() {
1076 ctx.codegen.mark_statement_start(start_pc);
1077 }
1078 Ok(())
1079}
1080
1081fn declare_callable(
1086 symtable: &mut LocalSymtable,
1087 name: &VarRef,
1088 name_pos: LineCol,
1089 params: &[VarRef],
1090) -> Result<()> {
1091 let mut syntax = vec![];
1092 for (i, param) in params.iter().enumerate() {
1093 let sep = if i == params.len() - 1 {
1094 ArgSepSyntax::End
1095 } else {
1096 ArgSepSyntax::Exactly(ArgSep::Long)
1097 };
1098 syntax.push(SingularArgSyntax::RequiredValue(
1099 RequiredValueSyntax {
1100 name: Cow::Owned(param.name.to_owned()),
1101 vtype: param.ref_type.unwrap_or(ExprType::Integer),
1102 },
1103 sep,
1104 ));
1105 }
1106
1107 let mut builder = CallableMetadataBuilder::new_dynamic(name.name.to_owned())
1108 .with_dynamic_syntax(vec![(syntax, None)]);
1109 if let Some(ctype) = name.ref_type {
1110 builder = builder.with_return_type(ctype);
1111 }
1112
1113 symtable.declare_user_callable(name, builder.build()).map_err(|e| Error::from_syms(e, name_pos))
1114}
1115
1116fn compile_user_callable(
1118 ctx: &mut Context,
1119 symtable: &mut GlobalSymtable,
1120 callable: CallableSpan,
1121) -> Result<()> {
1122 if ctx.current_callable.is_some() {
1123 return Err(Error::CannotNestUserCallables(callable.name_pos));
1124 }
1125
1126 let skip_pc = ctx.codegen.emit(bytecode::make_nop(), callable.name_pos);
1127
1128 let start_pc = ctx.codegen.next_pc();
1129
1130 let key_pos = callable.name_pos;
1131 let key = SymbolKey::from(callable.name.name);
1132 ctx.current_callable = Some(if callable.name.ref_type.is_some() {
1133 CallableKind::Function
1134 } else {
1135 CallableKind::Sub
1136 });
1137 debug_assert!(ctx.callable_exit_jumps.is_empty());
1138
1139 let mut symtable = symtable.enter_scope();
1140
1141 if let Some(vtype) = callable.name.ref_type {
1144 let ret_reg = symtable
1145 .put_local(key.clone(), SymbolPrototype::Scalar(vtype))
1146 .map_err(|e| Error::from_syms(e, key_pos))?;
1147
1148 let value = match vtype {
1153 ExprType::Boolean | ExprType::Integer => 0,
1154 ExprType::Double => ctx.codegen.get_constant(ConstantDatum::Double(0.0), key_pos)?,
1155 ExprType::Text => {
1156 ctx.codegen.get_constant(ConstantDatum::Text(String::new()), key_pos)?
1157 }
1158 };
1159 ctx.codegen.emit(bytecode::make_load_integer(ret_reg, value), key_pos);
1160 }
1161 for param in callable.params {
1162 let key = SymbolKey::from(param.name);
1163 symtable
1164 .put_local(
1165 key.clone(),
1166 SymbolPrototype::Scalar(param.ref_type.unwrap_or(ExprType::Integer)),
1167 )
1168 .map_err(|e| Error::from_syms(e, key_pos))?;
1169 }
1170
1171 for stmt in callable.body {
1172 compile_stmt(ctx, &mut symtable, stmt)?;
1173 }
1174
1175 let return_addr = ctx.codegen.emit(bytecode::make_return(), callable.end_pos);
1176 for (addr, pos) in ctx.callable_exit_jumps.drain(..) {
1177 let target =
1178 u16::try_from(return_addr).map_err(|_| Error::TargetTooFar(pos, return_addr))?;
1179 ctx.codegen.patch(addr, bytecode::make_jump(target));
1180 }
1181 ctx.current_callable = None;
1182 let skip_addr = ctx.codegen.next_pc();
1183 ctx.codegen.define_user_callable(key, start_pc, skip_addr);
1184
1185 let target =
1186 u16::try_from(skip_addr).map_err(|_| Error::TargetTooFar(callable.end_pos, skip_addr))?;
1187 ctx.codegen.patch(skip_pc, bytecode::make_jump(target));
1188
1189 Ok(())
1190}
1191
1192pub fn only_metadata(
1194 upcalls_by_name: &HashMap<SymbolKey, Rc<dyn Callable>>,
1195) -> HashMap<SymbolKey, Rc<CallableMetadata>> {
1196 let mut upcalls = HashMap::with_capacity(upcalls_by_name.len());
1197 for (name, callable) in upcalls_by_name {
1198 upcalls.insert(name.clone(), callable.metadata());
1199 }
1200 upcalls
1201}
1202
1203#[derive(Clone)]
1205pub struct GlobalDef {
1206 pub name: String,
1208
1209 pub kind: GlobalDefKind,
1211}
1212
1213#[derive(Clone)]
1215pub enum GlobalDefKind {
1216 Scalar {
1218 etype: ExprType,
1220
1221 initial_value: Option<ConstantDatum>,
1225 },
1226
1227 Array {
1231 subtype: ExprType,
1233
1234 dimensions: Vec<usize>,
1236 },
1237}
1238
1239pub(super) fn prepare_globals(
1248 ctx: &mut Context,
1249 symtable: &mut GlobalSymtable,
1250 global_defs: &[GlobalDef],
1251) -> Result<()> {
1252 let preamble_pos = LineCol { line: 0, col: 0 };
1253
1254 let mut max_ndims: u8 = 0;
1256 let mut array_globals: Vec<(Register, &GlobalDef)> = vec![];
1257 for def in global_defs {
1258 let key = SymbolKey::from(&def.name);
1259 match &def.kind {
1260 GlobalDefKind::Array { subtype, dimensions } => {
1261 let ndims =
1262 u8::try_from(dimensions.len()).expect("Array must have at most 255 dimensions");
1263 let info = syms::ArrayInfo { subtype: *subtype, ndims: usize::from(ndims) };
1264 let reg = symtable
1265 .put_global(key, SymbolPrototype::Array(info))
1266 .map_err(|e| Error::from_syms(e, preamble_pos))?;
1267 max_ndims = max(max_ndims, ndims);
1268 array_globals.push((reg, def));
1269 }
1270
1271 GlobalDefKind::Scalar { etype, initial_value } => {
1272 let reg = symtable
1273 .put_global(key, SymbolPrototype::Scalar(*etype))
1274 .map_err(|e| Error::from_syms(e, preamble_pos))?;
1275 match initial_value {
1276 Some(datum) => {
1277 if datum.etype() != *etype {
1278 return Err(Error::TypeMismatch(preamble_pos, datum.etype(), *etype));
1279 }
1280 ctx.codegen.emit_value(reg, datum.clone(), preamble_pos)?;
1281 }
1282 None => ctx.codegen.emit_default(reg, *etype, preamble_pos),
1283 }
1284 }
1285 }
1286 }
1287
1288 if array_globals.is_empty() {
1293 ctx.codegen.emit(bytecode::make_eof(), preamble_pos);
1295 return Ok(());
1296 }
1297 for (reg, def) in array_globals {
1298 let GlobalDefKind::Array { subtype, dimensions } = &def.kind else {
1299 unreachable!("array_globals only contains array defs per the loop above")
1300 };
1301
1302 let ndims = u8::try_from(dimensions.len()).unwrap();
1303 for (i, &dim) in dimensions.iter().enumerate() {
1304 let dim_u16 =
1305 u16::try_from(dim).expect("Array dimension must fit in u16 for LOADI instruction");
1306 let local_reg =
1307 Register::local(u8::try_from(i).unwrap()).expect("Dimension index fits in u8");
1308 ctx.codegen.emit(bytecode::make_load_integer(local_reg, dim_u16), preamble_pos);
1309 }
1310
1311 let first_dim_reg = Register::local(0).expect("Local register 0 is always valid");
1312 let packed = PackedArrayType::new(*subtype, usize::from(ndims))
1313 .map_err(|_| Error::TooManyArrayDimensions(preamble_pos, usize::from(ndims)))?;
1314 ctx.codegen.emit(bytecode::make_alloc_array(reg, packed, first_dim_reg), preamble_pos);
1315 }
1316
1317 ctx.codegen.emit(bytecode::make_eof(), preamble_pos);
1319 Ok(())
1320}
1321
1322pub fn compile(
1324 input: &mut dyn io::Read,
1325 image: &Image,
1326 ctx: &mut Context,
1327 mut symtable: LocalSymtable,
1328) -> Result<(ImageDelta, LocalSymtableSnapshot)> {
1329 ctx.codegen.pop_eof();
1330 {
1331 for stmt in parser::parse(input) {
1332 compile_stmt(ctx, &mut symtable, stmt?)?;
1333 }
1334 }
1335 ctx.codegen.emit(bytecode::make_eof(), LineCol { line: 0, col: 0 });
1336
1337 let global_vars = symtable
1338 .iter_globals()
1339 .map(|(key, proto, reg)| {
1340 let (subtype, ndims) = match proto {
1341 SymbolPrototype::Array(info) => (info.subtype, info.ndims),
1342 SymbolPrototype::Scalar(etype) => (etype, 0),
1343 };
1344 (key.clone(), GlobalVarInfo { reg, subtype, ndims })
1345 })
1346 .collect();
1347 let program_vars = symtable
1348 .iter_locals()
1349 .map(|(key, proto, reg)| {
1350 let (subtype, ndims) = match proto {
1351 SymbolPrototype::Array(info) => (info.subtype, info.ndims),
1352 SymbolPrototype::Scalar(etype) => (etype, 0),
1353 };
1354 (key.clone(), GlobalVarInfo { reg, subtype, ndims })
1355 })
1356 .collect();
1357 let delta = ctx.codegen.build_image_delta(image, global_vars, program_vars, &ctx.data)?;
1358 Ok((delta, symtable.save()))
1359}
1360
1361#[cfg(test)]
1362mod tests {
1363 use super::*;
1364 use crate::Compiler;
1365 use crate::ast::ExprType;
1366 use crate::mem::ConstantDatum;
1367 use crate::vm::{StopReason, Vm};
1368
1369 fn compile_and_get_global(defs: &[GlobalDef], name: &str) -> ConstantDatum {
1370 let compiler = Compiler::new(&HashMap::default(), defs)
1371 .expect("constants initialization must succeed");
1372 let image = compiler.compile(&mut "".as_bytes()).expect("compilation should succeed");
1373 let mut vm = Vm::new(HashMap::default());
1374 match vm.exec(&image) {
1375 StopReason::End(code) if code.is_success() => {}
1376 StopReason::End(code) => panic!("unexpected exit code: {}", code.to_i32()),
1377 StopReason::Eof => {}
1378 StopReason::Exception(pos, msg) => panic!("exception at {pos}: {msg}"),
1379 StopReason::UpcallAsync(_) => panic!("unexpected upcall"),
1380 StopReason::Yield => unreachable!(),
1381 }
1382 let key = SymbolKey::from(name);
1383 vm.get_global(&image, &key).expect("get_global failed").expect("global not found")
1384 }
1385
1386 #[test]
1387 fn test_inject_boolean() {
1388 let defs = vec![GlobalDef {
1389 name: "b".to_owned(),
1390 kind: GlobalDefKind::Scalar {
1391 etype: ExprType::Boolean,
1392 initial_value: Some(ConstantDatum::Boolean(true)),
1393 },
1394 }];
1395 assert_eq!(ConstantDatum::Boolean(true), compile_and_get_global(&defs, "b"));
1396 }
1397
1398 #[test]
1399 fn test_inject_integer() {
1400 let defs = vec![GlobalDef {
1401 name: "n".to_owned(),
1402 kind: GlobalDefKind::Scalar {
1403 etype: ExprType::Integer,
1404 initial_value: Some(ConstantDatum::Integer(42)),
1405 },
1406 }];
1407 assert_eq!(ConstantDatum::Integer(42), compile_and_get_global(&defs, "n"));
1408 }
1409
1410 #[test]
1411 fn test_inject_integer_large() {
1412 let defs = vec![GlobalDef {
1413 name: "n".to_owned(),
1414 kind: GlobalDefKind::Scalar {
1415 etype: ExprType::Integer,
1416 initial_value: Some(ConstantDatum::Integer(70000)),
1417 },
1418 }];
1419 assert_eq!(ConstantDatum::Integer(70000), compile_and_get_global(&defs, "n"));
1420 }
1421
1422 #[test]
1423 fn test_inject_double() {
1424 let defs = vec![GlobalDef {
1425 name: "d".to_owned(),
1426 kind: GlobalDefKind::Scalar {
1427 etype: ExprType::Double,
1428 initial_value: Some(ConstantDatum::Double(1.5)),
1429 },
1430 }];
1431 assert_eq!(ConstantDatum::Double(1.5), compile_and_get_global(&defs, "d"));
1432 }
1433
1434 #[test]
1435 fn test_inject_text() {
1436 let defs = vec![GlobalDef {
1437 name: "s".to_owned(),
1438 kind: GlobalDefKind::Scalar {
1439 etype: ExprType::Text,
1440 initial_value: Some(ConstantDatum::Text("hello".to_owned())),
1441 },
1442 }];
1443 assert_eq!(ConstantDatum::Text("hello".to_owned()), compile_and_get_global(&defs, "s"),);
1444 }
1445
1446 #[test]
1447 fn test_inject_type_mismatch() {
1448 let defs = vec![GlobalDef {
1449 name: "n".to_owned(),
1450 kind: GlobalDefKind::Scalar {
1451 etype: ExprType::Integer,
1452 initial_value: Some(ConstantDatum::Double(1.5)),
1453 },
1454 }];
1455 let result = Compiler::new(&HashMap::default(), &defs);
1456 assert!(matches!(result, Err(Error::TypeMismatch(..))));
1457 }
1458}