mica_hl/
engine.rs

1use std::any::Any;
2use std::ops::Deref;
3use std::rc::Rc;
4
5use mica_language::ast::DumpAst;
6use mica_language::bytecode::{
7   BuiltinDispatchTables, BuiltinTraits, Chunk, DispatchTable, Environment, Function, FunctionKind,
8   FunctionSignature, Opcode, Opr24,
9};
10use mica_language::codegen::{self, CodeGenerator};
11use mica_language::gc::{Gc, Memory};
12use mica_language::lexer::Lexer;
13use mica_language::parser::Parser;
14use mica_language::value::{Closure, RawValue};
15use mica_language::vm::{self, Globals};
16
17use crate::{
18   create_trait_value, ffvariants, BuiltType, Error, Fiber, ForeignFunction, MicaResultExt,
19   StandardLibrary, TraitBuilder, TryFromValue, TypeBuilder, UserData, Value,
20};
21
22/// The implementation of a raw foreign function.
23pub use mica_language::bytecode::ForeignFunction as RawForeignFunction;
24/// The kind of a raw function.
25pub use mica_language::bytecode::FunctionKind as RawFunctionKind;
26
27/// Options for debugging the language implementation.
28#[derive(Debug, Clone, Copy, Default)]
29pub struct DebugOptions {
30   /// Set to `true` to print the AST to stdout after parsing.
31   pub dump_ast: bool,
32   /// Set to `true` to print the bytecode to stdout after successful compilation.
33   pub dump_bytecode: bool,
34}
35
36/// **Start here!** An execution engine. Contains information about things like globals, registered
37/// types, etc.
38pub struct Engine {
39   pub(crate) env: Environment,
40   pub(crate) builtin_traits: BuiltinTraits,
41   pub(crate) globals: Globals,
42   // This field is needed to keep all builtin dispatch tables alive for longer than `gc`.
43   pub(crate) gc: Memory,
44   debug_options: DebugOptions,
45}
46
47impl Engine {
48   /// Creates a new engine.
49   pub fn new(stdlib: impl StandardLibrary) -> Self {
50      Self::with_debug_options(stdlib, Default::default())
51   }
52
53   /// Creates a new engine with specific debug options.
54   ///
55   /// [`Engine::new`] creates an engine with [`Default`] debug options, and should generally be
56   /// preferred unless you're debugging the language's internals.
57   ///
58   /// Constructing the engine can fail if the standard library defines way too many methods.
59   pub fn with_debug_options(
60      mut stdlib: impl StandardLibrary,
61      debug_options: DebugOptions,
62   ) -> Self {
63      let mut gc = Memory::new();
64      // This is a little bad because it allocates a bunch of empty dtables only to discard them.
65      let mut env = Environment::new(BuiltinDispatchTables::empty());
66
67      let builtin_traits = BuiltinTraits::register_in(&mut env);
68      let iterator = create_trait_value(&mut env, &mut gc, builtin_traits.iterator);
69
70      macro_rules! get_dtables {
71         ($type_name:tt, $define:tt) => {{
72            let tb = TypeBuilder::new($type_name);
73            let tb = stdlib.$define(tb);
74            tb.build(&mut env, &mut gc, &builtin_traits).expect("stdlib declares too many methods")
75         }};
76      }
77      let nil = get_dtables!("Nil", define_nil);
78      let boolean = get_dtables!("Boolean", define_boolean);
79      let number = get_dtables!("Number", define_number);
80      let string = get_dtables!("String", define_string);
81      let list = get_dtables!("List", define_list);
82      let dict = get_dtables!("Dict", define_dict);
83      env.builtin_dtables = BuiltinDispatchTables {
84         nil: Gc::clone(&nil.instance_dtable),
85         boolean: Gc::clone(&boolean.instance_dtable),
86         number: Gc::clone(&number.instance_dtable),
87         string: Gc::clone(&string.instance_dtable),
88         function: Gc::new(DispatchTable::new_for_instance("Function")),
89         list: Gc::clone(&list.instance_dtable),
90         dict: Gc::clone(&dict.instance_dtable),
91      };
92
93      let mut engine = Self {
94         env,
95         builtin_traits,
96         globals: Globals::new(),
97         gc,
98         debug_options,
99      };
100      // Unwrapping here is fine because at this point we haven't got quite that many globals
101      // registered to overflow an Opr24.
102      engine.set_built_type(&nil).unwrap();
103      engine.set_built_type(&boolean).unwrap();
104      engine.set_built_type(&number).unwrap();
105      engine.set_built_type(&string).unwrap();
106      engine.set_built_type(&list).unwrap();
107      engine.set("Iterator", iterator).unwrap();
108
109      engine
110   }
111
112   /// Compiles a script.
113   ///
114   /// # Errors
115   ///  - [`Error::Compile`] - Syntax or semantic error
116   pub fn compile(
117      &mut self,
118      filename: impl AsRef<str>,
119      source: impl Into<String>,
120   ) -> Result<Script, Error> {
121      let module_name = Rc::from(filename.as_ref());
122      let lexer = Lexer::new(Rc::clone(&module_name), source.into());
123      let (ast, root_node) = Parser::new(lexer).parse()?;
124      if self.debug_options.dump_ast {
125         eprintln!("Mica - AST dump:");
126         eprintln!("{:?}", DumpAst(&ast, root_node));
127      }
128
129      let main_chunk = CodeGenerator::new(module_name, &mut self.env, &self.builtin_traits)
130         .generate(&ast, root_node)?;
131      if self.debug_options.dump_bytecode {
132         eprintln!("Mica - global environment:");
133         eprintln!("{:#?}", self.env);
134         eprintln!("Mica - main chunk disassembly:");
135         eprintln!("{:#?}", main_chunk);
136      }
137
138      Ok(Script {
139         engine: self,
140         main_chunk,
141      })
142   }
143
144   /// Compiles and starts running a script.
145   ///
146   /// This can be used as a shorthand if you don't intend to reuse the compiled bytecode.
147   ///
148   /// # Errors
149   /// See [`compile`][`Self::compile`].
150   pub fn start(
151      &mut self,
152      filename: impl AsRef<str>,
153      source: impl Into<String>,
154   ) -> Result<Fiber, Error> {
155      let script = self.compile(filename, source)?;
156      Ok(script.into_fiber())
157   }
158
159   /// Calls the provided function with the given arguments.
160   ///
161   /// # Errors
162   ///
163   /// - [`Error::Runtime`] - if a runtime error occurs - `function` isn't callable or an error is
164   ///   raised during execution
165   /// - [`Error::TooManyArguments`] - if more arguments than the implementation can support is
166   ///   passed to the function
167   pub fn call<T>(
168      &mut self,
169      function: Value,
170      arguments: impl IntoIterator<Item = Value>,
171   ) -> Result<T, Error>
172   where
173      T: TryFromValue,
174   {
175      let stack: Vec<_> =
176         Some(function).into_iter().chain(arguments).map(|x| x.to_raw(&mut self.gc)).collect();
177      // Having to construct a chunk here isn't the most clean, but it's the simplest way of making
178      // the VM perform a function call. It reuses sanity checks such as ensuring `function`
179      // can actually be called.
180      let mut chunk = Chunk::new(Rc::from("(call)"));
181      chunk.emit((
182         Opcode::Call,
183         // 1 has to be subtracted from the stack length there because the VM itself adds 1 to
184         // count in the function argument.
185         Opr24::try_from(stack.len() - 1).map_err(|_| Error::TooManyArguments)?,
186      ));
187      chunk.emit(Opcode::Halt);
188      let chunk = Rc::new(chunk);
189      let fiber = Fiber {
190         engine: self,
191         inner: vm::Fiber::new(chunk, stack),
192      };
193      fiber.trampoline()
194   }
195
196   /// Returns the unique ID of a method with a given name and arity.
197   ///
198   /// Note that there can only exist about 65 thousand unique method signatures. This is usually
199   /// not a problem as method names often repeat. Also note that unlike functions, a method can
200   /// only accept up to 256 arguments. Which, to be quite frankly honest, should be enough for
201   /// anyone.
202   ///
203   /// # Errors
204   ///
205   /// - [`Error::TooManyMethods`] - raised when too many unique method signatures exist at once
206   pub fn method_id(&mut self, signature: impl MethodSignature) -> Result<MethodId, Error> {
207      signature.to_method_id(&mut self.env)
208   }
209
210   /// Calls a method on a receiver with the given arguments.
211   ///
212   /// Note that if you're calling a method often, it's cheaper to precompute the method signature
213   /// into a [`MethodId`] by using the [`method_id`][`Self::method_id`] function, compared to
214   /// passing a name+arity pair every single time.
215   ///
216   /// # Errors
217   ///
218   /// - [`Error::Runtime`] - if a runtime error occurs - `function` isn't callable or an error is
219   ///   raised during execution
220   /// - [`Error::TooManyArguments`] - if more arguments than the implementation can support is
221   ///   passed to the function
222   /// - [`Error::TooManyMethods`] - if too many methods with different signatures exist at the same
223   ///   time
224   /// - [`Error::ArgumentCount`] - if `arguments.count()` does not match the argument count of the
225   ///   signature
226   pub fn call_method<T>(
227      &mut self,
228      receiver: Value,
229      signature: impl MethodSignature,
230      arguments: impl IntoIterator<Item = Value>,
231   ) -> Result<T, Error>
232   where
233      T: TryFromValue,
234   {
235      let method_id = signature.to_method_id(&mut self.env)?;
236      // Unwrapping here is fine because `to_method_id` ensures that a method with a given ID
237      // exists.
238      let signature = self.env.get_method_signature(method_id.0).unwrap();
239      let stack: Vec<_> =
240         Some(receiver).into_iter().chain(arguments).map(|x| x.to_raw(&mut self.gc)).collect();
241      let argument_count = u8::try_from(stack.len()).map_err(|_| Error::TooManyArguments)?;
242      if Some(argument_count as u16) != signature.arity {
243         return Err(Error::ArgumentCount {
244            // Unwrapping here is fine because signatures of methods created by `to_method_id`
245            // always have a static arity.
246            expected: signature.arity.unwrap() as usize - 1,
247            got: argument_count as usize - 1,
248         });
249      }
250      let mut chunk = Chunk::new(Rc::from("(call)"));
251      chunk.emit((
252         Opcode::CallMethod,
253         Opr24::pack((method_id.0, argument_count)),
254      ));
255      chunk.emit(Opcode::Halt);
256      let chunk = Rc::new(chunk);
257      let fiber = Fiber {
258         engine: self,
259         inner: vm::Fiber::new(chunk, stack),
260      };
261      fiber.trampoline()
262   }
263
264   /// Returns the unique global ID for the global with the given name, or an error if there
265   /// are too many globals in scope.
266   ///
267   /// The maximum amount of globals is about 16 million, so you shouldn't worry too much about
268   /// hitting that limit unless you're stress-testing the VM or accepting untrusted input as
269   /// globals.
270   ///
271   /// # Errors
272   ///  - [`Error::TooManyGlobals`] - Too many globals with unique names were created
273   pub fn global_id(&mut self, name: impl GlobalName) -> Result<GlobalId, Error> {
274      name.to_global_id(&mut self.env)
275   }
276
277   /// Sets a global variable that'll be available to scripts executed by the engine.
278   ///
279   /// The `id` parameter can be either an `&str` or a prefetched [`global_id`][`Self::global_id`].
280   ///
281   /// # Errors
282   ///  - [`Error::TooManyGlobals`] - Too many globals with unique names were created
283   pub fn set<G, T>(&mut self, id: G, value: T) -> Result<(), Error>
284   where
285      G: GlobalName,
286      T: Into<Value>,
287   {
288      let id = id.to_global_id(&mut self.env)?;
289      self.globals.set(id.0, value.into().to_raw(&mut self.gc));
290      Ok(())
291   }
292
293   /// Returns the value of a global variable, or `nil` if it's not set.
294   ///
295   /// The `id` parameter can be either an `&str` or a prefetched [`global_id`][`Self::global_id`].
296   ///
297   /// # Errors
298   ///  - [`Error::TooManyGlobals`] - Too many globals with unique names were created
299   ///  - [`Error::TypeMismatch`] - The type of the value is not convertible to `T`
300   pub fn get<G, T>(&self, id: G) -> Result<T, Error>
301   where
302      G: OptionalGlobalName,
303      T: TryFromValue,
304   {
305      if let Some(id) = id.try_to_global_id(&self.env) {
306         T::try_from_value(&Value::from_raw(self.globals.get(id.0)))
307      } else {
308         T::try_from_value(&Value::from_raw(RawValue::from(())))
309      }
310   }
311
312   /// Declares a "raw" function in the global scope. Raw functions do not perform any type checks
313   /// by default and accept a variable number of arguments.
314   ///
315   /// You should generally prefer [`add_function`][`Self::add_function`] instead of this.
316   ///
317   /// Note that this cannot accept [`GlobalId`]s, because a name is required to create the function
318   /// and global IDs have their name erased.
319   ///
320   /// `parameter_count` should reflect the parameter count of the function. Pass `None` if the
321   /// function accepts a variable number of arguments. Note that because this function omits type
322   /// checks you may receive a different amount of arguments than specified.
323   ///
324   /// # Errors
325   ///  - [`Error::TooManyGlobals`] - Too many globals with unique names were created
326   ///  - [`Error::TooManyFunctions`] - Too many functions were registered into the engine
327   pub fn add_raw_function(
328      &mut self,
329      name: &str,
330      parameter_count: impl Into<Option<u16>>,
331      f: FunctionKind,
332   ) -> Result<(), Error> {
333      let global_id = name.to_global_id(&mut self.env)?;
334      let function_id = self
335         .env
336         .create_function(Function {
337            name: Rc::from(name),
338            parameter_count: parameter_count.into(), // doesn't matter for non-methods
339            kind: f,
340            hidden_in_stack_traces: false,
341         })
342         .map_err(|_| Error::TooManyFunctions)?;
343      let function = RawValue::from(self.gc.allocate(Closure {
344         function_id,
345         captures: Vec::new(),
346      }));
347      self.globals.set(global_id.0, function);
348      Ok(())
349   }
350
351   /// Declares a function in the global scope.
352   ///
353   /// # Errors
354   /// See [`add_raw_function`][`Self::add_raw_function`].
355   pub fn add_function<F, V>(&mut self, name: &str, f: F) -> Result<(), Error>
356   where
357      V: ffvariants::Bare,
358      F: ForeignFunction<V>,
359   {
360      self.add_raw_function(
361         name,
362         F::parameter_count(),
363         FunctionKind::Foreign(f.into_raw_foreign_function()),
364      )
365   }
366
367   /// Declares a type in the global scope.
368   ///
369   /// # Errors
370   ///  - [`Error::TooManyGlobals`] - Too many globals with unique names were created
371   ///  - [`Error::TooManyFunctions`] - Too many functions were registered into the engine
372   ///  - [`Error::TooManyMethods`] - Too many unique method signatures were created
373   pub fn add_type<T>(&mut self, builder: TypeBuilder<T>) -> Result<(), Error>
374   where
375      T: Any + UserData,
376   {
377      let built = builder.build(&mut self.env, &mut self.gc, &self.builtin_traits)?;
378      self.set_built_type(&built)?;
379      Ok(())
380   }
381
382   pub(crate) fn set_built_type<T>(&mut self, typ: &BuiltType<T>) -> Result<(), Error>
383   where
384      T: Any,
385   {
386      let value = typ.make_type(&mut self.gc);
387      self.set(typ.type_name.deref(), value)
388   }
389
390   /// Starts building a new trait.
391   pub fn build_trait(&mut self, name: &str) -> Result<TraitBuilder<'_>, Error> {
392      Ok(TraitBuilder {
393         inner: codegen::TraitBuilder::new(&mut self.env, None, Rc::from(name)).mica()?,
394         gc: &mut self.gc,
395      })
396   }
397}
398
399/// A script pre-compiled into bytecode.
400pub struct Script<'e> {
401   engine: &'e mut Engine,
402   main_chunk: Rc<Chunk>,
403}
404
405impl<'e> Script<'e> {
406   /// Starts running a script in a new fiber.
407   pub fn start(&mut self) -> Fiber {
408      Fiber {
409         engine: self.engine,
410         inner: vm::Fiber::new(Rc::clone(&self.main_chunk), Vec::new()),
411      }
412   }
413
414   /// Starts running a script in a new fiber, consuming the script.
415   pub fn into_fiber(self) -> Fiber<'e> {
416      Fiber {
417         engine: self.engine,
418         inner: vm::Fiber::new(Rc::clone(&self.main_chunk), Vec::new()),
419      }
420   }
421}
422
423/// An ID unique to an engine, identifying a global variable.
424///
425/// Note that these IDs are not portable across different engine instances.
426#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
427#[repr(transparent)]
428pub struct GlobalId(Opr24);
429
430mod global_id {
431   use crate::GlobalId;
432
433   pub trait Sealed {}
434   impl Sealed for GlobalId {}
435   impl Sealed for &str {}
436}
437
438/// A trait for names convertible to global IDs.
439pub trait GlobalName: global_id::Sealed {
440   #[doc(hidden)]
441   fn to_global_id(&self, env: &mut Environment) -> Result<GlobalId, Error>;
442}
443
444impl GlobalName for GlobalId {
445   fn to_global_id(&self, _: &mut Environment) -> Result<GlobalId, Error> {
446      Ok(*self)
447   }
448}
449
450impl GlobalName for &str {
451   fn to_global_id(&self, env: &mut Environment) -> Result<GlobalId, Error> {
452      Ok(if let Some(slot) = env.get_global(self) {
453         GlobalId(slot)
454      } else {
455         env.create_global(self).map(GlobalId).map_err(|_| Error::TooManyGlobals)?
456      })
457   }
458}
459
460/// A trait for names convertible to global IDs.
461pub trait OptionalGlobalName {
462   #[doc(hidden)]
463   fn try_to_global_id(&self, env: &Environment) -> Option<GlobalId>;
464}
465
466impl OptionalGlobalName for GlobalId {
467   fn try_to_global_id(&self, _: &Environment) -> Option<GlobalId> {
468      Some(*self)
469   }
470}
471
472impl OptionalGlobalName for &str {
473   fn try_to_global_id(&self, env: &Environment) -> Option<GlobalId> {
474      env.get_global(self).map(GlobalId)
475   }
476}
477
478mod method_id {
479   use crate::MethodId;
480
481   pub trait Sealed {}
482
483   impl Sealed for MethodId {}
484   impl Sealed for (&str, u8) {}
485}
486
487/// An ID unique to an engine, identifying a method signature.
488///
489/// Note that these IDs are not portable across different engine instances.
490#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
491#[repr(transparent)]
492pub struct MethodId(pub(crate) u16);
493
494/// Implemented by every type that can be used as a method signature.
495///
496/// See [`Engine::call_method`].
497pub trait MethodSignature: method_id::Sealed {
498   #[doc(hidden)]
499   fn to_method_id(&self, env: &mut Environment) -> Result<MethodId, Error>;
500}
501
502impl MethodSignature for MethodId {
503   fn to_method_id(&self, _: &mut Environment) -> Result<MethodId, Error> {
504      Ok(*self)
505   }
506}
507
508/// Tuples of string slices and `u8`s are a user-friendly representation of method signatures.
509/// For instance, `("cat", 1)` represents the method `cat/1`.
510impl MethodSignature for (&str, u8) {
511   fn to_method_id(&self, env: &mut Environment) -> Result<MethodId, Error> {
512      env.get_method_index(&FunctionSignature {
513         name: Rc::from(self.0),
514         arity: Some(self.1 as u16 + 1),
515         trait_id: None,
516      })
517      .map(MethodId)
518      .map_err(|_| Error::TooManyMethods)
519   }
520}