1use core::pin::Pin;
11use genawaiter::rc::Co;
12pub use genawaiter::rc::Gen;
13use std::ffi::OsString;
14use std::fmt::Display;
15use std::future::Future;
16
17use crate::FileType;
18use crate::NixString;
19use crate::value::PointerEquality;
20use crate::warnings::{EvalWarning, WarningKind};
21
22use super::*;
23
24pub(crate) enum GeneratorState {
28 Running,
30
31 AwaitingValue,
33}
34
35pub enum VMRequest {
41 ForceValue(Value),
45
46 DeepForceValue(Value),
48
49 WithValue(usize),
54
55 CapturedWithValue(usize),
58
59 NixEquality(Box<(Value, Value)>, PointerEquality),
62
63 StackPush(Value),
69
70 StackPop,
72
73 StringCoerce(Value, CoercionKind),
75
76 Call(Value),
79
80 EnterLambda {
83 lambda: Rc<Lambda>,
84 upvalues: Rc<Upvalues>,
85 span: Span,
86 },
87
88 EmitWarning(EvalWarning),
90
91 EmitWarningKind(WarningKind),
94
95 ImportCacheLookup(PathBuf),
98
99 ImportCachePut(PathBuf, Value),
102
103 PathImport(PathBuf),
105
106 OpenFile(PathBuf),
108
109 PathExists(PathBuf),
111
112 ReadDir(PathBuf),
114
115 Span,
117
118 TryForce(Value),
120
121 ReadFileType(PathBuf),
123
124 GetEnv(OsString),
126
127 GetSpanPos(Span),
130}
131
132impl Display for VMRequest {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 VMRequest::ForceValue(v) => write!(f, "force_value({})", v.type_of()),
137 VMRequest::DeepForceValue(v) => {
138 write!(f, "deep_force_value({})", v.type_of())
139 }
140 VMRequest::WithValue(_) => write!(f, "with_value"),
141 VMRequest::CapturedWithValue(_) => write!(f, "captured_with_value"),
142 VMRequest::NixEquality(values, ptr_eq) => {
143 write!(
144 f,
145 "nix_eq({}, {}, PointerEquality::{:?})",
146 values.0.type_of(),
147 values.1.type_of(),
148 ptr_eq
149 )
150 }
151 VMRequest::StackPush(v) => write!(f, "stack_push({})", v.type_of()),
152 VMRequest::StackPop => write!(f, "stack_pop"),
153 VMRequest::StringCoerce(
154 v,
155 CoercionKind {
156 strong,
157 import_paths,
158 },
159 ) => write!(
160 f,
161 "{}_{}importing_string_coerce({})",
162 if *strong { "strong" } else { "weak" },
163 if *import_paths { "" } else { "non_" },
164 v.type_of()
165 ),
166 VMRequest::Call(v) => write!(f, "call({v})"),
167 VMRequest::EnterLambda { lambda, .. } => {
168 write!(f, "enter_lambda({:p})", *lambda)
169 }
170 VMRequest::EmitWarning(_) => write!(f, "emit_warning"),
171 VMRequest::EmitWarningKind(_) => write!(f, "emit_warning_kind"),
172 VMRequest::ImportCacheLookup(p) => {
173 write!(f, "import_cache_lookup({})", p.to_string_lossy())
174 }
175 VMRequest::ImportCachePut(p, _) => {
176 write!(f, "import_cache_put({})", p.to_string_lossy())
177 }
178 VMRequest::PathImport(p) => write!(f, "path_import({})", p.to_string_lossy()),
179 VMRequest::OpenFile(p) => {
180 write!(f, "open_file({})", p.to_string_lossy())
181 }
182 VMRequest::PathExists(p) => write!(f, "path_exists({})", p.to_string_lossy()),
183 VMRequest::ReadDir(p) => write!(f, "read_dir({})", p.to_string_lossy()),
184 VMRequest::Span => write!(f, "span"),
185 VMRequest::TryForce(v) => write!(f, "try_force({})", v.type_of()),
186 VMRequest::ReadFileType(p) => write!(f, "read_file_type({})", p.to_string_lossy()),
187 VMRequest::GetEnv(p) => write!(f, "get_env({})", p.to_string_lossy()),
188 VMRequest::GetSpanPos(span) => write!(f, "get_span_pos({:?})", span),
189 }
190 }
191}
192
193pub enum VMResponse {
195 Empty,
198
199 Value(Value),
201
202 Path(PathBuf),
204
205 Directory(Vec<(bytes::Bytes, FileType)>),
207
208 Span(Span),
210
211 Reader(Box<dyn std::io::Read>),
213
214 FileType(FileType),
215
216 Env(OsString),
218
219 GetSpanPos(Value),
220}
221
222impl Display for VMResponse {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 match self {
225 VMResponse::Empty => write!(f, "empty"),
226 VMResponse::Value(v) => write!(f, "value({v})"),
227 VMResponse::Path(p) => write!(f, "path({})", p.to_string_lossy()),
228 VMResponse::Directory(d) => write!(f, "dir(len = {})", d.len()),
229 VMResponse::Span(_) => write!(f, "span"),
230 VMResponse::Reader(_) => write!(f, "reader"),
231 VMResponse::FileType(t) => write!(f, "file_type({t})"),
232 VMResponse::Env(t) => write!(f, "env({})", t.to_string_lossy()),
233 VMResponse::GetSpanPos(pos) => write!(f, "get_span_pos({:?})", pos),
234 }
235 }
236}
237
238pub(crate) type Generator =
239 Gen<VMRequest, VMResponse, Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>>>;
240
241pub fn pin_generator(
244 f: impl Future<Output = Result<Value, ErrorKind>> + 'static,
245) -> Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>> {
246 Box::pin(f)
247}
248
249impl<IO> VM<'_, IO>
250where
251 IO: AsRef<dyn EvalIO> + 'static,
252{
253 fn reenqueue_generator(&mut self, name: &'static str, span: Span, generator: Generator) {
256 self.frames.push(Frame::Generator {
257 name,
258 generator,
259 span,
260 state: GeneratorState::AwaitingValue,
261 });
262 }
263
264 pub(super) fn enqueue_generator<F, G>(&mut self, name: &'static str, span: Span, r#gen: G)
266 where
267 F: Future<Output = Result<Value, ErrorKind>> + 'static,
268 G: FnOnce(GenCo) -> F,
269 {
270 self.frames.push(Frame::Generator {
271 name,
272 span,
273 state: GeneratorState::Running,
274 generator: Gen::new(|co| pin_generator(r#gen(co))),
275 });
276 }
277
278 pub(crate) fn run_generator(
284 &mut self,
285 name: &'static str,
286 span: Span,
287 frame_id: usize,
288 state: GeneratorState,
289 mut generator: Generator,
290 initial_message: Option<VMResponse>,
291 ) -> EvalResult<bool> {
292 let mut message = match (initial_message, state) {
294 (Some(msg), _) => msg,
295 (_, GeneratorState::Running) => VMResponse::Empty,
296
297 (_, GeneratorState::AwaitingValue) => VMResponse::Value(self.stack_pop()),
300 };
301
302 loop {
303 match generator.resume_with(message) {
304 genawaiter::GeneratorState::Yielded(request) => {
307 self.observer.observe_generator_request(name, &request);
308
309 match request {
310 VMRequest::StackPush(value) => {
311 self.stack.push(value);
312 message = VMResponse::Empty;
313 }
314
315 VMRequest::StackPop => {
316 message = VMResponse::Value(self.stack_pop());
317 }
318
319 VMRequest::ForceValue(value) => {
323 self.reenqueue_generator(name, span, generator);
324 self.enqueue_generator("force", span, |co| {
325 value.force_owned_genco(co, span)
326 });
327 return Ok(false);
328 }
329
330 VMRequest::DeepForceValue(value) => {
332 self.reenqueue_generator(name, span, generator);
333 self.enqueue_generator("deep_force", span, |co| {
334 value.deep_force(co, span)
335 });
336 return Ok(false);
337 }
338
339 VMRequest::WithValue(idx) => {
343 self.reenqueue_generator(name, span, generator);
344
345 let value = self.stack[self.with_stack[idx]].clone();
346 self.enqueue_generator("force", span, |co| {
347 value.force_owned_genco(co, span)
348 });
349
350 return Ok(false);
351 }
352
353 VMRequest::CapturedWithValue(idx) => {
357 self.reenqueue_generator(name, span, generator);
358
359 let bytecode_frame = self.last_bytecode_frame()
360 .expect("Snix bug: generator requested captured with-value, but there is no bytecode frame");
361
362 let value = bytecode_frame
363 .upvalues
364 .get_from_with_stack(idx)
365 .expect("Snix bug: upvalue not found on stack");
366 self.enqueue_generator("force", span, |co| {
367 value.force_owned_genco(co, span)
368 });
369
370 return Ok(false);
371 }
372
373 VMRequest::NixEquality(values, ptr_eq) => {
374 let values = *values;
375 self.reenqueue_generator(name, span, generator);
376 self.enqueue_generator("nix_eq", span, |co| {
377 values.0.nix_eq_owned_genco(values.1, co, ptr_eq, span)
378 });
379 return Ok(false);
380 }
381
382 VMRequest::StringCoerce(val, kind) => {
383 self.reenqueue_generator(name, span, generator);
384 self.enqueue_generator("coerce_to_string", span, |co| {
385 val.coerce_to_string(co, kind, span)
386 });
387 return Ok(false);
388 }
389
390 VMRequest::Call(callable) => {
391 self.reenqueue_generator(name, span, generator);
392 self.call_value(span, None, callable)?;
393 return Ok(false);
394 }
395
396 VMRequest::EnterLambda {
397 lambda,
398 upvalues,
399 span,
400 } => {
401 self.reenqueue_generator(name, span, generator);
402
403 self.frames.push(Frame::BytecodeFrame {
404 span,
405 bytecode_frame: BytecodeFrame {
406 lambda,
407 upvalues,
408 ip: CodeIdx(0),
409 stack_offset: self.stack.len(),
410 },
411 });
412
413 return Ok(false);
414 }
415
416 VMRequest::EmitWarning(warning) => {
417 self.push_warning(warning);
418 message = VMResponse::Empty;
419 }
420
421 VMRequest::EmitWarningKind(kind) => {
422 self.emit_warning(kind);
423 message = VMResponse::Empty;
424 }
425
426 VMRequest::ImportCacheLookup(path) => {
427 if let Some(cached) = self.import_cache.get(&path) {
428 message = VMResponse::Value(cached.clone());
429 } else {
430 message = VMResponse::Empty;
431 }
432 }
433
434 VMRequest::ImportCachePut(path, value) => {
435 self.import_cache.insert(path, value);
436 message = VMResponse::Empty;
437 }
438
439 VMRequest::PathImport(path) => {
440 let imported = if let Some(p) = self.path_import_cache.get(&path) {
441 p.to_owned()
442 } else {
443 let imported = self
444 .io_handle
445 .as_ref()
446 .import_path(&path)
447 .map_err(|e| ErrorKind::IO {
448 path: Some(path.to_owned()),
449 error: e.into(),
450 })
451 .with_span(span, self)?;
452
453 self.path_import_cache.insert(path, imported.clone());
454 imported
455 };
456
457 message = VMResponse::Path(imported);
458 }
459
460 VMRequest::OpenFile(path) => {
461 let reader = self
462 .io_handle
463 .as_ref()
464 .open(&path)
465 .map_err(|e| ErrorKind::IO {
466 path: Some(path),
467 error: e.into(),
468 })
469 .with_span(span, self)?;
470
471 message = VMResponse::Reader(reader)
472 }
473
474 VMRequest::PathExists(path) => {
475 let exists = self
476 .io_handle
477 .as_ref()
478 .path_exists(&path)
479 .map_err(|e| ErrorKind::IO {
480 path: Some(path),
481 error: e.into(),
482 })
483 .map(Value::Bool)
484 .with_span(span, self)?;
485
486 message = VMResponse::Value(exists);
487 }
488
489 VMRequest::ReadDir(path) => {
490 let dir = self
491 .io_handle
492 .as_ref()
493 .read_dir(&path)
494 .map_err(|e| ErrorKind::IO {
495 path: Some(path),
496 error: e.into(),
497 })
498 .with_span(span, self)?;
499 message = VMResponse::Directory(dir);
500 }
501
502 VMRequest::Span => {
503 message = VMResponse::Span(self.reasonable_span);
504 }
505
506 VMRequest::TryForce(value) => {
507 self.try_eval_frames.push(frame_id);
508 self.reenqueue_generator(name, span, generator);
509
510 debug_assert!(
511 self.frames.len() == frame_id + 1,
512 "generator should be reenqueued with the same frame ID"
513 );
514
515 self.enqueue_generator("force", span, |co| {
516 value.force_owned_genco(co, span)
517 });
518 return Ok(false);
519 }
520
521 VMRequest::ReadFileType(path) => {
522 let file_type = self
523 .io_handle
524 .as_ref()
525 .file_type(&path)
526 .map_err(|e| ErrorKind::IO {
527 path: Some(path),
528 error: e.into(),
529 })
530 .with_span(span, self)?;
531
532 message = VMResponse::FileType(file_type);
533 }
534 VMRequest::GetEnv(key) => {
535 let env = self.io_handle.as_ref().get_env(&key).unwrap_or_default();
536
537 message = VMResponse::Env(env);
538 }
539
540 VMRequest::GetSpanPos(span) => {
541 let file = self.source.get_file(span);
542 let pos = file.find_line_col(span.low());
543 let abs_path = std::fs::canonicalize(file.name())
544 .unwrap_or(PathBuf::from(file.name()))
547 .to_string_lossy()
548 .to_string();
549
550 let v = NixAttrs::from_iter([
551 ("line", Value::Integer((pos.line + 1) as i64)),
552 ("column", Value::Integer((pos.column + 1) as i64)),
553 ("file", Value::String(NixString::from(abs_path))),
554 ]);
555 message = VMResponse::GetSpanPos(Value::Attrs(v));
556 }
557 }
558 }
559
560 genawaiter::GeneratorState::Complete(result) => {
563 let value = result.with_span(span, self)?;
564 self.stack.push(value);
565 return Ok(true);
566 }
567 }
568 }
569 }
570}
571
572pub type GenCo = Co<VMRequest, VMResponse>;
573
574pub async fn request_stack_push(co: &GenCo, val: Value) {
578 match co.yield_(VMRequest::StackPush(val)).await {
579 VMResponse::Empty => {}
580 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
581 }
582}
583
584pub async fn request_stack_pop(co: &GenCo) -> Value {
587 match co.yield_(VMRequest::StackPop).await {
588 VMResponse::Value(value) => value,
589 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
590 }
591}
592
593pub async fn request_force(co: &GenCo, val: Value) -> Value {
595 if let Value::Thunk(_) = val {
596 match co.yield_(VMRequest::ForceValue(val)).await {
597 VMResponse::Value(value) => value,
598 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
599 }
600 } else {
601 val
602 }
603}
604
605pub(crate) async fn request_try_force(co: &GenCo, val: Value) -> Value {
607 if let Value::Thunk(_) = val {
608 match co.yield_(VMRequest::TryForce(val)).await {
609 VMResponse::Value(value) => value,
610 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
611 }
612 } else {
613 val
614 }
615}
616
617pub async fn request_call(co: &GenCo, val: Value) -> Value {
620 let val = request_force(co, val).await;
621 match co.yield_(VMRequest::Call(val)).await {
622 VMResponse::Value(value) => value,
623 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
624 }
625}
626
627pub async fn request_call_with<I>(co: &GenCo, mut callable: Value, args: I) -> Value
630where
631 I: IntoIterator<Item = Value>,
632 I::IntoIter: DoubleEndedIterator,
633{
634 let mut num_args = 0_usize;
635 for arg in args.into_iter().rev() {
636 num_args += 1;
637 request_stack_push(co, arg).await;
638 }
639
640 debug_assert!(num_args > 0, "call_with called with an empty list of args");
641
642 while num_args > 0 {
643 callable = request_call(co, callable).await;
644 num_args -= 1;
645 }
646
647 callable
648}
649
650pub async fn request_string_coerce(
651 co: &GenCo,
652 val: Value,
653 kind: CoercionKind,
654) -> Result<NixString, CatchableErrorKind> {
655 match val {
656 Value::String(s) => Ok(s),
657 _ => match co.yield_(VMRequest::StringCoerce(val, kind)).await {
658 VMResponse::Value(Value::Catchable(c)) => Err(*c),
659 VMResponse::Value(value) => Ok(value
660 .to_contextful_str()
661 .expect("coerce_to_string always returns a string")),
662 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
663 },
664 }
665}
666
667pub async fn request_deep_force(co: &GenCo, val: Value) -> Value {
669 match co.yield_(VMRequest::DeepForceValue(val)).await {
670 VMResponse::Value(value) => value,
671 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
672 }
673}
674
675pub(crate) async fn check_equality(
677 co: &GenCo,
678 a: Value,
679 b: Value,
680 ptr_eq: PointerEquality,
681) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
682 match co
683 .yield_(VMRequest::NixEquality(Box::new((a, b)), ptr_eq))
684 .await
685 {
686 VMResponse::Value(Value::Bool(b)) => Ok(Ok(b)),
687 VMResponse::Value(Value::Catchable(cek)) => Ok(Err(*cek)),
688 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
689 }
690}
691
692pub(crate) async fn emit_warning(co: &GenCo, warning: EvalWarning) {
694 match co.yield_(VMRequest::EmitWarning(warning)).await {
695 VMResponse::Empty => {}
696 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
697 }
698}
699
700pub async fn emit_warning_kind(co: &GenCo, kind: WarningKind) {
702 match co.yield_(VMRequest::EmitWarningKind(kind)).await {
703 VMResponse::Empty => {}
704 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
705 }
706}
707
708pub(crate) async fn request_enter_lambda(
710 co: &GenCo,
711 lambda: Rc<Lambda>,
712 upvalues: Rc<Upvalues>,
713 span: Span,
714) -> Value {
715 let msg = VMRequest::EnterLambda {
716 lambda,
717 upvalues,
718 span,
719 };
720
721 match co.yield_(msg).await {
722 VMResponse::Value(value) => value,
723 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
724 }
725}
726
727pub(crate) async fn request_import_cache_lookup(co: &GenCo, path: PathBuf) -> Option<Value> {
729 match co.yield_(VMRequest::ImportCacheLookup(path)).await {
730 VMResponse::Value(value) => Some(value),
731 VMResponse::Empty => None,
732 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
733 }
734}
735
736pub(crate) async fn request_import_cache_put(co: &GenCo, path: PathBuf, value: Value) {
738 match co.yield_(VMRequest::ImportCachePut(path, value)).await {
739 VMResponse::Empty => {}
740 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
741 }
742}
743
744pub(crate) async fn request_path_import(co: &GenCo, path: PathBuf) -> PathBuf {
746 match co.yield_(VMRequest::PathImport(path)).await {
747 VMResponse::Path(path) => path,
748 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
749 }
750}
751
752pub async fn request_open_file(co: &GenCo, path: PathBuf) -> Box<dyn std::io::Read> {
754 match co.yield_(VMRequest::OpenFile(path)).await {
755 VMResponse::Reader(value) => value,
756 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
757 }
758}
759
760#[cfg_attr(not(feature = "impure"), allow(unused))]
761pub(crate) async fn request_path_exists(co: &GenCo, path: PathBuf) -> Value {
762 match co.yield_(VMRequest::PathExists(path)).await {
763 VMResponse::Value(value) => value,
764 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
765 }
766}
767
768#[cfg_attr(not(feature = "impure"), allow(unused))]
769pub(crate) async fn request_read_dir(co: &GenCo, path: PathBuf) -> Vec<(bytes::Bytes, FileType)> {
770 match co.yield_(VMRequest::ReadDir(path)).await {
771 VMResponse::Directory(dir) => dir,
772 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
773 }
774}
775
776pub(crate) async fn request_span(co: &GenCo) -> Span {
777 match co.yield_(VMRequest::Span).await {
778 VMResponse::Span(span) => span,
779 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
780 }
781}
782
783pub(crate) async fn request_span_pos(co: &GenCo, span: Span) -> Value {
784 match co.yield_(VMRequest::GetSpanPos(span)).await {
785 VMResponse::GetSpanPos(span) => span,
786 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
787 }
788}
789
790#[cfg_attr(not(feature = "impure"), allow(unused))]
791pub(crate) async fn request_read_file_type(co: &GenCo, path: PathBuf) -> FileType {
792 match co.yield_(VMRequest::ReadFileType(path)).await {
793 VMResponse::FileType(file_type) => file_type,
794 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
795 }
796}
797
798#[cfg_attr(not(feature = "impure"), allow(unused))]
799pub(crate) async fn request_get_env(co: &GenCo, key: OsString) -> OsString {
800 match co.yield_(VMRequest::GetEnv(key)).await {
801 VMResponse::Env(env) => env,
802 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
803 }
804}
805
806pub(crate) async fn call_functor(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
811 let attrs = value.to_attrs()?;
812
813 match attrs.select("__functor") {
814 None => Err(ErrorKind::NotCallable("set without `__functor_` attribute")),
815 Some(functor) => {
816 let functor = request_force(&co, functor.clone()).await;
819 let primed = request_call_with(&co, functor, [value]).await;
820 Ok(request_call(&co, primed).await)
821 }
822 }
823}