aiscript_vm/vm/
mod.rs

1use std::{fmt::Display, fs, ops, path::PathBuf};
2
3use aiscript_arena::{Arena, Mutation, Rootable, arena::CollectionPhase};
4use sqlx::{PgPool, SqlitePool};
5pub use state::State;
6
7use crate::{
8    ReturnValue, Value,
9    ast::ChunkId,
10    builtins, stdlib,
11    string::{InternedString, InternedStringSet},
12};
13use fuel::Fuel;
14
15mod extra;
16mod fuel;
17mod state;
18
19#[derive(Debug)]
20pub enum VmError {
21    CompileError,
22    RuntimeError(std::string::String),
23}
24
25impl std::error::Error for VmError {}
26
27impl Display for VmError {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            Self::CompileError => write!(f, "CompileError"),
31            Self::RuntimeError(s) => write!(f, "RuntimeError: {s}"),
32        }
33    }
34}
35
36impl Default for Vm {
37    fn default() -> Self {
38        Self::new(None, None, None)
39    }
40}
41
42pub struct Vm {
43    arena: Arena<Rootable![State<'_>]>,
44}
45
46impl Vm {
47    pub fn new(
48        pg_connection: Option<PgPool>,
49        sqlite_connection: Option<SqlitePool>,
50        redis_connection: Option<redis::aio::MultiplexedConnection>,
51    ) -> Self {
52        let mut vm = Vm {
53            arena: Arena::<Rootable![State<'_>]>::new(|mc| {
54                let mut state = State::new(mc);
55                state.pg_connection = pg_connection;
56                state.sqlite_connection = sqlite_connection;
57                state.redis_connection = redis_connection;
58                state
59            }),
60        };
61        vm.init_stdlib();
62        vm
63    }
64
65    pub fn run_file(&mut self, path: PathBuf) {
66        match fs::read_to_string(&path) {
67            Ok(source) => {
68                let source: &'static str = Box::leak(source.into_boxed_str());
69                if let Err(VmError::CompileError) = self.compile(source) {
70                    std::process::exit(65);
71                }
72                if let Err(VmError::RuntimeError(err)) = self.interpret() {
73                    eprintln!("{err}");
74                    std::process::exit(70);
75                }
76            }
77            Err(err) => {
78                eprintln!("Failed to read file '{}': {}", path.display(), err);
79                std::process::exit(1);
80            }
81        }
82    }
83
84    fn init_stdlib(&mut self) {
85        self.arena.mutate_root(|_mc, state| {
86            let ctx = state.get_context();
87
88            state.builtin_methods.init(ctx);
89            state.globals.insert(
90                ctx.intern(b"ValidationError!"),
91                Value::Class(builtins::create_validation_error(ctx)),
92            );
93
94            // Initialize standard library modules
95            state.module_manager.register_native_module(
96                ctx.intern(b"std.auth.jwt"),
97                stdlib::create_jwt_module(ctx),
98            );
99            state
100                .module_manager
101                .register_native_module(ctx.intern(b"std.env"), stdlib::create_env_module(ctx));
102            state
103                .module_manager
104                .register_native_module(ctx.intern(b"std.math"), stdlib::create_math_module(ctx));
105            state
106                .module_manager
107                .register_native_module(ctx.intern(b"std.http"), stdlib::create_http_module(ctx));
108            state
109                .module_manager
110                .register_native_module(ctx.intern(b"std.io"), stdlib::create_io_module(ctx));
111            state
112                .module_manager
113                .register_native_module(ctx.intern(b"std.time"), stdlib::create_time_module(ctx));
114            state.module_manager.register_native_module(
115                ctx.intern(b"std.random"),
116                stdlib::create_random_module(ctx),
117            );
118            state
119                .module_manager
120                .register_native_module(ctx.intern(b"std.serde"), stdlib::create_serde_module(ctx));
121            state
122                .module_manager
123                .register_native_module(ctx.intern(b"std.db.pg"), stdlib::create_pg_module(ctx));
124            state.module_manager.register_native_module(
125                ctx.intern(b"std.db.sqlite"),
126                stdlib::create_sqlite_module(ctx),
127            );
128            state.module_manager.register_native_module(
129                ctx.intern(b"std.db.redis"),
130                stdlib::create_redis_module(ctx),
131            );
132        });
133    }
134
135    pub fn compile(&mut self, source: &'static str) -> Result<(), VmError> {
136        self.arena.mutate_root(|_mc, state| {
137            let context = state.get_context();
138            state.chunks = crate::compiler::compile(context, source)?;
139            builtins::define_builtin_functions(state);
140            // The script function's chunk id is always the highest chunk id.
141            let script_chunk_id = state.chunks.keys().max().copied().unwrap();
142            let function = state.get_chunk(script_chunk_id)?;
143            state.call_function(function, &[])
144        })
145    }
146
147    pub fn eval_function(
148        &mut self,
149        chunk_id: ChunkId,
150        params: &[serde_json::Value],
151    ) -> Result<ReturnValue, VmError> {
152        self.arena.mutate_root(|_mc, state| {
153            let ctx = state.get_context();
154            let return_value = state.eval_function_with_id(
155                chunk_id,
156                &params
157                    .iter()
158                    .map(|v| Value::from_serde_value(ctx, v))
159                    .collect::<Vec<_>>(),
160            )?;
161            Ok(ReturnValue::from(return_value))
162        })
163    }
164
165    pub fn interpret(&mut self) -> Result<ReturnValue, VmError> {
166        loop {
167            const FUEL_PER_GC: i32 = 1024 * 10;
168            let mut fuel = Fuel::new(FUEL_PER_GC);
169            // periodically exit the arena in order to collect garbage concurrently with running the VM.
170            let result = self.arena.mutate_root(|_, state| state.step(&mut fuel));
171
172            const COLLECTOR_GRANULARITY: f64 = 10240.0;
173            if self.arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY {
174                // Do garbage collection.
175                #[cfg(feature = "debug")]
176                println!("Collecting...");
177                if self.arena.collection_phase() == CollectionPhase::Sweeping {
178                    self.arena.collect_debt();
179                } else {
180                    // Immediately transition to `CollectionPhase::Sweeping`.
181                    self.arena.mark_all().unwrap().start_sweeping();
182                }
183            }
184
185            match result {
186                Ok(result) => {
187                    if let Some(value) = result {
188                        return Ok(value);
189                    }
190                }
191                Err(err) => return Err(err),
192            }
193        }
194    }
195}
196
197#[derive(Copy, Clone)]
198pub struct Context<'gc> {
199    pub mutation: &'gc Mutation<'gc>,
200    pub strings: InternedStringSet<'gc>,
201}
202
203impl<'gc> Context<'gc> {
204    pub fn intern(self, s: &[u8]) -> InternedString<'gc> {
205        self.strings.intern(&self, s)
206    }
207
208    #[allow(unused)]
209    pub fn intern_static(self, s: &'static str) -> InternedString<'gc> {
210        self.strings.intern_static(&self, s.as_bytes())
211    }
212}
213
214impl<'gc> ops::Deref for Context<'gc> {
215    type Target = Mutation<'gc>;
216
217    fn deref(&self) -> &Self::Target {
218        self.mutation
219    }
220}