1use std::cell::RefCell;
8use std::rc::Rc;
9
10use rnix::ast::{self, AstToken, HasEntry, InterpolPart};
11use rowan::ast::AstNode;
12
13use crate::chunk::Chunk;
14use crate::error::CompileError;
15use crate::intern::Interner;
16use crate::opcode::OpCode;
17use crate::value::{VMClosure, VMValue};
18
19#[derive(Debug, Clone)]
21struct Local {
22 name: String,
24 depth: u32,
26 is_captured: bool,
28 slot: u16,
33}
34
35#[derive(Debug, Clone, Copy)]
37struct UpvalueDesc {
38 is_local: bool,
41 index: u16,
43}
44
45enum LetBinding {
47 Value(ast::Expr),
49 Inherit,
51 InheritFrom(ast::Expr, String),
53}
54
55enum RecAttrBinding {
57 Value(ast::Expr),
59 Inherit,
61 InheritFrom(ast::Expr, String),
63 Dotted(Vec<(Vec<String>, ast::Expr)>),
65}
66
67pub struct Compiler {
76 chunk: Chunk,
78 locals: Vec<Local>,
80 upvalues: Vec<UpvalueDesc>,
82 scope_depth: u32,
84 current_line: u32,
86 interner: Rc<RefCell<Interner>>,
88 enclosing: Option<*mut Compiler>,
90 with_depth: u32,
92 base_dir: Option<std::path::PathBuf>,
94 stack_depth: u16,
100 source_text: Option<Rc<String>>,
103 tail_position: bool,
108 with_scope_locals: Vec<u16>,
114}
115
116impl Compiler {
117 fn new() -> Self {
119 Self {
120 chunk: Chunk::new(),
121 locals: Vec::new(),
122 upvalues: Vec::new(),
123 scope_depth: 0,
124 current_line: 0,
125 interner: Rc::new(RefCell::new(Interner::new())),
126 enclosing: None,
127 with_depth: 0,
128 base_dir: None,
129 stack_depth: 0,
130 source_text: None,
131 tail_position: false,
132 with_scope_locals: Vec::new(),
133 }
134 }
135
136 fn with_interner(interner: Rc<RefCell<Interner>>) -> Self {
138 Self {
139 chunk: Chunk::new(),
140 locals: Vec::new(),
141 upvalues: Vec::new(),
142 scope_depth: 0,
143 current_line: 0,
144 interner,
145 enclosing: None,
146 with_depth: 0,
147 base_dir: None,
148 stack_depth: 0,
149 source_text: None,
150 tail_position: false,
151 with_scope_locals: Vec::new(),
152 }
153 }
154
155 pub fn compile_with_base_dir(
158 input: &str,
159 base_dir: std::path::PathBuf,
160 ) -> Result<(Chunk, Interner), CompileError> {
161 let parse = rnix::Root::parse(input);
162 if !parse.errors().is_empty() {
163 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
164 return Err(CompileError::ParseError(msgs.join("; ")));
165 }
166 let root = parse.tree();
167 let expr = root
168 .expr()
169 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
170 let mut compiler = Self::new();
171 compiler.base_dir = Some(base_dir);
172 compiler.compile_expr(&expr)?;
173 compiler.emit(OpCode::Return);
174 let interner = match Rc::try_unwrap(compiler.interner) {
175 Ok(cell) => cell.into_inner(),
176 Err(rc) => (*rc).borrow().clone(),
177 };
178 Ok((compiler.chunk, interner))
179 }
180
181 pub fn compile_with_shared_interner(
185 input: &str,
186 base_dir: std::path::PathBuf,
187 interner: Rc<RefCell<Interner>>,
188 ) -> Result<Chunk, CompileError> {
189 let parse = rnix::Root::parse(input);
190 if !parse.errors().is_empty() {
191 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
192 return Err(CompileError::ParseError(msgs.join("; ")));
193 }
194 let root = parse.tree();
195 let expr = root
196 .expr()
197 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
198 let mut compiler = Self::with_interner(interner);
199 compiler.base_dir = Some(base_dir);
200 compiler.source_text = Some(Rc::new(input.to_string()));
201 compiler.compile_expr(&expr)?;
202 compiler.emit(OpCode::Return);
203 Ok(compiler.chunk)
204 }
205
206 pub fn compile_expression(
209 input: &str,
210 base_dir: &std::path::Path,
211 interner: Rc<RefCell<Interner>>,
212 ) -> Result<Chunk, CompileError> {
213 let parse = rnix::Root::parse(input);
214 if !parse.errors().is_empty() {
215 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
216 return Err(CompileError::ParseError(msgs.join("; ")));
217 }
218 let root = parse.tree();
219 let expr = root
220 .expr()
221 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
222 let mut compiler = Self::with_interner(interner);
223 compiler.base_dir = Some(base_dir.to_path_buf());
224 compiler.compile_expr(&expr)?;
225 compiler.emit(OpCode::Return);
226 Ok(compiler.chunk)
227 }
228
229 pub fn compile(input: &str) -> Result<(Chunk, Interner), CompileError> {
231 let parse = rnix::Root::parse(input);
232 if !parse.errors().is_empty() {
233 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
234 return Err(CompileError::ParseError(msgs.join("; ")));
235 }
236 let root = parse.tree();
237 let expr = root
238 .expr()
239 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
240 let mut compiler = Self::new();
241 compiler.compile_expr(&expr)?;
242 compiler.emit(OpCode::Return);
243 let interner = match Rc::try_unwrap(compiler.interner) {
244 Ok(cell) => cell.into_inner(),
245 Err(rc) => (*rc).borrow().clone(),
246 };
247 Ok((compiler.chunk, interner))
248 }
249
250 fn try_eval_const(expr: &ast::Expr) -> Option<VMValue> {
256 match expr {
257 ast::Expr::Literal(lit) => Self::try_eval_literal(lit),
258 ast::Expr::Paren(p) => Self::try_eval_const(&p.expr()?),
259 ast::Expr::UnaryOp(op) => Self::try_fold_unary(op),
260 ast::Expr::BinOp(binop) => Self::try_fold_binop(binop),
261 ast::Expr::IfElse(ie) => Self::try_fold_if(ie),
262 ast::Expr::Ident(id) => {
263 let name = ident_text(id);
264 match name.as_str() {
265 "true" => Some(VMValue::Bool(true)),
266 "false" => Some(VMValue::Bool(false)),
267 "null" => Some(VMValue::Null),
268 _ => None,
269 }
270 }
271 _ => None,
272 }
273 }
274
275 fn try_eval_literal(lit: &ast::Literal) -> Option<VMValue> {
277 match lit.kind() {
278 ast::LiteralKind::Integer(tok) => {
279 Some(VMValue::Int(tok.value().ok()?))
280 }
281 ast::LiteralKind::Float(tok) => {
282 Some(VMValue::Float(tok.value().ok()?))
283 }
284 ast::LiteralKind::Uri(_) => None,
285 }
286 }
287
288 fn try_fold_unary(op: &ast::UnaryOp) -> Option<VMValue> {
290 let inner = Self::try_eval_const(&op.expr()?)?;
291 let kind = op.operator()?;
292 match kind {
293 ast::UnaryOpKind::Negate => match inner {
294 VMValue::Int(n) => Some(VMValue::Int(-n)),
295 VMValue::Float(f) => Some(VMValue::Float(-f)),
296 _ => None,
297 },
298 ast::UnaryOpKind::Invert => match inner {
299 VMValue::Bool(b) => Some(VMValue::Bool(!b)),
300 _ => None,
301 },
302 }
303 }
304
305 fn try_fold_binop(binop: &ast::BinOp) -> Option<VMValue> {
307 let lhs = Self::try_eval_const(&binop.lhs()?)?;
308 let rhs = Self::try_eval_const(&binop.rhs()?)?;
309 let op = binop.operator()?;
310
311 match op {
312 ast::BinOpKind::Add => match (&lhs, &rhs) {
313 (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a + b)),
314 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a + b)),
315 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 + b)),
316 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a + *b as f64)),
317 (VMValue::String(a), VMValue::String(b)) => {
318 Some(VMValue::String(format!("{a}{b}")))
319 }
320 _ => None,
321 },
322 ast::BinOpKind::Sub => match (&lhs, &rhs) {
323 (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a - b)),
324 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a - b)),
325 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 - b)),
326 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a - *b as f64)),
327 _ => None,
328 },
329 ast::BinOpKind::Mul => match (&lhs, &rhs) {
330 (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a * b)),
331 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a * b)),
332 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 * b)),
333 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a * *b as f64)),
334 _ => None,
335 },
336 ast::BinOpKind::Div => match (&lhs, &rhs) {
337 (VMValue::Int(_), VMValue::Int(0)) => None, (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a / b)),
339 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a / b)),
340 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 / b)),
341 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a / *b as f64)),
342 _ => None,
343 },
344 ast::BinOpKind::Equal => Some(VMValue::Bool(Self::const_eq(&lhs, &rhs))),
345 ast::BinOpKind::NotEqual => Some(VMValue::Bool(!Self::const_eq(&lhs, &rhs))),
346 ast::BinOpKind::Less => Self::const_cmp(&lhs, &rhs)
347 .map(|o| VMValue::Bool(o == std::cmp::Ordering::Less)),
348 ast::BinOpKind::LessOrEq => Self::const_cmp(&lhs, &rhs)
349 .map(|o| VMValue::Bool(o != std::cmp::Ordering::Greater)),
350 ast::BinOpKind::More => Self::const_cmp(&lhs, &rhs)
351 .map(|o| VMValue::Bool(o == std::cmp::Ordering::Greater)),
352 ast::BinOpKind::MoreOrEq => Self::const_cmp(&lhs, &rhs)
353 .map(|o| VMValue::Bool(o != std::cmp::Ordering::Less)),
354 ast::BinOpKind::And => match (&lhs, &rhs) {
355 (VMValue::Bool(a), VMValue::Bool(b)) => Some(VMValue::Bool(*a && *b)),
356 _ => None,
357 },
358 ast::BinOpKind::Or => match (&lhs, &rhs) {
359 (VMValue::Bool(a), VMValue::Bool(b)) => Some(VMValue::Bool(*a || *b)),
360 _ => None,
361 },
362 ast::BinOpKind::Implication => match (&lhs, &rhs) {
363 (VMValue::Bool(a), VMValue::Bool(b)) => Some(VMValue::Bool(!a || *b)),
364 _ => None,
365 },
366 _ => None,
367 }
368 }
369
370 fn try_fold_if(ie: &ast::IfElse) -> Option<VMValue> {
372 let cond = Self::try_eval_const(&ie.condition()?)?;
373 match cond {
374 VMValue::Bool(true) => Self::try_eval_const(&ie.body()?),
375 VMValue::Bool(false) => Self::try_eval_const(&ie.else_body()?),
376 _ => None,
377 }
378 }
379
380 fn const_eq(a: &VMValue, b: &VMValue) -> bool {
382 match (a, b) {
383 (VMValue::Null, VMValue::Null) => true,
384 (VMValue::Bool(a), VMValue::Bool(b)) => a == b,
385 (VMValue::Int(a), VMValue::Int(b)) => a == b,
386 (VMValue::Float(a), VMValue::Float(b)) => a == b,
387 (VMValue::Int(a), VMValue::Float(b)) | (VMValue::Float(b), VMValue::Int(a)) => {
388 (*a as f64) == *b
389 }
390 (VMValue::String(a), VMValue::String(b)) => a == b,
391 _ => false,
392 }
393 }
394
395 fn const_cmp(a: &VMValue, b: &VMValue) -> Option<std::cmp::Ordering> {
397 match (a, b) {
398 (VMValue::Int(a), VMValue::Int(b)) => Some(a.cmp(b)),
399 (VMValue::Float(a), VMValue::Float(b)) => a.partial_cmp(b),
400 (VMValue::Int(a), VMValue::Float(b)) => (*a as f64).partial_cmp(b),
401 (VMValue::Float(a), VMValue::Int(b)) => a.partial_cmp(&(*b as f64)),
402 (VMValue::String(a), VMValue::String(b)) => Some(a.cmp(b)),
403 _ => None,
404 }
405 }
406
407 fn compile_expr(&mut self, expr: &ast::Expr) -> Result<(), CompileError> {
410 self.current_line = line_of(expr);
411
412 if let Some(folded) = Self::try_eval_const(expr) {
415 return self.emit_constant(folded);
416 }
417
418 let tail = self.tail_position;
423 self.tail_position = false;
424
425 match expr {
426 ast::Expr::Literal(lit) => self.compile_literal(lit),
427 ast::Expr::Str(s) => self.compile_str(s),
428 ast::Expr::Ident(id) => self.compile_ident(id),
429 ast::Expr::LetIn(letin) => self.compile_let(letin),
430 ast::Expr::AttrSet(set) => self.compile_attrset(set),
431 ast::Expr::Select(sel) => self.compile_select(sel),
432 ast::Expr::HasAttr(ha) => self.compile_has_attr(ha),
433 ast::Expr::IfElse(ie) => {
434 self.tail_position = tail;
435 self.compile_if(ie)
436 }
437 ast::Expr::Lambda(lam) => self.compile_lambda(lam),
438 ast::Expr::Apply(app) => {
439 self.tail_position = tail;
440 self.compile_apply(app)
441 }
442 ast::Expr::BinOp(op) => self.compile_binop(op),
443 ast::Expr::UnaryOp(op) => self.compile_unary(op),
444 ast::Expr::With(w) => self.compile_with(w),
445 ast::Expr::Assert(a) => {
446 self.tail_position = tail;
447 self.compile_assert(a)
448 }
449 ast::Expr::List(l) => self.compile_list(l),
450 ast::Expr::Paren(p) => {
451 self.tail_position = tail;
452 let inner = p
453 .expr()
454 .ok_or_else(|| CompileError::MissingNode("paren expr".to_string()))?;
455 self.compile_expr(&inner)
456 }
457 ast::Expr::Root(r) => {
458 self.tail_position = tail;
459 let inner = r
460 .expr()
461 .ok_or_else(|| CompileError::MissingNode("root expr".to_string()))?;
462 self.compile_expr(&inner)
463 }
464 ast::Expr::PathAbs(p) => {
465 let text = p.syntax().text().to_string();
466 self.emit_constant(VMValue::Path(text))
467 }
468 ast::Expr::PathRel(p) => {
469 let text = p.syntax().text().to_string();
470 let resolved = self.resolve_relative_path(&text);
473 self.emit_constant(VMValue::Path(resolved))
474 }
475 ast::Expr::PathHome(p) => {
476 let text = p.syntax().text().to_string();
477 self.emit_constant(VMValue::Path(text))
478 }
479 ast::Expr::PathSearch(p) => {
480 let text = p.syntax().text().to_string();
481 let inner = text
482 .strip_prefix('<')
483 .and_then(|s| s.strip_suffix('>'))
484 .unwrap_or(&text);
485 if let Some(resolved) = resolve_search_path(inner) {
486 self.emit_constant(VMValue::Path(resolved))
487 } else {
488 let msg = format!("search path '{text}' not in NIX_PATH");
492 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
493 tc.scope_depth = 1;
494 tc.base_dir = self.base_dir.clone();
495 tc.emit_constant(VMValue::String(msg))?;
496 tc.emit(OpCode::Throw);
497 tc.emit(OpCode::Return);
498 let closure = VMValue::Closure(VMClosure {
499 chunk: Rc::new(tc.chunk),
500 upvalues: Vec::new(),
501 arity: 0,
502 name: None,
503 formals: Vec::new(),
504 });
505 let idx = self.chunk.add_constant(closure)?;
506 self.emit(OpCode::MakeThunk);
507 self.stack_depth += 1;
508 self.emit_u16(idx);
509 self.emit_u16(0); Ok(())
511 }
512 }
513 ast::Expr::LegacyLet(ll) => {
514 self.compile_legacy_let(&ll)
518 }
519 ast::Expr::CurPos(_) => {
520 self.emit_constant(VMValue::Null)
522 }
523 other => Err(CompileError::Unsupported(format!("{other:?}"))),
524 }
525 }
526
527 fn compile_literal(&mut self, lit: &ast::Literal) -> Result<(), CompileError> {
530 match lit.kind() {
531 ast::LiteralKind::Integer(tok) => {
532 let n = tok.value().map_err(|e| {
533 CompileError::ParseError(format!("invalid integer: {e}"))
534 })?;
535 self.emit_constant(VMValue::Int(n))
536 }
537 ast::LiteralKind::Float(tok) => {
538 let f = tok.value().map_err(|e| {
539 CompileError::ParseError(format!("invalid float: {e}"))
540 })?;
541 self.emit_constant(VMValue::Float(f))
542 }
543 ast::LiteralKind::Uri(tok) => {
544 let s = tok.syntax().text().to_string();
545 self.emit_constant(VMValue::String(s))
546 }
547 }
548 }
549
550 fn compile_str(&mut self, s: &ast::Str) -> Result<(), CompileError> {
553 let parts: Vec<_> = s.normalized_parts().into_iter().collect();
554
555 if parts.len() == 1 {
557 if let InterpolPart::Literal(text) = &parts[0] {
558 return self.emit_constant(VMValue::String(String::from(text.as_str())));
559 }
560 }
561
562 let mut count: u16 = 0;
564 for part in &parts {
565 match part {
566 InterpolPart::Literal(text) => {
567 self.emit_constant(VMValue::String(text.to_string()))?;
568 count += 1;
569 }
570 InterpolPart::Interpolation(interp) => {
571 let expr = interp
572 .expr()
573 .ok_or_else(|| CompileError::MissingNode("interpolation expr".to_string()))?;
574 self.compile_expr(&expr)?;
575 count += 1;
576 }
577 }
578 }
579
580 if count == 0 {
581 self.emit_constant(VMValue::String(String::new()))
583 } else if count == 1 {
584 Ok(())
586 } else {
587 self.emit(OpCode::Interpolate);
588 self.emit_u16(count);
589 self.stack_depth = self.stack_depth.saturating_sub(count) + 1;
591 Ok(())
592 }
593 }
594
595 fn compile_ident(&mut self, ident: &ast::Ident) -> Result<(), CompileError> {
598 let name = ident_text(ident);
599 match name.as_str() {
600 "true" => {
601 self.emit(OpCode::True);
602 Ok(())
603 }
604 "false" => {
605 self.emit(OpCode::False);
606 Ok(())
607 }
608 "null" => {
609 self.emit(OpCode::Null);
610 Ok(())
611 }
612 _ => {
613 if let Some(idx) = self.resolve_local(&name) {
615 self.emit(OpCode::GetLocal);
616 self.emit_u16(self.local_stack_slot(idx));
617 return Ok(());
618 }
619 if let Some(idx) = self.resolve_upvalue(&name) {
621 self.emit(OpCode::GetUpvalue);
622 self.emit_u16(idx as u16);
623 return Ok(());
624 }
625 if name == "builtins" {
627 self.emit(OpCode::PushBuiltins);
628 return Ok(());
629 }
630 if is_global_builtin(&name) {
633 self.emit(OpCode::PushBuiltins);
634 let key_idx = self.add_attr_key(name)?;
635 self.emit(OpCode::GetAttr);
636 self.emit_u16(key_idx);
637 return Ok(());
638 }
639 if self.has_with_scope() {
641 let name_idx = self.chunk.add_constant(VMValue::String(name))?;
642 self.emit(OpCode::LookupWith);
643 self.emit_u16(name_idx);
644 return Ok(());
645 }
646 Err(CompileError::Unsupported(format!(
647 "unresolved variable: {name}"
648 )))
649 }
650 }
651 }
652
653 fn compile_let(&mut self, letin: &ast::LetIn) -> Result<(), CompileError> {
656 match sui_normalize::plan_for_group_total(letin, true) {
665 Ok(plan) if plan.dynamics.is_empty() => {
666 let body = letin
667 .body()
668 .ok_or_else(|| CompileError::MissingNode("let body".to_string()))?;
669 self.begin_scope();
670 let local_count = self.bind_plan_group_locals(&plan)?;
671 self.compile_expr(&body)?;
673 self.end_scope(local_count);
674 return Ok(());
675 }
676 Ok(_) => {}
680 Err(_) => {}
682 }
683
684 self.begin_scope();
685
686 let mut bindings: Vec<(String, LetBinding)> = Vec::new();
690
691 for entry in letin.entries() {
692 match entry {
693 ast::Entry::AttrpathValue(ref apv) => {
694 let attrpath = apv.attrpath().ok_or_else(|| {
695 CompileError::MissingNode("binding attrpath".to_string())
696 })?;
697 let keys: Vec<_> = attrpath.attrs().collect();
698 if keys.len() != 1 {
699 return Err(CompileError::Unsupported(
700 "dotted let bindings".to_string(),
701 ));
702 }
703 let key = static_attr_name(&keys[0])?;
704 let value_expr = apv.value().ok_or_else(|| {
705 CompileError::MissingNode("binding value".to_string())
706 })?;
707 bindings.push((key, LetBinding::Value(value_expr)));
708 }
709 ast::Entry::Inherit(ref inherit) => {
710 if let Some(from) = inherit.from() {
711 let source_expr = from.expr().ok_or_else(|| {
712 CompileError::MissingNode("inherit from expr".to_string())
713 })?;
714 for attr in inherit.attrs() {
715 let name = static_attr_name(&attr)?;
716 bindings.push((name.clone(), LetBinding::InheritFrom(source_expr.clone(), name)));
717 }
718 } else {
719 for attr in inherit.attrs() {
720 let name = static_attr_name(&attr)?;
721 bindings.push((name, LetBinding::Inherit));
722 }
723 }
724 }
725 }
726 }
727
728 {
730 let pairs: Vec<(String, &ast::Expr)> = bindings
731 .iter()
732 .filter_map(|(name, binding)| match binding {
733 LetBinding::Value(expr) => Some((name.clone(), expr as &ast::Expr)),
734 _ => None,
735 })
736 .collect();
737 for warning in detect_trivial_cycles(&pairs) {
738 eprintln!("{warning}");
739 }
740 }
741
742 let binding_count = u16::try_from(bindings.len())
743 .map_err(|_| CompileError::TooManyLocals)?;
744
745 for (name, _) in &bindings {
747 self.emit(OpCode::Null); self.add_local(name.clone())?;
749 }
750
751 let mut thunk_slots: Vec<(u16, Vec<UpvalueDesc>)> = Vec::new();
756
757 for (name, binding) in &bindings {
758 let local_idx = self.resolve_local(name).unwrap();
759 let slot = self.locals[local_idx as usize].slot;
760 match binding {
761 LetBinding::Value(expr) => {
762 if Self::is_trivial_value_for_rec(expr) {
767 self.compile_expr(expr)?;
768 } else {
769 let uv_descs = self.compile_thunk_deferred(expr)?;
770 if !uv_descs.is_empty() {
771 thunk_slots.push((slot, uv_descs));
772 }
773 }
774 self.emit(OpCode::SetLocal);
775 self.emit_u16(slot);
776 self.emit(OpCode::Pop);
777 }
778 LetBinding::Inherit => {
779 let saved_depth = self.locals[local_idx as usize].depth;
781 self.locals[local_idx as usize].depth = u32::MAX;
782 if let Some(outer_idx) = self.resolve_local(name) {
783 self.emit(OpCode::GetLocal);
784 self.emit_u16(self.local_stack_slot(outer_idx));
785 } else if let Some(uv_idx) = self.resolve_upvalue(name) {
786 self.emit(OpCode::GetUpvalue);
787 self.emit_u16(uv_idx as u16);
788 } else if self.has_with_scope() {
789 let name_idx = self.chunk.add_constant(VMValue::String(name.clone()))?;
790 self.emit(OpCode::LookupWith);
791 self.emit_u16(name_idx);
792 } else {
793 self.locals[local_idx as usize].depth = saved_depth;
794 return Err(CompileError::Unsupported(format!(
795 "inherit: cannot resolve '{name}' in enclosing scope"
796 )));
797 }
798 self.locals[local_idx as usize].depth = saved_depth;
799 self.emit(OpCode::SetLocal);
800 self.emit_u16(slot);
801 self.emit(OpCode::Pop);
802 }
803 LetBinding::InheritFrom(source_expr, attr_name) => {
804 let uv_descs = self.compile_inherit_from_thunk_deferred(source_expr, attr_name)?;
808 if !uv_descs.is_empty() {
809 thunk_slots.push((slot, uv_descs));
810 }
811 self.emit(OpCode::SetLocal);
812 self.emit_u16(slot);
813 self.emit(OpCode::Pop);
814 }
815 }
816 }
817
818 for (slot, uv_descs) in &thunk_slots {
820 self.emit(OpCode::PatchThunkUpvalues);
821 self.emit_u16(*slot);
822 self.emit_u16(uv_descs.len() as u16);
823 for uv in uv_descs {
824 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
825 self.emit_u16(uv.index);
826 }
827 }
828
829 let body = letin
832 .body()
833 .ok_or_else(|| CompileError::MissingNode("let body".to_string()))?;
834 self.compile_expr(&body)?;
835
836 self.end_scope(binding_count);
838
839 Ok(())
840 }
841
842 fn is_trivial_value(expr: &ast::Expr) -> bool {
844 match expr {
845 ast::Expr::Literal(_) => true,
846 ast::Expr::Str(s) => {
847 for part in s.normalized_parts() {
848 if !matches!(part, InterpolPart::Literal(_)) {
849 return false;
850 }
851 }
852 true
853 }
854 ast::Expr::Ident(id) => {
855 let name = ident_text(id);
856 matches!(name.as_str(), "true" | "false" | "null")
857 }
858 ast::Expr::Lambda(_) => true,
859 ast::Expr::Paren(p) => p.expr().map_or(false, |inner| Self::is_trivial_value(&inner)),
860 ast::Expr::List(list) => list.items().next().is_none(),
861 ast::Expr::AttrSet(set) => set.rec_token().is_none() && set.entries().next().is_none(),
862 _ => false,
863 }
864 }
865
866 fn is_trivial_value_for_rec(expr: &ast::Expr) -> bool {
875 match expr {
876 ast::Expr::Lambda(_) => false,
878 ast::Expr::Paren(p) => p.expr().map_or(false, |inner| Self::is_trivial_value_for_rec(&inner)),
879 _ => Self::is_trivial_value(expr),
880 }
881 }
882
883 fn compile_deferred_thunk<F>(&mut self, body: F) -> Result<Vec<UpvalueDesc>, CompileError>
901 where
902 F: FnOnce(&mut Compiler) -> Result<(), CompileError>,
903 {
904 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
905 tc.scope_depth = 1;
906 tc.enclosing = Some(self as *mut Compiler);
907 tc.with_depth = 0;
908 tc.base_dir = self.base_dir.clone();
909 let with_count = self.emit_with_scope_preamble(&mut tc);
910 body(&mut tc)?;
911 for _ in 0..with_count {
912 tc.emit(OpCode::PopWith);
913 }
914 tc.emit(OpCode::Return);
915 let uv_descs: Vec<UpvalueDesc> = tc.upvalues.clone();
916 let closure = VMValue::Closure(VMClosure {
917 chunk: Rc::new(tc.chunk),
918 upvalues: Vec::new(),
919 arity: 0,
920 name: None,
921 formals: Vec::new(),
922 });
923 let idx = self.chunk.add_constant(closure)?;
924 self.emit(OpCode::MakeThunk);
925 self.stack_depth += 1; self.emit_u16(idx);
927 self.emit_u16(0); Ok(uv_descs)
929 }
930
931 fn compile_thunk_deferred(&mut self, expr: &ast::Expr) -> Result<Vec<UpvalueDesc>, CompileError> {
932 self.compile_deferred_thunk(|tc| tc.compile_expr(expr))
933 }
934
935 fn compile_arg_maybe_thunk(&mut self, arg: &ast::Expr) -> Result<(), CompileError> {
937 if Self::is_trivial_arg(arg) {
938 self.compile_expr(arg)
939 } else {
940 self.compile_thunk_immediate(arg)
941 }
942 }
943
944 fn is_trivial_arg(expr: &ast::Expr) -> bool {
945 match expr {
946 ast::Expr::Literal(_) | ast::Expr::Ident(_)
947 | ast::Expr::PathAbs(_) | ast::Expr::PathRel(_)
948 | ast::Expr::PathHome(_) | ast::Expr::Lambda(_) => true,
949 ast::Expr::Paren(p) => p.expr().map_or(false, |inner| Self::is_trivial_arg(&inner)),
951 ast::Expr::Str(s) => s.normalized_parts().iter().all(|p| matches!(p, InterpolPart::Literal(_))),
953 _ => false,
954 }
955 }
956
957 fn compile_inherit_from_thunk_deferred(
960 &mut self,
961 source_expr: &ast::Expr,
962 attr_name: &str,
963 ) -> Result<Vec<UpvalueDesc>, CompileError> {
964 self.compile_deferred_thunk(|tc| {
965 tc.compile_expr(source_expr)?;
966 let key_idx = tc.add_attr_key(attr_name.to_string())?;
967 tc.emit(OpCode::GetAttr);
968 tc.emit_u16(key_idx);
969 Ok(())
970 })
971 }
972
973 fn compile_nested_attrset_thunk_deferred(
980 &mut self,
981 sub_bindings: &[(Vec<String>, ast::Expr)],
982 ) -> Result<Vec<UpvalueDesc>, CompileError> {
983 self.compile_deferred_thunk(|tc| tc.compile_nested_attrset_lazy(sub_bindings))
984 }
985
986 fn emit_with_scope_preamble(&mut self, tc: &mut Compiler) -> usize {
991 let slots: Vec<u16> = self.with_scope_locals.clone();
992 for &slot in &slots {
993 let local_idx = self.locals.iter().rposition(|l| l.slot == slot);
995 if let Some(idx) = local_idx {
996 self.locals[idx].is_captured = true;
997 if let Ok(uv_idx) = tc.add_upvalue(true, slot) {
998 tc.emit(OpCode::GetUpvalue);
999 tc.emit_u16(uv_idx as u16);
1000 tc.emit(OpCode::PushWith);
1001 tc.with_depth += 1;
1002 }
1003 }
1004 }
1005 slots.len()
1006 }
1007
1008 fn compile_thunk_immediate(&mut self, expr: &ast::Expr) -> Result<(), CompileError> {
1015 if let Some(ref source) = self.source_text {
1018 if self.locals.is_empty() && self.with_depth == 0 && self.upvalues.is_empty() {
1019 let range = AstNode::syntax(expr).text_range();
1020 let offset: usize = range.start().into();
1021 let length: usize = range.len().into();
1022 let base_dir_str = self.base_dir
1023 .as_ref()
1024 .map(|p| p.to_string_lossy().to_string())
1025 .unwrap_or_default();
1026
1027 let src_idx = self.chunk.add_constant(VMValue::String((**source).clone()))?;
1029 let dir_idx = self.chunk.add_constant(VMValue::String(base_dir_str))?;
1030
1031 self.emit(OpCode::MakeLazyThunk);
1032 self.stack_depth += 1;
1033 self.emit_u16(src_idx);
1034 self.chunk.write_u32(offset as u32, self.current_line);
1035 self.chunk.write_u32(length as u32, self.current_line);
1036 self.emit_u16(dir_idx);
1037 self.emit_u16(0); return Ok(());
1039 }
1040 }
1041
1042 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
1044 tc.scope_depth = 1;
1045 tc.enclosing = Some(self as *mut Compiler);
1046 tc.with_depth = 0; tc.base_dir = self.base_dir.clone();
1048
1049 let with_count = self.emit_with_scope_preamble(&mut tc);
1052
1053 tc.compile_expr(expr)?;
1054
1055 for _ in 0..with_count {
1057 tc.emit(OpCode::PopWith);
1058 }
1059
1060 tc.emit(OpCode::Return);
1061 let uv_descs: Vec<UpvalueDesc> = tc.upvalues.clone();
1062 let closure = VMValue::Closure(VMClosure {
1063 chunk: Rc::new(tc.chunk), upvalues: Vec::new(), arity: 0, name: None, formals: Vec::new(),
1064 });
1065 let idx = self.chunk.add_constant(closure)?;
1066 self.emit(OpCode::MakeThunk);
1067 self.stack_depth += 1; self.emit_u16(idx);
1069 self.emit_u16(uv_descs.len() as u16);
1070 for uv in &uv_descs {
1071 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
1072 self.emit_u16(uv.index);
1073 }
1074 Ok(())
1075 }
1076
1077 fn compile_inherit_from_thunk(
1080 &mut self,
1081 source_expr: &ast::Expr,
1082 attr_name: &str,
1083 ) -> Result<(), CompileError> {
1084 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
1085 tc.scope_depth = 1;
1086 tc.enclosing = Some(self as *mut Compiler);
1087 tc.with_depth = 0;
1088 tc.base_dir = self.base_dir.clone();
1089 let with_count = self.emit_with_scope_preamble(&mut tc);
1090 tc.compile_expr(source_expr)?;
1091 let key_idx = tc.add_attr_key(attr_name.to_string())?;
1092 tc.emit(OpCode::GetAttr);
1093 tc.emit_u16(key_idx);
1094 for _ in 0..with_count { tc.emit(OpCode::PopWith); }
1095 tc.emit(OpCode::Return);
1096 let uv_descs: Vec<UpvalueDesc> = tc.upvalues.clone();
1097 let closure = VMValue::Closure(VMClosure {
1098 chunk: Rc::new(tc.chunk),
1099 upvalues: Vec::new(),
1100 arity: 0, formals: Vec::new(),
1101 name: None,
1102 });
1103 let idx = self.chunk.add_constant(closure)?;
1104 self.emit(OpCode::MakeThunk);
1105 self.stack_depth += 1; self.emit_u16(idx);
1107 self.emit_u16(uv_descs.len() as u16);
1108 for uv in &uv_descs {
1109 self.chunk
1110 .write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
1111 self.emit_u16(uv.index);
1112 }
1113 Ok(())
1114 }
1115
1116 fn compile_attrset(&mut self, set: &ast::AttrSet) -> Result<(), CompileError> {
1119 let rec = set.rec_token().is_some();
1120
1121 match sui_normalize::plan_for_group_total(set, rec) {
1136 Ok(plan) => return self.compile_plan_group(&plan),
1137 Err(_) => { }
1138 }
1139
1140 if rec {
1141 return self.compile_rec_attrset(set);
1142 }
1143
1144 let mut flat_entries: Vec<(String, ast::Expr)> = Vec::new();
1147 let mut dotted_entries: std::collections::BTreeMap<String, Vec<(Vec<String>, ast::Expr)>> =
1148 std::collections::BTreeMap::new();
1149 let mut inherit_entries: Vec<(String, Option<ast::Expr>)> = Vec::new();
1150 let mut dynamic_entries: Vec<(ast::Expr, ast::Expr)> = Vec::new();
1151 let mut dynamic_dotted_entries: Vec<(ast::Attr, Vec<String>, ast::Expr)> = Vec::new();
1152
1153 for entry in set.entries() {
1154 match entry {
1155 ast::Entry::AttrpathValue(ref apv) => {
1156 let attrpath = apv.attrpath().ok_or_else(|| {
1157 CompileError::MissingNode("attrset attrpath".to_string())
1158 })?;
1159 let keys: Vec<_> = attrpath.attrs().collect();
1160 let value_expr = apv.value().ok_or_else(|| {
1161 CompileError::MissingNode("attrset value".to_string())
1162 })?;
1163
1164 if keys.len() == 1 {
1165 match &keys[0] {
1167 ast::Attr::Dynamic(dyn_attr) => {
1168 let key_expr = dyn_attr.expr().ok_or_else(|| {
1169 CompileError::MissingNode("dynamic attr key".to_string())
1170 })?;
1171 dynamic_entries.push((key_expr, value_expr));
1172 }
1173 ast::Attr::Str(s) => {
1174 if let Ok(key) = static_attr_name(&keys[0]) {
1181 flat_entries.push((key, value_expr));
1182 } else {
1183 let key_expr = ast::Expr::Str(s.clone());
1185 dynamic_entries.push((key_expr, value_expr));
1186 }
1187 }
1188 _ => {
1189 let key = static_attr_name(&keys[0])?;
1190 flat_entries.push((key, value_expr));
1191 }
1192 }
1193 } else {
1194 match static_attr_name(&keys[0]) {
1196 Ok(top_key) => {
1197 let rest_keys: Vec<String> = keys[1..]
1198 .iter()
1199 .map(static_attr_name)
1200 .collect::<Result<_, _>>()?;
1201 dotted_entries
1202 .entry(top_key)
1203 .or_default()
1204 .push((rest_keys, value_expr));
1205 }
1206 Err(_) => {
1207 let rest_keys: Vec<String> = keys[1..]
1211 .iter()
1212 .map(static_attr_name)
1213 .collect::<Result<_, _>>()?;
1214 dynamic_dotted_entries.push((
1217 keys[0].clone(),
1218 rest_keys,
1219 value_expr,
1220 ));
1221 }
1222 }
1223 }
1224 }
1225 ast::Entry::Inherit(ref inherit) => {
1226 let source_expr = inherit.from().and_then(|f| f.expr());
1227 for attr in inherit.attrs() {
1228 let name = static_attr_name(&attr)?;
1229 inherit_entries.push((name, source_expr.clone()));
1230 }
1231 }
1232 }
1233 }
1234
1235 let mut count: u16 = 0;
1236
1237 for (key, value_expr) in &flat_entries {
1241 if Self::is_trivial_value(value_expr) {
1242 self.compile_expr(value_expr)?;
1243 } else {
1244 self.compile_thunk_immediate(value_expr)?;
1245 }
1246 self.emit_constant(VMValue::String(key.clone()))?;
1247 count += 1;
1248 }
1249
1250 for (top_key, sub_bindings) in &dotted_entries {
1252 self.compile_nested_attrset(sub_bindings)?;
1253 self.emit_constant(VMValue::String(top_key.clone()))?;
1254 count += 1;
1255 }
1256
1257 for (name, source_expr) in &inherit_entries {
1260 if let Some(src) = source_expr {
1261 self.compile_inherit_from_thunk(src, name)?;
1265 } else {
1266 self.emit_variable_load(name)?;
1268 }
1269 self.emit_constant(VMValue::String(name.clone()))?;
1270 count += 1;
1271 }
1272
1273 for (key_expr, value_expr) in &dynamic_entries {
1276 if Self::is_trivial_value(value_expr) {
1277 self.compile_expr(value_expr)?;
1278 } else {
1279 self.compile_thunk_immediate(value_expr)?;
1280 }
1281 self.compile_expr(key_expr)?;
1282 count += 1;
1283 }
1284
1285 for (key_attr, rest_keys, value_expr) in &dynamic_dotted_entries {
1289 self.compile_nested_attrset(&[(rest_keys.clone(), value_expr.clone())])?;
1291 self.compile_dynamic_attr_key(key_attr)?;
1293 count += 1;
1294 }
1295
1296 self.emit(OpCode::MakeAttrs);
1297 self.emit_u16(count);
1298 self.stack_depth = self.stack_depth.saturating_sub(2 * count) + 1;
1300
1301 Ok(())
1308 }
1309
1310 fn compile_rec_attrset(&mut self, set: &ast::AttrSet) -> Result<(), CompileError> {
1312 self.begin_scope();
1313
1314 let mut bindings: Vec<(String, RecAttrBinding)> = Vec::new();
1316 let mut dotted_entries: std::collections::BTreeMap<String, Vec<(Vec<String>, ast::Expr)>> =
1317 std::collections::BTreeMap::new();
1318
1319 for entry in set.entries() {
1320 match entry {
1321 ast::Entry::AttrpathValue(ref apv) => {
1322 let attrpath = apv.attrpath().ok_or_else(|| {
1323 CompileError::MissingNode("rec attrset attrpath".to_string())
1324 })?;
1325 let keys: Vec<_> = attrpath.attrs().collect();
1326 let value_expr = apv.value().ok_or_else(|| {
1327 CompileError::MissingNode("rec attrset value".to_string())
1328 })?;
1329 if keys.len() == 1 {
1330 let key = static_attr_name(&keys[0])?;
1331 bindings.push((key, RecAttrBinding::Value(value_expr)));
1332 } else {
1333 let top_key = static_attr_name(&keys[0])?;
1334 let rest_keys: Vec<String> = keys[1..]
1335 .iter()
1336 .map(static_attr_name)
1337 .collect::<Result<_, _>>()?;
1338 dotted_entries
1339 .entry(top_key)
1340 .or_default()
1341 .push((rest_keys, value_expr));
1342 }
1343 }
1344 ast::Entry::Inherit(ref inherit) => {
1345 if let Some(from) = inherit.from() {
1346 let source_expr = from.expr().ok_or_else(|| {
1347 CompileError::MissingNode("inherit from expr".to_string())
1348 })?;
1349 for attr in inherit.attrs() {
1350 let name = static_attr_name(&attr)?;
1351 bindings.push((name.clone(), RecAttrBinding::InheritFrom(source_expr.clone(), name)));
1352 }
1353 } else {
1354 for attr in inherit.attrs() {
1355 let name = static_attr_name(&attr)?;
1356 bindings.push((name, RecAttrBinding::Inherit));
1357 }
1358 }
1359 }
1360 }
1361 }
1362
1363 for (top_key, sub) in &dotted_entries {
1365 bindings.push((top_key.clone(), RecAttrBinding::Dotted(sub.clone())));
1366 }
1367
1368 {
1370 let pairs: Vec<(String, &ast::Expr)> = bindings
1371 .iter()
1372 .filter_map(|(name, binding)| match binding {
1373 RecAttrBinding::Value(expr) => Some((name.clone(), expr as &ast::Expr)),
1374 _ => None,
1375 })
1376 .collect();
1377 for warning in detect_trivial_cycles(&pairs) {
1378 eprintln!("{warning}");
1379 }
1380 }
1381
1382 let binding_count = u16::try_from(bindings.len())
1383 .map_err(|_| CompileError::TooManyLocals)?;
1384
1385 for (name, _) in &bindings {
1387 self.emit(OpCode::Null); self.add_local(name.clone())?;
1389 }
1390
1391 let mut thunk_slots: Vec<(u16, Vec<UpvalueDesc>)> = Vec::new();
1394
1395 for (name, binding) in &bindings {
1396 let local_idx = self.resolve_local(name).unwrap();
1397 let slot = self.locals[local_idx as usize].slot;
1398 match binding {
1399 RecAttrBinding::Value(expr) => {
1400 if Self::is_trivial_value_for_rec(expr) {
1408 self.compile_expr(expr)?;
1409 } else {
1410 let uv_descs = self.compile_thunk_deferred(expr)?;
1411 if !uv_descs.is_empty() {
1412 thunk_slots.push((slot, uv_descs));
1413 }
1414 }
1415 }
1416 RecAttrBinding::Inherit => {
1417 let saved_depth = self.locals[local_idx as usize].depth;
1419 self.locals[local_idx as usize].depth = u32::MAX;
1420 self.emit_variable_load_restore(name, local_idx, saved_depth)?;
1421 self.locals[local_idx as usize].depth = saved_depth;
1422 }
1423 RecAttrBinding::InheritFrom(source_expr, attr_name) => {
1424 let uv_descs = self.compile_inherit_from_thunk_deferred(source_expr, attr_name)?;
1426 if !uv_descs.is_empty() {
1427 thunk_slots.push((slot, uv_descs));
1428 }
1429 }
1430 RecAttrBinding::Dotted(sub_bindings) => {
1431 let uv_descs = self.compile_nested_attrset_thunk_deferred(sub_bindings)?;
1438 if !uv_descs.is_empty() {
1439 thunk_slots.push((slot, uv_descs));
1440 }
1441 }
1442 }
1443 self.emit(OpCode::SetLocal);
1444 self.emit_u16(slot);
1445 self.emit(OpCode::Pop);
1446 }
1447
1448 for (slot, uv_descs) in &thunk_slots {
1450 self.emit(OpCode::PatchThunkUpvalues);
1451 self.emit_u16(*slot);
1452 self.emit_u16(uv_descs.len() as u16);
1453 for uv in uv_descs {
1454 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
1455 self.emit_u16(uv.index);
1456 }
1457 }
1458
1459 for (name, _) in &bindings {
1461 let slot = self.find_local_slot(name);
1462 self.emit(OpCode::GetLocal);
1463 self.emit_u16(slot);
1464 self.emit_constant(VMValue::String(name.clone()))?;
1465 }
1466 self.emit(OpCode::MakeAttrs);
1467 self.emit_u16(binding_count);
1468 self.stack_depth = self.stack_depth.saturating_sub(2 * binding_count) + 1;
1470
1471 self.end_scope(binding_count);
1473
1474 Ok(())
1475 }
1476
1477 fn compile_plan_group(&mut self, plan: &sui_normalize::GroupPlan) -> Result<(), CompileError> {
1503 if plan.recursive {
1504 self.compile_plan_group_rec(plan)
1505 } else {
1506 self.compile_plan_group_flat(plan)
1507 }
1508 }
1509
1510 fn compile_plan_group_flat(
1512 &mut self,
1513 plan: &sui_normalize::GroupPlan,
1514 ) -> Result<(), CompileError> {
1515 let mut count: u16 = 0;
1516 for b in &plan.statics {
1517 let name = sui_intern::resolve(b.name);
1518 self.emit_plan_binding(&b.binding, &name, plan)?;
1519 self.emit_constant(VMValue::String(name))?;
1520 count += 1;
1521 }
1522 count += self.emit_plan_dynamics(plan)?;
1523 self.emit(OpCode::MakeAttrs);
1524 self.emit_u16(count);
1525 self.stack_depth = self.stack_depth.saturating_sub(2 * count) + 1;
1527 Ok(())
1528 }
1529
1530 fn emit_plan_binding(
1532 &mut self,
1533 binding: &sui_normalize::Binding,
1534 name: &str,
1535 plan: &sui_normalize::GroupPlan,
1536 ) -> Result<(), CompileError> {
1537 use sui_normalize::Binding;
1538 match binding {
1539 Binding::Leaf(expr) => {
1540 if Self::is_trivial_value(expr) {
1541 self.compile_expr(expr)
1542 } else {
1543 self.compile_thunk_immediate(expr)
1544 }
1545 }
1546 Binding::Group(sub) => self.compile_plan_group(sub),
1552 Binding::Inherit => self.emit_variable_load(name),
1556 Binding::InheritFrom { from } => {
1557 let src = plan.inherit_froms.get(*from).ok_or_else(|| {
1558 CompileError::Unsupported(format!(
1559 "inherit-from index {from} out of range for '{name}'"
1560 ))
1561 })?;
1562 let src = src.clone();
1563 self.compile_inherit_from_thunk(&src, name)
1564 }
1565 }
1566 }
1567
1568 fn emit_plan_dynamics(
1572 &mut self,
1573 plan: &sui_normalize::GroupPlan,
1574 ) -> Result<u16, CompileError> {
1575 use sui_normalize::Binding;
1576 let mut count: u16 = 0;
1577 for d in &plan.dynamics {
1578 match &d.value {
1579 Binding::Leaf(expr) => {
1580 if Self::is_trivial_value(expr) {
1581 self.compile_expr(expr)?;
1582 } else {
1583 self.compile_thunk_immediate(expr)?;
1584 }
1585 }
1586 Binding::Group(sub) => self.compile_plan_group(sub)?,
1587 Binding::Inherit | Binding::InheritFrom { .. } => {
1593 return Err(CompileError::Unsupported(
1594 "an inherited binding cannot have a dynamic key".to_string(),
1595 ))
1596 }
1597 }
1598 self.compile_expr(&d.key)?;
1599 count += 1;
1600 }
1601 Ok(count)
1602 }
1603
1604 fn compile_plan_group_rec(
1616 &mut self,
1617 plan: &sui_normalize::GroupPlan,
1618 ) -> Result<(), CompileError> {
1619 self.begin_scope();
1620 let local_count = self.bind_plan_group_locals(plan)?;
1621
1622 let mut count = local_count;
1624 for b in &plan.statics {
1625 let name = sui_intern::resolve(b.name);
1626 let slot = self.find_local_slot(&name);
1627 self.emit(OpCode::GetLocal);
1628 self.emit_u16(slot);
1629 self.emit_constant(VMValue::String(name))?;
1630 }
1631 count += self.emit_plan_dynamics(plan)?;
1634
1635 self.emit(OpCode::MakeAttrs);
1636 self.emit_u16(count);
1637 self.stack_depth = self.stack_depth.saturating_sub(2 * count) + 1;
1638
1639 self.end_scope(local_count);
1641 Ok(())
1642 }
1643
1644 fn bind_plan_group_locals(
1657 &mut self,
1658 plan: &sui_normalize::GroupPlan,
1659 ) -> Result<u16, CompileError> {
1660 use sui_normalize::Binding;
1661
1662 let local_count =
1663 u16::try_from(plan.statics.len()).map_err(|_| CompileError::TooManyLocals)?;
1664
1665 for b in &plan.statics {
1667 self.emit(OpCode::Null); self.add_local(sui_intern::resolve(b.name))?;
1669 }
1670
1671 let mut thunk_slots: Vec<(u16, Vec<UpvalueDesc>)> = Vec::new();
1673 for b in &plan.statics {
1674 let name = sui_intern::resolve(b.name);
1675 let local_idx = self
1676 .resolve_local(&name)
1677 .ok_or_else(|| CompileError::Unsupported(format!("rec local '{name}' vanished")))?;
1678 let slot = self.locals[local_idx as usize].slot;
1679 match &b.binding {
1680 Binding::Leaf(expr) => {
1681 if Self::is_trivial_value_for_rec(expr) {
1682 self.compile_expr(expr)?;
1683 } else {
1684 let uv = self.compile_thunk_deferred(expr)?;
1685 if !uv.is_empty() {
1686 thunk_slots.push((slot, uv));
1687 }
1688 }
1689 }
1690 Binding::Group(sub) => {
1691 let sub = sub.clone();
1695 let uv = self.compile_deferred_thunk(|tc| tc.compile_plan_group(&sub))?;
1696 if !uv.is_empty() {
1697 thunk_slots.push((slot, uv));
1698 }
1699 }
1700 Binding::Inherit => {
1701 let saved_depth = self.locals[local_idx as usize].depth;
1704 self.locals[local_idx as usize].depth = u32::MAX;
1705 self.emit_variable_load_restore(&name, local_idx, saved_depth)?;
1706 self.locals[local_idx as usize].depth = saved_depth;
1707 }
1708 Binding::InheritFrom { from } => {
1709 let src = plan
1710 .inherit_froms
1711 .get(*from)
1712 .ok_or_else(|| {
1713 CompileError::Unsupported(format!(
1714 "inherit-from index {from} out of range for '{name}'"
1715 ))
1716 })?
1717 .clone();
1718 let uv = self.compile_inherit_from_thunk_deferred(&src, &name)?;
1719 if !uv.is_empty() {
1720 thunk_slots.push((slot, uv));
1721 }
1722 }
1723 }
1724 self.emit(OpCode::SetLocal);
1725 self.emit_u16(slot);
1726 self.emit(OpCode::Pop);
1727 }
1728
1729 for (slot, uv_descs) in &thunk_slots {
1731 self.emit(OpCode::PatchThunkUpvalues);
1732 self.emit_u16(*slot);
1733 self.emit_u16(u16::try_from(uv_descs.len()).map_err(|_| CompileError::TooManyLocals)?);
1734 for uv in uv_descs {
1735 self.chunk
1736 .write_byte(u8::from(uv.is_local), self.current_line);
1737 self.emit_u16(uv.index);
1738 }
1739 }
1740
1741 Ok(local_count)
1742 }
1743
1744 fn compile_legacy_let(&mut self, ll: &ast::LegacyLet) -> Result<(), CompileError> {
1750 match sui_normalize::plan_for_group_total(ll, true) {
1757 Ok(plan) if plan.dynamics.is_empty() => {
1758 let body_sym = sui_intern::intern("body");
1759 if plan.statics.iter().any(|b| b.name == body_sym) {
1760 self.begin_scope();
1761 let local_count = self.bind_plan_group_locals(&plan)?;
1762 let slot = self.find_local_slot("body");
1763 self.emit(OpCode::GetLocal);
1764 self.emit_u16(slot);
1765 self.end_scope(local_count);
1766 return Ok(());
1767 }
1768 }
1771 Ok(_) | Err(_) => {}
1772 }
1773
1774 self.begin_scope();
1775
1776 let mut bindings: Vec<(String, RecAttrBinding)> = Vec::new();
1779 let mut dotted_entries: std::collections::BTreeMap<String, Vec<(Vec<String>, ast::Expr)>> =
1780 std::collections::BTreeMap::new();
1781
1782 for entry in ll.entries() {
1783 match entry {
1784 ast::Entry::AttrpathValue(ref apv) => {
1785 let attrpath = apv.attrpath().ok_or_else(|| {
1786 CompileError::MissingNode("legacy let attrpath".to_string())
1787 })?;
1788 let keys: Vec<_> = attrpath.attrs().collect();
1789 let value_expr = apv.value().ok_or_else(|| {
1790 CompileError::MissingNode("legacy let value".to_string())
1791 })?;
1792 if keys.len() == 1 {
1793 let key = static_attr_name(&keys[0])?;
1794 bindings.push((key, RecAttrBinding::Value(value_expr)));
1795 } else {
1796 let top_key = static_attr_name(&keys[0])?;
1797 let rest_keys: Vec<String> = keys[1..]
1798 .iter()
1799 .map(static_attr_name)
1800 .collect::<Result<_, _>>()?;
1801 dotted_entries
1802 .entry(top_key)
1803 .or_default()
1804 .push((rest_keys, value_expr));
1805 }
1806 }
1807 ast::Entry::Inherit(ref inherit) => {
1808 if let Some(from) = inherit.from() {
1809 let source_expr = from.expr().ok_or_else(|| {
1810 CompileError::MissingNode("inherit from expr".to_string())
1811 })?;
1812 for attr in inherit.attrs() {
1813 let name = static_attr_name(&attr)?;
1814 bindings.push((name.clone(), RecAttrBinding::InheritFrom(source_expr.clone(), name)));
1815 }
1816 } else {
1817 for attr in inherit.attrs() {
1818 let name = static_attr_name(&attr)?;
1819 bindings.push((name, RecAttrBinding::Inherit));
1820 }
1821 }
1822 }
1823 }
1824 }
1825
1826 for (top_key, sub) in &dotted_entries {
1828 bindings.push((top_key.clone(), RecAttrBinding::Dotted(sub.clone())));
1829 }
1830
1831 let binding_count = u16::try_from(bindings.len())
1832 .map_err(|_| CompileError::TooManyLocals)?;
1833
1834 for (name, _) in &bindings {
1836 self.emit(OpCode::Null);
1837 self.add_local(name.clone())?;
1838 }
1839
1840 let mut thunk_slots: Vec<(u16, Vec<UpvalueDesc>)> = Vec::new();
1842
1843 for (name, binding) in &bindings {
1844 let local_idx = self.resolve_local(name).unwrap();
1845 let slot = self.locals[local_idx as usize].slot;
1846 match binding {
1847 RecAttrBinding::Value(expr) => {
1848 if Self::is_trivial_value_for_rec(expr) {
1851 self.compile_expr(expr)?;
1852 } else {
1853 let uv_descs = self.compile_thunk_deferred(expr)?;
1854 if !uv_descs.is_empty() {
1855 thunk_slots.push((slot, uv_descs));
1856 }
1857 }
1858 }
1859 RecAttrBinding::Inherit => {
1860 let saved_depth = self.locals[local_idx as usize].depth;
1861 self.locals[local_idx as usize].depth = u32::MAX;
1862 self.emit_variable_load_restore(name, local_idx, saved_depth)?;
1863 self.locals[local_idx as usize].depth = saved_depth;
1864 }
1865 RecAttrBinding::InheritFrom(source_expr, attr_name) => {
1866 let uv_descs = self.compile_inherit_from_thunk_deferred(source_expr, attr_name)?;
1867 if !uv_descs.is_empty() {
1868 thunk_slots.push((slot, uv_descs));
1869 }
1870 }
1871 RecAttrBinding::Dotted(sub_bindings) => {
1872 let uv_descs = self.compile_nested_attrset_thunk_deferred(sub_bindings)?;
1874 if !uv_descs.is_empty() {
1875 thunk_slots.push((slot, uv_descs));
1876 }
1877 }
1878 }
1879 self.emit(OpCode::SetLocal);
1880 self.emit_u16(slot);
1881 self.emit(OpCode::Pop);
1882 }
1883
1884 for (slot, uv_descs) in &thunk_slots {
1886 self.emit(OpCode::PatchThunkUpvalues);
1887 self.emit_u16(*slot);
1888 self.emit_u16(uv_descs.len() as u16);
1889 for uv in uv_descs {
1890 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
1891 self.emit_u16(uv.index);
1892 }
1893 }
1894
1895 let body_slot = self.find_local_slot_opt("body").ok_or_else(|| {
1899 CompileError::MissingNode("legacy let missing 'body' binding".to_string())
1900 })?;
1901 self.emit(OpCode::GetLocal);
1902 self.emit_u16(body_slot);
1903
1904 self.end_scope(binding_count);
1906
1907 Ok(())
1908 }
1909
1910 fn compile_nested_attrset(
1917 &mut self,
1918 sub_bindings: &[(Vec<String>, ast::Expr)],
1919 ) -> Result<(), CompileError> {
1920 self.compile_nested_attrset_inner(sub_bindings, false, &[])
1921 }
1922
1923 fn compile_nested_attrset_lazy(
1924 &mut self,
1925 sub_bindings: &[(Vec<String>, ast::Expr)],
1926 ) -> Result<(), CompileError> {
1927 self.compile_nested_attrset_inner(sub_bindings, true, &[])
1928 }
1929
1930 fn compile_nested_attrset_inner(
1933 &mut self,
1934 sub_bindings: &[(Vec<String>, ast::Expr)],
1935 lazy_leaves: bool,
1936 prefix: &[String],
1937 ) -> Result<(), CompileError> {
1938 let mut groups: std::collections::BTreeMap<String, Vec<(Vec<String>, ast::Expr)>> =
1940 std::collections::BTreeMap::new();
1941
1942 for (path, expr) in sub_bindings {
1943 let Some((head, rest)) = path.split_first() else {
1957 let full = if prefix.is_empty() {
1958 "<unknown>".to_string()
1959 } else {
1960 prefix.join(".")
1961 };
1962 return Err(CompileError::Unsupported(format!(
1963 "attribute '{full}' is defined more than once; CppNix \
1964 rejects this at parse time and the bytecode compiler \
1965 cannot represent it"
1966 )));
1967 };
1968 groups
1969 .entry(head.clone())
1970 .or_default()
1971 .push((rest.to_vec(), expr.clone()));
1972 }
1973
1974 let mut count: u16 = 0;
1975 for (key, nested) in &groups {
1976 if nested.len() == 1 && nested[0].0.is_empty() {
1977 if lazy_leaves && !Self::is_trivial_value(&nested[0].1) {
1979 self.compile_thunk_immediate(&nested[0].1)?;
1980 } else {
1981 self.compile_expr(&nested[0].1)?;
1982 }
1983 } else {
1984 let mut deeper = prefix.to_vec();
1986 deeper.push(key.clone());
1987 self.compile_nested_attrset_inner(nested, lazy_leaves, &deeper)?;
1988 }
1989 self.emit_constant(VMValue::String(key.clone()))?;
1990 count += 1;
1991 }
1992
1993 self.emit(OpCode::MakeAttrs);
1994 self.emit_u16(count);
1995 self.stack_depth = self.stack_depth.saturating_sub(2 * count) + 1;
1996 Ok(())
1997 }
1998
1999 fn emit_variable_load(&mut self, name: &str) -> Result<(), CompileError> {
2001 if let Some(idx) = self.resolve_local(name) {
2002 self.emit(OpCode::GetLocal);
2003 self.emit_u16(self.local_stack_slot(idx));
2004 } else if let Some(uv_idx) = self.resolve_upvalue(name) {
2005 self.emit(OpCode::GetUpvalue);
2006 self.emit_u16(uv_idx as u16);
2007 } else if self.has_with_scope() {
2008 let name_idx = self.chunk.add_constant(VMValue::String(name.to_string()))?;
2009 self.emit(OpCode::LookupWith);
2010 self.emit_u16(name_idx);
2011 } else {
2012 return Err(CompileError::Unsupported(format!(
2013 "inherit: cannot resolve '{name}'"
2014 )));
2015 }
2016 Ok(())
2017 }
2018
2019 fn emit_variable_load_restore(
2022 &mut self,
2023 name: &str,
2024 local_idx: u16,
2025 saved_depth: u32,
2026 ) -> Result<(), CompileError> {
2027 if let Some(outer_idx) = self.resolve_local(name) {
2028 self.emit(OpCode::GetLocal);
2029 self.emit_u16(self.local_stack_slot(outer_idx));
2030 } else if let Some(uv_idx) = self.resolve_upvalue(name) {
2031 self.emit(OpCode::GetUpvalue);
2032 self.emit_u16(uv_idx as u16);
2033 } else if self.has_with_scope() {
2034 let name_idx = self.chunk.add_constant(VMValue::String(name.to_string()))?;
2035 self.emit(OpCode::LookupWith);
2036 self.emit_u16(name_idx);
2037 } else {
2038 self.locals[local_idx as usize].depth = saved_depth;
2039 return Err(CompileError::Unsupported(format!(
2040 "inherit: cannot resolve '{name}' in enclosing scope"
2041 )));
2042 }
2043 Ok(())
2044 }
2045
2046 fn try_resolve_as_local(&self, expr: &ast::Expr) -> Option<u16> {
2050 if let ast::Expr::Ident(id) = expr {
2051 let name = ident_text(id);
2052 let idx = self.resolve_local(&name)?;
2053 Some(self.local_stack_slot(idx))
2054 } else {
2055 None
2056 }
2057 }
2058
2059 fn compile_select(&mut self, sel: &ast::Select) -> Result<(), CompileError> {
2060 let base = sel
2061 .expr()
2062 .ok_or_else(|| CompileError::MissingNode("select base".to_string()))?;
2063 let attrpath = sel
2064 .attrpath()
2065 .ok_or_else(|| CompileError::MissingNode("select attrpath".to_string()))?;
2066
2067 let segments: Vec<_> = attrpath.attrs().collect();
2068
2069 if let Some(default_expr) = sel.default_expr() {
2070 self.compile_expr(&base)?;
2094 let depth_before = self.stack_depth; let mut miss_jumps: Vec<usize> = Vec::new();
2096 for (_i, attr) in segments.iter().enumerate() {
2097 if let Ok(key) = static_attr_name(attr) {
2098 let key_idx = self.add_attr_key(key)?;
2099 self.emit(OpCode::Dup); self.emit(OpCode::HasAttr); self.emit_u16(key_idx);
2102 miss_jumps.push(self.emit_jump(OpCode::JumpIfFalse)); self.emit(OpCode::GetAttr); self.emit_u16(key_idx);
2105 } else {
2106 self.emit(OpCode::Dup); self.compile_dynamic_attr_key(attr)?; self.emit(OpCode::DynHasAttr); miss_jumps.push(self.emit_jump(OpCode::JumpIfFalse)); self.compile_dynamic_attr_key(attr)?; self.emit(OpCode::DynGetAttr); }
2113 }
2114 let end_jump = self.emit_jump(OpCode::Jump);
2117 for mj in miss_jumps {
2119 self.patch_jump(mj)?;
2120 }
2121 self.stack_depth = depth_before;
2123 self.emit(OpCode::Pop); self.compile_expr(&default_expr)?; self.patch_jump(end_jump)?;
2126 } else {
2128 let local_slot = self.try_resolve_as_local(&base);
2131
2132 for (i, attr) in segments.iter().enumerate() {
2133 if let Ok(key) = static_attr_name(attr) {
2134 let key_idx = self.add_attr_key(key)?;
2135
2136 if i == 0 {
2137 if let Some(slot) = local_slot {
2138 self.emit(OpCode::GetLocalAttr);
2140 self.emit_u16(slot);
2141 self.emit_u16(key_idx);
2142 } else {
2143 self.compile_expr(&base)?;
2144 self.emit(OpCode::GetAttr);
2145 self.emit_u16(key_idx);
2146 }
2147 } else {
2148 self.emit(OpCode::GetAttr);
2149 self.emit_u16(key_idx);
2150 }
2151 } else {
2152 if i == 0 {
2154 self.compile_expr(&base)?;
2155 }
2156 self.compile_dynamic_attr_key(attr)?;
2157 self.emit(OpCode::DynGetAttr);
2158 }
2159 }
2160 }
2161
2162 Ok(())
2163 }
2164
2165 fn compile_dynamic_attr_key(&mut self, attr: &ast::Attr) -> Result<(), CompileError> {
2167 match attr {
2168 ast::Attr::Dynamic(d) => {
2169 let expr = d.expr().ok_or_else(|| {
2170 CompileError::MissingNode("dynamic attr key expr".to_string())
2171 })?;
2172 self.compile_expr(&expr)
2173 }
2174 ast::Attr::Str(s) => {
2175 let key_expr = ast::Expr::Str(s.clone());
2176 self.compile_expr(&key_expr)
2177 }
2178 ast::Attr::Ident(ident) => {
2179 self.emit_constant(VMValue::String(ident_text(ident)))
2180 }
2181 }
2182 }
2183
2184 fn compile_has_attr(&mut self, ha: &ast::HasAttr) -> Result<(), CompileError> {
2187 let base = ha
2188 .expr()
2189 .ok_or_else(|| CompileError::MissingNode("hasattr base".to_string()))?;
2190 let attrpath = ha
2191 .attrpath()
2192 .ok_or_else(|| CompileError::MissingNode("hasattr attrpath".to_string()))?;
2193
2194 let segments: Vec<_> = attrpath.attrs().collect();
2195
2196 if segments.len() == 1 {
2197 self.compile_expr(&base)?;
2199 if let Ok(key) = static_attr_name(&segments[0]) {
2200 let key_idx = self.add_attr_key(key)?;
2201 self.emit(OpCode::HasAttr);
2202 self.emit_u16(key_idx);
2203 } else {
2204 self.compile_dynamic_attr_key(&segments[0])?;
2205 self.emit(OpCode::DynHasAttr);
2206 }
2207 return Ok(());
2208 }
2209
2210 let mut false_jumps: Vec<usize> = Vec::new();
2219 let depth_before = self.stack_depth;
2222
2223 for (i, seg) in segments.iter().enumerate() {
2224 self.compile_expr(&base)?;
2226 for prev_seg in &segments[..i] {
2227 if let Ok(prev_key) = static_attr_name(prev_seg) {
2228 let prev_idx = self.add_attr_key(prev_key)?;
2229 self.emit(OpCode::GetAttr);
2230 self.emit_u16(prev_idx);
2231 } else {
2232 self.compile_dynamic_attr_key(prev_seg)?;
2233 self.emit(OpCode::DynGetAttr);
2234 }
2235 }
2236 if let Ok(key) = static_attr_name(seg) {
2237 let key_idx = self.add_attr_key(key)?;
2238 self.emit(OpCode::HasAttr);
2239 self.emit_u16(key_idx);
2240 } else {
2241 self.compile_dynamic_attr_key(seg)?;
2242 self.emit(OpCode::DynHasAttr);
2243 }
2244
2245 if i < segments.len() - 1 {
2247 false_jumps.push(self.emit_jump(OpCode::JumpIfFalse));
2248 self.stack_depth = depth_before;
2253 }
2254 }
2255
2256 let done_jump = self.emit_jump(OpCode::Jump);
2258
2259 self.stack_depth = depth_before;
2262 for fj in false_jumps {
2263 self.patch_jump(fj)?;
2264 }
2265 self.emit(OpCode::False);
2266 self.patch_jump(done_jump)?;
2269 Ok(())
2270 }
2271
2272 fn compile_if(&mut self, ie: &ast::IfElse) -> Result<(), CompileError> {
2275 let cond = ie
2276 .condition()
2277 .ok_or_else(|| CompileError::MissingNode("if condition".to_string()))?;
2278 let then_body = ie
2279 .body()
2280 .ok_or_else(|| CompileError::MissingNode("if then".to_string()))?;
2281 let else_body = ie
2282 .else_body()
2283 .ok_or_else(|| CompileError::MissingNode("if else".to_string()))?;
2284
2285 let tail = self.tail_position;
2287
2288 self.tail_position = false;
2290 self.compile_expr(&cond)?;
2291 let else_jump = self.emit_jump(OpCode::JumpIfFalse);
2293 let depth_at_branch = self.stack_depth;
2296 self.tail_position = tail;
2298 self.compile_expr(&then_body)?;
2299 let end_jump = self.emit_jump(OpCode::Jump);
2301 self.stack_depth = depth_at_branch;
2304 self.patch_jump(else_jump)?;
2305 self.tail_position = tail;
2307 self.compile_expr(&else_body)?;
2308 self.patch_jump(end_jump)?;
2312 Ok(())
2313 }
2314
2315 fn compile_lambda(&mut self, lam: &ast::Lambda) -> Result<(), CompileError> {
2318 let param = lam
2319 .param()
2320 .ok_or_else(|| CompileError::MissingNode("lambda param".to_string()))?;
2321 let body = lam
2322 .body()
2323 .ok_or_else(|| CompileError::MissingNode("lambda body".to_string()))?;
2324
2325 let mut func_compiler = Compiler::with_interner(Rc::clone(&self.interner));
2327 func_compiler.scope_depth = 1; func_compiler.enclosing = Some(self as *mut Compiler);
2330 func_compiler.base_dir = self.base_dir.clone();
2332 func_compiler.stack_depth = 1;
2334
2335 let mut formals_metadata: Vec<(String, bool)> = Vec::new();
2336 let (arity, name) = match ¶m {
2337 ast::Param::IdentParam(ip) => {
2338 let ident = ip
2339 .ident()
2340 .ok_or_else(|| CompileError::MissingNode("lambda ident".to_string()))?;
2341 let name = ident_text(&ident);
2342 func_compiler.add_local(name.clone())?;
2344 (1, Some(name))
2345 }
2346 ast::Param::Pattern(pat) => {
2347 let bind_name = pat
2351 .pat_bind()
2352 .and_then(|pb| pb.ident())
2353 .map(|id| ident_text(&id));
2354
2355 if let Some(ref bname) = bind_name {
2356 func_compiler.add_local(bname.clone())?;
2357 } else {
2358 func_compiler.add_local("__arg".to_string())?;
2360 }
2361
2362 let mut field_names: Vec<(String, Option<ast::Expr>)> = Vec::new();
2364 for entry in pat.pat_entries() {
2365 let ident = entry
2366 .ident()
2367 .ok_or_else(|| CompileError::MissingNode("pattern entry ident".to_string()))?;
2368 let fname = ident_text(&ident);
2369 let default = entry.default();
2370 formals_metadata.push((fname.clone(), default.is_some()));
2371 field_names.push((fname, default));
2372 }
2373
2374 for (fname, _) in &field_names {
2376 func_compiler.emit(OpCode::Null); func_compiler.add_local(fname.clone())?;
2378 }
2379
2380 for (i, (fname, default)) in field_names.iter().enumerate() {
2382 let key_idx = func_compiler.add_attr_key(fname.clone())?;
2383 if let Some(default_expr) = default {
2384 func_compiler.emit(OpCode::GetLocal);
2402 func_compiler.emit_u16(0); func_compiler.emit(OpCode::HasAttr);
2404 func_compiler.emit_u16(key_idx);
2405 let else_jump = func_compiler.emit_jump(OpCode::JumpIfFalse);
2406 let depth_at_branch = func_compiler.stack_depth;
2408 func_compiler.emit(OpCode::GetLocal);
2410 func_compiler.emit_u16(0);
2411 func_compiler.emit(OpCode::GetAttr);
2412 func_compiler.emit_u16(key_idx);
2413 let end_jump = func_compiler.emit_jump(OpCode::Jump);
2414 func_compiler.stack_depth = depth_at_branch;
2416 func_compiler.patch_jump(else_jump)?;
2417 func_compiler.compile_thunk_immediate(default_expr)?;
2418 func_compiler.patch_jump(end_jump)?;
2420 } else {
2421 func_compiler.emit(OpCode::GetLocal);
2423 func_compiler.emit_u16(0); func_compiler.emit(OpCode::GetAttr);
2425 func_compiler.emit_u16(key_idx);
2426 }
2427 let field_slot = func_compiler.find_local_slot(fname);
2429 func_compiler.emit(OpCode::SetLocal);
2430 func_compiler.emit_u16(field_slot);
2431 func_compiler.emit(OpCode::Pop);
2432 let _ = i; }
2434
2435 (1, bind_name)
2436 }
2437 };
2438
2439 func_compiler.tail_position = true;
2442 func_compiler.compile_expr(&body)?;
2443 func_compiler.emit(OpCode::Return);
2444
2445 let upvalue_count = func_compiler.upvalues.len();
2447 let upvalue_descs: Vec<UpvalueDesc> = func_compiler.upvalues.clone();
2448
2449 let closure = VMValue::Closure(VMClosure {
2451 chunk: Rc::new(func_compiler.chunk),
2452 upvalues: Vec::new(), arity,
2454 name,
2455 formals: formals_metadata,
2456 });
2457
2458 if upvalue_count == 0 {
2459 self.emit_constant(closure)
2461 } else {
2462 let idx = self.chunk.add_constant(closure)?;
2464 self.emit(OpCode::MakeClosure);
2465 self.stack_depth += 1; self.emit_u16(idx);
2467 self.emit_u16(upvalue_count as u16);
2469 for uv in &upvalue_descs {
2471 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
2472 self.emit_u16(uv.index);
2473 }
2474 Ok(())
2475 }
2476 }
2477
2478 fn compile_apply(&mut self, app: &ast::Apply) -> Result<(), CompileError> {
2481 let func = app
2482 .lambda()
2483 .ok_or_else(|| CompileError::MissingNode("apply function".to_string()))?;
2484 let arg = app
2485 .argument()
2486 .ok_or_else(|| CompileError::MissingNode("apply argument".to_string()))?;
2487
2488 let tail = self.tail_position;
2490 self.tail_position = false;
2491
2492 if let ast::Expr::Ident(ref id) = func {
2494 let name = ident_text(id);
2495 if name == "import" {
2496 self.compile_expr(&arg)?;
2497 self.emit(OpCode::Import);
2498 return Ok(());
2499 }
2500 }
2501
2502 let call_op = if tail { OpCode::TailCall } else { OpCode::Call };
2504
2505 if !tail {
2509 if let Some(slot) = self.try_resolve_as_local(&func) {
2510 self.compile_arg_maybe_thunk(&arg)?;
2511 self.emit(OpCode::GetLocalCall);
2512 self.emit_u16(slot);
2513 return Ok(());
2514 }
2515 }
2516
2517 self.compile_expr(&func)?;
2519 self.compile_arg_maybe_thunk(&arg)?;
2520 self.emit(call_op);
2521 Ok(())
2522 }
2523
2524 fn compile_binop(&mut self, binop: &ast::BinOp) -> Result<(), CompileError> {
2532 let lhs = binop
2533 .lhs()
2534 .ok_or_else(|| CompileError::MissingNode("binop lhs".to_string()))?;
2535 let rhs = binop
2536 .rhs()
2537 .ok_or_else(|| CompileError::MissingNode("binop rhs".to_string()))?;
2538 let op = binop
2539 .operator()
2540 .ok_or_else(|| CompileError::MissingNode("binop operator".to_string()))?;
2541
2542 match op {
2543 ast::BinOpKind::And => {
2545 self.compile_expr(&lhs)?;
2546 let false_jump = self.emit_jump(OpCode::JumpIfFalse);
2547 let depth_at_branch = self.stack_depth;
2549 self.compile_expr(&rhs)?;
2550 let end_jump = self.emit_jump(OpCode::Jump);
2551 self.stack_depth = depth_at_branch;
2553 self.patch_jump(false_jump)?;
2554 self.emit(OpCode::False);
2555 self.patch_jump(end_jump)?;
2556 }
2557 ast::BinOpKind::Or => {
2559 self.compile_expr(&lhs)?;
2560 let true_jump = self.emit_jump(OpCode::JumpIfTrue);
2561 let depth_at_branch = self.stack_depth;
2563 self.compile_expr(&rhs)?;
2564 let end_jump = self.emit_jump(OpCode::Jump);
2565 self.stack_depth = depth_at_branch;
2567 self.patch_jump(true_jump)?;
2568 self.emit(OpCode::True);
2569 self.patch_jump(end_jump)?;
2570 }
2571 ast::BinOpKind::Implication => {
2573 self.compile_expr(&lhs)?;
2574 let false_jump = self.emit_jump(OpCode::JumpIfFalse);
2575 let depth_at_branch = self.stack_depth;
2577 self.compile_expr(&rhs)?;
2578 let end_jump = self.emit_jump(OpCode::Jump);
2579 self.stack_depth = depth_at_branch;
2581 self.patch_jump(false_jump)?;
2582 self.emit(OpCode::True);
2583 self.patch_jump(end_jump)?;
2584 }
2585 _ => {
2587 self.compile_expr(&lhs)?;
2588 self.compile_expr(&rhs)?;
2589 match op {
2590 ast::BinOpKind::Add => self.emit(OpCode::Add),
2591 ast::BinOpKind::Sub => self.emit(OpCode::Sub),
2592 ast::BinOpKind::Mul => self.emit(OpCode::Mul),
2593 ast::BinOpKind::Div => self.emit(OpCode::Div),
2594 ast::BinOpKind::Equal => self.emit(OpCode::Equal),
2595 ast::BinOpKind::NotEqual => self.emit(OpCode::NotEqual),
2596 ast::BinOpKind::Less => self.emit(OpCode::Less),
2597 ast::BinOpKind::LessOrEq => self.emit(OpCode::LessEqual),
2598 ast::BinOpKind::More => self.emit(OpCode::Greater),
2599 ast::BinOpKind::MoreOrEq => self.emit(OpCode::GreaterEqual),
2600 ast::BinOpKind::Update => self.emit(OpCode::UpdateAttrs),
2601 ast::BinOpKind::Concat => self.emit(OpCode::Concat),
2602 ast::BinOpKind::And
2603 | ast::BinOpKind::Or
2604 | ast::BinOpKind::Implication => unreachable!(),
2605 ast::BinOpKind::PipeRight | ast::BinOpKind::PipeLeft => {
2606 return Err(CompileError::Unsupported("pipe operators".to_string()));
2607 }
2608 }
2609 }
2610 }
2611 Ok(())
2612 }
2613
2614 fn compile_unary(&mut self, op: &ast::UnaryOp) -> Result<(), CompileError> {
2617 let inner = op
2618 .expr()
2619 .ok_or_else(|| CompileError::MissingNode("unary expr".to_string()))?;
2620 let kind = op
2621 .operator()
2622 .ok_or_else(|| CompileError::MissingNode("unary operator".to_string()))?;
2623 self.compile_expr(&inner)?;
2624 match kind {
2625 ast::UnaryOpKind::Negate => self.emit(OpCode::Negate),
2626 ast::UnaryOpKind::Invert => self.emit(OpCode::Not),
2627 }
2628 Ok(())
2629 }
2630
2631 fn compile_with(&mut self, with: &ast::With) -> Result<(), CompileError> {
2634 let ns = with
2635 .namespace()
2636 .ok_or_else(|| CompileError::MissingNode("with namespace".to_string()))?;
2637 let body = with
2638 .body()
2639 .ok_or_else(|| CompileError::MissingNode("with body".to_string()))?;
2640
2641 self.compile_expr(&ns)?;
2643
2644 self.emit(OpCode::Dup);
2648 self.emit(OpCode::PushWith);
2649
2650 let slot = self.add_local("__with_scope".to_string())?;
2652 self.with_scope_locals.push(slot);
2653 self.with_depth += 1;
2654
2655 self.compile_expr(&body)?;
2657
2658 self.emit(OpCode::PopWith);
2660 self.with_depth -= 1;
2661 self.with_scope_locals.pop();
2662
2663 self.emit(OpCode::SetLocal);
2669 self.emit_u16(slot);
2670 self.emit(OpCode::Pop);
2671 self.stack_depth = slot + 1;
2673 self.locals.pop();
2674
2675 Ok(())
2676 }
2677
2678 fn compile_assert(&mut self, assert: &ast::Assert) -> Result<(), CompileError> {
2681 let cond = assert
2682 .condition()
2683 .ok_or_else(|| CompileError::MissingNode("assert condition".to_string()))?;
2684 let body = assert
2685 .body()
2686 .ok_or_else(|| CompileError::MissingNode("assert body".to_string()))?;
2687 let tail = self.tail_position;
2689 self.tail_position = false;
2690 self.compile_expr(&cond)?;
2691 self.emit(OpCode::Assert);
2692 self.tail_position = tail;
2694 self.compile_expr(&body)?;
2695 Ok(())
2696 }
2697
2698 fn compile_list(&mut self, list: &ast::List) -> Result<(), CompileError> {
2701 let items: Vec<_> = list.items().collect();
2702 let count = u16::try_from(items.len())
2703 .map_err(|_| CompileError::Unsupported("list too large".to_string()))?;
2704 for item in &items {
2705 self.compile_expr(item)?;
2706 }
2707 self.emit(OpCode::MakeList);
2708 self.emit_u16(count);
2709 self.stack_depth = self.stack_depth.saturating_sub(count) + 1;
2711 Ok(())
2712 }
2713
2714 fn emit(&mut self, op: OpCode) {
2717 self.chunk.write_op(op, self.current_line);
2718 match op {
2720 OpCode::Null | OpCode::True | OpCode::False
2722 | OpCode::GetLocal | OpCode::GetUpvalue
2723 | OpCode::PushBuiltins | OpCode::LookupWith => {
2724 self.stack_depth += 1;
2725 }
2726 OpCode::Dup => {
2728 self.stack_depth += 1;
2729 }
2730 OpCode::Pop | OpCode::PushWith
2732 | OpCode::Assert | OpCode::Throw | OpCode::Return => {
2733 self.stack_depth = self.stack_depth.saturating_sub(1);
2734 }
2735 OpCode::Add | OpCode::Sub | OpCode::Mul | OpCode::Div
2737 | OpCode::Equal | OpCode::NotEqual | OpCode::Less
2738 | OpCode::Greater | OpCode::LessEqual | OpCode::GreaterEqual
2739 | OpCode::And | OpCode::Or | OpCode::Implication
2740 | OpCode::Concat | OpCode::UpdateAttrs
2741 | OpCode::Call | OpCode::TailCall | OpCode::DynGetAttr | OpCode::DynHasAttr => {
2742 self.stack_depth = self.stack_depth.saturating_sub(1);
2743 }
2744 OpCode::Negate | OpCode::Not | OpCode::Force
2746 | OpCode::GetAttr | OpCode::HasAttr
2747 | OpCode::Import => {}
2748 OpCode::SetLocal | OpCode::SetUpvalue => {}
2750 OpCode::PopWith => {}
2752 OpCode::Jump => {}
2754 OpCode::JumpIfFalse | OpCode::JumpIfTrue => {
2756 self.stack_depth = self.stack_depth.saturating_sub(1);
2757 }
2758 OpCode::SelectOrDefault => {
2760 self.stack_depth = self.stack_depth.saturating_sub(1);
2761 }
2762 OpCode::DynSelectOrDefault => {
2764 self.stack_depth = self.stack_depth.saturating_sub(2);
2765 }
2766 OpCode::GetLocalAttr => {
2768 self.stack_depth += 1;
2769 }
2770 OpCode::GetLocalCall => {
2772 self.stack_depth = self.stack_depth.saturating_sub(1);
2773 }
2774 OpCode::CallBuiltin => {
2776 self.stack_depth = self.stack_depth.saturating_sub(1);
2777 }
2778 OpCode::Constant | OpCode::MakeAttrs | OpCode::MakeList
2786 | OpCode::MakeClosure | OpCode::MakeThunk | OpCode::MakeLazyThunk
2787 | OpCode::Interpolate | OpCode::PatchThunkUpvalues => {}
2788 }
2789 }
2790
2791
2792 fn emit_u16(&mut self, value: u16) {
2793 self.chunk.write_u16(value, self.current_line);
2794 }
2795
2796 fn emit_constant(&mut self, value: VMValue) -> Result<(), CompileError> {
2797 let idx = self.chunk.add_constant(value)?;
2798 self.emit(OpCode::Constant);
2799 self.stack_depth += 1; self.emit_u16(idx);
2801 Ok(())
2802 }
2803
2804 fn add_attr_key(&mut self, key: String) -> Result<u16, CompileError> {
2809 let sym = self.interner.borrow_mut().intern(&key);
2810 self.chunk.add_key_constant(VMValue::String(key), sym)
2811 }
2812
2813 fn emit_jump(&mut self, op: OpCode) -> usize {
2816 self.emit(op);
2817 let offset = self.chunk.len();
2818 self.emit_u16(0xFFFF); offset
2820 }
2821
2822 fn patch_jump(&mut self, placeholder_offset: usize) -> Result<(), CompileError> {
2824 let target = self.chunk.len();
2825 let target_u16 = u16::try_from(target).map_err(|_| CompileError::JumpOverflow)?;
2826 self.chunk.patch_u16(placeholder_offset, target_u16);
2827 Ok(())
2828 }
2829
2830 fn begin_scope(&mut self) {
2833 self.scope_depth += 1;
2834 }
2835
2836 fn end_scope(&mut self, binding_count: u16) {
2837 if binding_count > 0 {
2908 let first_local_idx = self.locals.len() - binding_count as usize;
2912 let base_slot = self.locals[first_local_idx].slot;
2913 self.emit(OpCode::SetLocal);
2914 self.emit_u16(base_slot);
2915 for _ in 0..binding_count {
2916 self.emit(OpCode::Pop);
2917 }
2918 self.stack_depth = base_slot + 1;
2921 }
2922
2923 while let Some(local) = self.locals.last() {
2925 if local.depth < self.scope_depth {
2926 break;
2927 }
2928 self.locals.pop();
2929 }
2930 self.scope_depth -= 1;
2931 }
2932
2933 fn add_local(&mut self, name: String) -> Result<u16, CompileError> {
2935 if self.locals.len() >= u16::MAX as usize {
2936 return Err(CompileError::TooManyLocals);
2937 }
2938 let slot = self.stack_depth - 1;
2942 self.locals.push(Local {
2943 name,
2944 depth: self.scope_depth,
2945 is_captured: false,
2946 slot,
2947 });
2948 Ok(slot)
2949 }
2950
2951 fn resolve_local(&self, name: &str) -> Option<u16> {
2954 for (i, local) in self.locals.iter().enumerate().rev() {
2955 if local.name == name && local.depth != u32::MAX {
2956 return Some(i as u16);
2957 }
2958 }
2959 None
2960 }
2961
2962 fn local_stack_slot(&self, locals_idx: u16) -> u16 {
2964 self.locals[locals_idx as usize].slot
2965 }
2966
2967 fn find_local_slot(&self, name: &str) -> u16 {
2971 let idx = self.resolve_local(name)
2972 .unwrap_or_else(|| panic!("local '{name}' not found"));
2973 self.locals[idx as usize].slot
2974 }
2975
2976 fn find_local_slot_opt(&self, name: &str) -> Option<u16> {
2978 self.resolve_local(name)
2979 .map(|idx| self.locals[idx as usize].slot)
2980 }
2981
2982 fn add_upvalue(&mut self, is_local: bool, index: u16) -> Result<u8, CompileError> {
2986 for (i, uv) in self.upvalues.iter().enumerate() {
2988 if uv.is_local == is_local && uv.index == index {
2989 return Ok(i as u8);
2990 }
2991 }
2992 if self.upvalues.len() >= 256 {
2993 return Err(CompileError::Unsupported("too many upvalues (max 256)".to_string()));
2994 }
2995 let idx = self.upvalues.len() as u8;
2996 self.upvalues.push(UpvalueDesc { is_local, index });
2997 Ok(idx)
2998 }
2999
3000 fn resolve_upvalue(&mut self, name: &str) -> Option<u8> {
3005 let enclosing_ptr = self.enclosing?;
3006 let enclosing = unsafe { &mut *enclosing_ptr };
3010
3011 if let Some(local_idx) = enclosing.resolve_local(name) {
3013 enclosing.locals[local_idx as usize].is_captured = true;
3014 let stack_slot = enclosing.locals[local_idx as usize].slot;
3016 return Some(self.add_upvalue(true, stack_slot).ok()?);
3017 }
3018
3019 if let Some(uv_idx) = enclosing.resolve_upvalue(name) {
3021 return Some(self.add_upvalue(false, uv_idx as u16).ok()?);
3022 }
3023
3024 None
3030 }
3031
3032 fn has_with_scope(&self) -> bool {
3034 if self.with_depth > 0 {
3035 return true;
3036 }
3037 if let Some(enclosing_ptr) = self.enclosing {
3038 let enclosing = unsafe { &*enclosing_ptr };
3039 return enclosing.has_with_scope();
3040 }
3041 false
3042 }
3043
3044 fn resolve_relative_path(&self, rel_path: &str) -> String {
3047 if let Some(ref base) = self.base_dir {
3048 return base.join(rel_path).to_string_lossy().to_string();
3049 }
3050 if let Some(enclosing_ptr) = self.enclosing {
3051 let enclosing = unsafe { &*enclosing_ptr };
3052 return enclosing.resolve_relative_path(rel_path);
3053 }
3054 rel_path.to_string()
3055 }
3056}
3057
3058fn ident_text(ident: &ast::Ident) -> String {
3062 ident
3063 .ident_token()
3064 .map(|t| t.text().to_string())
3065 .unwrap_or_default()
3066}
3067
3068fn static_attr_name(attr: &ast::Attr) -> Result<String, CompileError> {
3071 match attr {
3072 ast::Attr::Ident(ident) => Ok(ident_text(ident)),
3073 ast::Attr::Str(s) => {
3074 let parts: Vec<_> = s.normalized_parts().into_iter().collect();
3076 if parts.len() == 1 {
3077 if let InterpolPart::Literal(text) = &parts[0] {
3078 return Ok(text.to_string());
3079 }
3080 }
3081 Err(CompileError::Unsupported(
3082 "interpolated string attribute keys".to_string(),
3083 ))
3084 }
3085 ast::Attr::Dynamic(_) => Err(CompileError::Unsupported(
3086 "dynamic attribute keys".to_string(),
3087 )),
3088 }
3089}
3090
3091fn is_global_builtin(name: &str) -> bool {
3140 sui_compat::scope::CALLABLE_GLOBALS.contains(&name)
3141}
3142
3143fn line_of(expr: &ast::Expr) -> u32 {
3145 let offset = AstNode::syntax(expr).text_range().start();
3148 u32::from(offset)
3150}
3151
3152fn detect_trivial_cycles(bindings: &[(String, &ast::Expr)]) -> Vec<String> {
3161 let mut warnings = Vec::new();
3162 for (name, expr) in bindings {
3163 if let ast::Expr::Ident(id) = expr {
3164 if id
3165 .ident_token()
3166 .map(|t| t.text() == name.as_str())
3167 .unwrap_or(false)
3168 {
3169 warnings.push(format!("warning: `{name}` directly references itself"));
3170 }
3171 }
3172 }
3173 warnings
3174}
3175
3176fn parse_nix_path(s: &str) -> Vec<(String, String)> {
3182 if s.is_empty() {
3183 return Vec::new();
3184 }
3185 s.split(':')
3186 .filter(|e| !e.is_empty())
3187 .map(|entry| match entry.split_once('=') {
3188 Some((prefix, path)) => (prefix.to_string(), path.to_string()),
3189 None => (String::new(), entry.to_string()),
3190 })
3191 .collect()
3192}
3193
3194fn resolve_search_path(name: &str) -> Option<String> {
3197 let nix_path = std::env::var("NIX_PATH").ok()?;
3198 for (prefix, path) in parse_nix_path(&nix_path) {
3199 if !prefix.is_empty() && name == prefix {
3200 if std::path::Path::new(&path).exists() {
3201 return Some(path);
3202 }
3203 continue;
3204 }
3205 if !prefix.is_empty() {
3206 let needle = format!("{prefix}/");
3207 if let Some(rest) = name.strip_prefix(&needle) {
3208 let full = format!("{path}/{rest}");
3209 if std::path::Path::new(&full).exists() {
3210 return Some(full);
3211 }
3212 continue;
3213 }
3214 }
3215 if prefix.is_empty() {
3216 let full = format!("{path}/{name}");
3217 if std::path::Path::new(&full).exists() {
3218 return Some(full);
3219 }
3220 }
3221 }
3222 None
3223}
3224
3225#[cfg(test)]
3226mod tests {
3227 use super::*;
3228
3229 fn compile(input: &str) -> Chunk {
3230 let (chunk, _interner) =
3231 Compiler::compile(input).unwrap_or_else(|e| panic!("compile failed for '{input}': {e}"));
3232 chunk
3233 }
3234
3235 #[test]
3256 fn duplicate_dotted_path_errors_instead_of_panicking() {
3257 for src in [
3258 "{ a.b = 1; a.b = 2; }",
3259 "{ a.b.c = 1; a.b.c = 2; }",
3260 "{ a.b.c.d = 1; a.b.c.d = 2; }",
3261 ] {
3262 let err = Compiler::compile(src)
3263 .err()
3264 .unwrap_or_else(|| panic!("{src} compiled; it must be refused, not accepted"));
3265 let msg = err.to_string();
3266 assert!(
3267 msg.contains("defined more than once"),
3268 "{src}: expected a duplicate-attribute refusal, got: {msg}"
3269 );
3270 }
3271 }
3272
3273 #[test]
3278 fn legal_nested_paths_still_compile() {
3279 for src in [
3280 "{ a.b = 1; a.c = 2; }",
3281 "{ a.b.c = 1; a.b.d = 2; }",
3282 "{ a.b = 1; a = { c = 2; }; }",
3283 "{ x.y.z = 1; }",
3284 "{ a = { b = 1; }; }",
3285 ] {
3286 assert!(
3287 Compiler::compile(src).is_ok(),
3288 "{src} must still compile — it is legal nix"
3289 );
3290 }
3291 }
3292
3293 #[test]
3294 fn compile_integer() {
3295 let chunk = compile("42");
3296 assert!(!chunk.code.is_empty());
3297 assert_eq!(chunk.constants.len(), 1);
3298 assert_eq!(chunk.constants[0], VMValue::Int(42));
3299 }
3300
3301 #[test]
3302 fn compile_float() {
3303 let chunk = compile("3.14");
3304 assert_eq!(chunk.constants[0], VMValue::Float(3.14));
3305 }
3306
3307 #[test]
3308 fn compile_bool_true() {
3309 let chunk = compile("true");
3310 assert_eq!(chunk.code[0], OpCode::Constant as u8);
3312 assert_eq!(chunk.constants[0], VMValue::Bool(true));
3313 }
3314
3315 #[test]
3316 fn compile_bool_false() {
3317 let chunk = compile("false");
3318 assert_eq!(chunk.code[0], OpCode::Constant as u8);
3320 assert_eq!(chunk.constants[0], VMValue::Bool(false));
3321 }
3322
3323 #[test]
3324 fn compile_null() {
3325 let chunk = compile("null");
3326 assert_eq!(chunk.code[0], OpCode::Constant as u8);
3328 assert_eq!(chunk.constants[0], VMValue::Null);
3329 }
3330
3331 #[test]
3332 fn compile_string() {
3333 let chunk = compile(r#""hello""#);
3334 assert_eq!(chunk.constants[0], VMValue::String("hello".to_string()));
3335 }
3336
3337 #[test]
3338 fn compile_addition() {
3339 let chunk = compile("1 + 2");
3340 assert_eq!(chunk.constants[0], VMValue::Int(3));
3342 assert!(!chunk.code.contains(&(OpCode::Add as u8)));
3343 }
3344
3345 #[test]
3346 fn compile_addition_non_foldable() {
3347 let chunk = compile("let x = 1; in x + 2");
3349 assert!(chunk.code.contains(&(OpCode::Add as u8)));
3350 }
3351
3352 #[test]
3353 fn compile_if_else() {
3354 let chunk = compile("if true then 1 else 2");
3355 assert_eq!(chunk.constants[0], VMValue::Int(1));
3357 assert!(!chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
3358 }
3359
3360 #[test]
3361 fn compile_if_else_non_foldable() {
3362 let chunk = compile("let b = true; in if b then 1 else 2");
3364 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
3365 }
3366
3367 #[test]
3368 fn compile_list() {
3369 let chunk = compile("[1 2 3]");
3370 assert!(chunk.code.contains(&(OpCode::MakeList as u8)));
3371 }
3372
3373 #[test]
3374 fn compile_attrset() {
3375 let chunk = compile("{ a = 1; b = 2; }");
3376 assert!(chunk.code.contains(&(OpCode::MakeAttrs as u8)));
3377 }
3378
3379 #[test]
3380 fn compile_select() {
3381 let chunk = compile("{ a = 1; }.a");
3382 assert!(chunk.code.contains(&(OpCode::GetAttr as u8)));
3383 }
3384
3385 #[test]
3386 fn compile_lambda() {
3387 let chunk = compile("x: x + 1");
3388 assert!(chunk.constants.iter().any(|c| matches!(c, VMValue::Closure(_))));
3390 }
3391
3392 #[test]
3393 fn compile_negate() {
3394 let chunk = compile("-42");
3395 assert_eq!(chunk.constants[0], VMValue::Int(-42));
3397 assert!(!chunk.code.contains(&(OpCode::Negate as u8)));
3398 }
3399
3400 #[test]
3401 fn compile_negate_non_foldable() {
3402 let chunk = compile("let x = 42; in -x");
3403 assert!(chunk.code.contains(&(OpCode::Negate as u8)));
3404 }
3405
3406 #[test]
3407 fn compile_not() {
3408 let chunk = compile("!true");
3409 assert_eq!(chunk.constants[0], VMValue::Bool(false));
3411 assert!(!chunk.code.contains(&(OpCode::Not as u8)));
3412 }
3413
3414 #[test]
3415 fn compile_assert() {
3416 let chunk = compile("assert true; 42");
3417 assert!(chunk.code.contains(&(OpCode::Assert as u8)));
3418 }
3419
3420 #[test]
3421 fn compile_let_in() {
3422 let chunk = compile("let x = 1; y = 2; in x + y");
3423 assert!(chunk.code.contains(&(OpCode::GetLocal as u8)));
3424 }
3425
3426 #[test]
3427 fn compile_parse_error() {
3428 let result = Compiler::compile("let in");
3429 assert!(result.is_err());
3430 }
3431
3432 #[test]
3433 fn compile_comparison() {
3434 let chunk = compile("1 < 2");
3435 assert_eq!(chunk.constants[0], VMValue::Bool(true));
3437 }
3438
3439 #[test]
3440 fn compile_equality() {
3441 let chunk = compile("1 == 1");
3442 assert_eq!(chunk.constants[0], VMValue::Bool(true));
3444 }
3445
3446 #[test]
3447 fn compile_update_attrs() {
3448 let chunk = compile("{ a = 1; } // { b = 2; }");
3449 assert!(chunk.code.contains(&(OpCode::UpdateAttrs as u8)));
3450 }
3451
3452 #[test]
3453 fn compile_list_concat() {
3454 let chunk = compile("[1] ++ [2]");
3455 assert!(chunk.code.contains(&(OpCode::Concat as u8)));
3456 }
3457
3458 #[test]
3459 fn compile_and_short_circuit() {
3460 let chunk = compile("true && false");
3461 assert_eq!(chunk.constants[0], VMValue::Bool(false));
3463 }
3464
3465 #[test]
3466 fn compile_and_short_circuit_non_foldable() {
3467 let chunk = compile("let a = true; in a && false");
3468 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
3469 }
3470
3471 #[test]
3472 fn compile_or_short_circuit() {
3473 let chunk = compile("false || true");
3474 assert_eq!(chunk.constants[0], VMValue::Bool(true));
3476 }
3477
3478 #[test]
3479 fn compile_or_short_circuit_non_foldable() {
3480 let chunk = compile("let a = false; in a || true");
3481 assert!(chunk.code.contains(&(OpCode::JumpIfTrue as u8)));
3482 }
3483
3484 #[test]
3485 fn compile_has_attr() {
3486 let chunk = compile("{ a = 1; } ? a");
3487 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3488 }
3489
3490 #[test]
3491 fn compile_select_or_default() {
3492 let chunk = compile("{ a = 1; }.b or 0");
3495 assert!(chunk.code.contains(&(OpCode::Dup as u8)));
3496 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3497 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
3498 assert!(chunk.code.contains(&(OpCode::GetAttr as u8)));
3499 }
3500
3501 #[test]
3502 fn compile_dyn_select_or_default() {
3503 let chunk = compile(r#"let x = "a"; in { a = 1; }.${ x } or 0"#);
3506 assert!(chunk.code.contains(&(OpCode::Dup as u8)));
3507 assert!(chunk.code.contains(&(OpCode::DynHasAttr as u8)));
3508 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
3509 assert!(chunk.code.contains(&(OpCode::DynGetAttr as u8)));
3511 }
3512
3513 #[test]
3514 fn compile_multi_segment_select_or_default() {
3515 let chunk = compile("{ a = { b = 1; }; }.a.b.c or 0");
3517 let has_attr_count = chunk.code.iter().filter(|&&b| b == OpCode::HasAttr as u8).count();
3519 assert!(has_attr_count >= 3, "expected >= 3 HasAttr ops for 3 segments, got {has_attr_count}");
3520 }
3521
3522 #[test]
3523 fn compile_pattern_lambda() {
3524 let chunk = compile("{ a, b }: a + b");
3525 assert!(chunk.constants.iter().any(|c| matches!(c, VMValue::Closure(_))));
3526 }
3527
3528 #[test]
3529 fn compile_string_interpolation() {
3530 let chunk = compile(r#"let x = "world"; in "hello ${x}""#);
3531 assert!(chunk.code.contains(&(OpCode::Interpolate as u8)));
3533 }
3534
3535 #[test]
3538 fn detect_trivial_self_reference() {
3539 let root = rnix::Root::parse("x");
3540 let expr = root.tree().expr().unwrap();
3541 let bindings = vec![("x".to_string(), &expr)];
3542 let warnings = detect_trivial_cycles(&bindings);
3543 assert_eq!(warnings.len(), 1);
3544 assert!(warnings[0].contains("directly references itself"));
3545 }
3546
3547 #[test]
3548 fn detect_no_false_positive() {
3549 let root = rnix::Root::parse("y");
3550 let expr = root.tree().expr().unwrap();
3551 let bindings = vec![("x".to_string(), &expr)];
3552 let warnings = detect_trivial_cycles(&bindings);
3553 assert!(warnings.is_empty());
3554 }
3555
3556 #[test]
3557 fn detect_non_ident_no_warning() {
3558 let root = rnix::Root::parse("1 + 2");
3559 let expr = root.tree().expr().unwrap();
3560 let bindings = vec![("x".to_string(), &expr)];
3561 let warnings = detect_trivial_cycles(&bindings);
3562 assert!(warnings.is_empty());
3563 }
3564
3565 #[test]
3566 fn detect_trivial_cycles_multiple() {
3567 let root_x = rnix::Root::parse("x");
3568 let expr_x = root_x.tree().expr().unwrap();
3569 let root_y = rnix::Root::parse("y");
3570 let expr_y = root_y.tree().expr().unwrap();
3571 let root_z = rnix::Root::parse("1");
3572 let expr_z = root_z.tree().expr().unwrap();
3573 let bindings = vec![
3574 ("x".to_string(), &expr_x),
3575 ("y".to_string(), &expr_y),
3576 ("z".to_string(), &expr_z),
3577 ];
3578 let warnings = detect_trivial_cycles(&bindings);
3579 assert_eq!(warnings.len(), 2);
3580 }
3581
3582 fn nix_path_lock() -> std::sync::MutexGuard<'static, ()> {
3601 static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
3602 LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
3603 }
3604
3605 #[test]
3606 fn path_search_compiles_with_matching_nix_path() {
3607 let _nix_path = nix_path_lock();
3608 let dir = tempfile::tempdir().unwrap();
3611 let target = dir.path().join("mypkg");
3612 std::fs::create_dir(&target).unwrap();
3613 let nix_path_val = format!("mypkg={}", target.display());
3615 unsafe { std::env::set_var("NIX_PATH", &nix_path_val) };
3617 let result = Compiler::compile("<mypkg>");
3618 unsafe { std::env::remove_var("NIX_PATH") };
3619 assert!(result.is_ok(), "expected compile success, got: {result:?}");
3620 let (chunk, _) = result.unwrap();
3621 assert!(
3623 chunk.constants.iter().any(|c| matches!(c, VMValue::Path(p) if p == &target.display().to_string())),
3624 "expected path constant for {:?}, got: {:?}",
3625 target.display(),
3626 chunk.constants,
3627 );
3628 }
3629
3630 #[test]
3631 fn path_search_fails_when_nix_path_no_match() {
3632 let _nix_path = nix_path_lock();
3633 unsafe { std::env::set_var("NIX_PATH", "other=/nonexistent") };
3636 let result = Compiler::compile("<nosuchpkg>");
3637 unsafe { std::env::remove_var("NIX_PATH") };
3638
3639 assert!(
3654 result.is_ok(),
3655 "an unresolvable search path is deferred to force-time, not a \
3656 compile error; got: {result:?}"
3657 );
3658 let (chunk, _) = result.unwrap();
3659 assert!(
3660 chunk.constants.iter().any(|c| matches!(c, VMValue::Closure(_))),
3661 "expected a deferred-throw closure in the constant pool, got: {:?}",
3662 chunk.constants,
3663 );
3664 }
3665
3666 #[test]
3667 fn path_search_with_sub_path() {
3668 let _nix_path = nix_path_lock();
3669 let dir = tempfile::tempdir().unwrap();
3671 let nixpkgs = dir.path().join("nixpkgs-src");
3672 let lib_dir = nixpkgs.join("lib");
3673 std::fs::create_dir_all(&lib_dir).unwrap();
3674 let nix_path_val = format!("nixpkgs={}", nixpkgs.display());
3675 unsafe { std::env::set_var("NIX_PATH", &nix_path_val) };
3677 let result = Compiler::compile("<nixpkgs/lib>");
3678 unsafe { std::env::remove_var("NIX_PATH") };
3679 assert!(result.is_ok(), "expected compile success for sub-path, got: {result:?}");
3680 let (chunk, _) = result.unwrap();
3681 let expected_path = lib_dir.display().to_string();
3682 assert!(
3683 chunk.constants.iter().any(|c| matches!(c, VMValue::Path(p) if p == &expected_path)),
3684 "expected path constant for {expected_path}, got: {:?}",
3685 chunk.constants,
3686 );
3687 }
3688
3689 #[test]
3692 fn lambda_body_apply_emits_tail_call() {
3693 let chunk = compile("x: x 1");
3695 let closure_chunk = chunk
3698 .constants
3699 .iter()
3700 .find_map(|c| match c {
3701 VMValue::Closure(cl) => Some(&cl.chunk),
3702 _ => None,
3703 })
3704 .expect("expected a closure constant");
3705 assert!(
3706 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
3707 "lambda body call should emit TailCall, bytecode: {:?}",
3708 closure_chunk.code,
3709 );
3710 }
3711
3712 #[test]
3713 fn if_then_apply_emits_tail_call() {
3714 let chunk = compile("x: if true then x 1 else 0");
3716 let closure_chunk = chunk
3717 .constants
3718 .iter()
3719 .find_map(|c| match c {
3720 VMValue::Closure(cl) => Some(&cl.chunk),
3721 _ => None,
3722 })
3723 .expect("expected a closure constant");
3724 assert!(
3725 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
3726 "if-then call should emit TailCall, bytecode: {:?}",
3727 closure_chunk.code,
3728 );
3729 }
3730
3731 #[test]
3732 fn if_else_apply_emits_tail_call() {
3733 let chunk = compile("x: if false then 0 else x 1");
3735 let closure_chunk = chunk
3736 .constants
3737 .iter()
3738 .find_map(|c| match c {
3739 VMValue::Closure(cl) => Some(&cl.chunk),
3740 _ => None,
3741 })
3742 .expect("expected a closure constant");
3743 assert!(
3744 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
3745 "if-else call should emit TailCall, bytecode: {:?}",
3746 closure_chunk.code,
3747 );
3748 }
3749
3750 #[test]
3751 fn non_tail_apply_emits_regular_call() {
3752 let chunk = compile("let f = x: x; in f (f 1)");
3755 assert!(
3758 chunk.code.contains(&(OpCode::Call as u8))
3759 || chunk.code.contains(&(OpCode::GetLocalCall as u8)),
3760 "non-tail call should emit Call or GetLocalCall, bytecode: {:?}",
3761 chunk.code,
3762 );
3763 }
3764
3765 #[test]
3766 fn assert_body_apply_emits_tail_call() {
3767 let chunk = compile("f: assert true; f 1");
3769 let closure_chunk = chunk
3770 .constants
3771 .iter()
3772 .find_map(|c| match c {
3773 VMValue::Closure(cl) => Some(&cl.chunk),
3774 _ => None,
3775 })
3776 .expect("expected a closure constant");
3777 assert!(
3778 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
3779 "assert body call should emit TailCall, bytecode: {:?}",
3780 closure_chunk.code,
3781 );
3782 }
3783
3784 #[test]
3787 fn multi_segment_hasattr_compiles() {
3788 let chunk = compile("{ a = { b = 1; }; } ? a");
3790 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3791 }
3792
3793 #[test]
3794 fn single_segment_hasattr_still_works() {
3795 let chunk = compile("{ x = 1; } ? x");
3797 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3798 }
3799
3800 #[test]
3801 fn multi_segment_hasattr_deep_path() {
3802 let chunk = compile("{ a = { b = 1; }; } ? a.b");
3804 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3806 }
3807}