#![doc(html_root_url = "https://docs.rs/gluon/0.18.2")] #![recursion_limit = "128"]
#[cfg(test)]
extern crate env_logger;
pub extern crate either;
#[macro_use]
extern crate log;
#[macro_use]
extern crate quick_error;
#[cfg(feature = "serde_derive_state")]
#[macro_use]
extern crate serde_derive_state;
#[cfg(feature = "serde")]
extern crate serde_state as serde;
#[macro_use]
pub extern crate gluon_base as base;
pub extern crate gluon_check as check;
extern crate gluon_format as format;
pub extern crate gluon_parser as parser;
#[macro_use]
extern crate gluon_codegen;
#[macro_use]
pub extern crate gluon_vm as vm;
pub use salsa;
macro_rules! try_future {
($e:expr) => {
try_future!($e, Box::pin)
};
($e:expr, $f:expr) => {
match $e {
Ok(x) => x,
Err(err) => return $f(::futures::future::err(err.into())),
}
};
}
pub mod compiler_pipeline;
#[macro_use]
pub mod import;
pub mod lift_io;
#[doc(hidden)]
pub mod query;
pub mod std_lib;
pub use crate::vm::{
field_decl, primitive, record, record_p, record_type,
thread::{RootedThread, Thread},
};
use either::Either;
use std as real_std;
use std::{
env, error::Error as StdError, fmt, path::PathBuf, result::Result as StdResult, sync::Arc,
};
use crate::base::{
ast::{self, OwnedExpr, SpannedExpr},
error::{Errors, InFile},
filename_to_module,
metadata::Metadata,
pos::{BytePos, Span, Spanned},
source::FileId,
symbol::{Symbol, Symbols},
types::{ArcType, TypeCache},
};
use crate::format::Formatter;
use crate::vm::{
api::{Getable, Hole, OpaqueValue, VmType},
compiler::CompiledModule,
macros,
};
use crate::{
compiler_pipeline::*,
import::{add_extern_module, add_extern_module_with_deps, DefaultImporter, Import},
query::{AsyncCompilation, Compilation, CompilationBase, CompilerDatabase},
};
quick_error! {
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
#[allow(deprecated)]
pub enum Error {
Parse(err: InFile<parser::Error>) {
display("{}", err)
from()
}
Typecheck(err: InFile<check::typecheck::HelpError<Symbol>>) {
display("{}", err)
from()
}
IO(err: IoError) {
display("{}", err)
from()
}
VM(err: crate::vm::Error) {
display("{}", err)
from()
}
Macro(err: InFile<macros::Error>) {
display("{}", err)
from()
}
Other(err: macros::Error) {
display("{}", err)
from()
}
Multiple(err: Errors<Error>) {
display("{}", err)
}
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::IO(err.into())
}
}
impl Error {
pub fn merge(self, other: Self) -> Self {
match (self, other) {
(Error::Multiple(mut errors), Error::Multiple(err)) => {
errors.extend(err);
Error::Multiple(errors)
}
(Error::Multiple(mut errors), err) | (err, Error::Multiple(mut errors)) => {
errors.push(err);
Error::Multiple(errors)
}
(l, r) => Error::Multiple(vec![l, r].into_iter().collect()),
}
}
}
#[derive(Debug, Clone)]
pub struct IoError(Arc<std::io::Error>);
impl fmt::Display for IoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl StdError for IoError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.0.source()
}
}
impl<E> From<E> for IoError
where
std::io::Error: From<E>,
{
fn from(err: E) -> Self {
IoError(Arc::new(err.into()))
}
}
impl Eq for IoError {}
impl PartialEq for IoError {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(&*self.0, &*other.0)
}
}
impl std::hash::Hash for IoError {
fn hash<H>(&self, state: &mut H)
where
H: std::hash::Hasher,
{
(&*self.0 as *const std::io::Error).hash(state)
}
}
impl base::error::AsDiagnostic for Error {
fn as_diagnostic(
&self,
_map: &base::source::CodeMap,
) -> codespan_reporting::diagnostic::Diagnostic<FileId> {
codespan_reporting::diagnostic::Diagnostic::error().with_message(self.to_string())
}
}
impl From<String> for Error {
fn from(s: String) -> Self {
Error::VM(s.into())
}
}
impl From<Errors<Spanned<macros::Error, BytePos>>> for Error {
fn from(mut errors: Errors<Spanned<macros::Error, BytePos>>) -> Error {
if errors.len() == 1 {
let err = errors.pop().unwrap();
match err.value.downcast::<Error>() {
Ok(err) => *err,
Err(err) => Error::Other(err),
}
} else {
Error::Multiple(
errors
.into_iter()
.map(|err| match err.value.downcast::<Error>() {
Ok(err) => *err,
Err(err) => Error::Other(err),
})
.collect(),
)
}
}
}
impl From<Errors<Error>> for Error {
fn from(mut errors: Errors<Error>) -> Error {
if errors.len() == 1 {
errors.pop().unwrap()
} else {
errors = errors
.into_iter()
.flat_map(|err| match err {
Error::Multiple(errors) => Either::Left(errors.into_iter()),
err => Either::Right(Some(err).into_iter()),
})
.collect();
Error::Multiple(errors)
}
}
}
impl Error {
pub fn emit_string(&self) -> base::source::Result<String> {
let mut output = Vec::new();
self.emit(&mut codespan_reporting::term::termcolor::NoColor::new(
&mut output,
))?;
Ok(String::from_utf8(output).unwrap())
}
pub fn emit(
&self,
writer: &mut dyn codespan_reporting::term::termcolor::WriteColor,
) -> base::source::Result<()> {
match self {
Error::Parse(err) => err.emit(writer)?,
Error::Typecheck(err) => err.emit(writer)?,
Error::IO(err) => write!(writer, "{}", err)?,
Error::VM(err) => write!(writer, "{}", err)?,
Error::Macro(err) => err.emit(writer)?,
Error::Other(err) => write!(writer, "{}", err)?,
Error::Multiple(errors) => {
for err in errors {
err.emit(writer)?;
}
}
}
Ok(())
}
}
pub type Result<T> = StdResult<T, Error>;
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Settings {
pub implicit_prelude: bool,
pub emit_debug_info: bool,
pub full_metadata: bool,
pub use_standard_lib: bool,
pub optimize: bool,
pub run_io: bool,
}
impl Default for Settings {
fn default() -> Self {
Self {
implicit_prelude: true,
emit_debug_info: true,
full_metadata: false,
use_standard_lib: true,
optimize: true,
run_io: false,
}
}
}
#[doc(hidden)]
pub trait IntoDb<'a, 'b> {
fn into_db(self) -> salsa::OwnedDb<'a, dyn Compilation + 'b>;
}
impl<'a, 'b> IntoDb<'a, 'b> for &'a mut salsa::OwnedDb<'_, dyn Compilation + 'b> {
fn into_db(self) -> salsa::OwnedDb<'a, dyn Compilation + 'b> {
self.into()
}
}
impl<'a, 'b> IntoDb<'a, 'b> for salsa::OwnedDb<'a, dyn Compilation + 'b> {
fn into_db(self) -> salsa::OwnedDb<'a, dyn Compilation + 'b> {
self
}
}
impl<'a, 'b> IntoDb<'a, 'b> for &'a mut salsa::Snapshot<CompilerDatabase> {
fn into_db(self) -> salsa::OwnedDb<'a, dyn Compilation + 'b> {
salsa::cast_owned_db!(salsa::OwnedDb::<CompilerDatabase>::from(self) => &mut dyn Compilation)
}
}
pub struct ModuleCompiler<'a, 'b> {
pub database: salsa::OwnedDb<'a, dyn Compilation + 'b>,
symbols: Symbols,
}
impl<'a, 'b> ModuleCompiler<'a, 'b> {
fn new(database: impl IntoDb<'a, 'b>) -> Self {
Self {
database: database.into_db(),
symbols: Symbols::default(),
}
}
}
impl<'a, 'b> std::ops::Deref for ModuleCompiler<'a, 'b> {
type Target = salsa::OwnedDb<'a, dyn Compilation + 'b>;
fn deref(&self) -> &Self::Target {
&self.database
}
}
impl std::ops::DerefMut for ModuleCompiler<'_, '_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.database
}
}
macro_rules! option {
($(#[$attr:meta])* $name: ident $set_name: ident : $typ: ty) => {
$(#[$attr])*
pub fn $name(mut self, $name: $typ) -> Self {
self.$name = $name;
self
}
pub fn $set_name(&mut self, $name: $typ) {
self.$name = $name;
}
};
}
macro_rules! runtime_option {
($(#[$attr:meta])* $name: ident $set_name: ident : $typ: ty) => {
$(#[$attr])*
pub fn $name(mut self, $name: $typ) -> Self {
self.$set_name($name);
self
}
pub fn $set_name(&mut self, $name: $typ) {
let mut settings = self.compiler_settings();
settings.$name = $name;
self.set_compiler_settings(settings);
}
};
}
impl import::DatabaseMut {
runtime_option! {
implicit_prelude set_implicit_prelude: bool
}
runtime_option! {
emit_debug_info set_emit_debug_info: bool
}
runtime_option! {
full_metadata set_full_metadata: bool
}
runtime_option! {
use_standard_lib set_use_standard_lib: bool
}
runtime_option! {
optimize set_optimize: bool
}
runtime_option! {
run_io set_run_io: bool
}
}
#[async_trait::async_trait]
pub trait ThreadExt: Send + Sync {
fn get_database(&self) -> salsa::Snapshot<CompilerDatabase>;
fn get_database_mut(&self) -> import::DatabaseMut;
fn run_io(&self, run: bool) {
self.get_database_mut().run_io(run);
}
#[doc(hidden)]
fn thread(&self) -> &Thread;
fn module_compiler<'a, 'b>(&'a self, database: impl IntoDb<'a, 'b>) -> ModuleCompiler<'a, 'b> {
ModuleCompiler::new(database)
}
fn parse_expr(
&self,
type_cache: &TypeCache<Symbol, ArcType>,
file: &str,
expr_str: &str,
) -> StdResult<OwnedExpr<Symbol>, InFile<parser::Error>> {
Ok(self.parse_partial_expr(type_cache, file, expr_str)?)
}
fn parse_partial_expr(
&self,
type_cache: &TypeCache<Symbol, ArcType>,
file: &str,
expr_str: &str,
) -> SalvageResult<OwnedExpr<Symbol>, InFile<parser::Error>> {
let vm = self.thread();
parse_expr(
&mut ModuleCompiler::new(&mut vm.get_database()),
type_cache,
file,
expr_str,
)
}
async fn typecheck_expr(
&self,
file: &str,
expr_str: &str,
expr: &mut OwnedExpr<Symbol>,
) -> Result<ArcType> {
let vm = self.thread();
Ok(expr
.typecheck_expected(
&mut ModuleCompiler::new(&mut vm.get_database()),
vm,
file,
expr_str,
None,
)
.await
.map(|result| result.typ)?)
}
fn typecheck_str(
&self,
file: &str,
expr_str: &str,
expected_type: Option<&ArcType>,
) -> Result<(Arc<OwnedExpr<Symbol>>, ArcType)> {
futures::executor::block_on(self.typecheck_str_async(file, expr_str, expected_type))
}
async fn typecheck_str_async(
&self,
file: &str,
expr_str: &str,
expected_type: Option<&ArcType>,
) -> Result<(Arc<OwnedExpr<Symbol>>, ArcType)> {
let vm = self.thread();
{
let mut db = vm.get_database_mut();
db.add_module(file.into(), expr_str.into());
}
let mut db = vm.get_database();
let TypecheckValue { expr, typ, .. } = db
.typechecked_source_module(file.into(), expected_type.cloned())
.await?;
db.module_type(file.into(), None).await?;
db.module_metadata(file.into(), None).await?;
Ok((expr, typ))
}
async fn compile_script(
&self,
filename: &str,
expr_str: &str,
expr: &OwnedExpr<Symbol>,
) -> Result<CompiledModule> {
let vm = self.thread();
TypecheckValue {
expr,
typ: vm.global_env().type_cache().hole(),
metadata: Default::default(),
metadata_map: Default::default(),
}
.compile(
&mut ModuleCompiler::new(&mut vm.get_database()),
vm,
filename,
expr_str,
(),
)
.await
.map(|result| result.module)
}
#[cfg(feature = "serialization")]
async fn compile_to_bytecode<S>(
&self,
name: &str,
expr_str: &str,
serializer: S,
) -> StdResult<S::Ok, Either<Error, S::Error>>
where
S: serde::Serializer + Send,
S::Error: 'static,
{
let thread = self.thread();
compile_to(
expr_str,
&mut ModuleCompiler::new(&mut thread.get_database()),
&thread,
name,
expr_str,
None,
serializer,
)
.await
}
#[cfg(feature = "serialization")]
async fn load_bytecode<'vm, D, E>(&self, name: &str, deserializer: D) -> Result<()>
where
D: for<'de> serde::Deserializer<'de, Error = E> + Send,
E: Send + Sync,
{
let thread = self.thread();
Precompiled(deserializer)
.load_script(
&mut ModuleCompiler::new(&mut thread.get_database()),
thread,
name,
"",
(),
)
.await
}
async fn extract_metadata(
&self,
file: &str,
expr_str: &str,
) -> Result<(Arc<OwnedExpr<Symbol>>, ArcType, Arc<Metadata>)> {
use crate::check::metadata;
let module_name = filename_to_module(file);
let vm = self.thread();
{
let mut db = vm.get_database_mut();
db.add_module(module_name.clone(), expr_str.into());
}
let mut db = vm.get_database();
let TypecheckValue {
expr,
typ,
metadata,
..
} = db
.typechecked_source_module(module_name.clone(), None)
.await?;
db.module_type(module_name.clone(), None).await?;
db.module_metadata(module_name.clone(), None).await?;
if db.compiler_settings().full_metadata {
Ok((expr, typ, metadata))
} else {
let (metadata, _) = metadata::metadata(&vm.get_env(), &expr.expr());
Ok((expr, typ, metadata))
}
}
fn load_script(&self, filename: &str, input: &str) -> Result<()> {
futures::executor::block_on(self.load_script_async(filename, input))
}
async fn load_script_async(&self, filename: &str, input: &str) -> Result<()> {
let module_name = filename_to_module(filename);
let vm = self.thread();
{
let mut db = vm.get_database_mut();
db.add_module(module_name.clone(), input.into());
}
let mut db = vm.get_database();
db.import(module_name)
.await
.map(|_| ())
.map_err(|err| err.error)
}
fn load_file<'vm>(&'vm self, filename: &str) -> Result<()> {
futures::executor::block_on(self.load_file_async(filename))
}
async fn load_file_async<'vm>(&self, filename: &str) -> Result<()> {
let vm = self.thread();
let import = get_import(vm);
let module_name = Symbol::from(format!("@{}", filename_to_module(filename)));
import
.load_module(
&mut ModuleCompiler::new(&mut import.snapshot(vm.root_thread())),
vm,
&module_name,
)
.await?;
Ok(())
}
fn run_expr<'vm, T>(&'vm self, name: &str, expr_str: &str) -> Result<(T, ArcType)>
where
T: for<'value> Getable<'vm, 'value> + VmType + Send + 'vm,
{
futures::executor::block_on(self.run_expr_async(name, expr_str))
}
async fn run_expr_async<'vm, T>(&'vm self, name: &str, expr_str: &str) -> Result<(T, ArcType)>
where
T: for<'value> Getable<'vm, 'value> + VmType + Send + 'vm,
{
let vm = self.thread();
let expected = T::make_type(&vm);
let execute_value = expr_str
.run_expr(
&mut ModuleCompiler::new(&mut vm.get_database()),
vm,
name,
expr_str,
Some(&expected),
)
.await?;
Ok((
T::from_value(vm, execute_value.value.get_variant()),
execute_value.typ,
))
}
fn format_expr(&self, formatter: &mut Formatter, file: &str, input: &str) -> Result<String> {
futures::executor::block_on(self.format_expr_async(formatter, file, input))
}
async fn format_expr_async(
&self,
formatter: &mut Formatter,
file: &str,
input: &str,
) -> Result<String> {
fn has_format_disabling_errors(file: &str, err: &Error) -> bool {
match *err {
Error::Multiple(ref errors) => errors
.iter()
.any(|err| has_format_disabling_errors(file, err)),
Error::Parse(ref err) => err.source_name() == file,
_ => false,
}
}
let thread = self.thread();
let mut db = thread.get_database();
let mut compiler = ModuleCompiler::new(&mut db);
let compiler = &mut compiler;
let expr = match input.reparse_infix(compiler, thread, file, input).await {
Ok(expr) => expr.expr,
Err(Salvage {
value: Some(expr),
error,
}) => {
if has_format_disabling_errors(file, &error) {
return Err(error);
}
expr.expr
}
Err(Salvage { value: None, error }) => return Err(error),
};
let file_map = db.get_filemap(file).unwrap();
let expr = skip_implicit_prelude(file_map.span(), &expr.expr());
Ok(formatter.pretty_expr(&*file_map, expr))
}
}
fn skip_implicit_prelude<'a, 'ast>(
span: Span<BytePos>,
mut l: &'a SpannedExpr<'ast, Symbol>,
) -> &'a SpannedExpr<'ast, Symbol> {
loop {
match l.value {
ast::Expr::LetBindings(_, ref e) if !span.contains(l.span) => l = e,
_ => break l,
}
}
}
impl ThreadExt for Thread {
fn get_database(&self) -> salsa::Snapshot<CompilerDatabase> {
self.global_env()
.get_capability(self)
.expect("Database is missing")
}
fn get_database_mut(&self) -> import::DatabaseMut {
self.global_env()
.get_capability(self)
.expect("Database is missing")
}
fn thread(&self) -> &Thread {
self
}
}
fn get_import(vm: &Thread) -> Arc<dyn import::ImportApi> {
vm.get_macros()
.get_capability::<Arc<dyn import::ImportApi>>(vm)
.unwrap_or_else(|| panic!("Missing import macro"))
}
impl ModuleCompiler<'_, '_> {
pub fn mut_symbols(&mut self) -> &mut Symbols {
&mut self.symbols
}
fn include_implicit_prelude<'ast>(
&mut self,
arena: ast::ArenaRef<'_, 'ast, Symbol>,
type_cache: &TypeCache<Symbol, ArcType>,
name: &str,
expr: &mut SpannedExpr<'ast, Symbol>,
) {
use std::mem;
if name == "std.prelude" {
return;
}
let prelude_expr = parse_expr_inner(arena, self, type_cache, "", PRELUDE).unwrap();
let original_expr = mem::replace(expr, prelude_expr);
fn assign_last_body<'ast>(
mut l: &mut SpannedExpr<'ast, Symbol>,
original_expr: SpannedExpr<'ast, Symbol>,
) {
while let ast::Expr::LetBindings(_, ref mut e) = l.value {
l = e;
}
*l = original_expr;
}
assign_last_body(expr, original_expr);
}
}
pub const PRELUDE: &'static str = r#"
let __implicit_prelude = import! std.prelude
let { IO, Num, Eq, Ord, Show, Functor, Applicative, Monad, Option, Bool, ? } = __implicit_prelude
let { (+), (-), (*), (/), negate, (==), (/=), (<), (<=), (>=), (>), (++), show, not, flat_map, (<|) } = __implicit_prelude
let { ? } = import! std.bool
let { ? } = import! std.option
let { ? } = import! std.float
let { ? } = import! std.int
let { ? } = import! std.string
let { ? } = import! std.array
let { error } = import! std.prim
let __error = error
let __string_eq: String -> String -> Bool = (==)
in ()
"#;
#[derive(Default)]
pub struct VmBuilder {
import_paths: Option<Vec<PathBuf>>,
}
impl VmBuilder {
pub fn new() -> VmBuilder {
VmBuilder::default()
}
option! {
import_paths set_import_paths: Option<Vec<PathBuf>>
}
pub fn build(self) -> RootedThread {
futures::executor::block_on(self.build_inner(None))
}
pub async fn build_async(self) -> RootedThread {
#[allow(unused_mut, unused_assignments)]
let mut spawner = None;
#[cfg(feature = "tokio")]
{
struct TokioSpawn;
impl futures::task::Spawn for TokioSpawn {
fn spawn_obj(
&self,
future: futures::task::FutureObj<'static, ()>,
) -> StdResult<(), futures::task::SpawnError> {
tokio::spawn(future);
Ok(())
}
}
spawner = Some(Box::new(TokioSpawn) as Box<dyn futures::task::Spawn + Send + Sync>);
}
self.build_inner(spawner).await
}
async fn build_inner(
self,
spawner: Option<Box<dyn futures::task::Spawn + Send + Sync>>,
) -> RootedThread {
let vm = RootedThread::with_global_state(
crate::vm::vm::GlobalVmStateBuilder::new()
.spawner(spawner)
.build(),
);
{
let macros = vm.get_macros();
{
let import = Import::new(DefaultImporter);
if let Some(import_paths) = self.import_paths {
import.set_paths(import_paths);
}
if let Ok(gluon_path) = env::var("GLUON_PATH") {
import.add_path(gluon_path);
}
macros.insert(String::from("import"), import);
}
macros.insert(String::from("lift_io"), lift_io::LiftIo);
}
add_extern_module_with_deps(
&vm,
"std.prim",
crate::vm::primitives::load,
vec!["std.types".into()],
);
vm.run_expr_async::<OpaqueValue<RootedThread, Hole>>(
"",
r#"//@NO-IMPLICIT-PRELUDE
let _ = import! std.types
let _ = import! std.prim
()
"#,
)
.await
.unwrap_or_else(|err| panic!("{}", err));
let deps: &[(_, fn(&Thread) -> _)] = &[
("std.byte.prim", crate::vm::primitives::load_byte),
("std.int.prim", crate::vm::primitives::load_int),
("std.float.prim", crate::vm::primitives::load_float),
("std.string.prim", crate::vm::primitives::load_string),
("std.fs.prim", crate::vm::primitives::load_fs),
("std.char.prim", crate::vm::primitives::load_char),
("std.char.prim", crate::vm::primitives::load_char),
("std.thread.prim", crate::vm::channel::load_thread),
("std.io.prim", crate::std_lib::io::load),
];
for (name, load_fn) in deps {
add_extern_module_with_deps(&vm, name, load_fn, vec!["std.types".into()]);
}
add_extern_module_with_deps(
&vm,
"std.path.prim",
crate::vm::primitives::load_path,
vec!["std.path.types".into()],
);
add_extern_module_with_deps(
&vm,
"std.st.reference.prim",
crate::vm::reference::st::load,
vec!["std.reference".into()],
);
let deps: &[(_, fn(&Thread) -> _)] = &[
("std.array.prim", crate::vm::primitives::load_array),
("std.lazy.prim", crate::vm::lazy::load),
("std.reference.prim", crate::vm::reference::load),
("std.channel.prim", crate::vm::channel::load_channel),
("std.debug.prim", crate::vm::debug::load),
("std.process.prim", crate::std_lib::process::load),
("std.env.prim", crate::std_lib::env::load),
];
for (name, load_fn) in deps {
add_extern_module(&vm, name, load_fn);
}
add_extern_module(
&vm,
"std.effect.st.string.prim",
crate::vm::primitives::load_string_buf,
);
add_extern_module_if!(
#[cfg(feature = "serialization")],
available_if = "gluon is compiled with the 'serialization' feature",
dependencies = ["std.json"],
args(&vm, "std.json.prim", crate::vm::api::json::load)
);
add_extern_module_if!(
#[cfg(feature = "regex")],
available_if = "gluon is compiled with the 'regex' feature",
dependencies = ["std.regex.types"],
args(&vm, "std.regex.prim", crate::std_lib::regex::load)
);
add_extern_module_if!(
#[cfg(feature = "web")],
available_if = "gluon is compiled with the 'web' feature",
args(&vm, "std.http.prim_types", crate::std_lib::http::load_types)
);
add_extern_module_if!(
#[cfg(feature = "web")],
available_if = "gluon is compiled with the 'web' feature",
dependencies = ["std.http.types"],
args(&vm, "std.http.prim", crate::std_lib::http::load)
);
add_extern_module_if!(
#[cfg(all(feature = "random", not(target_arch = "wasm32")))],
available_if = "gluon is compiled with the 'random' feature and is not targeting WASM",
args(&vm, "std.random.prim", crate::std_lib::random::load)
);
vm
}
}
pub fn new_vm() -> RootedThread {
VmBuilder::default().build()
}
pub async fn new_vm_async() -> RootedThread {
VmBuilder::default().build_async().await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn implicit_prelude() {
let _ = ::env_logger::try_init();
let thread = new_vm();
thread.get_database_mut().set_implicit_prelude(false);
thread
.run_expr::<()>("prelude", PRELUDE)
.unwrap_or_else(|err| panic!("{}", err));
}
}