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.upvalues.with_stack().unwrap()[idx].clone();
355 self.enqueue_generator("force", span, |co| {
356 value.force_owned_genco(co, span)
357 });
358
359 return Ok(false);
360 }
361
362 VMRequest::NixEquality(values, ptr_eq) => {
363 let values = *values;
364 self.reenqueue_generator(name, span, generator);
365 self.enqueue_generator("nix_eq", span, |co| {
366 values.0.nix_eq_owned_genco(values.1, co, ptr_eq, span)
367 });
368 return Ok(false);
369 }
370
371 VMRequest::StringCoerce(val, kind) => {
372 self.reenqueue_generator(name, span, generator);
373 self.enqueue_generator("coerce_to_string", span, |co| {
374 val.coerce_to_string(co, kind, span)
375 });
376 return Ok(false);
377 }
378
379 VMRequest::Call(callable) => {
380 self.reenqueue_generator(name, span, generator);
381 self.call_value(span, None, callable)?;
382 return Ok(false);
383 }
384
385 VMRequest::EnterLambda {
386 lambda,
387 upvalues,
388 span,
389 } => {
390 self.reenqueue_generator(name, span, generator);
391
392 self.frames.push(Frame::CallFrame {
393 span,
394 call_frame: CallFrame {
395 lambda,
396 upvalues,
397 ip: CodeIdx(0),
398 stack_offset: self.stack.len(),
399 },
400 });
401
402 return Ok(false);
403 }
404
405 VMRequest::EmitWarning(warning) => {
406 self.push_warning(warning);
407 message = VMResponse::Empty;
408 }
409
410 VMRequest::EmitWarningKind(kind) => {
411 self.emit_warning(kind);
412 message = VMResponse::Empty;
413 }
414
415 VMRequest::ImportCacheLookup(path) => {
416 if let Some(cached) = self.import_cache.get(path) {
417 message = VMResponse::Value(cached.clone());
418 } else {
419 message = VMResponse::Empty;
420 }
421 }
422
423 VMRequest::ImportCachePut(path, value) => {
424 self.import_cache.insert(path, value);
425 message = VMResponse::Empty;
426 }
427
428 VMRequest::PathImport(path) => {
429 let imported = self
430 .io_handle
431 .as_ref()
432 .import_path(&path)
433 .map_err(|e| ErrorKind::IO {
434 path: Some(path),
435 error: e.into(),
436 })
437 .with_span(span, self)?;
438
439 message = VMResponse::Path(imported);
440 }
441
442 VMRequest::OpenFile(path) => {
443 let reader = self
444 .io_handle
445 .as_ref()
446 .open(&path)
447 .map_err(|e| ErrorKind::IO {
448 path: Some(path),
449 error: e.into(),
450 })
451 .with_span(span, self)?;
452
453 message = VMResponse::Reader(reader)
454 }
455
456 VMRequest::PathExists(path) => {
457 let exists = self
458 .io_handle
459 .as_ref()
460 .path_exists(&path)
461 .map_err(|e| ErrorKind::IO {
462 path: Some(path),
463 error: e.into(),
464 })
465 .map(Value::Bool)
466 .with_span(span, self)?;
467
468 message = VMResponse::Value(exists);
469 }
470
471 VMRequest::ReadDir(path) => {
472 let dir = self
473 .io_handle
474 .as_ref()
475 .read_dir(&path)
476 .map_err(|e| ErrorKind::IO {
477 path: Some(path),
478 error: e.into(),
479 })
480 .with_span(span, self)?;
481 message = VMResponse::Directory(dir);
482 }
483
484 VMRequest::Span => {
485 message = VMResponse::Span(self.reasonable_span);
486 }
487
488 VMRequest::TryForce(value) => {
489 self.try_eval_frames.push(frame_id);
490 self.reenqueue_generator(name, span, generator);
491
492 debug_assert!(
493 self.frames.len() == frame_id + 1,
494 "generator should be reenqueued with the same frame ID"
495 );
496
497 self.enqueue_generator("force", span, |co| {
498 value.force_owned_genco(co, span)
499 });
500 return Ok(false);
501 }
502
503 VMRequest::ReadFileType(path) => {
504 let file_type = self
505 .io_handle
506 .as_ref()
507 .file_type(&path)
508 .map_err(|e| ErrorKind::IO {
509 path: Some(path),
510 error: e.into(),
511 })
512 .with_span(span, self)?;
513
514 message = VMResponse::FileType(file_type);
515 }
516 VMRequest::GetEnv(key) => {
517 let env = self.io_handle.as_ref().get_env(&key).unwrap_or_default();
518
519 message = VMResponse::Env(env);
520 }
521 }
522 }
523
524 genawaiter::GeneratorState::Complete(result) => {
527 let value = result.with_span(span, self)?;
528 self.stack.push(value);
529 return Ok(true);
530 }
531 }
532 }
533 }
534}
535
536pub type GenCo = Co<VMRequest, VMResponse>;
537
538pub async fn request_stack_push(co: &GenCo, val: Value) {
542 match co.yield_(VMRequest::StackPush(val)).await {
543 VMResponse::Empty => {}
544 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
545 }
546}
547
548pub async fn request_stack_pop(co: &GenCo) -> Value {
551 match co.yield_(VMRequest::StackPop).await {
552 VMResponse::Value(value) => value,
553 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
554 }
555}
556
557pub async fn request_force(co: &GenCo, val: Value) -> Value {
559 if let Value::Thunk(_) = val {
560 match co.yield_(VMRequest::ForceValue(val)).await {
561 VMResponse::Value(value) => value,
562 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
563 }
564 } else {
565 val
566 }
567}
568
569pub(crate) async fn request_try_force(co: &GenCo, val: Value) -> Value {
571 if let Value::Thunk(_) = val {
572 match co.yield_(VMRequest::TryForce(val)).await {
573 VMResponse::Value(value) => value,
574 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
575 }
576 } else {
577 val
578 }
579}
580
581pub async fn request_call(co: &GenCo, val: Value) -> Value {
584 let val = request_force(co, val).await;
585 match co.yield_(VMRequest::Call(val)).await {
586 VMResponse::Value(value) => value,
587 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
588 }
589}
590
591pub async fn request_call_with<I>(co: &GenCo, mut callable: Value, args: I) -> Value
594where
595 I: IntoIterator<Item = Value>,
596 I::IntoIter: DoubleEndedIterator,
597{
598 let mut num_args = 0_usize;
599 for arg in args.into_iter().rev() {
600 num_args += 1;
601 request_stack_push(co, arg).await;
602 }
603
604 debug_assert!(num_args > 0, "call_with called with an empty list of args");
605
606 while num_args > 0 {
607 callable = request_call(co, callable).await;
608 num_args -= 1;
609 }
610
611 callable
612}
613
614pub async fn request_string_coerce(
615 co: &GenCo,
616 val: Value,
617 kind: CoercionKind,
618) -> Result<NixString, CatchableErrorKind> {
619 match val {
620 Value::String(s) => Ok(s),
621 _ => match co.yield_(VMRequest::StringCoerce(val, kind)).await {
622 VMResponse::Value(Value::Catchable(c)) => Err(*c),
623 VMResponse::Value(value) => Ok(value
624 .to_contextful_str()
625 .expect("coerce_to_string always returns a string")),
626 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
627 },
628 }
629}
630
631pub async fn request_deep_force(co: &GenCo, val: Value) -> Value {
633 match co.yield_(VMRequest::DeepForceValue(val)).await {
634 VMResponse::Value(value) => value,
635 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
636 }
637}
638
639pub(crate) async fn check_equality(
641 co: &GenCo,
642 a: Value,
643 b: Value,
644 ptr_eq: PointerEquality,
645) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
646 match co
647 .yield_(VMRequest::NixEquality(Box::new((a, b)), ptr_eq))
648 .await
649 {
650 VMResponse::Value(Value::Bool(b)) => Ok(Ok(b)),
651 VMResponse::Value(Value::Catchable(cek)) => Ok(Err(*cek)),
652 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
653 }
654}
655
656pub(crate) async fn emit_warning(co: &GenCo, warning: EvalWarning) {
658 match co.yield_(VMRequest::EmitWarning(warning)).await {
659 VMResponse::Empty => {}
660 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
661 }
662}
663
664pub async fn emit_warning_kind(co: &GenCo, kind: WarningKind) {
666 match co.yield_(VMRequest::EmitWarningKind(kind)).await {
667 VMResponse::Empty => {}
668 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
669 }
670}
671
672pub(crate) async fn request_enter_lambda(
674 co: &GenCo,
675 lambda: Rc<Lambda>,
676 upvalues: Rc<Upvalues>,
677 span: Span,
678) -> Value {
679 let msg = VMRequest::EnterLambda {
680 lambda,
681 upvalues,
682 span,
683 };
684
685 match co.yield_(msg).await {
686 VMResponse::Value(value) => value,
687 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
688 }
689}
690
691pub(crate) async fn request_import_cache_lookup(co: &GenCo, path: PathBuf) -> Option<Value> {
693 match co.yield_(VMRequest::ImportCacheLookup(path)).await {
694 VMResponse::Value(value) => Some(value),
695 VMResponse::Empty => None,
696 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
697 }
698}
699
700pub(crate) async fn request_import_cache_put(co: &GenCo, path: PathBuf, value: Value) {
702 match co.yield_(VMRequest::ImportCachePut(path, value)).await {
703 VMResponse::Empty => {}
704 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
705 }
706}
707
708pub(crate) async fn request_path_import(co: &GenCo, path: PathBuf) -> PathBuf {
710 match co.yield_(VMRequest::PathImport(path)).await {
711 VMResponse::Path(path) => path,
712 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
713 }
714}
715
716pub async fn request_open_file(co: &GenCo, path: PathBuf) -> Box<dyn std::io::Read> {
718 match co.yield_(VMRequest::OpenFile(path)).await {
719 VMResponse::Reader(value) => value,
720 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
721 }
722}
723
724#[cfg_attr(not(feature = "impure"), allow(unused))]
725pub(crate) async fn request_path_exists(co: &GenCo, path: PathBuf) -> Value {
726 match co.yield_(VMRequest::PathExists(path)).await {
727 VMResponse::Value(value) => value,
728 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
729 }
730}
731
732#[cfg_attr(not(feature = "impure"), allow(unused))]
733pub(crate) async fn request_read_dir(co: &GenCo, path: PathBuf) -> Vec<(bytes::Bytes, FileType)> {
734 match co.yield_(VMRequest::ReadDir(path)).await {
735 VMResponse::Directory(dir) => dir,
736 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
737 }
738}
739
740pub(crate) async fn request_span(co: &GenCo) -> Span {
741 match co.yield_(VMRequest::Span).await {
742 VMResponse::Span(span) => span,
743 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
744 }
745}
746
747#[cfg_attr(not(feature = "impure"), allow(unused))]
748pub(crate) async fn request_read_file_type(co: &GenCo, path: PathBuf) -> FileType {
749 match co.yield_(VMRequest::ReadFileType(path)).await {
750 VMResponse::FileType(file_type) => file_type,
751 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
752 }
753}
754
755#[cfg_attr(not(feature = "impure"), allow(unused))]
756pub(crate) async fn request_get_env(co: &GenCo, key: OsString) -> OsString {
757 match co.yield_(VMRequest::GetEnv(key)).await {
758 VMResponse::Env(env) => env,
759 msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"),
760 }
761}
762
763pub(crate) async fn call_functor(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
768 let attrs = value.to_attrs()?;
769
770 match attrs.select("__functor") {
771 None => Err(ErrorKind::NotCallable("set without `__functor_` attribute")),
772 Some(functor) => {
773 let functor = request_force(&co, functor.clone()).await;
776 let primed = request_call_with(&co, functor, [value]).await;
777 Ok(request_call(&co, primed).await)
778 }
779 }
780}