#![allow(dead_code)]
#![allow(unused_macros)]
#![allow(clippy::upper_case_acronyms)]
pub mod compiler;
pub mod data;
pub mod runtime;
mod collections;
mod convert;
mod format;
mod func;
mod gc;
mod lang;
mod modres;
mod rng;
mod selector;
mod stdlib;
mod string;
mod util;
mod value;
mod value_eq;
mod var;
#[cfg(test)]
mod gc_tests;
pub use crate::collections::*;
pub use crate::convert::*;
pub use crate::func::*;
pub use crate::modres::*;
pub use crate::selector::*;
pub use crate::string::*;
pub use crate::value::*;
pub use crate::var::*;
use crate::compiler::*;
use crate::lang::Sequence;
use crate::rng::RantyRng;
use crate::runtime::{IntoRuntimeResult, RuntimeError, RuntimeErrorType, RuntimeResult, VM};
use data::DataSource;
use fnv::FnvBuildHasher;
use rand::Rng;
use std::env;
use std::error::Error;
use std::{collections::HashMap, fmt::Display, path::Path, path::PathBuf, rc::Rc};
type IOErrorKind = std::io::ErrorKind;
pub(crate) type InternalString = smartstring::alias::CompactString;
pub const BUILD_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const RANTY_LANG_VERSION: &str = "1.0.0";
pub const DEFAULT_PROGRAM_NAME: &str = "program";
pub const RANTY_FILE_EXTENSION: &str = "ranty";
pub const RANTY_COMPAT_FILE_EXTENSION: &str = "rant";
pub const RANTY_SUPPORTED_FILE_EXTENSIONS: [&str; 2] =
[RANTY_FILE_EXTENSION, RANTY_COMPAT_FILE_EXTENSION];
pub(crate) const MODULES_CACHE_KEY: &str = "__MODULES";
pub fn collect_garbage() {
gc::collect();
}
fn normalize_module_cache_path<P: AsRef<Path>>(path: P) -> String {
path.as_ref()
.canonicalize()
.unwrap_or_else(|_| path.as_ref().to_path_buf())
.to_string_lossy()
.into_owned()
}
pub(crate) fn module_request_cache_key(
module_path: &str,
dependant: Option<&RantyProgramInfo>,
) -> String {
let normalized_module_path = module_path.replace('\\', "/");
let dependant_root = dependant
.and_then(|info| info.path())
.map(PathBuf::from)
.or_else(|| env::current_dir().ok())
.and_then(|path| path.parent().map(PathBuf::from).or(Some(path)))
.map(normalize_module_cache_path);
match dependant_root {
Some(root) => format!("req:{root}::{normalized_module_path}"),
None => format!("req::{normalized_module_path}"),
}
}
pub(crate) fn module_resolved_cache_key(program: &RantyProgram) -> Option<String> {
program
.path()
.map(|path| format!("path:{}", normalize_module_cache_path(path)))
}
#[derive(Debug)]
pub struct Ranty {
options: RantyOptions,
module_resolver: Rc<dyn ModuleResolver>,
rng: Rc<RantyRng>,
data_sources: HashMap<InternalString, Box<dyn DataSource>, FnvBuildHasher>,
globals: HashMap<InternalString, RantyVar, FnvBuildHasher>,
}
impl Ranty {
#[inline(always)]
pub fn new() -> Self {
Self::with_seed(0)
}
pub fn with_seed(seed: u64) -> Self {
Self::with_options(RantyOptions {
seed,
..Default::default()
})
}
pub fn with_random_seed() -> Self {
Self::with_options(RantyOptions {
seed: rand::thread_rng().gen(),
..Default::default()
})
}
#[inline(always)]
pub fn with_options(options: RantyOptions) -> Self {
let mut ranty = Self {
module_resolver: Rc::new(DefaultModuleResolver::default()),
globals: Default::default(),
data_sources: Default::default(),
rng: Rc::new(RantyRng::new(options.seed)),
options,
};
if ranty.options.use_stdlib {
stdlib::load_stdlib(&mut ranty);
}
ranty
}
#[inline]
pub fn using_module_resolver<R: ModuleResolver + 'static>(self, module_resolver: R) -> Self {
Self {
module_resolver: Rc::new(module_resolver),
..self
}
}
}
impl Default for Ranty {
fn default() -> Self {
Self::new()
}
}
impl Ranty {
#[must_use = "compiling a program without storing or running it achieves nothing"]
pub fn compile<R: Reporter>(
&self,
source: &str,
reporter: &mut R,
) -> Result<RantyProgram, CompilerError> {
compiler::compile_string(
source,
reporter,
self.options.debug_mode,
RantyProgramInfo {
name: None,
path: None,
},
)
}
#[must_use = "compiling a program without storing or running it achieves nothing"]
pub fn compile_named<R: Reporter>(
&self,
source: &str,
reporter: &mut R,
name: &str,
) -> Result<RantyProgram, CompilerError> {
compiler::compile_string(
source,
reporter,
self.options.debug_mode,
RantyProgramInfo {
name: Some(name.to_owned()),
path: None,
},
)
}
#[must_use = "compiling a program without storing or running it achieves nothing"]
pub fn compile_quiet(&self, source: &str) -> Result<RantyProgram, CompilerError> {
compiler::compile_string(
source,
&mut (),
self.options.debug_mode,
RantyProgramInfo {
name: None,
path: None,
},
)
}
#[must_use = "compiling a program without storing or running it achieves nothing"]
pub fn compile_quiet_named(
&self,
source: &str,
name: &str,
) -> Result<RantyProgram, CompilerError> {
compiler::compile_string(
source,
&mut (),
self.options.debug_mode,
RantyProgramInfo {
name: Some(name.to_owned()),
path: None,
},
)
}
#[must_use = "compiling a program without storing or running it achieves nothing"]
pub fn compile_file<P: AsRef<Path>, R: Reporter>(
&self,
path: P,
reporter: &mut R,
) -> Result<RantyProgram, CompilerError> {
compiler::compile_file(path, reporter, self.options.debug_mode)
}
#[must_use = "compiling a program without storing or running it achieves nothing"]
pub fn compile_file_quiet<P: AsRef<Path>>(
&self,
path: P,
) -> Result<RantyProgram, CompilerError> {
compiler::compile_file(path, &mut (), self.options.debug_mode)
}
#[inline]
pub fn set_global(&mut self, key: &str, value: RantyValue) -> bool {
if let Some(global_var) = self.globals.get_mut(key) {
global_var.write(value)
} else {
self.globals
.insert(InternalString::from(key), RantyVar::ByVal(value));
true
}
}
#[inline]
pub fn set_global_const(&mut self, key: &str, value: RantyValue) -> bool {
if let Some(global_var) = self.globals.get(key) {
if global_var.is_const() {
return false;
}
}
self.globals
.insert(InternalString::from(key), RantyVar::ByValConst(value));
true
}
#[inline]
pub fn set_global_force(&mut self, key: &str, value: RantyValue, is_const: bool) {
self.globals.insert(
InternalString::from(key),
if is_const {
RantyVar::ByValConst(value)
} else {
RantyVar::ByVal(value)
},
);
}
#[inline]
pub fn get_global(&self, key: &str) -> Option<RantyValue> {
self.globals.get(key).map(|var| var.value_cloned())
}
#[inline]
pub(crate) fn get_global_var(&self, key: &str) -> Option<&RantyVar> {
self.globals.get(key)
}
#[inline]
pub(crate) fn set_global_var(&mut self, key: &str, var: RantyVar) {
self.globals.insert(InternalString::from(key), var);
}
#[inline]
pub(crate) fn get_global_var_mut(&mut self, key: &str) -> Option<&mut RantyVar> {
self.globals.get_mut(key)
}
#[inline]
pub fn has_global(&self, key: &str) -> bool {
self.globals.contains_key(key)
}
#[inline]
pub fn delete_global(&mut self, key: &str) -> bool {
self.globals.remove(key).is_some()
}
#[inline]
pub fn global_names(&self) -> impl Iterator<Item = &str> {
self.globals.keys().map(|k| k.as_str())
}
pub fn options(&self) -> &RantyOptions {
&self.options
}
pub fn options_mut(&mut self) -> &mut RantyOptions {
&mut self.options
}
pub fn seed(&self) -> u64 {
self.rng.seed()
}
pub fn set_seed(&mut self, seed: u64) {
self.rng = Rc::new(RantyRng::new(seed));
}
pub fn reset_seed(&mut self) {
let seed = self.rng.seed();
self.rng = Rc::new(RantyRng::new(seed));
}
pub fn add_data_source(
&mut self,
data_source: impl DataSource + 'static,
) -> Result<(), DataSourceRegisterError> {
let id = data_source.type_id();
if self.has_data_source(id) {
return Err(DataSourceRegisterError::AlreadyExists(id.into()));
}
self.data_sources.insert(id.into(), Box::new(data_source));
Ok(())
}
pub fn remove_data_source(&mut self, name: &str) -> Option<Box<dyn DataSource>> {
self.data_sources.remove(name)
}
pub fn has_data_source(&self, name: &str) -> bool {
self.data_sources.contains_key(name)
}
pub fn clear_data_sources(&mut self) {
self.data_sources.clear();
}
pub fn data_source(&self, name: &str) -> Option<&dyn DataSource> {
self.data_sources.get(name).map(Box::as_ref)
}
pub fn iter_data_sources(
&self,
) -> impl Iterator<Item = (&'_ str, &'_ Box<dyn DataSource + 'static>)> {
self.data_sources.iter().map(|(k, v)| (k.as_str(), v))
}
pub fn run(&mut self, program: &RantyProgram) -> RuntimeResult<RantyValue> {
let _gc_guard = gc::AllocationThresholdGuard::new(self.options.gc_allocation_threshold);
let result = VM::new(self.rng.clone(), self, program).run();
gc::collect();
result
}
pub fn run_with<A>(&mut self, program: &RantyProgram, args: A) -> RuntimeResult<RantyValue>
where
A: Into<Option<HashMap<String, RantyValue>>>,
{
let _gc_guard = gc::AllocationThresholdGuard::new(self.options.gc_allocation_threshold);
let result = VM::new(self.rng.clone(), self, program).run_with(args);
gc::collect();
result
}
#[inline]
pub fn collect_garbage(&self) {
gc::collect();
}
pub fn try_load_global_module(&mut self, module_path: &str) -> Result<(), ModuleLoadError> {
if PathBuf::from(&module_path)
.with_extension("")
.file_name()
.map(|name| name.to_str())
.flatten()
.is_some()
{
let request_key = module_request_cache_key(module_path, None);
if self.get_cached_module(request_key.as_str()).is_some() {
return Ok(());
}
let module_resolver = Rc::clone(&self.module_resolver);
match module_resolver.try_resolve(self, module_path, None) {
Ok(module_program) => {
if let Some(resolved_key) = module_resolved_cache_key(&module_program) {
if let Some(cached_module) = self.get_cached_module(resolved_key.as_str()) {
self.cache_module(request_key.as_str(), cached_module);
return Ok(());
}
}
match self.run(&module_program) {
Ok(module) => {
self.cache_module(request_key.as_str(), module.clone());
if let Some(resolved_key) = module_resolved_cache_key(&module_program) {
self.cache_module(resolved_key.as_str(), module.clone());
}
Ok(module)
}
Err(err) => Err(ModuleLoadError::RuntimeError(Rc::new(err))),
}
}
Err(err) => Err(ModuleLoadError::ResolveError(err)),
}?;
Ok(())
} else {
Err(ModuleLoadError::InvalidPath(format!(
"missing module name from path: '{module_path}'"
)))
}
}
#[inline]
pub(crate) fn get_cached_module(&self, cache_key: &str) -> Option<RantyValue> {
if let Some(RantyValue::Map(module_cache_ref)) = self.get_global(MODULES_CACHE_KEY) {
if let Some(module) = module_cache_ref.borrow().raw_get(cache_key) {
return Some(module.clone());
}
}
None
}
#[inline]
pub(crate) fn cache_module(&mut self, cache_key: &str, module: RantyValue) {
if let Some(RantyValue::Map(module_cache_ref)) = self.get_global(MODULES_CACHE_KEY) {
module_cache_ref.borrow_mut().raw_set(cache_key, module);
} else {
let mut cache = RantyMap::new();
cache.raw_set(cache_key, module);
self.set_global(MODULES_CACHE_KEY, cache.into_ranty());
}
}
}
#[derive(Debug, Clone)]
pub struct RantyOptions {
pub use_stdlib: bool,
pub debug_mode: bool,
pub top_level_defs_are_globals: bool,
pub seed: u64,
pub gc_allocation_threshold: usize,
}
impl Default for RantyOptions {
fn default() -> Self {
Self {
use_stdlib: true,
debug_mode: false,
top_level_defs_are_globals: false,
seed: 0,
gc_allocation_threshold: gc::DEFAULT_ALLOCATION_THRESHOLD,
}
}
}
#[derive(Debug)]
pub struct RantyProgram {
info: Rc<RantyProgramInfo>,
root: Rc<Sequence>,
}
impl RantyProgram {
pub(crate) fn new(root: Rc<Sequence>, info: Rc<RantyProgramInfo>) -> Self {
Self { info, root }
}
#[inline]
pub fn name(&self) -> Option<&str> {
self.info.name.as_deref()
}
#[inline]
pub fn path(&self) -> Option<&str> {
self.info.path.as_deref()
}
#[inline]
pub fn info(&self) -> &RantyProgramInfo {
self.info.as_ref()
}
}
#[derive(Debug)]
pub struct RantyProgramInfo {
path: Option<String>,
name: Option<String>,
}
impl RantyProgramInfo {
#[inline]
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
#[inline]
pub fn path(&self) -> Option<&str> {
self.path.as_deref()
}
}
#[derive(Debug)]
pub enum ModuleLoadError {
InvalidPath(String),
RuntimeError(Rc<RuntimeError>),
ResolveError(ModuleResolveError),
}
impl Display for ModuleLoadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ModuleLoadError::InvalidPath(errmsg) => write!(f, "{}", errmsg),
ModuleLoadError::RuntimeError(err) => {
write!(f, "runtime error while loading module: {}", err)
}
ModuleLoadError::ResolveError(err) => write!(f, "unable to resolve module: {}", err),
}
}
}
impl Error for ModuleLoadError {}
#[derive(Debug)]
pub enum DataSourceRegisterError {
InvalidTypeId(String),
AlreadyExists(String),
}
impl Display for DataSourceRegisterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidTypeId(id) => write!(f, "the type id '{id}' is invalid"),
Self::AlreadyExists(id) => write!(
f,
"the type id '{id}' was already registered on the context"
),
}
}
}