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