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 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 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 ¶ms
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 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 #[cfg(feature = "debug")]
176 println!("Collecting...");
177 if self.arena.collection_phase() == CollectionPhase::Sweeping {
178 self.arena.collect_debt();
179 } else {
180 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}