rustpython_vm/vm/interpreter.rs
1use super::{setting::Settings, thread, Context, VirtualMachine};
2use crate::{stdlib::atexit, vm::PyBaseExceptionRef, PyResult};
3use std::sync::atomic::Ordering;
4
5/// The general interface for the VM
6///
7/// # Examples
8/// Runs a simple embedded hello world program.
9/// ```
10/// use rustpython_vm::Interpreter;
11/// use rustpython_vm::compiler::Mode;
12/// Interpreter::without_stdlib(Default::default()).enter(|vm| {
13/// let scope = vm.new_scope_with_builtins();
14/// let source = r#"print("Hello World!")"#;
15/// let code_obj = vm.compile(
16/// source,
17/// Mode::Exec,
18/// "<embedded>".to_owned(),
19/// ).map_err(|err| vm.new_syntax_error(&err, Some(source))).unwrap();
20/// vm.run_code_obj(code_obj, scope).unwrap();
21/// });
22/// ```
23pub struct Interpreter {
24 vm: VirtualMachine,
25}
26
27impl Interpreter {
28 /// This is a bare unit to build up an interpreter without the standard library.
29 /// To create an interpreter with the standard library with the `rustpython` crate, use `rustpython::InterpreterConfig`.
30 /// To create an interpreter without the `rustpython` crate, but only with `rustpython-vm`,
31 /// try to build one from the source code of `InterpreterConfig`. It will not be a one-liner but it also will not be too hard.
32 pub fn without_stdlib(settings: Settings) -> Self {
33 Self::with_init(settings, |_| {})
34 }
35
36 /// Create with initialize function taking mutable vm reference.
37 /// ```
38 /// use rustpython_vm::Interpreter;
39 /// Interpreter::with_init(Default::default(), |vm| {
40 /// // put this line to add stdlib to the vm
41 /// // vm.add_native_modules(rustpython_stdlib::get_module_inits());
42 /// }).enter(|vm| {
43 /// vm.run_code_string(vm.new_scope_with_builtins(), "print(1)", "<...>".to_owned());
44 /// });
45 /// ```
46 pub fn with_init<F>(settings: Settings, init: F) -> Self
47 where
48 F: FnOnce(&mut VirtualMachine),
49 {
50 let ctx = Context::genesis();
51 crate::types::TypeZoo::extend(ctx);
52 crate::exceptions::ExceptionZoo::extend(ctx);
53 let mut vm = VirtualMachine::new(settings, ctx.clone());
54 init(&mut vm);
55 vm.initialize();
56 Self { vm }
57 }
58
59 /// Run a function with the main virtual machine and return a PyResult of the result.
60 ///
61 /// To enter vm context multiple times or to avoid buffer/exception management, this function is preferred.
62 /// `enter` is lightweight and it returns a python object in PyResult.
63 /// You can stop or continue the execution multiple times by calling `enter`.
64 ///
65 /// To finalize the vm once all desired `enter`s are called, calling `finalize` will be helpful.
66 ///
67 /// See also [`run`] for managed way to run the interpreter.
68 pub fn enter<F, R>(&self, f: F) -> R
69 where
70 F: FnOnce(&VirtualMachine) -> R,
71 {
72 thread::enter_vm(&self.vm, || f(&self.vm))
73 }
74
75 /// Run [`enter`] and call `expect_pyresult` for the result.
76 ///
77 /// This function is useful when you want to expect a result from the function,
78 /// but also print useful panic information when exception raised.
79 ///
80 /// See [`enter`] for more information.
81 /// See [`expect_pyresult`] for more information.
82 pub fn enter_and_expect<F, R>(&self, f: F, msg: &str) -> R
83 where
84 F: FnOnce(&VirtualMachine) -> PyResult<R>,
85 {
86 self.enter(|vm| {
87 let result = f(vm);
88 vm.expect_pyresult(result, msg)
89 })
90 }
91
92 /// Run a function with the main virtual machine and return exit code.
93 ///
94 /// To enter vm context only once and safely terminate the vm, this function is preferred.
95 /// Unlike [`enter`], `run` calls finalize and returns exit code.
96 /// You will not be able to obtain Python exception in this way.
97 ///
98 /// See [`finalize`] for the finalization steps.
99 /// See also [`enter`] for pure function call to obtain Python exception.
100 pub fn run<F>(self, f: F) -> u8
101 where
102 F: FnOnce(&VirtualMachine) -> PyResult<()>,
103 {
104 let res = self.enter(|vm| f(vm));
105 self.finalize(res.err())
106 }
107
108 /// Finalize vm and turns an exception to exit code.
109 ///
110 /// Finalization steps including 4 steps:
111 /// 1. Flush stdout and stderr.
112 /// 1. Handle exit exception and turn it to exit code.
113 /// 1. Run atexit exit functions.
114 /// 1. Mark vm as finalized.
115 ///
116 /// Note that calling `finalize` is not necessary by purpose though.
117 pub fn finalize(self, exc: Option<PyBaseExceptionRef>) -> u8 {
118 self.enter(|vm| {
119 vm.flush_std();
120
121 // See if any exception leaked out:
122 let exit_code = if let Some(exc) = exc {
123 vm.handle_exit_exception(exc)
124 } else {
125 0
126 };
127
128 atexit::_run_exitfuncs(vm);
129
130 vm.state.finalizing.store(true, Ordering::Release);
131
132 vm.flush_std();
133
134 exit_code
135 })
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::{
143 builtins::{int, PyStr},
144 PyObjectRef,
145 };
146 use malachite_bigint::ToBigInt;
147
148 #[test]
149 fn test_add_py_integers() {
150 Interpreter::without_stdlib(Default::default()).enter(|vm| {
151 let a: PyObjectRef = vm.ctx.new_int(33_i32).into();
152 let b: PyObjectRef = vm.ctx.new_int(12_i32).into();
153 let res = vm._add(&a, &b).unwrap();
154 let value = int::get_value(&res);
155 assert_eq!(*value, 45_i32.to_bigint().unwrap());
156 })
157 }
158
159 #[test]
160 fn test_multiply_str() {
161 Interpreter::without_stdlib(Default::default()).enter(|vm| {
162 let a = vm.new_pyobj(crate::common::ascii!("Hello "));
163 let b = vm.new_pyobj(4_i32);
164 let res = vm._mul(&a, &b).unwrap();
165 let value = res.payload::<PyStr>().unwrap();
166 assert_eq!(value.as_ref(), "Hello Hello Hello Hello ")
167 })
168 }
169}