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
128impl Display for VMRequest {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 match self {
132 VMRequest::ForceValue(v) => write!(f, "force_value({})", v.type_of()),
133 VMRequest::DeepForceValue(v) => {
134 write!(f, "deep_force_value({})", v.type_of())
135 }
136 VMRequest::WithValue(_) => write!(f, "with_value"),
137 VMRequest::CapturedWithValue(_) => write!(f, "captured_with_value"),
138 VMRequest::NixEquality(values, ptr_eq) => {
139 write!(
140 f,
141 "nix_eq({}, {}, PointerEquality::{:?})",
142 values.0.type_of(),
143 values.1.type_of(),
144 ptr_eq
145 )
146 }
147 VMRequest::StackPush(v) => write!(f, "stack_push({})", v.type_of()),
148 VMRequest::StackPop => write!(f, "stack_pop"),
149 VMRequest::StringCoerce(
150 v,
151 CoercionKind {
152 strong,
153 import_paths,
154 },
155 ) => write!(
156 f,
157 "{}_{}importing_string_coerce({})",
158 if *strong { "strong" } else { "weak" },
159 if *import_paths { "" } else { "non_" },
160 v.type_of()
161 ),
162 VMRequest::Call(v) => write!(f, "call({v})"),
163 VMRequest::EnterLambda { lambda, .. } => {
164 write!(f, "enter_lambda({:p})", *lambda)
165 }
166 VMRequest::EmitWarning(_) => write!(f, "emit_warning"),
167 VMRequest::EmitWarningKind(_) => write!(f, "emit_warning_kind"),
168 VMRequest::ImportCacheLookup(p) => {
169 write!(f, "import_cache_lookup({})", p.to_string_lossy())
170 }
171 VMRequest::ImportCachePut(p, _) => {
172 write!(f, "import_cache_put({})", p.to_string_lossy())
173 }
174 VMRequest::PathImport(p) => write!(f, "path_import({})", p.to_string_lossy()),
175 VMRequest::OpenFile(p) => {
176 write!(f, "open_file({})", p.to_string_lossy())
177 }
178 VMRequest::PathExists(p) => write!(f, "path_exists({})", p.to_string_lossy()),
179 VMRequest::ReadDir(p) => write!(f, "read_dir({})", p.to_string_lossy()),
180 VMRequest::Span => write!(f, "span"),
181 VMRequest::TryForce(v) => write!(f, "try_force({})", v.type_of()),
182 VMRequest::ReadFileType(p) => write!(f, "read_file_type({})", p.to_string_lossy()),
183 VMRequest::GetEnv(p) => write!(f, "get_env({})", p.to_string_lossy()),
184 }
185 }
186}
187
188pub enum VMResponse {
190 Empty,
193
194 Value(Value),
196
197 Path(PathBuf),
199
200 Directory(Vec<(bytes::Bytes, FileType)>),
202
203 Span(Span),
205
206 Reader(Box<dyn std::io::Read>),
208
209 FileType(FileType),
210
211 Env(OsString),
213}
214
215impl Display for VMResponse {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 match self {
218 VMResponse::Empty => write!(f, "empty"),
219 VMResponse::Value(v) => write!(f, "value({v})"),
220 VMResponse::Path(p) => write!(f, "path({})", p.to_string_lossy()),
221 VMResponse::Directory(d) => write!(f, "dir(len = {})", d.len()),
222 VMResponse::Span(_) => write!(f, "span"),
223 VMResponse::Reader(_) => write!(f, "reader"),
224 VMResponse::FileType(t) => write!(f, "file_type({t})"),
225 VMResponse::Env(t) => write!(f, "env({})", t.to_string_lossy()),
226 }
227 }
228}
229
230pub(crate) type Generator =
231 Gen<VMRequest, VMResponse, Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>>>;
232
233pub fn pin_generator(
236 f: impl Future<Output = Result<Value, ErrorKind>> + 'static,
237) -> Pin<Box<dyn Future<Output = Result<Value, ErrorKind>>>> {
238 Box::pin(f)
239}
240
241impl<IO> VM<'_, IO>
242where
243 IO: AsRef<dyn EvalIO> + 'static,
244{
245 fn reenqueue_generator(&mut self, name: &'static str, span: Span, generator: Generator) {
248 self.frames.push(Frame::Generator {
249 name,
250 generator,
251 span,
252 state: GeneratorState::AwaitingValue,
253 });
254 }
255
256 pub(super) fn enqueue_generator<F, G>(&mut self, name: &'static str, span: Span, r#gen: G)
258 where
259 F: Future<Output = Result<Value, ErrorKind>> + 'static,
260 G: FnOnce(GenCo) -> F,
261 {
262 self.frames.push(Frame::Generator {
263 name,
264 span,
265 state: GeneratorState::Running,
266 generator: Gen::new(|co| pin_generator(r#gen(co))),
267 });
268 }
269
270 pub(crate) fn run_generator(
276 &mut self,
277 name: &'static str,
278 span: Span,
279 frame_id: usize,
280 state: GeneratorState,
281 mut generator: Generator,
282 initial_message: Option<VMResponse>,
283 ) -> EvalResult<bool> {
284 let mut message = match (initial_message, state) {
286 (Some(msg), _) => msg,
287 (_, GeneratorState::Running) => VMResponse::Empty,
288
289 (_, GeneratorState::AwaitingValue) => VMResponse::Value(self.stack_pop()),
292 };
293
294 loop {
295 match generator.resume_with(message) {
296 genawaiter::GeneratorState::Yielded(request) => {
299 self.observer.observe_generator_request(name, &request);
300
301 match request {
302 VMRequest::StackPush(value) => {
303 self.stack.push(value);
304 message = VMResponse::Empty;
305 }
306
307 VMRequest::StackPop => {
308 message = VMResponse::Value(self.stack_pop());
309 }
310
311 VMRequest::ForceValue(value) => {
315 self.reenqueue_generator(name, span, generator);
316 self.enqueue_generator("force", span, |co| {
317 value.force_owned_genco(co, span)
318 });
319 return Ok(false);
320 }
321
322 VMRequest::DeepForceValue(value) => {
324 self.reenqueue_generator(name, span, generator);
325 self.enqueue_generator("deep_force", span, |co| {
326 value.deep_force(co, span)
327 });
328 return Ok(false);
329 }
330
331 VMRequest::WithValue(idx) => {
335 self.reenqueue_generator(name, span, generator);
336
337 let value = self.stack[self.with_stack[idx]].clone();
338 self.enqueue_generator("force", span, |co| {
339 value.force_owned_genco(co, span)
340 });
341
342 return Ok(false);
343 }
344
345 VMRequest::CapturedWithValue(idx) => {
349 self.reenqueue_generator(name, span, generator);
350
351 let call_frame = self.last_call_frame()
352 .expect("Snix bug: generator requested captured with-value, but there is no call frame");
353
354 let value = call_frame
355 .upvalues
356 .get_from_with_stack(idx)
357 .expect("Snix bug: upvalue not found on stack");
358 self.enqueue_generator("force", span, |co| {
359 value.force_owned_genco(co, span)
360 });
361
362 return Ok(false);
363 }
364
365 VMRequest::NixEquality(values, ptr_eq) => {
366 let values = *values;
367 self.reenqueue_generator(name, span, generator);
368 self.enqueue_generator("nix_eq", span, |co| {
369 values.0.nix_eq_owned_genco(values.1, co, ptr_eq, span)
370 });
371 return Ok(false);
372 }
373
374 VMRequest::StringCoerce(val, kind) => {
375 self.reenqueue_generator(name, span, generator);
376 self.enqueue_generator("coerce_to_string", span, |co| {
377 val.coerce_to_string(co, kind, span)
378 });
379 return Ok(false);
380 }
381
382 VMRequest::Call(callable) => {
383 self.reenqueue_generator(name, span, generator);
384 self.call_value(span, None, callable)?;
385 return Ok(false);
386 }
387
388 VMRequest::EnterLambda {
389 lambda,
390 upvalues,
391 span,
392 } => {
393 self.reenqueue_generator(name, span, generator);
394
395 self.frames.push(Frame::CallFrame {
396 span,
397 call_frame: CallFrame {
398 lambda,
399 upvalues,
400 ip: CodeIdx(0),
401 stack_offset: self.stack.len(),
402 },
403 });
404
405 return Ok(false);
406 }
407
408 VMRequest::EmitWarning(warning) => {
409 self.push_warning(warning);
410 message = VMResponse::Empty;
411 }
412
413 VMRequest::EmitWarningKind(kind) => {
414 self.emit_warning(kind);
415 message = VMResponse::Empty;
416 }
417
418 VMRequest::ImportCacheLookup(path) => {
419 if let Some(cached) = self.import_cache.get(&path) {
420 message = VMResponse::Value(cached.clone());
421 } else {
422 message = VMResponse::Empty;
423 }
424 }
425
426 VMRequest::ImportCachePut(path, value) => {
427 self.import_cache.insert(path, value);
428 message = VMResponse::Empty;
429 }
430
431 VMRequest::PathImport(path) => {
432 let imported = if let Some(p) = self.path_import_cache.get(&path) {
433 p.to_owned()
434 } else {
435 let imported = self
436 .io_handle
437 .as_ref()
438 .import_path(&path)
439 .map_err(|e| ErrorKind::IO {
440 path: Some(path.to_owned()),
441 error: e.into(),
442 })
443 .with_span(span, self)?;
444
445 self.path_import_cache.insert(path, imported.clone());
446 imported
447 };
448
449 message = VMResponse::Path(imported);
450 }
451
452 VMRequest::OpenFile(path) => {
453 let reader = self
454 .io_handle
455 .as_ref()
456 .open(&path)
457 .map_err(|e| ErrorKind::IO {
458 path: Some(path),
459 error: e.into(),
460 })
461 .with_span(span, self)?;
462
463 message = VMResponse::Reader(reader)
464 }
465
466 VMRequest::PathExists(path) => {
467 let exists = self
468 .io_handle
469 .as_ref()
470 .path_exists(&path)
471 .map_err(|e| ErrorKind::IO {
472 path: Some(path),
473 error: e.into(),
474 })
475 .map(Value::Bool)
476 .with_span(span, self)?;
477
478 message = VMResponse::Value(exists);
479 }
480
481 VMRequest::ReadDir(path) => {
482 let dir = self
483 .io_handle
484 .as_ref()
485 .read_dir(&path)
486 .map_err(|e| ErrorKind::IO {
487 path: Some(path),
488 error: e.into(),
489 })
490 .with_span(span, self)?;
491 message = VMResponse::Directory(dir);
492 }
493
494 VMRequest::Span => {
495 message = VMResponse::Span(self.reasonable_span);
496 }
497
498 VMRequest::TryForce(value) => {
499 self.try_eval_frames.push(frame_id);
500 self.reenqueue_generator(name, span, generator);
501
502 debug_assert!(
503 self.frames.len() == frame_id + 1,
504 "generator should be reenqueued with the same frame ID"
505 );
506
507 self.enqueue_generator("force", span, |co| {
508 value.force_owned_genco(co, span)
509 });
510 return Ok(false);
511 }
512
513 VMRequest::ReadFileType(path) => {
514 let file_type = self
515 .io_handle
516 .as_ref()
517 .file_type(&path)
518 .map_err(|e| ErrorKind::IO {
519 path: Some(path),
520 error: e.into(),
521 })
522 .with_span(span, self)?;
523
524 message = VMResponse::FileType(file_type);
525 }
526 VMRequest::GetEnv(key) => {
527 let env = self.io_handle.as_ref().get_env(&key).unwrap_or_default();
528
529 message = VMResponse::Env(env);
530 }
531 }
532 }
533
534 genawaiter::GeneratorState::Complete(result) => {
537 let value = result.with_span(span, self)?;
538 self.stack.push(value);
539 return Ok(true);
540 }
541 }
542 }
543 }
544}
545
546pub type GenCo = Co<VMRequest, VMResponse>;
547
548pub async fn request_stack_push(co: &GenCo, val: Value) {
552 match co.yield_(VMRequest::StackPush(val)).await {
553 VMResponse::Empty => {}
554 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
555 }
556}
557
558pub async fn request_stack_pop(co: &GenCo) -> Value {
561 match co.yield_(VMRequest::StackPop).await {
562 VMResponse::Value(value) => value,
563 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
564 }
565}
566
567pub async fn request_force(co: &GenCo, val: Value) -> Value {
569 if let Value::Thunk(_) = val {
570 match co.yield_(VMRequest::ForceValue(val)).await {
571 VMResponse::Value(value) => value,
572 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
573 }
574 } else {
575 val
576 }
577}
578
579pub(crate) async fn request_try_force(co: &GenCo, val: Value) -> Value {
581 if let Value::Thunk(_) = val {
582 match co.yield_(VMRequest::TryForce(val)).await {
583 VMResponse::Value(value) => value,
584 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
585 }
586 } else {
587 val
588 }
589}
590
591pub async fn request_call(co: &GenCo, val: Value) -> Value {
594 let val = request_force(co, val).await;
595 match co.yield_(VMRequest::Call(val)).await {
596 VMResponse::Value(value) => value,
597 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
598 }
599}
600
601pub async fn request_call_with<I>(co: &GenCo, mut callable: Value, args: I) -> Value
604where
605 I: IntoIterator<Item = Value>,
606 I::IntoIter: DoubleEndedIterator,
607{
608 let mut num_args = 0_usize;
609 for arg in args.into_iter().rev() {
610 num_args += 1;
611 request_stack_push(co, arg).await;
612 }
613
614 debug_assert!(num_args > 0, "call_with called with an empty list of args");
615
616 while num_args > 0 {
617 callable = request_call(co, callable).await;
618 num_args -= 1;
619 }
620
621 callable
622}
623
624pub async fn request_string_coerce(
625 co: &GenCo,
626 val: Value,
627 kind: CoercionKind,
628) -> Result<NixString, CatchableErrorKind> {
629 match val {
630 Value::String(s) => Ok(s),
631 _ => match co.yield_(VMRequest::StringCoerce(val, kind)).await {
632 VMResponse::Value(Value::Catchable(c)) => Err(*c),
633 VMResponse::Value(value) => Ok(value
634 .to_contextful_str()
635 .expect("coerce_to_string always returns a string")),
636 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
637 },
638 }
639}
640
641pub async fn request_deep_force(co: &GenCo, val: Value) -> Value {
643 match co.yield_(VMRequest::DeepForceValue(val)).await {
644 VMResponse::Value(value) => value,
645 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
646 }
647}
648
649pub(crate) async fn check_equality(
651 co: &GenCo,
652 a: Value,
653 b: Value,
654 ptr_eq: PointerEquality,
655) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
656 match co
657 .yield_(VMRequest::NixEquality(Box::new((a, b)), ptr_eq))
658 .await
659 {
660 VMResponse::Value(Value::Bool(b)) => Ok(Ok(b)),
661 VMResponse::Value(Value::Catchable(cek)) => Ok(Err(*cek)),
662 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
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!("Snix bug: VM responded with incorrect generator message: {msg}"),
671 }
672}
673
674pub async fn emit_warning_kind(co: &GenCo, kind: WarningKind) {
676 match co.yield_(VMRequest::EmitWarningKind(kind)).await {
677 VMResponse::Empty => {}
678 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
679 }
680}
681
682pub(crate) async fn request_enter_lambda(
684 co: &GenCo,
685 lambda: Rc<Lambda>,
686 upvalues: Rc<Upvalues>,
687 span: Span,
688) -> Value {
689 let msg = VMRequest::EnterLambda {
690 lambda,
691 upvalues,
692 span,
693 };
694
695 match co.yield_(msg).await {
696 VMResponse::Value(value) => value,
697 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
698 }
699}
700
701pub(crate) async fn request_import_cache_lookup(co: &GenCo, path: PathBuf) -> Option<Value> {
703 match co.yield_(VMRequest::ImportCacheLookup(path)).await {
704 VMResponse::Value(value) => Some(value),
705 VMResponse::Empty => None,
706 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
707 }
708}
709
710pub(crate) async fn request_import_cache_put(co: &GenCo, path: PathBuf, value: Value) {
712 match co.yield_(VMRequest::ImportCachePut(path, value)).await {
713 VMResponse::Empty => {}
714 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
715 }
716}
717
718pub(crate) async fn request_path_import(co: &GenCo, path: PathBuf) -> PathBuf {
720 match co.yield_(VMRequest::PathImport(path)).await {
721 VMResponse::Path(path) => path,
722 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
723 }
724}
725
726pub async fn request_open_file(co: &GenCo, path: PathBuf) -> Box<dyn std::io::Read> {
728 match co.yield_(VMRequest::OpenFile(path)).await {
729 VMResponse::Reader(value) => value,
730 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
731 }
732}
733
734#[cfg_attr(not(feature = "impure"), allow(unused))]
735pub(crate) async fn request_path_exists(co: &GenCo, path: PathBuf) -> Value {
736 match co.yield_(VMRequest::PathExists(path)).await {
737 VMResponse::Value(value) => value,
738 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
739 }
740}
741
742#[cfg_attr(not(feature = "impure"), allow(unused))]
743pub(crate) async fn request_read_dir(co: &GenCo, path: PathBuf) -> Vec<(bytes::Bytes, FileType)> {
744 match co.yield_(VMRequest::ReadDir(path)).await {
745 VMResponse::Directory(dir) => dir,
746 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
747 }
748}
749
750pub(crate) async fn request_span(co: &GenCo) -> Span {
751 match co.yield_(VMRequest::Span).await {
752 VMResponse::Span(span) => span,
753 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
754 }
755}
756
757#[cfg_attr(not(feature = "impure"), allow(unused))]
758pub(crate) async fn request_read_file_type(co: &GenCo, path: PathBuf) -> FileType {
759 match co.yield_(VMRequest::ReadFileType(path)).await {
760 VMResponse::FileType(file_type) => file_type,
761 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
762 }
763}
764
765#[cfg_attr(not(feature = "impure"), allow(unused))]
766pub(crate) async fn request_get_env(co: &GenCo, key: OsString) -> OsString {
767 match co.yield_(VMRequest::GetEnv(key)).await {
768 VMResponse::Env(env) => env,
769 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
770 }
771}
772
773pub(crate) async fn call_functor(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
778 let attrs = value.to_attrs()?;
779
780 match attrs.select("__functor") {
781 None => Err(ErrorKind::NotCallable("set without `__functor_` attribute")),
782 Some(functor) => {
783 let functor = request_force(&co, functor.clone()).await;
786 let primed = request_call_with(&co, functor, [value]).await;
787 Ok(request_call(&co, primed).await)
788 }
789 }
790}