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
45pub struct Compiler {
54 chunk: Chunk,
56 locals: Vec<Local>,
58 upvalues: Vec<UpvalueDesc>,
60 scope_depth: u32,
62 current_line: u32,
64 interner: Rc<RefCell<Interner>>,
66 enclosing: Option<*mut Compiler>,
68 with_depth: u32,
70 base_dir: Option<std::path::PathBuf>,
72 stack_depth: u16,
78 source_text: Option<Rc<String>>,
81 tail_position: bool,
86 with_scope_locals: Vec<u16>,
92}
93
94impl Compiler {
95 fn new() -> Self {
97 Self {
98 chunk: Chunk::new(),
99 locals: Vec::new(),
100 upvalues: Vec::new(),
101 scope_depth: 0,
102 current_line: 0,
103 interner: Rc::new(RefCell::new(Interner::new())),
104 enclosing: None,
105 with_depth: 0,
106 base_dir: None,
107 stack_depth: 0,
108 source_text: None,
109 tail_position: false,
110 with_scope_locals: Vec::new(),
111 }
112 }
113
114 fn with_interner(interner: Rc<RefCell<Interner>>) -> Self {
116 Self {
117 chunk: Chunk::new(),
118 locals: Vec::new(),
119 upvalues: Vec::new(),
120 scope_depth: 0,
121 current_line: 0,
122 interner,
123 enclosing: None,
124 with_depth: 0,
125 base_dir: None,
126 stack_depth: 0,
127 source_text: None,
128 tail_position: false,
129 with_scope_locals: Vec::new(),
130 }
131 }
132
133 pub fn compile_with_base_dir(
136 input: &str,
137 base_dir: std::path::PathBuf,
138 ) -> Result<(Chunk, Interner), CompileError> {
139 let parse = rnix::Root::parse(input);
140 if !parse.errors().is_empty() {
141 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
142 return Err(CompileError::ParseError(msgs.join("; ")));
143 }
144 let root = parse.tree();
145 let expr = root
146 .expr()
147 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
148 let mut compiler = Self::new();
149 compiler.base_dir = Some(base_dir);
150 compiler.compile_expr(&expr)?;
151 compiler.emit(OpCode::Return);
152 let interner = match Rc::try_unwrap(compiler.interner) {
153 Ok(cell) => cell.into_inner(),
154 Err(rc) => (*rc).borrow().clone(),
155 };
156 Ok((compiler.chunk, interner))
157 }
158
159 pub fn compile_with_shared_interner(
163 input: &str,
164 base_dir: std::path::PathBuf,
165 interner: Rc<RefCell<Interner>>,
166 ) -> Result<Chunk, CompileError> {
167 let parse = rnix::Root::parse(input);
168 if !parse.errors().is_empty() {
169 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
170 return Err(CompileError::ParseError(msgs.join("; ")));
171 }
172 let root = parse.tree();
173 let expr = root
174 .expr()
175 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
176 let mut compiler = Self::with_interner(interner);
177 compiler.base_dir = Some(base_dir);
178 compiler.source_text = Some(Rc::new(input.to_string()));
179 compiler.compile_expr(&expr)?;
180 compiler.emit(OpCode::Return);
181 Ok(compiler.chunk)
182 }
183
184 pub fn compile_expression(
187 input: &str,
188 base_dir: &std::path::Path,
189 interner: Rc<RefCell<Interner>>,
190 ) -> Result<Chunk, CompileError> {
191 let parse = rnix::Root::parse(input);
192 if !parse.errors().is_empty() {
193 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
194 return Err(CompileError::ParseError(msgs.join("; ")));
195 }
196 let root = parse.tree();
197 let expr = root
198 .expr()
199 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
200 let mut compiler = Self::with_interner(interner);
201 compiler.base_dir = Some(base_dir.to_path_buf());
202 compiler.compile_expr(&expr)?;
203 compiler.emit(OpCode::Return);
204 Ok(compiler.chunk)
205 }
206
207 pub fn compile(input: &str) -> Result<(Chunk, Interner), CompileError> {
209 let parse = rnix::Root::parse(input);
210 if !parse.errors().is_empty() {
211 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
212 return Err(CompileError::ParseError(msgs.join("; ")));
213 }
214 let root = parse.tree();
215 let expr = root
216 .expr()
217 .ok_or_else(|| CompileError::ParseError("empty expression".to_string()))?;
218 let mut compiler = Self::new();
219 compiler.compile_expr(&expr)?;
220 compiler.emit(OpCode::Return);
221 let interner = match Rc::try_unwrap(compiler.interner) {
222 Ok(cell) => cell.into_inner(),
223 Err(rc) => (*rc).borrow().clone(),
224 };
225 Ok((compiler.chunk, interner))
226 }
227
228 fn try_eval_const(expr: &ast::Expr) -> Option<VMValue> {
234 match expr {
235 ast::Expr::Literal(lit) => Self::try_eval_literal(lit),
236 ast::Expr::Paren(p) => Self::try_eval_const(&p.expr()?),
237 ast::Expr::UnaryOp(op) => Self::try_fold_unary(op),
238 ast::Expr::BinOp(binop) => Self::try_fold_binop(binop),
239 ast::Expr::IfElse(ie) => Self::try_fold_if(ie),
240 ast::Expr::Ident(id) => {
241 let name = ident_text(id);
242 match name.as_str() {
243 "true" => Some(VMValue::Bool(true)),
244 "false" => Some(VMValue::Bool(false)),
245 "null" => Some(VMValue::Null),
246 _ => None,
247 }
248 }
249 _ => None,
250 }
251 }
252
253 fn try_eval_literal(lit: &ast::Literal) -> Option<VMValue> {
255 match lit.kind() {
256 ast::LiteralKind::Integer(tok) => {
257 Some(VMValue::Int(tok.value().ok()?))
258 }
259 ast::LiteralKind::Float(tok) => {
260 Some(VMValue::Float(tok.value().ok()?))
261 }
262 ast::LiteralKind::Uri(_) => None,
263 }
264 }
265
266 fn try_fold_unary(op: &ast::UnaryOp) -> Option<VMValue> {
268 let inner = Self::try_eval_const(&op.expr()?)?;
269 let kind = op.operator()?;
270 match kind {
271 ast::UnaryOpKind::Negate => match inner {
272 VMValue::Int(n) => Some(VMValue::Int(-n)),
273 VMValue::Float(f) => Some(VMValue::Float(-f)),
274 _ => None,
275 },
276 ast::UnaryOpKind::Invert => match inner {
277 VMValue::Bool(b) => Some(VMValue::Bool(!b)),
278 _ => None,
279 },
280 }
281 }
282
283 fn try_fold_binop(binop: &ast::BinOp) -> Option<VMValue> {
285 let lhs = Self::try_eval_const(&binop.lhs()?)?;
286 let rhs = Self::try_eval_const(&binop.rhs()?)?;
287 let op = binop.operator()?;
288
289 match op {
290 ast::BinOpKind::Add => match (&lhs, &rhs) {
291 (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a + b)),
292 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a + b)),
293 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 + b)),
294 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a + *b as f64)),
295 (VMValue::String(a), VMValue::String(b)) => {
296 Some(VMValue::String(format!("{a}{b}")))
297 }
298 _ => None,
299 },
300 ast::BinOpKind::Sub => match (&lhs, &rhs) {
301 (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a - b)),
302 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a - b)),
303 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 - b)),
304 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a - *b as f64)),
305 _ => None,
306 },
307 ast::BinOpKind::Mul => match (&lhs, &rhs) {
308 (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a * b)),
309 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a * b)),
310 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 * b)),
311 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a * *b as f64)),
312 _ => None,
313 },
314 ast::BinOpKind::Div => match (&lhs, &rhs) {
315 (VMValue::Int(_), VMValue::Int(0)) => None, (VMValue::Int(a), VMValue::Int(b)) => Some(VMValue::Int(a / b)),
317 (VMValue::Float(a), VMValue::Float(b)) => Some(VMValue::Float(a / b)),
318 (VMValue::Int(a), VMValue::Float(b)) => Some(VMValue::Float(*a as f64 / b)),
319 (VMValue::Float(a), VMValue::Int(b)) => Some(VMValue::Float(a / *b as f64)),
320 _ => None,
321 },
322 ast::BinOpKind::Equal => Some(VMValue::Bool(Self::const_eq(&lhs, &rhs))),
323 ast::BinOpKind::NotEqual => Some(VMValue::Bool(!Self::const_eq(&lhs, &rhs))),
324 ast::BinOpKind::Less => Self::const_cmp(&lhs, &rhs)
325 .map(|o| VMValue::Bool(o == std::cmp::Ordering::Less)),
326 ast::BinOpKind::LessOrEq => Self::const_cmp(&lhs, &rhs)
327 .map(|o| VMValue::Bool(o != std::cmp::Ordering::Greater)),
328 ast::BinOpKind::More => Self::const_cmp(&lhs, &rhs)
329 .map(|o| VMValue::Bool(o == std::cmp::Ordering::Greater)),
330 ast::BinOpKind::MoreOrEq => Self::const_cmp(&lhs, &rhs)
331 .map(|o| VMValue::Bool(o != std::cmp::Ordering::Less)),
332 ast::BinOpKind::And => match (&lhs, &rhs) {
333 (VMValue::Bool(a), VMValue::Bool(b)) => Some(VMValue::Bool(*a && *b)),
334 _ => None,
335 },
336 ast::BinOpKind::Or => match (&lhs, &rhs) {
337 (VMValue::Bool(a), VMValue::Bool(b)) => Some(VMValue::Bool(*a || *b)),
338 _ => None,
339 },
340 ast::BinOpKind::Implication => match (&lhs, &rhs) {
341 (VMValue::Bool(a), VMValue::Bool(b)) => Some(VMValue::Bool(!a || *b)),
342 _ => None,
343 },
344 _ => None,
345 }
346 }
347
348 fn try_fold_if(ie: &ast::IfElse) -> Option<VMValue> {
350 let cond = Self::try_eval_const(&ie.condition()?)?;
351 match cond {
352 VMValue::Bool(true) => Self::try_eval_const(&ie.body()?),
353 VMValue::Bool(false) => Self::try_eval_const(&ie.else_body()?),
354 _ => None,
355 }
356 }
357
358 fn const_eq(a: &VMValue, b: &VMValue) -> bool {
360 match (a, b) {
361 (VMValue::Null, VMValue::Null) => true,
362 (VMValue::Bool(a), VMValue::Bool(b)) => a == b,
363 (VMValue::Int(a), VMValue::Int(b)) => a == b,
364 (VMValue::Float(a), VMValue::Float(b)) => a == b,
365 (VMValue::Int(a), VMValue::Float(b)) | (VMValue::Float(b), VMValue::Int(a)) => {
366 (*a as f64) == *b
367 }
368 (VMValue::String(a), VMValue::String(b)) => a == b,
369 _ => false,
370 }
371 }
372
373 fn const_cmp(a: &VMValue, b: &VMValue) -> Option<std::cmp::Ordering> {
375 match (a, b) {
376 (VMValue::Int(a), VMValue::Int(b)) => Some(a.cmp(b)),
377 (VMValue::Float(a), VMValue::Float(b)) => a.partial_cmp(b),
378 (VMValue::Int(a), VMValue::Float(b)) => (*a as f64).partial_cmp(b),
379 (VMValue::Float(a), VMValue::Int(b)) => a.partial_cmp(&(*b as f64)),
380 (VMValue::String(a), VMValue::String(b)) => Some(a.cmp(b)),
381 _ => None,
382 }
383 }
384
385 fn compile_expr(&mut self, expr: &ast::Expr) -> Result<(), CompileError> {
388 self.current_line = line_of(expr);
389
390 if let Some(folded) = Self::try_eval_const(expr) {
393 return self.emit_constant(folded);
394 }
395
396 let tail = self.tail_position;
401 self.tail_position = false;
402
403 match expr {
404 ast::Expr::Literal(lit) => self.compile_literal(lit),
405 ast::Expr::Str(s) => self.compile_str(s),
406 ast::Expr::Ident(id) => self.compile_ident(id),
407 ast::Expr::LetIn(letin) => self.compile_let(letin),
408 ast::Expr::AttrSet(set) => self.compile_attrset(set),
409 ast::Expr::Select(sel) => self.compile_select(sel),
410 ast::Expr::HasAttr(ha) => self.compile_has_attr(ha),
411 ast::Expr::IfElse(ie) => {
412 self.tail_position = tail;
413 self.compile_if(ie)
414 }
415 ast::Expr::Lambda(lam) => self.compile_lambda(lam),
416 ast::Expr::Apply(app) => {
417 self.tail_position = tail;
418 self.compile_apply(app)
419 }
420 ast::Expr::BinOp(op) => self.compile_binop(op),
421 ast::Expr::UnaryOp(op) => self.compile_unary(op),
422 ast::Expr::With(w) => self.compile_with(w),
423 ast::Expr::Assert(a) => {
424 self.tail_position = tail;
425 self.compile_assert(a)
426 }
427 ast::Expr::List(l) => self.compile_list(l),
428 ast::Expr::Paren(p) => {
429 self.tail_position = tail;
430 let inner = p
431 .expr()
432 .ok_or_else(|| CompileError::MissingNode("paren expr".to_string()))?;
433 self.compile_expr(&inner)
434 }
435 ast::Expr::Root(r) => {
436 self.tail_position = tail;
437 let inner = r
438 .expr()
439 .ok_or_else(|| CompileError::MissingNode("root expr".to_string()))?;
440 self.compile_expr(&inner)
441 }
442 ast::Expr::PathAbs(p) => {
443 let text = p.syntax().text().to_string();
444 self.emit_constant(VMValue::Path(text))
445 }
446 ast::Expr::PathRel(p) => {
447 let text = p.syntax().text().to_string();
448 let resolved = self.resolve_relative_path(&text);
451 self.emit_constant(VMValue::Path(resolved))
452 }
453 ast::Expr::PathHome(p) => {
454 let text = p.syntax().text().to_string();
455 self.emit_constant(VMValue::Path(text))
456 }
457 ast::Expr::PathSearch(p) => {
458 let text = p.syntax().text().to_string();
459 let inner = text
460 .strip_prefix('<')
461 .and_then(|s| s.strip_suffix('>'))
462 .unwrap_or(&text);
463 if let Some(resolved) = resolve_search_path(inner) {
464 self.emit_constant(VMValue::Path(resolved))
465 } else {
466 let msg = format!("search path '{text}' not in NIX_PATH");
470 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
471 tc.scope_depth = 1;
472 tc.base_dir = self.base_dir.clone();
473 tc.emit_constant(VMValue::String(msg))?;
474 tc.emit(OpCode::Throw);
475 tc.emit(OpCode::Return);
476 let closure = VMValue::Closure(VMClosure {
477 chunk: Rc::new(tc.chunk),
478 upvalues: Vec::new(),
479 arity: 0,
480 name: None,
481 formals: Vec::new(),
482 });
483 let idx = self.chunk.add_constant(closure)?;
484 self.emit(OpCode::MakeThunk);
485 self.stack_depth += 1;
486 self.emit_u16(idx);
487 self.emit_u16(0); Ok(())
489 }
490 }
491 ast::Expr::LegacyLet(ll) => {
492 self.compile_legacy_let(&ll)
496 }
497 ast::Expr::CurPos(_) => {
498 self.emit_constant(VMValue::Null)
500 }
501 other => Err(CompileError::Unsupported(format!("{other:?}"))),
502 }
503 }
504
505 fn compile_literal(&mut self, lit: &ast::Literal) -> Result<(), CompileError> {
508 match lit.kind() {
509 ast::LiteralKind::Integer(tok) => {
510 let n = tok.value().map_err(|e| {
511 CompileError::ParseError(format!("invalid integer: {e}"))
512 })?;
513 self.emit_constant(VMValue::Int(n))
514 }
515 ast::LiteralKind::Float(tok) => {
516 let f = tok.value().map_err(|e| {
517 CompileError::ParseError(format!("invalid float: {e}"))
518 })?;
519 self.emit_constant(VMValue::Float(f))
520 }
521 ast::LiteralKind::Uri(tok) => {
522 let s = tok.syntax().text().to_string();
523 self.emit_constant(VMValue::String(s))
524 }
525 }
526 }
527
528 fn compile_str(&mut self, s: &ast::Str) -> Result<(), CompileError> {
531 let parts: Vec<_> = s.normalized_parts().into_iter().collect();
532
533 if parts.len() == 1 {
535 if let InterpolPart::Literal(text) = &parts[0] {
536 return self.emit_constant(VMValue::String(String::from(text.as_str())));
537 }
538 }
539
540 let mut count: u16 = 0;
542 for part in &parts {
543 match part {
544 InterpolPart::Literal(text) => {
545 self.emit_constant(VMValue::String(text.to_string()))?;
546 count += 1;
547 }
548 InterpolPart::Interpolation(interp) => {
549 let expr = interp
550 .expr()
551 .ok_or_else(|| CompileError::MissingNode("interpolation expr".to_string()))?;
552 self.compile_expr(&expr)?;
553 count += 1;
554 }
555 }
556 }
557
558 if count == 0 {
559 self.emit_constant(VMValue::String(String::new()))
561 } else if count == 1 {
562 Ok(())
564 } else {
565 self.emit(OpCode::Interpolate);
566 self.emit_u16(count);
567 self.stack_depth = self.stack_depth.saturating_sub(count) + 1;
569 Ok(())
570 }
571 }
572
573 fn compile_ident(&mut self, ident: &ast::Ident) -> Result<(), CompileError> {
576 let name = ident_text(ident);
577 match name.as_str() {
578 "true" => {
579 self.emit(OpCode::True);
580 Ok(())
581 }
582 "false" => {
583 self.emit(OpCode::False);
584 Ok(())
585 }
586 "null" => {
587 self.emit(OpCode::Null);
588 Ok(())
589 }
590 _ => {
591 if let Some(idx) = self.resolve_local(&name) {
593 self.emit(OpCode::GetLocal);
594 self.emit_u16(self.local_stack_slot(idx));
595 return Ok(());
596 }
597 if let Some(idx) = self.resolve_upvalue(&name) {
599 self.emit(OpCode::GetUpvalue);
600 self.emit_u16(idx as u16);
601 return Ok(());
602 }
603 if name == "builtins" {
605 self.emit(OpCode::PushBuiltins);
606 return Ok(());
607 }
608 if is_global_builtin(&name) {
611 self.emit(OpCode::PushBuiltins);
612 let key_idx = self.add_attr_key(name)?;
613 self.emit(OpCode::GetAttr);
614 self.emit_u16(key_idx);
615 return Ok(());
616 }
617 if self.has_with_scope() {
619 let name_idx = self.chunk.add_constant(VMValue::String(name))?;
620 self.emit(OpCode::LookupWith);
621 self.emit_u16(name_idx);
622 return Ok(());
623 }
624 Err(CompileError::Unsupported(format!(
625 "unresolved variable: {name}"
626 )))
627 }
628 }
629 }
630
631 fn compile_let(&mut self, letin: &ast::LetIn) -> Result<(), CompileError> {
634 let plan = sui_normalize::plan_for_group_total(letin, true).map_err(reject)?;
643
644 if !plan.dynamics.is_empty() {
648 return Err(CompileError::Unsupported(
649 "dynamic attribute in a let binding".to_string(),
650 ));
651 }
652
653 let body = letin
654 .body()
655 .ok_or_else(|| CompileError::MissingNode("let body".to_string()))?;
656 self.begin_scope();
657 let local_count = self.bind_plan_group_locals(&plan)?;
658 self.compile_expr(&body)?;
660 self.end_scope(local_count);
661 Ok(())
662 }
663
664 fn is_trivial_value(expr: &ast::Expr) -> bool {
666 match expr {
667 ast::Expr::Literal(_) => true,
668 ast::Expr::Str(s) => {
669 for part in s.normalized_parts() {
670 if !matches!(part, InterpolPart::Literal(_)) {
671 return false;
672 }
673 }
674 true
675 }
676 ast::Expr::Ident(id) => {
677 let name = ident_text(id);
678 matches!(name.as_str(), "true" | "false" | "null")
679 }
680 ast::Expr::Lambda(_) => true,
681 ast::Expr::Paren(p) => p.expr().map_or(false, |inner| Self::is_trivial_value(&inner)),
682 ast::Expr::List(list) => list.items().next().is_none(),
683 ast::Expr::AttrSet(set) => set.rec_token().is_none() && set.entries().next().is_none(),
684 _ => false,
685 }
686 }
687
688 fn is_trivial_value_for_rec(expr: &ast::Expr) -> bool {
697 match expr {
698 ast::Expr::Lambda(_) => false,
700 ast::Expr::Paren(p) => p.expr().map_or(false, |inner| Self::is_trivial_value_for_rec(&inner)),
701 _ => Self::is_trivial_value(expr),
702 }
703 }
704
705 fn compile_deferred_thunk<F>(&mut self, body: F) -> Result<Vec<UpvalueDesc>, CompileError>
723 where
724 F: FnOnce(&mut Compiler) -> Result<(), CompileError>,
725 {
726 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
727 tc.scope_depth = 1;
728 tc.enclosing = Some(self as *mut Compiler);
729 tc.with_depth = 0;
730 tc.base_dir = self.base_dir.clone();
731 let with_count = self.emit_with_scope_preamble(&mut tc);
732 body(&mut tc)?;
733 for _ in 0..with_count {
734 tc.emit(OpCode::PopWith);
735 }
736 tc.emit(OpCode::Return);
737 let uv_descs: Vec<UpvalueDesc> = tc.upvalues.clone();
738 let closure = VMValue::Closure(VMClosure {
739 chunk: Rc::new(tc.chunk),
740 upvalues: Vec::new(),
741 arity: 0,
742 name: None,
743 formals: Vec::new(),
744 });
745 let idx = self.chunk.add_constant(closure)?;
746 self.emit(OpCode::MakeThunk);
747 self.stack_depth += 1; self.emit_u16(idx);
749 self.emit_u16(0); Ok(uv_descs)
751 }
752
753 fn compile_thunk_deferred(&mut self, expr: &ast::Expr) -> Result<Vec<UpvalueDesc>, CompileError> {
754 self.compile_deferred_thunk(|tc| tc.compile_expr(expr))
755 }
756
757 fn compile_arg_maybe_thunk(&mut self, arg: &ast::Expr) -> Result<(), CompileError> {
759 if Self::is_trivial_arg(arg) {
760 self.compile_expr(arg)
761 } else {
762 self.compile_thunk_immediate(arg)
763 }
764 }
765
766 fn is_trivial_arg(expr: &ast::Expr) -> bool {
767 match expr {
768 ast::Expr::Literal(_) | ast::Expr::Ident(_)
769 | ast::Expr::PathAbs(_) | ast::Expr::PathRel(_)
770 | ast::Expr::PathHome(_) | ast::Expr::Lambda(_) => true,
771 ast::Expr::Paren(p) => p.expr().map_or(false, |inner| Self::is_trivial_arg(&inner)),
773 ast::Expr::Str(s) => s.normalized_parts().iter().all(|p| matches!(p, InterpolPart::Literal(_))),
775 _ => false,
776 }
777 }
778
779 fn compile_inherit_from_thunk_deferred(
782 &mut self,
783 source_expr: &ast::Expr,
784 attr_name: &str,
785 ) -> Result<Vec<UpvalueDesc>, CompileError> {
786 self.compile_deferred_thunk(|tc| {
787 tc.compile_expr(source_expr)?;
788 let key_idx = tc.add_attr_key(attr_name.to_string())?;
789 tc.emit(OpCode::GetAttr);
790 tc.emit_u16(key_idx);
791 Ok(())
792 })
793 }
794
795 fn emit_with_scope_preamble(&mut self, tc: &mut Compiler) -> usize {
800 let slots: Vec<u16> = self.with_scope_locals.clone();
801 for &slot in &slots {
802 let local_idx = self.locals.iter().rposition(|l| l.slot == slot);
804 if let Some(idx) = local_idx {
805 self.locals[idx].is_captured = true;
806 if let Ok(uv_idx) = tc.add_upvalue(true, slot) {
807 tc.emit(OpCode::GetUpvalue);
808 tc.emit_u16(uv_idx as u16);
809 tc.emit(OpCode::PushWith);
810 tc.with_depth += 1;
811 }
812 }
813 }
814 slots.len()
815 }
816
817 fn compile_thunk_immediate(&mut self, expr: &ast::Expr) -> Result<(), CompileError> {
824 if let Some(ref source) = self.source_text {
827 if self.locals.is_empty() && self.with_depth == 0 && self.upvalues.is_empty() {
828 let range = AstNode::syntax(expr).text_range();
829 let offset: usize = range.start().into();
830 let length: usize = range.len().into();
831 let base_dir_str = self.base_dir
832 .as_ref()
833 .map(|p| p.to_string_lossy().to_string())
834 .unwrap_or_default();
835
836 let src_idx = self.chunk.add_constant(VMValue::String((**source).clone()))?;
838 let dir_idx = self.chunk.add_constant(VMValue::String(base_dir_str))?;
839
840 self.emit(OpCode::MakeLazyThunk);
841 self.stack_depth += 1;
842 self.emit_u16(src_idx);
843 self.chunk.write_u32(offset as u32, self.current_line);
844 self.chunk.write_u32(length as u32, self.current_line);
845 self.emit_u16(dir_idx);
846 self.emit_u16(0); return Ok(());
848 }
849 }
850
851 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
853 tc.scope_depth = 1;
854 tc.enclosing = Some(self as *mut Compiler);
855 tc.with_depth = 0; tc.base_dir = self.base_dir.clone();
857
858 let with_count = self.emit_with_scope_preamble(&mut tc);
861
862 tc.compile_expr(expr)?;
863
864 for _ in 0..with_count {
866 tc.emit(OpCode::PopWith);
867 }
868
869 tc.emit(OpCode::Return);
870 let uv_descs: Vec<UpvalueDesc> = tc.upvalues.clone();
871 let closure = VMValue::Closure(VMClosure {
872 chunk: Rc::new(tc.chunk), upvalues: Vec::new(), arity: 0, name: None, formals: Vec::new(),
873 });
874 let idx = self.chunk.add_constant(closure)?;
875 self.emit(OpCode::MakeThunk);
876 self.stack_depth += 1; self.emit_u16(idx);
878 self.emit_u16(uv_descs.len() as u16);
879 for uv in &uv_descs {
880 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
881 self.emit_u16(uv.index);
882 }
883 Ok(())
884 }
885
886 fn compile_inherit_from_thunk(
889 &mut self,
890 source_expr: &ast::Expr,
891 attr_name: &str,
892 ) -> Result<(), CompileError> {
893 let mut tc = Compiler::with_interner(Rc::clone(&self.interner));
894 tc.scope_depth = 1;
895 tc.enclosing = Some(self as *mut Compiler);
896 tc.with_depth = 0;
897 tc.base_dir = self.base_dir.clone();
898 let with_count = self.emit_with_scope_preamble(&mut tc);
899 tc.compile_expr(source_expr)?;
900 let key_idx = tc.add_attr_key(attr_name.to_string())?;
901 tc.emit(OpCode::GetAttr);
902 tc.emit_u16(key_idx);
903 for _ in 0..with_count { tc.emit(OpCode::PopWith); }
904 tc.emit(OpCode::Return);
905 let uv_descs: Vec<UpvalueDesc> = tc.upvalues.clone();
906 let closure = VMValue::Closure(VMClosure {
907 chunk: Rc::new(tc.chunk),
908 upvalues: Vec::new(),
909 arity: 0, formals: Vec::new(),
910 name: None,
911 });
912 let idx = self.chunk.add_constant(closure)?;
913 self.emit(OpCode::MakeThunk);
914 self.stack_depth += 1; self.emit_u16(idx);
916 self.emit_u16(uv_descs.len() as u16);
917 for uv in &uv_descs {
918 self.chunk
919 .write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
920 self.emit_u16(uv.index);
921 }
922 Ok(())
923 }
924
925 fn compile_attrset(&mut self, set: &ast::AttrSet) -> Result<(), CompileError> {
928 let rec = set.rec_token().is_some();
929
930 let plan = sui_normalize::plan_for_group_total(set, rec).map_err(reject)?;
940 self.compile_plan_group(&plan)
941 }
942
943 fn compile_plan_group(&mut self, plan: &sui_normalize::GroupPlan) -> Result<(), CompileError> {
969 if plan.recursive {
970 self.compile_plan_group_rec(plan)
971 } else {
972 self.compile_plan_group_flat(plan)
973 }
974 }
975
976 fn compile_plan_group_flat(
978 &mut self,
979 plan: &sui_normalize::GroupPlan,
980 ) -> Result<(), CompileError> {
981 let mut count: u16 = 0;
982 for b in &plan.statics {
983 let name = sui_intern::resolve(b.name);
984 self.emit_plan_binding(&b.binding, &name, plan)?;
985 self.emit_constant(VMValue::String(name))?;
986 count += 1;
987 }
988 count += self.emit_plan_dynamics(plan)?;
989 self.emit(OpCode::MakeAttrs);
990 self.emit_u16(count);
991 self.stack_depth = self.stack_depth.saturating_sub(2 * count) + 1;
993 Ok(())
994 }
995
996 fn emit_plan_binding(
998 &mut self,
999 binding: &sui_normalize::Binding,
1000 name: &str,
1001 plan: &sui_normalize::GroupPlan,
1002 ) -> Result<(), CompileError> {
1003 use sui_normalize::Binding;
1004 match binding {
1005 Binding::Leaf(expr) => {
1006 if Self::is_trivial_value(expr) {
1007 self.compile_expr(expr)
1008 } else {
1009 self.compile_thunk_immediate(expr)
1010 }
1011 }
1012 Binding::Group(sub) => self.compile_plan_group(sub),
1018 Binding::Inherit => self.emit_variable_load(name),
1022 Binding::InheritFrom { from } => {
1023 let src = plan.inherit_froms.get(*from).ok_or_else(|| {
1024 CompileError::Unsupported(format!(
1025 "inherit-from index {from} out of range for '{name}'"
1026 ))
1027 })?;
1028 let src = src.clone();
1029 self.compile_inherit_from_thunk(&src, name)
1030 }
1031 }
1032 }
1033
1034 fn emit_plan_dynamics(
1038 &mut self,
1039 plan: &sui_normalize::GroupPlan,
1040 ) -> Result<u16, CompileError> {
1041 use sui_normalize::Binding;
1042 let mut count: u16 = 0;
1043 for d in &plan.dynamics {
1044 match &d.value {
1045 Binding::Leaf(expr) => {
1046 if Self::is_trivial_value(expr) {
1047 self.compile_expr(expr)?;
1048 } else {
1049 self.compile_thunk_immediate(expr)?;
1050 }
1051 }
1052 Binding::Group(sub) => self.compile_plan_group(sub)?,
1053 Binding::Inherit | Binding::InheritFrom { .. } => {
1059 return Err(CompileError::Unsupported(
1060 "an inherited binding cannot have a dynamic key".to_string(),
1061 ))
1062 }
1063 }
1064 self.compile_expr(&d.key)?;
1065 count += 1;
1066 }
1067 Ok(count)
1068 }
1069
1070 fn compile_plan_group_rec(
1082 &mut self,
1083 plan: &sui_normalize::GroupPlan,
1084 ) -> Result<(), CompileError> {
1085 self.begin_scope();
1086 let local_count = self.bind_plan_group_locals(plan)?;
1087
1088 let mut count = local_count;
1090 for b in &plan.statics {
1091 let name = sui_intern::resolve(b.name);
1092 let slot = self.find_local_slot(&name);
1093 self.emit(OpCode::GetLocal);
1094 self.emit_u16(slot);
1095 self.emit_constant(VMValue::String(name))?;
1096 }
1097 count += self.emit_plan_dynamics(plan)?;
1100
1101 self.emit(OpCode::MakeAttrs);
1102 self.emit_u16(count);
1103 self.stack_depth = self.stack_depth.saturating_sub(2 * count) + 1;
1104
1105 self.end_scope(local_count);
1107 Ok(())
1108 }
1109
1110 fn bind_plan_group_locals(
1123 &mut self,
1124 plan: &sui_normalize::GroupPlan,
1125 ) -> Result<u16, CompileError> {
1126 use sui_normalize::Binding;
1127
1128 let local_count =
1129 u16::try_from(plan.statics.len()).map_err(|_| CompileError::TooManyLocals)?;
1130
1131 for b in &plan.statics {
1133 self.emit(OpCode::Null); self.add_local(sui_intern::resolve(b.name))?;
1135 }
1136
1137 let mut thunk_slots: Vec<(u16, Vec<UpvalueDesc>)> = Vec::new();
1139 for b in &plan.statics {
1140 let name = sui_intern::resolve(b.name);
1141 let local_idx = self
1142 .resolve_local(&name)
1143 .ok_or_else(|| CompileError::Unsupported(format!("rec local '{name}' vanished")))?;
1144 let slot = self.locals[local_idx as usize].slot;
1145 match &b.binding {
1146 Binding::Leaf(expr) => {
1147 if Self::is_trivial_value_for_rec(expr) {
1148 self.compile_expr(expr)?;
1149 } else {
1150 let uv = self.compile_thunk_deferred(expr)?;
1151 if !uv.is_empty() {
1152 thunk_slots.push((slot, uv));
1153 }
1154 }
1155 }
1156 Binding::Group(sub) => {
1157 let sub = sub.clone();
1161 let uv = self.compile_deferred_thunk(|tc| tc.compile_plan_group(&sub))?;
1162 if !uv.is_empty() {
1163 thunk_slots.push((slot, uv));
1164 }
1165 }
1166 Binding::Inherit => {
1167 let saved_depth = self.locals[local_idx as usize].depth;
1170 self.locals[local_idx as usize].depth = u32::MAX;
1171 self.emit_variable_load_restore(&name, local_idx, saved_depth)?;
1172 self.locals[local_idx as usize].depth = saved_depth;
1173 }
1174 Binding::InheritFrom { from } => {
1175 let src = plan
1176 .inherit_froms
1177 .get(*from)
1178 .ok_or_else(|| {
1179 CompileError::Unsupported(format!(
1180 "inherit-from index {from} out of range for '{name}'"
1181 ))
1182 })?
1183 .clone();
1184 let uv = self.compile_inherit_from_thunk_deferred(&src, &name)?;
1185 if !uv.is_empty() {
1186 thunk_slots.push((slot, uv));
1187 }
1188 }
1189 }
1190 self.emit(OpCode::SetLocal);
1191 self.emit_u16(slot);
1192 self.emit(OpCode::Pop);
1193 }
1194
1195 for (slot, uv_descs) in &thunk_slots {
1197 self.emit(OpCode::PatchThunkUpvalues);
1198 self.emit_u16(*slot);
1199 self.emit_u16(u16::try_from(uv_descs.len()).map_err(|_| CompileError::TooManyLocals)?);
1200 for uv in uv_descs {
1201 self.chunk
1202 .write_byte(u8::from(uv.is_local), self.current_line);
1203 self.emit_u16(uv.index);
1204 }
1205 }
1206
1207 Ok(local_count)
1208 }
1209
1210 fn compile_legacy_let(&mut self, ll: &ast::LegacyLet) -> Result<(), CompileError> {
1216 let plan = sui_normalize::plan_for_group_total(ll, true).map_err(reject)?;
1223 if !plan.dynamics.is_empty() {
1224 return Err(CompileError::Unsupported(
1225 "dynamic attribute in a legacy let".to_string(),
1226 ));
1227 }
1228 let body_sym = sui_intern::intern("body");
1229 if !plan.statics.iter().any(|b| b.name == body_sym) {
1230 return Err(CompileError::Unsupported(
1231 "legacy let without a 'body' attribute".to_string(),
1232 ));
1233 }
1234
1235 self.begin_scope();
1236 let local_count = self.bind_plan_group_locals(&plan)?;
1237 let slot = self.find_local_slot("body");
1238 self.emit(OpCode::GetLocal);
1239 self.emit_u16(slot);
1240 self.end_scope(local_count);
1241 Ok(())
1242 }
1243
1244 fn emit_variable_load(&mut self, name: &str) -> Result<(), CompileError> {
1246 if let Some(idx) = self.resolve_local(name) {
1247 self.emit(OpCode::GetLocal);
1248 self.emit_u16(self.local_stack_slot(idx));
1249 } else if let Some(uv_idx) = self.resolve_upvalue(name) {
1250 self.emit(OpCode::GetUpvalue);
1251 self.emit_u16(uv_idx as u16);
1252 } else if self.has_with_scope() {
1253 let name_idx = self.chunk.add_constant(VMValue::String(name.to_string()))?;
1254 self.emit(OpCode::LookupWith);
1255 self.emit_u16(name_idx);
1256 } else {
1257 return Err(CompileError::Unsupported(format!(
1258 "inherit: cannot resolve '{name}'"
1259 )));
1260 }
1261 Ok(())
1262 }
1263
1264 fn emit_variable_load_restore(
1267 &mut self,
1268 name: &str,
1269 local_idx: u16,
1270 saved_depth: u32,
1271 ) -> Result<(), CompileError> {
1272 if let Some(outer_idx) = self.resolve_local(name) {
1273 self.emit(OpCode::GetLocal);
1274 self.emit_u16(self.local_stack_slot(outer_idx));
1275 } else if let Some(uv_idx) = self.resolve_upvalue(name) {
1276 self.emit(OpCode::GetUpvalue);
1277 self.emit_u16(uv_idx as u16);
1278 } else if self.has_with_scope() {
1279 let name_idx = self.chunk.add_constant(VMValue::String(name.to_string()))?;
1280 self.emit(OpCode::LookupWith);
1281 self.emit_u16(name_idx);
1282 } else {
1283 self.locals[local_idx as usize].depth = saved_depth;
1284 return Err(CompileError::Unsupported(format!(
1285 "inherit: cannot resolve '{name}' in enclosing scope"
1286 )));
1287 }
1288 Ok(())
1289 }
1290
1291 fn try_resolve_as_local(&self, expr: &ast::Expr) -> Option<u16> {
1295 if let ast::Expr::Ident(id) = expr {
1296 let name = ident_text(id);
1297 let idx = self.resolve_local(&name)?;
1298 Some(self.local_stack_slot(idx))
1299 } else {
1300 None
1301 }
1302 }
1303
1304 fn compile_select(&mut self, sel: &ast::Select) -> Result<(), CompileError> {
1305 let base = sel
1306 .expr()
1307 .ok_or_else(|| CompileError::MissingNode("select base".to_string()))?;
1308 let attrpath = sel
1309 .attrpath()
1310 .ok_or_else(|| CompileError::MissingNode("select attrpath".to_string()))?;
1311
1312 let segments: Vec<_> = attrpath.attrs().collect();
1313
1314 if let Some(default_expr) = sel.default_expr() {
1315 self.compile_expr(&base)?;
1339 let depth_before = self.stack_depth; let mut miss_jumps: Vec<usize> = Vec::new();
1341 for (_i, attr) in segments.iter().enumerate() {
1342 if let Ok(key) = static_attr_name(attr) {
1343 let key_idx = self.add_attr_key(key)?;
1344 self.emit(OpCode::Dup); self.emit(OpCode::HasAttr); self.emit_u16(key_idx);
1347 miss_jumps.push(self.emit_jump(OpCode::JumpIfFalse)); self.emit(OpCode::GetAttr); self.emit_u16(key_idx);
1350 } else {
1351 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); }
1358 }
1359 let end_jump = self.emit_jump(OpCode::Jump);
1362 for mj in miss_jumps {
1364 self.patch_jump(mj)?;
1365 }
1366 self.stack_depth = depth_before;
1368 self.emit(OpCode::Pop); self.compile_expr(&default_expr)?; self.patch_jump(end_jump)?;
1371 } else {
1373 let local_slot = self.try_resolve_as_local(&base);
1376
1377 for (i, attr) in segments.iter().enumerate() {
1378 if let Ok(key) = static_attr_name(attr) {
1379 let key_idx = self.add_attr_key(key)?;
1380
1381 if i == 0 {
1382 if let Some(slot) = local_slot {
1383 self.emit(OpCode::GetLocalAttr);
1385 self.emit_u16(slot);
1386 self.emit_u16(key_idx);
1387 } else {
1388 self.compile_expr(&base)?;
1389 self.emit(OpCode::GetAttr);
1390 self.emit_u16(key_idx);
1391 }
1392 } else {
1393 self.emit(OpCode::GetAttr);
1394 self.emit_u16(key_idx);
1395 }
1396 } else {
1397 if i == 0 {
1399 self.compile_expr(&base)?;
1400 }
1401 self.compile_dynamic_attr_key(attr)?;
1402 self.emit(OpCode::DynGetAttr);
1403 }
1404 }
1405 }
1406
1407 Ok(())
1408 }
1409
1410 fn compile_dynamic_attr_key(&mut self, attr: &ast::Attr) -> Result<(), CompileError> {
1412 match attr {
1413 ast::Attr::Dynamic(d) => {
1414 let expr = d.expr().ok_or_else(|| {
1415 CompileError::MissingNode("dynamic attr key expr".to_string())
1416 })?;
1417 self.compile_expr(&expr)
1418 }
1419 ast::Attr::Str(s) => {
1420 let key_expr = ast::Expr::Str(s.clone());
1421 self.compile_expr(&key_expr)
1422 }
1423 ast::Attr::Ident(ident) => {
1424 self.emit_constant(VMValue::String(ident_text(ident)))
1425 }
1426 }
1427 }
1428
1429 fn compile_has_attr(&mut self, ha: &ast::HasAttr) -> Result<(), CompileError> {
1432 let base = ha
1433 .expr()
1434 .ok_or_else(|| CompileError::MissingNode("hasattr base".to_string()))?;
1435 let attrpath = ha
1436 .attrpath()
1437 .ok_or_else(|| CompileError::MissingNode("hasattr attrpath".to_string()))?;
1438
1439 let segments: Vec<_> = attrpath.attrs().collect();
1440
1441 if segments.len() == 1 {
1442 self.compile_expr(&base)?;
1444 if let Ok(key) = static_attr_name(&segments[0]) {
1445 let key_idx = self.add_attr_key(key)?;
1446 self.emit(OpCode::HasAttr);
1447 self.emit_u16(key_idx);
1448 } else {
1449 self.compile_dynamic_attr_key(&segments[0])?;
1450 self.emit(OpCode::DynHasAttr);
1451 }
1452 return Ok(());
1453 }
1454
1455 let mut false_jumps: Vec<usize> = Vec::new();
1464 let depth_before = self.stack_depth;
1467
1468 for (i, seg) in segments.iter().enumerate() {
1469 self.compile_expr(&base)?;
1471 for prev_seg in &segments[..i] {
1472 if let Ok(prev_key) = static_attr_name(prev_seg) {
1473 let prev_idx = self.add_attr_key(prev_key)?;
1474 self.emit(OpCode::GetAttr);
1475 self.emit_u16(prev_idx);
1476 } else {
1477 self.compile_dynamic_attr_key(prev_seg)?;
1478 self.emit(OpCode::DynGetAttr);
1479 }
1480 }
1481 if let Ok(key) = static_attr_name(seg) {
1482 let key_idx = self.add_attr_key(key)?;
1483 self.emit(OpCode::HasAttr);
1484 self.emit_u16(key_idx);
1485 } else {
1486 self.compile_dynamic_attr_key(seg)?;
1487 self.emit(OpCode::DynHasAttr);
1488 }
1489
1490 if i < segments.len() - 1 {
1492 false_jumps.push(self.emit_jump(OpCode::JumpIfFalse));
1493 self.stack_depth = depth_before;
1498 }
1499 }
1500
1501 let done_jump = self.emit_jump(OpCode::Jump);
1503
1504 self.stack_depth = depth_before;
1507 for fj in false_jumps {
1508 self.patch_jump(fj)?;
1509 }
1510 self.emit(OpCode::False);
1511 self.patch_jump(done_jump)?;
1514 Ok(())
1515 }
1516
1517 fn compile_if(&mut self, ie: &ast::IfElse) -> Result<(), CompileError> {
1520 let cond = ie
1521 .condition()
1522 .ok_or_else(|| CompileError::MissingNode("if condition".to_string()))?;
1523 let then_body = ie
1524 .body()
1525 .ok_or_else(|| CompileError::MissingNode("if then".to_string()))?;
1526 let else_body = ie
1527 .else_body()
1528 .ok_or_else(|| CompileError::MissingNode("if else".to_string()))?;
1529
1530 let tail = self.tail_position;
1532
1533 self.tail_position = false;
1535 self.compile_expr(&cond)?;
1536 let else_jump = self.emit_jump(OpCode::JumpIfFalse);
1538 let depth_at_branch = self.stack_depth;
1541 self.tail_position = tail;
1543 self.compile_expr(&then_body)?;
1544 let end_jump = self.emit_jump(OpCode::Jump);
1546 self.stack_depth = depth_at_branch;
1549 self.patch_jump(else_jump)?;
1550 self.tail_position = tail;
1552 self.compile_expr(&else_body)?;
1553 self.patch_jump(end_jump)?;
1557 Ok(())
1558 }
1559
1560 fn compile_lambda(&mut self, lam: &ast::Lambda) -> Result<(), CompileError> {
1563 let param = lam
1564 .param()
1565 .ok_or_else(|| CompileError::MissingNode("lambda param".to_string()))?;
1566 let body = lam
1567 .body()
1568 .ok_or_else(|| CompileError::MissingNode("lambda body".to_string()))?;
1569
1570 let mut func_compiler = Compiler::with_interner(Rc::clone(&self.interner));
1572 func_compiler.scope_depth = 1; func_compiler.enclosing = Some(self as *mut Compiler);
1575 func_compiler.base_dir = self.base_dir.clone();
1577 func_compiler.stack_depth = 1;
1579
1580 let mut formals_metadata: Vec<(String, bool)> = Vec::new();
1581 let (arity, name) = match ¶m {
1582 ast::Param::IdentParam(ip) => {
1583 let ident = ip
1584 .ident()
1585 .ok_or_else(|| CompileError::MissingNode("lambda ident".to_string()))?;
1586 let name = ident_text(&ident);
1587 func_compiler.add_local(name.clone())?;
1589 (1, Some(name))
1590 }
1591 ast::Param::Pattern(pat) => {
1592 let bind_name = pat
1596 .pat_bind()
1597 .and_then(|pb| pb.ident())
1598 .map(|id| ident_text(&id));
1599
1600 if let Some(ref bname) = bind_name {
1601 func_compiler.add_local(bname.clone())?;
1602 } else {
1603 func_compiler.add_local("__arg".to_string())?;
1605 }
1606
1607 let mut field_names: Vec<(String, Option<ast::Expr>)> = Vec::new();
1609 for entry in pat.pat_entries() {
1610 let ident = entry
1611 .ident()
1612 .ok_or_else(|| CompileError::MissingNode("pattern entry ident".to_string()))?;
1613 let fname = ident_text(&ident);
1614 let default = entry.default();
1615 formals_metadata.push((fname.clone(), default.is_some()));
1616 field_names.push((fname, default));
1617 }
1618
1619 for (fname, _) in &field_names {
1621 func_compiler.emit(OpCode::Null); func_compiler.add_local(fname.clone())?;
1623 }
1624
1625 for (i, (fname, default)) in field_names.iter().enumerate() {
1627 let key_idx = func_compiler.add_attr_key(fname.clone())?;
1628 if let Some(default_expr) = default {
1629 func_compiler.emit(OpCode::GetLocal);
1647 func_compiler.emit_u16(0); func_compiler.emit(OpCode::HasAttr);
1649 func_compiler.emit_u16(key_idx);
1650 let else_jump = func_compiler.emit_jump(OpCode::JumpIfFalse);
1651 let depth_at_branch = func_compiler.stack_depth;
1653 func_compiler.emit(OpCode::GetLocal);
1655 func_compiler.emit_u16(0);
1656 func_compiler.emit(OpCode::GetAttr);
1657 func_compiler.emit_u16(key_idx);
1658 let end_jump = func_compiler.emit_jump(OpCode::Jump);
1659 func_compiler.stack_depth = depth_at_branch;
1661 func_compiler.patch_jump(else_jump)?;
1662 func_compiler.compile_thunk_immediate(default_expr)?;
1663 func_compiler.patch_jump(end_jump)?;
1665 } else {
1666 func_compiler.emit(OpCode::GetLocal);
1668 func_compiler.emit_u16(0); func_compiler.emit(OpCode::GetAttr);
1670 func_compiler.emit_u16(key_idx);
1671 }
1672 let field_slot = func_compiler.find_local_slot(fname);
1674 func_compiler.emit(OpCode::SetLocal);
1675 func_compiler.emit_u16(field_slot);
1676 func_compiler.emit(OpCode::Pop);
1677 let _ = i; }
1679
1680 (1, bind_name)
1681 }
1682 };
1683
1684 func_compiler.tail_position = true;
1687 func_compiler.compile_expr(&body)?;
1688 func_compiler.emit(OpCode::Return);
1689
1690 let upvalue_count = func_compiler.upvalues.len();
1692 let upvalue_descs: Vec<UpvalueDesc> = func_compiler.upvalues.clone();
1693
1694 let closure = VMValue::Closure(VMClosure {
1696 chunk: Rc::new(func_compiler.chunk),
1697 upvalues: Vec::new(), arity,
1699 name,
1700 formals: formals_metadata,
1701 });
1702
1703 if upvalue_count == 0 {
1704 self.emit_constant(closure)
1706 } else {
1707 let idx = self.chunk.add_constant(closure)?;
1709 self.emit(OpCode::MakeClosure);
1710 self.stack_depth += 1; self.emit_u16(idx);
1712 self.emit_u16(upvalue_count as u16);
1714 for uv in &upvalue_descs {
1716 self.chunk.write_byte(if uv.is_local { 1 } else { 0 }, self.current_line);
1717 self.emit_u16(uv.index);
1718 }
1719 Ok(())
1720 }
1721 }
1722
1723 fn compile_apply(&mut self, app: &ast::Apply) -> Result<(), CompileError> {
1726 let func = app
1727 .lambda()
1728 .ok_or_else(|| CompileError::MissingNode("apply function".to_string()))?;
1729 let arg = app
1730 .argument()
1731 .ok_or_else(|| CompileError::MissingNode("apply argument".to_string()))?;
1732
1733 let tail = self.tail_position;
1735 self.tail_position = false;
1736
1737 if let ast::Expr::Ident(ref id) = func {
1739 let name = ident_text(id);
1740 if name == "import" {
1741 self.compile_expr(&arg)?;
1742 self.emit(OpCode::Import);
1743 return Ok(());
1744 }
1745 }
1746
1747 let call_op = if tail { OpCode::TailCall } else { OpCode::Call };
1749
1750 if !tail {
1754 if let Some(slot) = self.try_resolve_as_local(&func) {
1755 self.compile_arg_maybe_thunk(&arg)?;
1756 self.emit(OpCode::GetLocalCall);
1757 self.emit_u16(slot);
1758 return Ok(());
1759 }
1760 }
1761
1762 self.compile_expr(&func)?;
1764 self.compile_arg_maybe_thunk(&arg)?;
1765 self.emit(call_op);
1766 Ok(())
1767 }
1768
1769 fn compile_binop(&mut self, binop: &ast::BinOp) -> Result<(), CompileError> {
1777 let lhs = binop
1778 .lhs()
1779 .ok_or_else(|| CompileError::MissingNode("binop lhs".to_string()))?;
1780 let rhs = binop
1781 .rhs()
1782 .ok_or_else(|| CompileError::MissingNode("binop rhs".to_string()))?;
1783 let op = binop
1784 .operator()
1785 .ok_or_else(|| CompileError::MissingNode("binop operator".to_string()))?;
1786
1787 match op {
1788 ast::BinOpKind::And => {
1790 self.compile_expr(&lhs)?;
1791 let false_jump = self.emit_jump(OpCode::JumpIfFalse);
1792 let depth_at_branch = self.stack_depth;
1794 self.compile_expr(&rhs)?;
1795 let end_jump = self.emit_jump(OpCode::Jump);
1796 self.stack_depth = depth_at_branch;
1798 self.patch_jump(false_jump)?;
1799 self.emit(OpCode::False);
1800 self.patch_jump(end_jump)?;
1801 }
1802 ast::BinOpKind::Or => {
1804 self.compile_expr(&lhs)?;
1805 let true_jump = self.emit_jump(OpCode::JumpIfTrue);
1806 let depth_at_branch = self.stack_depth;
1808 self.compile_expr(&rhs)?;
1809 let end_jump = self.emit_jump(OpCode::Jump);
1810 self.stack_depth = depth_at_branch;
1812 self.patch_jump(true_jump)?;
1813 self.emit(OpCode::True);
1814 self.patch_jump(end_jump)?;
1815 }
1816 ast::BinOpKind::Implication => {
1818 self.compile_expr(&lhs)?;
1819 let false_jump = self.emit_jump(OpCode::JumpIfFalse);
1820 let depth_at_branch = self.stack_depth;
1822 self.compile_expr(&rhs)?;
1823 let end_jump = self.emit_jump(OpCode::Jump);
1824 self.stack_depth = depth_at_branch;
1826 self.patch_jump(false_jump)?;
1827 self.emit(OpCode::True);
1828 self.patch_jump(end_jump)?;
1829 }
1830 _ => {
1832 self.compile_expr(&lhs)?;
1833 self.compile_expr(&rhs)?;
1834 match op {
1835 ast::BinOpKind::Add => self.emit(OpCode::Add),
1836 ast::BinOpKind::Sub => self.emit(OpCode::Sub),
1837 ast::BinOpKind::Mul => self.emit(OpCode::Mul),
1838 ast::BinOpKind::Div => self.emit(OpCode::Div),
1839 ast::BinOpKind::Equal => self.emit(OpCode::Equal),
1840 ast::BinOpKind::NotEqual => self.emit(OpCode::NotEqual),
1841 ast::BinOpKind::Less => self.emit(OpCode::Less),
1842 ast::BinOpKind::LessOrEq => self.emit(OpCode::LessEqual),
1843 ast::BinOpKind::More => self.emit(OpCode::Greater),
1844 ast::BinOpKind::MoreOrEq => self.emit(OpCode::GreaterEqual),
1845 ast::BinOpKind::Update => self.emit(OpCode::UpdateAttrs),
1846 ast::BinOpKind::Concat => self.emit(OpCode::Concat),
1847 ast::BinOpKind::And
1848 | ast::BinOpKind::Or
1849 | ast::BinOpKind::Implication => unreachable!(),
1850 ast::BinOpKind::PipeRight | ast::BinOpKind::PipeLeft => {
1851 return Err(CompileError::Unsupported("pipe operators".to_string()));
1852 }
1853 }
1854 }
1855 }
1856 Ok(())
1857 }
1858
1859 fn compile_unary(&mut self, op: &ast::UnaryOp) -> Result<(), CompileError> {
1862 let inner = op
1863 .expr()
1864 .ok_or_else(|| CompileError::MissingNode("unary expr".to_string()))?;
1865 let kind = op
1866 .operator()
1867 .ok_or_else(|| CompileError::MissingNode("unary operator".to_string()))?;
1868 self.compile_expr(&inner)?;
1869 match kind {
1870 ast::UnaryOpKind::Negate => self.emit(OpCode::Negate),
1871 ast::UnaryOpKind::Invert => self.emit(OpCode::Not),
1872 }
1873 Ok(())
1874 }
1875
1876 fn compile_with(&mut self, with: &ast::With) -> Result<(), CompileError> {
1879 let ns = with
1880 .namespace()
1881 .ok_or_else(|| CompileError::MissingNode("with namespace".to_string()))?;
1882 let body = with
1883 .body()
1884 .ok_or_else(|| CompileError::MissingNode("with body".to_string()))?;
1885
1886 self.compile_expr(&ns)?;
1888
1889 self.emit(OpCode::Dup);
1893 self.emit(OpCode::PushWith);
1894
1895 let slot = self.add_local("__with_scope".to_string())?;
1897 self.with_scope_locals.push(slot);
1898 self.with_depth += 1;
1899
1900 self.compile_expr(&body)?;
1902
1903 self.emit(OpCode::PopWith);
1905 self.with_depth -= 1;
1906 self.with_scope_locals.pop();
1907
1908 self.emit(OpCode::SetLocal);
1914 self.emit_u16(slot);
1915 self.emit(OpCode::Pop);
1916 self.stack_depth = slot + 1;
1918 self.locals.pop();
1919
1920 Ok(())
1921 }
1922
1923 fn compile_assert(&mut self, assert: &ast::Assert) -> Result<(), CompileError> {
1926 let cond = assert
1927 .condition()
1928 .ok_or_else(|| CompileError::MissingNode("assert condition".to_string()))?;
1929 let body = assert
1930 .body()
1931 .ok_or_else(|| CompileError::MissingNode("assert body".to_string()))?;
1932 let tail = self.tail_position;
1934 self.tail_position = false;
1935 self.compile_expr(&cond)?;
1936 self.emit(OpCode::Assert);
1937 self.tail_position = tail;
1939 self.compile_expr(&body)?;
1940 Ok(())
1941 }
1942
1943 fn compile_list(&mut self, list: &ast::List) -> Result<(), CompileError> {
1946 let items: Vec<_> = list.items().collect();
1947 let count = u16::try_from(items.len())
1948 .map_err(|_| CompileError::Unsupported("list too large".to_string()))?;
1949 for item in &items {
1950 self.compile_expr(item)?;
1951 }
1952 self.emit(OpCode::MakeList);
1953 self.emit_u16(count);
1954 self.stack_depth = self.stack_depth.saturating_sub(count) + 1;
1956 Ok(())
1957 }
1958
1959 fn emit(&mut self, op: OpCode) {
1962 self.chunk.write_op(op, self.current_line);
1963 match op {
1965 OpCode::Null | OpCode::True | OpCode::False
1967 | OpCode::GetLocal | OpCode::GetUpvalue
1968 | OpCode::PushBuiltins | OpCode::LookupWith => {
1969 self.stack_depth += 1;
1970 }
1971 OpCode::Dup => {
1973 self.stack_depth += 1;
1974 }
1975 OpCode::Pop | OpCode::PushWith
1977 | OpCode::Assert | OpCode::Throw | OpCode::Return => {
1978 self.stack_depth = self.stack_depth.saturating_sub(1);
1979 }
1980 OpCode::Add | OpCode::Sub | OpCode::Mul | OpCode::Div
1982 | OpCode::Equal | OpCode::NotEqual | OpCode::Less
1983 | OpCode::Greater | OpCode::LessEqual | OpCode::GreaterEqual
1984 | OpCode::And | OpCode::Or | OpCode::Implication
1985 | OpCode::Concat | OpCode::UpdateAttrs
1986 | OpCode::Call | OpCode::TailCall | OpCode::DynGetAttr | OpCode::DynHasAttr => {
1987 self.stack_depth = self.stack_depth.saturating_sub(1);
1988 }
1989 OpCode::Negate | OpCode::Not | OpCode::Force
1991 | OpCode::GetAttr | OpCode::HasAttr
1992 | OpCode::Import => {}
1993 OpCode::SetLocal | OpCode::SetUpvalue => {}
1995 OpCode::PopWith => {}
1997 OpCode::Jump => {}
1999 OpCode::JumpIfFalse | OpCode::JumpIfTrue => {
2001 self.stack_depth = self.stack_depth.saturating_sub(1);
2002 }
2003 OpCode::SelectOrDefault => {
2005 self.stack_depth = self.stack_depth.saturating_sub(1);
2006 }
2007 OpCode::DynSelectOrDefault => {
2009 self.stack_depth = self.stack_depth.saturating_sub(2);
2010 }
2011 OpCode::GetLocalAttr => {
2013 self.stack_depth += 1;
2014 }
2015 OpCode::GetLocalCall => {
2017 self.stack_depth = self.stack_depth.saturating_sub(1);
2018 }
2019 OpCode::CallBuiltin => {
2021 self.stack_depth = self.stack_depth.saturating_sub(1);
2022 }
2023 OpCode::Constant | OpCode::MakeAttrs | OpCode::MakeList
2031 | OpCode::MakeClosure | OpCode::MakeThunk | OpCode::MakeLazyThunk
2032 | OpCode::Interpolate | OpCode::PatchThunkUpvalues => {}
2033 }
2034 }
2035
2036
2037 fn emit_u16(&mut self, value: u16) {
2038 self.chunk.write_u16(value, self.current_line);
2039 }
2040
2041 fn emit_constant(&mut self, value: VMValue) -> Result<(), CompileError> {
2042 let idx = self.chunk.add_constant(value)?;
2043 self.emit(OpCode::Constant);
2044 self.stack_depth += 1; self.emit_u16(idx);
2046 Ok(())
2047 }
2048
2049 fn add_attr_key(&mut self, key: String) -> Result<u16, CompileError> {
2054 let sym = self.interner.borrow_mut().intern(&key);
2055 self.chunk.add_key_constant(VMValue::String(key), sym)
2056 }
2057
2058 fn emit_jump(&mut self, op: OpCode) -> usize {
2061 self.emit(op);
2062 let offset = self.chunk.len();
2063 self.emit_u16(0xFFFF); offset
2065 }
2066
2067 fn patch_jump(&mut self, placeholder_offset: usize) -> Result<(), CompileError> {
2069 let target = self.chunk.len();
2070 let target_u16 = u16::try_from(target).map_err(|_| CompileError::JumpOverflow)?;
2071 self.chunk.patch_u16(placeholder_offset, target_u16);
2072 Ok(())
2073 }
2074
2075 fn begin_scope(&mut self) {
2078 self.scope_depth += 1;
2079 }
2080
2081 fn end_scope(&mut self, binding_count: u16) {
2082 if binding_count > 0 {
2153 let first_local_idx = self.locals.len() - binding_count as usize;
2157 let base_slot = self.locals[first_local_idx].slot;
2158 self.emit(OpCode::SetLocal);
2159 self.emit_u16(base_slot);
2160 for _ in 0..binding_count {
2161 self.emit(OpCode::Pop);
2162 }
2163 self.stack_depth = base_slot + 1;
2166 }
2167
2168 while let Some(local) = self.locals.last() {
2170 if local.depth < self.scope_depth {
2171 break;
2172 }
2173 self.locals.pop();
2174 }
2175 self.scope_depth -= 1;
2176 }
2177
2178 fn add_local(&mut self, name: String) -> Result<u16, CompileError> {
2180 if self.locals.len() >= u16::MAX as usize {
2181 return Err(CompileError::TooManyLocals);
2182 }
2183 let slot = self.stack_depth - 1;
2187 self.locals.push(Local {
2188 name,
2189 depth: self.scope_depth,
2190 is_captured: false,
2191 slot,
2192 });
2193 Ok(slot)
2194 }
2195
2196 fn resolve_local(&self, name: &str) -> Option<u16> {
2199 for (i, local) in self.locals.iter().enumerate().rev() {
2200 if local.name == name && local.depth != u32::MAX {
2201 return Some(i as u16);
2202 }
2203 }
2204 None
2205 }
2206
2207 fn local_stack_slot(&self, locals_idx: u16) -> u16 {
2209 self.locals[locals_idx as usize].slot
2210 }
2211
2212 fn find_local_slot(&self, name: &str) -> u16 {
2216 let idx = self.resolve_local(name)
2217 .unwrap_or_else(|| panic!("local '{name}' not found"));
2218 self.locals[idx as usize].slot
2219 }
2220
2221 fn find_local_slot_opt(&self, name: &str) -> Option<u16> {
2223 self.resolve_local(name)
2224 .map(|idx| self.locals[idx as usize].slot)
2225 }
2226
2227 fn add_upvalue(&mut self, is_local: bool, index: u16) -> Result<u8, CompileError> {
2231 for (i, uv) in self.upvalues.iter().enumerate() {
2233 if uv.is_local == is_local && uv.index == index {
2234 return Ok(i as u8);
2235 }
2236 }
2237 if self.upvalues.len() >= 256 {
2238 return Err(CompileError::Unsupported("too many upvalues (max 256)".to_string()));
2239 }
2240 let idx = self.upvalues.len() as u8;
2241 self.upvalues.push(UpvalueDesc { is_local, index });
2242 Ok(idx)
2243 }
2244
2245 fn resolve_upvalue(&mut self, name: &str) -> Option<u8> {
2250 let enclosing_ptr = self.enclosing?;
2251 let enclosing = unsafe { &mut *enclosing_ptr };
2255
2256 if let Some(local_idx) = enclosing.resolve_local(name) {
2258 enclosing.locals[local_idx as usize].is_captured = true;
2259 let stack_slot = enclosing.locals[local_idx as usize].slot;
2261 return Some(self.add_upvalue(true, stack_slot).ok()?);
2262 }
2263
2264 if let Some(uv_idx) = enclosing.resolve_upvalue(name) {
2266 return Some(self.add_upvalue(false, uv_idx as u16).ok()?);
2267 }
2268
2269 None
2275 }
2276
2277 fn has_with_scope(&self) -> bool {
2279 if self.with_depth > 0 {
2280 return true;
2281 }
2282 if let Some(enclosing_ptr) = self.enclosing {
2283 let enclosing = unsafe { &*enclosing_ptr };
2284 return enclosing.has_with_scope();
2285 }
2286 false
2287 }
2288
2289 fn resolve_relative_path(&self, rel_path: &str) -> String {
2292 if let Some(ref base) = self.base_dir {
2293 return base.join(rel_path).to_string_lossy().to_string();
2294 }
2295 if let Some(enclosing_ptr) = self.enclosing {
2296 let enclosing = unsafe { &*enclosing_ptr };
2297 return enclosing.resolve_relative_path(rel_path);
2298 }
2299 rel_path.to_string()
2300 }
2301}
2302
2303fn ident_text(ident: &ast::Ident) -> String {
2307 ident
2308 .ident_token()
2309 .map(|t| t.text().to_string())
2310 .unwrap_or_default()
2311}
2312
2313fn reject(e: sui_normalize::NormalizeError) -> CompileError {
2327 CompileError::ParseError(e.to_string())
2328}
2329
2330fn static_attr_name(attr: &ast::Attr) -> Result<String, CompileError> {
2331 match attr {
2332 ast::Attr::Ident(ident) => Ok(ident_text(ident)),
2333 ast::Attr::Str(s) => {
2334 let parts: Vec<_> = s.normalized_parts().into_iter().collect();
2336 if parts.len() == 1 {
2337 if let InterpolPart::Literal(text) = &parts[0] {
2338 return Ok(text.to_string());
2339 }
2340 }
2341 Err(CompileError::Unsupported(
2342 "interpolated string attribute keys".to_string(),
2343 ))
2344 }
2345 ast::Attr::Dynamic(_) => Err(CompileError::Unsupported(
2346 "dynamic attribute keys".to_string(),
2347 )),
2348 }
2349}
2350
2351fn is_global_builtin(name: &str) -> bool {
2400 sui_compat::scope::CALLABLE_GLOBALS.contains(&name)
2401}
2402
2403fn line_of(expr: &ast::Expr) -> u32 {
2405 let offset = AstNode::syntax(expr).text_range().start();
2408 u32::from(offset)
2410}
2411
2412fn detect_trivial_cycles(bindings: &[(String, &ast::Expr)]) -> Vec<String> {
2421 let mut warnings = Vec::new();
2422 for (name, expr) in bindings {
2423 if let ast::Expr::Ident(id) = expr {
2424 if id
2425 .ident_token()
2426 .map(|t| t.text() == name.as_str())
2427 .unwrap_or(false)
2428 {
2429 warnings.push(format!("warning: `{name}` directly references itself"));
2430 }
2431 }
2432 }
2433 warnings
2434}
2435
2436fn parse_nix_path(s: &str) -> Vec<(String, String)> {
2442 if s.is_empty() {
2443 return Vec::new();
2444 }
2445 s.split(':')
2446 .filter(|e| !e.is_empty())
2447 .map(|entry| match entry.split_once('=') {
2448 Some((prefix, path)) => (prefix.to_string(), path.to_string()),
2449 None => (String::new(), entry.to_string()),
2450 })
2451 .collect()
2452}
2453
2454fn resolve_search_path(name: &str) -> Option<String> {
2457 let nix_path = std::env::var("NIX_PATH").ok()?;
2458 for (prefix, path) in parse_nix_path(&nix_path) {
2459 if !prefix.is_empty() && name == prefix {
2460 if std::path::Path::new(&path).exists() {
2461 return Some(path);
2462 }
2463 continue;
2464 }
2465 if !prefix.is_empty() {
2466 let needle = format!("{prefix}/");
2467 if let Some(rest) = name.strip_prefix(&needle) {
2468 let full = format!("{path}/{rest}");
2469 if std::path::Path::new(&full).exists() {
2470 return Some(full);
2471 }
2472 continue;
2473 }
2474 }
2475 if prefix.is_empty() {
2476 let full = format!("{path}/{name}");
2477 if std::path::Path::new(&full).exists() {
2478 return Some(full);
2479 }
2480 }
2481 }
2482 None
2483}
2484
2485#[cfg(test)]
2486mod tests {
2487 use super::*;
2488
2489 fn compile(input: &str) -> Chunk {
2490 let (chunk, _interner) =
2491 Compiler::compile(input).unwrap_or_else(|e| panic!("compile failed for '{input}': {e}"));
2492 chunk
2493 }
2494
2495 #[test]
2520 fn duplicate_dotted_path_errors_instead_of_panicking() {
2521 for (src, path) in [
2522 ("{ a.b = 1; a.b = 2; }", "a.b"),
2523 ("{ a.b.c = 1; a.b.c = 2; }", "a.b.c"),
2524 ("{ a.b.c.d = 1; a.b.c.d = 2; }", "a.b.c.d"),
2525 ] {
2526 let err = Compiler::compile(src)
2527 .err()
2528 .unwrap_or_else(|| panic!("{src} compiled; it must be refused, not accepted"));
2529 let msg = err.to_string();
2530 assert!(
2531 msg.contains(&format!("attribute '{path}' already defined")),
2532 "{src}: expected a duplicate-attribute refusal naming '{path}', got: {msg}"
2533 );
2534 }
2535 }
2536
2537 #[test]
2542 fn legal_nested_paths_still_compile() {
2543 for src in [
2544 "{ a.b = 1; a.c = 2; }",
2545 "{ a.b.c = 1; a.b.d = 2; }",
2546 "{ a.b = 1; a = { c = 2; }; }",
2547 "{ x.y.z = 1; }",
2548 "{ a = { b = 1; }; }",
2549 ] {
2550 assert!(
2551 Compiler::compile(src).is_ok(),
2552 "{src} must still compile — it is legal nix"
2553 );
2554 }
2555 }
2556
2557 #[test]
2558 fn compile_integer() {
2559 let chunk = compile("42");
2560 assert!(!chunk.code.is_empty());
2561 assert_eq!(chunk.constants.len(), 1);
2562 assert_eq!(chunk.constants[0], VMValue::Int(42));
2563 }
2564
2565 #[test]
2566 fn compile_float() {
2567 let chunk = compile("3.14");
2568 assert_eq!(chunk.constants[0], VMValue::Float(3.14));
2569 }
2570
2571 #[test]
2572 fn compile_bool_true() {
2573 let chunk = compile("true");
2574 assert_eq!(chunk.code[0], OpCode::Constant as u8);
2576 assert_eq!(chunk.constants[0], VMValue::Bool(true));
2577 }
2578
2579 #[test]
2580 fn compile_bool_false() {
2581 let chunk = compile("false");
2582 assert_eq!(chunk.code[0], OpCode::Constant as u8);
2584 assert_eq!(chunk.constants[0], VMValue::Bool(false));
2585 }
2586
2587 #[test]
2588 fn compile_null() {
2589 let chunk = compile("null");
2590 assert_eq!(chunk.code[0], OpCode::Constant as u8);
2592 assert_eq!(chunk.constants[0], VMValue::Null);
2593 }
2594
2595 #[test]
2596 fn compile_string() {
2597 let chunk = compile(r#""hello""#);
2598 assert_eq!(chunk.constants[0], VMValue::String("hello".to_string()));
2599 }
2600
2601 #[test]
2602 fn compile_addition() {
2603 let chunk = compile("1 + 2");
2604 assert_eq!(chunk.constants[0], VMValue::Int(3));
2606 assert!(!chunk.code.contains(&(OpCode::Add as u8)));
2607 }
2608
2609 #[test]
2610 fn compile_addition_non_foldable() {
2611 let chunk = compile("let x = 1; in x + 2");
2613 assert!(chunk.code.contains(&(OpCode::Add as u8)));
2614 }
2615
2616 #[test]
2617 fn compile_if_else() {
2618 let chunk = compile("if true then 1 else 2");
2619 assert_eq!(chunk.constants[0], VMValue::Int(1));
2621 assert!(!chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
2622 }
2623
2624 #[test]
2625 fn compile_if_else_non_foldable() {
2626 let chunk = compile("let b = true; in if b then 1 else 2");
2628 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
2629 }
2630
2631 #[test]
2632 fn compile_list() {
2633 let chunk = compile("[1 2 3]");
2634 assert!(chunk.code.contains(&(OpCode::MakeList as u8)));
2635 }
2636
2637 #[test]
2638 fn compile_attrset() {
2639 let chunk = compile("{ a = 1; b = 2; }");
2640 assert!(chunk.code.contains(&(OpCode::MakeAttrs as u8)));
2641 }
2642
2643 #[test]
2644 fn compile_select() {
2645 let chunk = compile("{ a = 1; }.a");
2646 assert!(chunk.code.contains(&(OpCode::GetAttr as u8)));
2647 }
2648
2649 #[test]
2650 fn compile_lambda() {
2651 let chunk = compile("x: x + 1");
2652 assert!(chunk.constants.iter().any(|c| matches!(c, VMValue::Closure(_))));
2654 }
2655
2656 #[test]
2657 fn compile_negate() {
2658 let chunk = compile("-42");
2659 assert_eq!(chunk.constants[0], VMValue::Int(-42));
2661 assert!(!chunk.code.contains(&(OpCode::Negate as u8)));
2662 }
2663
2664 #[test]
2665 fn compile_negate_non_foldable() {
2666 let chunk = compile("let x = 42; in -x");
2667 assert!(chunk.code.contains(&(OpCode::Negate as u8)));
2668 }
2669
2670 #[test]
2671 fn compile_not() {
2672 let chunk = compile("!true");
2673 assert_eq!(chunk.constants[0], VMValue::Bool(false));
2675 assert!(!chunk.code.contains(&(OpCode::Not as u8)));
2676 }
2677
2678 #[test]
2679 fn compile_assert() {
2680 let chunk = compile("assert true; 42");
2681 assert!(chunk.code.contains(&(OpCode::Assert as u8)));
2682 }
2683
2684 #[test]
2685 fn compile_let_in() {
2686 let chunk = compile("let x = 1; y = 2; in x + y");
2687 assert!(chunk.code.contains(&(OpCode::GetLocal as u8)));
2688 }
2689
2690 #[test]
2691 fn compile_parse_error() {
2692 let result = Compiler::compile("let in");
2693 assert!(result.is_err());
2694 }
2695
2696 #[test]
2697 fn compile_comparison() {
2698 let chunk = compile("1 < 2");
2699 assert_eq!(chunk.constants[0], VMValue::Bool(true));
2701 }
2702
2703 #[test]
2704 fn compile_equality() {
2705 let chunk = compile("1 == 1");
2706 assert_eq!(chunk.constants[0], VMValue::Bool(true));
2708 }
2709
2710 #[test]
2711 fn compile_update_attrs() {
2712 let chunk = compile("{ a = 1; } // { b = 2; }");
2713 assert!(chunk.code.contains(&(OpCode::UpdateAttrs as u8)));
2714 }
2715
2716 #[test]
2717 fn compile_list_concat() {
2718 let chunk = compile("[1] ++ [2]");
2719 assert!(chunk.code.contains(&(OpCode::Concat as u8)));
2720 }
2721
2722 #[test]
2723 fn compile_and_short_circuit() {
2724 let chunk = compile("true && false");
2725 assert_eq!(chunk.constants[0], VMValue::Bool(false));
2727 }
2728
2729 #[test]
2730 fn compile_and_short_circuit_non_foldable() {
2731 let chunk = compile("let a = true; in a && false");
2732 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
2733 }
2734
2735 #[test]
2736 fn compile_or_short_circuit() {
2737 let chunk = compile("false || true");
2738 assert_eq!(chunk.constants[0], VMValue::Bool(true));
2740 }
2741
2742 #[test]
2743 fn compile_or_short_circuit_non_foldable() {
2744 let chunk = compile("let a = false; in a || true");
2745 assert!(chunk.code.contains(&(OpCode::JumpIfTrue as u8)));
2746 }
2747
2748 #[test]
2749 fn compile_has_attr() {
2750 let chunk = compile("{ a = 1; } ? a");
2751 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
2752 }
2753
2754 #[test]
2755 fn compile_select_or_default() {
2756 let chunk = compile("{ a = 1; }.b or 0");
2759 assert!(chunk.code.contains(&(OpCode::Dup as u8)));
2760 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
2761 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
2762 assert!(chunk.code.contains(&(OpCode::GetAttr as u8)));
2763 }
2764
2765 #[test]
2766 fn compile_dyn_select_or_default() {
2767 let chunk = compile(r#"let x = "a"; in { a = 1; }.${ x } or 0"#);
2770 assert!(chunk.code.contains(&(OpCode::Dup as u8)));
2771 assert!(chunk.code.contains(&(OpCode::DynHasAttr as u8)));
2772 assert!(chunk.code.contains(&(OpCode::JumpIfFalse as u8)));
2773 assert!(chunk.code.contains(&(OpCode::DynGetAttr as u8)));
2775 }
2776
2777 #[test]
2778 fn compile_multi_segment_select_or_default() {
2779 let chunk = compile("{ a = { b = 1; }; }.a.b.c or 0");
2781 let has_attr_count = chunk.code.iter().filter(|&&b| b == OpCode::HasAttr as u8).count();
2783 assert!(has_attr_count >= 3, "expected >= 3 HasAttr ops for 3 segments, got {has_attr_count}");
2784 }
2785
2786 #[test]
2787 fn compile_pattern_lambda() {
2788 let chunk = compile("{ a, b }: a + b");
2789 assert!(chunk.constants.iter().any(|c| matches!(c, VMValue::Closure(_))));
2790 }
2791
2792 #[test]
2793 fn compile_string_interpolation() {
2794 let chunk = compile(r#"let x = "world"; in "hello ${x}""#);
2795 assert!(chunk.code.contains(&(OpCode::Interpolate as u8)));
2797 }
2798
2799 #[test]
2802 fn detect_trivial_self_reference() {
2803 let root = rnix::Root::parse("x");
2804 let expr = root.tree().expr().unwrap();
2805 let bindings = vec![("x".to_string(), &expr)];
2806 let warnings = detect_trivial_cycles(&bindings);
2807 assert_eq!(warnings.len(), 1);
2808 assert!(warnings[0].contains("directly references itself"));
2809 }
2810
2811 #[test]
2812 fn detect_no_false_positive() {
2813 let root = rnix::Root::parse("y");
2814 let expr = root.tree().expr().unwrap();
2815 let bindings = vec![("x".to_string(), &expr)];
2816 let warnings = detect_trivial_cycles(&bindings);
2817 assert!(warnings.is_empty());
2818 }
2819
2820 #[test]
2821 fn detect_non_ident_no_warning() {
2822 let root = rnix::Root::parse("1 + 2");
2823 let expr = root.tree().expr().unwrap();
2824 let bindings = vec![("x".to_string(), &expr)];
2825 let warnings = detect_trivial_cycles(&bindings);
2826 assert!(warnings.is_empty());
2827 }
2828
2829 #[test]
2830 fn detect_trivial_cycles_multiple() {
2831 let root_x = rnix::Root::parse("x");
2832 let expr_x = root_x.tree().expr().unwrap();
2833 let root_y = rnix::Root::parse("y");
2834 let expr_y = root_y.tree().expr().unwrap();
2835 let root_z = rnix::Root::parse("1");
2836 let expr_z = root_z.tree().expr().unwrap();
2837 let bindings = vec![
2838 ("x".to_string(), &expr_x),
2839 ("y".to_string(), &expr_y),
2840 ("z".to_string(), &expr_z),
2841 ];
2842 let warnings = detect_trivial_cycles(&bindings);
2843 assert_eq!(warnings.len(), 2);
2844 }
2845
2846 fn nix_path_lock() -> std::sync::MutexGuard<'static, ()> {
2865 static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
2866 LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
2867 }
2868
2869 #[test]
2870 fn path_search_compiles_with_matching_nix_path() {
2871 let _nix_path = nix_path_lock();
2872 let dir = tempfile::tempdir().unwrap();
2875 let target = dir.path().join("mypkg");
2876 std::fs::create_dir(&target).unwrap();
2877 let nix_path_val = format!("mypkg={}", target.display());
2879 unsafe { std::env::set_var("NIX_PATH", &nix_path_val) };
2881 let result = Compiler::compile("<mypkg>");
2882 unsafe { std::env::remove_var("NIX_PATH") };
2883 assert!(result.is_ok(), "expected compile success, got: {result:?}");
2884 let (chunk, _) = result.unwrap();
2885 assert!(
2887 chunk.constants.iter().any(|c| matches!(c, VMValue::Path(p) if p == &target.display().to_string())),
2888 "expected path constant for {:?}, got: {:?}",
2889 target.display(),
2890 chunk.constants,
2891 );
2892 }
2893
2894 #[test]
2895 fn path_search_fails_when_nix_path_no_match() {
2896 let _nix_path = nix_path_lock();
2897 unsafe { std::env::set_var("NIX_PATH", "other=/nonexistent") };
2900 let result = Compiler::compile("<nosuchpkg>");
2901 unsafe { std::env::remove_var("NIX_PATH") };
2902
2903 assert!(
2918 result.is_ok(),
2919 "an unresolvable search path is deferred to force-time, not a \
2920 compile error; got: {result:?}"
2921 );
2922 let (chunk, _) = result.unwrap();
2923 assert!(
2924 chunk.constants.iter().any(|c| matches!(c, VMValue::Closure(_))),
2925 "expected a deferred-throw closure in the constant pool, got: {:?}",
2926 chunk.constants,
2927 );
2928 }
2929
2930 #[test]
2931 fn path_search_with_sub_path() {
2932 let _nix_path = nix_path_lock();
2933 let dir = tempfile::tempdir().unwrap();
2935 let nixpkgs = dir.path().join("nixpkgs-src");
2936 let lib_dir = nixpkgs.join("lib");
2937 std::fs::create_dir_all(&lib_dir).unwrap();
2938 let nix_path_val = format!("nixpkgs={}", nixpkgs.display());
2939 unsafe { std::env::set_var("NIX_PATH", &nix_path_val) };
2941 let result = Compiler::compile("<nixpkgs/lib>");
2942 unsafe { std::env::remove_var("NIX_PATH") };
2943 assert!(result.is_ok(), "expected compile success for sub-path, got: {result:?}");
2944 let (chunk, _) = result.unwrap();
2945 let expected_path = lib_dir.display().to_string();
2946 assert!(
2947 chunk.constants.iter().any(|c| matches!(c, VMValue::Path(p) if p == &expected_path)),
2948 "expected path constant for {expected_path}, got: {:?}",
2949 chunk.constants,
2950 );
2951 }
2952
2953 #[test]
2956 fn lambda_body_apply_emits_tail_call() {
2957 let chunk = compile("x: x 1");
2959 let closure_chunk = chunk
2962 .constants
2963 .iter()
2964 .find_map(|c| match c {
2965 VMValue::Closure(cl) => Some(&cl.chunk),
2966 _ => None,
2967 })
2968 .expect("expected a closure constant");
2969 assert!(
2970 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
2971 "lambda body call should emit TailCall, bytecode: {:?}",
2972 closure_chunk.code,
2973 );
2974 }
2975
2976 #[test]
2977 fn if_then_apply_emits_tail_call() {
2978 let chunk = compile("x: if true then x 1 else 0");
2980 let closure_chunk = chunk
2981 .constants
2982 .iter()
2983 .find_map(|c| match c {
2984 VMValue::Closure(cl) => Some(&cl.chunk),
2985 _ => None,
2986 })
2987 .expect("expected a closure constant");
2988 assert!(
2989 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
2990 "if-then call should emit TailCall, bytecode: {:?}",
2991 closure_chunk.code,
2992 );
2993 }
2994
2995 #[test]
2996 fn if_else_apply_emits_tail_call() {
2997 let chunk = compile("x: if false then 0 else x 1");
2999 let closure_chunk = chunk
3000 .constants
3001 .iter()
3002 .find_map(|c| match c {
3003 VMValue::Closure(cl) => Some(&cl.chunk),
3004 _ => None,
3005 })
3006 .expect("expected a closure constant");
3007 assert!(
3008 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
3009 "if-else call should emit TailCall, bytecode: {:?}",
3010 closure_chunk.code,
3011 );
3012 }
3013
3014 #[test]
3015 fn non_tail_apply_emits_regular_call() {
3016 let chunk = compile("let f = x: x; in f (f 1)");
3019 assert!(
3022 chunk.code.contains(&(OpCode::Call as u8))
3023 || chunk.code.contains(&(OpCode::GetLocalCall as u8)),
3024 "non-tail call should emit Call or GetLocalCall, bytecode: {:?}",
3025 chunk.code,
3026 );
3027 }
3028
3029 #[test]
3030 fn assert_body_apply_emits_tail_call() {
3031 let chunk = compile("f: assert true; f 1");
3033 let closure_chunk = chunk
3034 .constants
3035 .iter()
3036 .find_map(|c| match c {
3037 VMValue::Closure(cl) => Some(&cl.chunk),
3038 _ => None,
3039 })
3040 .expect("expected a closure constant");
3041 assert!(
3042 closure_chunk.code.contains(&(OpCode::TailCall as u8)),
3043 "assert body call should emit TailCall, bytecode: {:?}",
3044 closure_chunk.code,
3045 );
3046 }
3047
3048 #[test]
3051 fn multi_segment_hasattr_compiles() {
3052 let chunk = compile("{ a = { b = 1; }; } ? a");
3054 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3055 }
3056
3057 #[test]
3058 fn single_segment_hasattr_still_works() {
3059 let chunk = compile("{ x = 1; } ? x");
3061 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3062 }
3063
3064 #[test]
3065 fn multi_segment_hasattr_deep_path() {
3066 let chunk = compile("{ a = { b = 1; }; } ? a.b");
3068 assert!(chunk.code.contains(&(OpCode::HasAttr as u8)));
3070 }
3071}