rant/
lib.rs

1//! # Rant
2//!
3//! Rant is a high-level procedural templating language.
4//! It is designed to help you write more dynamic and expressive templates, dialogue, stories, names, test data, and much more.
5//!
6//! For language documentation, see the [Rant Reference](https://docs.rant-lang.org).
7//! 
8//! ## The Rant context
9//!
10//! All programs are run through a Rant context, represented by the [`Rant`] struct.
11//! It allows you to execute Rant programs, define and retrieve global variables, manipulate the RNG, and compile Rant code.
12//! 
13//! ## Reading compiler errors
14//! 
15//! You will notice that the `Err` variant of the `Rant::compile*` methods is `()` instead of providing an error list. Instead, 
16//! errors and warnings are reported via implementors of the [`Reporter`] trait, which allows the user to control what happens to messages emitted by the compiler.
17//! Currently, Rant has two built-in `Reporter` implementations: the unit type `()`, and `Vec<CompilerMessage>`.
18//! You can also make your own custom reporters to suit your specific needs.
19//!
20//! [`Rant`]: struct.Rant.html
21//! [`Reporter`]: compiler/trait.Reporter.html
22//! [`Vec<CompilerMessage>`]: compiler/struct.CompilerMessage.html
23
24
25// Some branches are incorrectly detected as dead
26#![allow(dead_code)]
27
28// Some macro usages aren't detected, causing false warnings
29#![allow(unused_macros)]
30
31// Disable clippy's silly whining about "VM", "IO", etc. in type names
32#![allow(clippy::upper_case_acronyms)]
33
34// Public modules
35pub mod data;
36pub mod compiler;
37pub mod runtime;
38
39// Internal modules
40mod collections;
41mod convert;
42mod format;
43mod func;
44mod lang;
45mod modres;
46mod rng;
47mod selector;
48mod stdlib;
49mod string;
50mod util;
51mod value;
52mod var;
53
54// Re-exports
55pub use crate::collections::*;
56pub use crate::convert::*;
57pub use crate::string::*;
58pub use crate::value::*;
59pub use crate::func::*;
60pub use crate::var::*;
61pub use crate::modres::*;
62pub use crate::selector::*;
63
64use crate::compiler::*;
65use crate::lang::Sequence;
66use crate::rng::RantRng;
67use crate::runtime::{RuntimeResult, IntoRuntimeResult, RuntimeError, RuntimeErrorType, VM};
68
69use std::error::Error;
70use std::{path::Path, rc::Rc, fmt::Display, path::PathBuf, collections::HashMap};
71use std::env;
72use data::DataSource;
73use fnv::FnvBuildHasher;
74use rand::Rng;
75
76type IOErrorKind = std::io::ErrorKind;
77
78pub(crate) type InternalString = smartstring::alias::CompactString;
79
80/// The build version according to the crate metadata at the time of compiling.
81pub const BUILD_VERSION: &str = env!("CARGO_PKG_VERSION");
82
83/// The Rant language version implemented by this library.
84pub const RANT_LANG_VERSION: &str = "4.0";
85
86/// The default name given to programs compiled from raw strings.
87pub const DEFAULT_PROGRAM_NAME: &str = "program";
88
89/// The file extension that Rant expects modules to have.
90pub const RANT_FILE_EXTENSION: &str = "rant";
91
92/// Name of global variable that stores cached modules.
93pub(crate) const MODULES_CACHE_KEY: &str = "__MODULES";
94
95/// A Rant execution context.
96#[derive(Debug)]
97pub struct Rant {
98  options: RantOptions,
99  module_resolver: Rc<dyn ModuleResolver>,
100  rng: Rc<RantRng>,
101  data_sources: HashMap<InternalString, Box<dyn DataSource>, FnvBuildHasher>,
102  globals: HashMap<InternalString, RantVar, FnvBuildHasher>,
103}
104
105impl Rant {
106  /// Creates a new Rant context with the default seed (0) and loads the standard library.
107  #[inline(always)]
108  pub fn new() -> Self {
109    Self::with_seed(0)
110  }
111  
112  /// Creates a new Rant context with the specified seed and loads the standard library.
113  pub fn with_seed(seed: u64) -> Self {
114    Self::with_options(RantOptions {
115      seed,
116      .. Default::default()
117    })
118  }
119
120  /// Creates a new Rant context with a seed generated by a thread-local PRNG and loads the standard library.
121  pub fn with_random_seed() -> Self {
122    Self::with_options(RantOptions {
123      seed: rand::thread_rng().gen(),
124      .. Default::default()
125    })
126  }
127
128  /// Creates a new Rant context with the specified options.
129  #[inline(always)]
130  pub fn with_options(options: RantOptions) -> Self {
131    let mut rant = Self {
132      module_resolver: Rc::new(DefaultModuleResolver::default()),
133      globals: Default::default(),
134      data_sources: Default::default(),
135      rng: Rc::new(RantRng::new(options.seed)),
136      options,
137    };
138
139    // Load standard library
140    if rant.options.use_stdlib {
141      stdlib::load_stdlib(&mut rant);
142    }
143
144    rant
145  }
146
147  /// Replaces the module resolver.
148  #[inline]
149  pub fn using_module_resolver<R: ModuleResolver + 'static>(self, module_resolver: R) -> Self {
150    Self {
151      module_resolver: Rc::new(module_resolver),
152      .. self
153    }
154  }
155}
156
157impl Default for Rant {
158  /// Creates a default `Rant` instance.
159  fn default() -> Self {
160    Self::new()
161  }
162}
163
164impl Rant {
165  /// Compiles a source string using the specified reporter.
166  #[must_use = "compiling a program without storing or running it achieves nothing"]
167  pub fn compile<R: Reporter>(&self, source: &str, reporter: &mut R) -> Result<RantProgram, CompilerError> {
168    compiler::compile_string(source, reporter, self.options.debug_mode, RantProgramInfo {
169      name: None,
170      path: None,
171    })
172  }
173
174  /// Compiles a source string using the specified reporter and source name.
175  #[must_use = "compiling a program without storing or running it achieves nothing"]
176  pub fn compile_named<R: Reporter>(&self, source: &str, reporter: &mut R, name: &str) -> Result<RantProgram, CompilerError> {
177    compiler::compile_string(source, reporter, self.options.debug_mode, RantProgramInfo {
178      name: Some(name.to_owned()),
179      path: None,
180    })
181  }
182
183  /// Compiles a source string without reporting problems.
184  ///
185  /// ## Note
186  ///
187  /// This method will not generate any compiler messages, even if it fails.
188  ///
189  /// If you require this information, use the `compile()` method instead.
190  #[must_use = "compiling a program without storing or running it achieves nothing"]
191  pub fn compile_quiet(&self, source: &str) -> Result<RantProgram, CompilerError> {
192    compiler::compile_string(source, &mut (), self.options.debug_mode, RantProgramInfo {
193      name: None,
194      path: None,
195    })
196  }
197
198  /// Compiles a source string without reporting problems and assigns it the specified name.
199  ///
200  /// ## Note
201  ///
202  /// This method will not generate any compiler messages, even if it fails.
203  ///
204  /// If you require this information, use the `compile()` method instead.
205  #[must_use = "compiling a program without storing or running it achieves nothing"]
206  pub fn compile_quiet_named(&self, source: &str, name: &str) -> Result<RantProgram, CompilerError> {
207    compiler::compile_string(source, &mut (), self.options.debug_mode, RantProgramInfo {
208      name: Some(name.to_owned()),
209      path: None,
210    })
211  }
212  
213  /// Compiles a source file using the specified reporter.
214  #[must_use = "compiling a program without storing or running it achieves nothing"]
215  pub fn compile_file<P: AsRef<Path>, R: Reporter>(&self, path: P, reporter: &mut R) -> Result<RantProgram, CompilerError> {
216    compiler::compile_file(path, reporter, self.options.debug_mode)
217  }
218
219  /// Compiles a source file without reporting problems.
220  ///
221  /// ## Note
222  ///
223  /// This method will not generate any compiler messages, even if it fails.
224  ///
225  /// If you require this information, use the `compile_file()` method instead.
226  #[must_use = "compiling a program without storing or running it achieves nothing"]
227  pub fn compile_file_quiet<P: AsRef<Path>>(&self, path: P) -> Result<RantProgram, CompilerError> {
228    compiler::compile_file(path, &mut (), self.options.debug_mode)
229  }
230
231  /// Sets a global variable. This will auto-define the global if it doesn't exist. 
232  ///
233  /// If the global already exists and is a constant, the write will not succeed.
234  ///
235  /// Returns `true` if the write succeeded; otherwise, `false`.
236  #[inline]
237  pub fn set_global(&mut self, key: &str, value: RantValue) -> bool {
238    if let Some(global_var) = self.globals.get_mut(key) {
239      global_var.write(value)
240    } else {
241      self.globals.insert(InternalString::from(key), RantVar::ByVal(value));
242      true
243    }
244  }
245
246  /// Sets a global constant. This will auto-define the global if it doesn't exist.
247  ///
248  /// If the global already exists and is a constant, the write will not succeed.
249  ///
250  /// Returns `true` if the write succeeded; otherwise, `false`.
251  #[inline]
252  pub fn set_global_const(&mut self, key: &str, value: RantValue) -> bool {
253    if let Some(global_var) = self.globals.get(key) {
254      if global_var.is_const() {
255        return false
256      }
257    }
258    self.globals.insert(InternalString::from(key), RantVar::ByValConst(value));
259    true
260  }
261
262  /// Sets a global's value, forcing the write even if the existing global is a constant.
263  /// This will auto-define the global if it doesn't exist.
264  #[inline]
265  pub fn set_global_force(&mut self, key: &str, value: RantValue, is_const: bool) {
266    self.globals.insert(InternalString::from(key), if is_const { RantVar::ByValConst(value) } else { RantVar::ByVal(value) });
267  }
268
269  /// Gets the value of a global variable.
270  #[inline]
271  pub fn get_global(&self, key: &str) -> Option<RantValue> {
272    self.globals.get(key).map(|var| var.value_cloned())
273  }
274
275  /// Gets a global variable by its `RantVar` representation.
276  #[inline]
277  pub(crate) fn get_global_var(&self, key: &str) -> Option<&RantVar> {
278    self.globals.get(key)
279  }
280
281  /// Sets a global variable to the provided `RantVar`.
282  #[inline]
283  pub(crate) fn set_global_var(&mut self, key: &str, var: RantVar) {
284    self.globals.insert(InternalString::from(key), var);
285  }
286
287  /// Gets a mutable reference to the `RantVar` representation of the specified variable.
288  #[inline]
289  pub(crate) fn get_global_var_mut(&mut self, key: &str) -> Option<&mut RantVar> {
290    self.globals.get_mut(key)
291  }
292
293  /// Returns `true` if a global with the specified key exists.
294  #[inline]
295  pub fn has_global(&self, key: &str) -> bool {
296    self.globals.contains_key(key)
297  }
298
299  /// Removes the global with the specified key. Returns `true` if the global existed prior to removal.
300  #[inline]
301  pub fn delete_global(&mut self, key: &str) -> bool {
302    self.globals.remove(key).is_some()
303  }
304
305  /// Iterates over the names of all globals stored in the context.
306  #[inline]
307  pub fn global_names(&self) -> impl Iterator<Item = &str> {
308    self.globals.keys().map(|k| k.as_str())
309  }
310
311  /// Gets the options used to initialize the context.
312  pub fn options(&self) -> &RantOptions {
313    &self.options
314  }
315  
316  /// Gets the current RNG seed.
317  pub fn seed(&self) -> u64 {
318    self.rng.seed()
319  }
320  
321  /// Re-seeds the RNG with the specified seed.
322  pub fn set_seed(&mut self, seed: u64) {
323    self.rng = Rc::new(RantRng::new(seed));
324  }
325  
326  /// Resets the RNG back to its initial state with the current seed.
327  pub fn reset_seed(&mut self) {
328    let seed = self.rng.seed();
329    self.rng = Rc::new(RantRng::new(seed));
330  }
331
332  /// Registers a data source to the context, making it available to scripts.
333  pub fn add_data_source(&mut self, data_source: impl DataSource + 'static) -> Result<(), DataSourceRegisterError> {
334    let id = data_source.type_id();
335
336    if self.has_data_source(id) {
337      return Err(DataSourceRegisterError::AlreadyExists(id.into()))
338    }
339
340    self.data_sources.insert(id.into(), Box::new(data_source));
341    Ok(())
342  }
343
344  /// Removes the data source with the specified name from the context, making it no longer available to scripts.
345  pub fn remove_data_source(&mut self, name: &str) -> Option<Box<dyn DataSource>> {
346    self.data_sources.remove(name)
347  }
348
349  /// Returns a `bool` indicating whether a data source with the specified name is present in the context.
350  pub fn has_data_source(&self, name: &str) -> bool {
351    self.data_sources.contains_key(name)
352  }
353
354  /// Removes all data sources from the context.
355  pub fn clear_data_sources(&mut self) {
356    self.data_sources.clear();
357  }
358
359  /// Returns a reference to the data source associated with the specified name.
360  pub fn data_source(&self, name: &str) -> Option<&dyn DataSource> {
361    self.data_sources.get(name).map(Box::as_ref)
362  }
363
364  /// Iterates over all data sources (and their names) in the context.
365  pub fn iter_data_sources(&self) -> impl Iterator<Item = (&'_ str, &'_ Box<dyn DataSource + 'static>)> {
366    self.data_sources.iter().map(|(k, v)| (k.as_str(), v))
367  }
368  
369  /// Runs a program and returns the output value.
370  pub fn run(&mut self, program: &RantProgram) -> RuntimeResult<RantValue> {
371    VM::new(self.rng.clone(), self, program).run()
372  }
373
374  /// Runs a program with the specified arguments and returns the output value.
375  pub fn run_with<A>(&mut self, program: &RantProgram, args: A) -> RuntimeResult<RantValue>
376  where A: Into<Option<HashMap<String, RantValue>>>
377  {
378    VM::new(self.rng.clone(), self, program).run_with(args)
379  }
380
381  pub fn try_load_global_module(&mut self, module_path: &str) -> Result<(), ModuleLoadError> {
382    if let Some(module_name) = 
383    PathBuf::from(&module_path)
384    .with_extension("")
385    .file_name()
386    .map(|name| name.to_str())
387    .flatten()
388    .map(|name| name.to_owned())
389    {
390      // Check if module is cached; if so, don't do anything
391      if self.get_cached_module(module_name.as_ref()).is_some() {
392        return Ok(())
393      }
394
395      let module_resolver = Rc::clone(&self.module_resolver);
396
397      // Resolve and load the module
398      let module = match module_resolver.try_resolve(self, module_path, None) {
399        Ok(module_program) => match self.run(&module_program) {
400          Ok(module) => Ok(module),
401          Err(err) => Err(ModuleLoadError::RuntimeError(Rc::new(err))),
402        },
403        Err(err) => Err(ModuleLoadError::ResolveError(err)),
404      }?;
405
406      // Cache the module
407      if let Some(RantValue::Map(module_cache_ref)) = self.get_global(MODULES_CACHE_KEY) {
408        module_cache_ref.borrow_mut().raw_set(&module_name, module);
409      } else {
410        let mut cache = RantMap::new();
411        cache.raw_set(&module_name, module);
412        self.set_global(MODULES_CACHE_KEY, cache.into_rant());
413      }
414
415      Ok(())
416    } else {
417      Err(ModuleLoadError::InvalidPath(format!("missing module name from path: '{module_path}'")))
418    }
419  }
420
421  #[inline]
422  pub(crate) fn get_cached_module(&self, module_name: &str) -> Option<RantValue> {
423    if let Some(RantValue::Map(module_cache_ref)) = self.get_global(MODULES_CACHE_KEY) {
424      if let Some(module @ RantValue::Map(..)) = module_cache_ref.borrow().raw_get(module_name) {
425        return Some(module.clone())
426      }
427    }
428    None
429  }
430}
431
432/// Provides options for customizing the creation of a `Rant` instance.
433#[derive(Debug, Clone)]
434pub struct RantOptions {
435  /// Specifies whether the standard library should be loaded.
436  pub use_stdlib: bool,
437  /// Enables debug mode, which includes additional debug information in compiled programs and more detailed runtime error data.
438  pub debug_mode: bool,
439  /// The initial seed to pass to the RNG. Defaults to 0.
440  pub seed: u64,
441}
442
443impl Default for RantOptions {
444  fn default() -> Self {
445    Self {
446      use_stdlib: true,
447      debug_mode: false,
448      seed: 0,
449    }
450  }
451}
452
453/// A compiled Rant program.
454#[derive(Debug)]
455pub struct RantProgram {
456  info: Rc<RantProgramInfo>,
457  root: Rc<Sequence>
458}
459
460impl RantProgram {
461  pub(crate) fn new(root: Rc<Sequence>, info: Rc<RantProgramInfo>) -> Self {
462    Self {
463      info,
464      root,
465    }
466  }
467
468  /// Gets the display name of the program, if any.
469  #[inline]
470  pub fn name(&self) -> Option<&str> {
471    self.info.name.as_deref()
472  }
473
474  /// Gets the path to the program's source file, if any.
475  #[inline]
476  pub fn path(&self) -> Option<&str> {
477    self.info.path.as_deref()
478  }
479
480  /// Gets the metadata associated with the program.
481  #[inline]
482  pub fn info(&self) -> &RantProgramInfo {
483    self.info.as_ref()
484  }
485}
486
487/// Contains metadata used to identify a loaded program.
488#[derive(Debug)]
489pub struct RantProgramInfo {
490  path: Option<String>,
491  name: Option<String>,
492}
493
494impl RantProgramInfo {
495  /// Gets the display name of the program, if any.
496  #[inline]
497  pub fn name(&self) -> Option<&str> {
498    self.name.as_deref()
499  }
500
501  /// Gets tha path to the program's source file, if any.
502  #[inline]
503  pub fn path(&self) -> Option<&str> {
504    self.path.as_deref()
505  }
506}
507
508/// Represents error states that can occur when loading a module.
509#[derive(Debug)]
510pub enum ModuleLoadError {
511  /// The specified path was invalid; see attached reason. 
512  InvalidPath(String),
513  /// The module failed to load because it encountered a runtime error during initialization.
514  RuntimeError(Rc<RuntimeError>),
515  /// The module failed to load because it couldn't be resolved.
516  ResolveError(ModuleResolveError),
517}
518
519impl Display for ModuleLoadError {
520  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521    match self {
522      ModuleLoadError::InvalidPath(errmsg) => write!(f, "{}", errmsg),
523      ModuleLoadError::RuntimeError(err) => write!(f, "runtime error while loading module: {}", err),
524      ModuleLoadError::ResolveError(err) => write!(f, "unable to resolve module: {}", err),
525    }
526  }
527}
528
529impl Error for ModuleLoadError {}
530
531/// Represents error states that can occur when registering a data source on a Rant execution context.
532#[derive(Debug)]
533pub enum DataSourceRegisterError {
534  /// The type ID provided by the data source was invalid.
535  InvalidTypeId(String),
536  /// A data source with the specified type ID was already registered on the context.
537  AlreadyExists(String),
538}
539
540impl Display for DataSourceRegisterError {
541  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
542    match self {
543      Self::InvalidTypeId(id) => write!(f, "the type id '{id}' is invalid"),
544      Self::AlreadyExists(id) => write!(f, "the type id '{id}' was already registered on the context"),
545    }
546  }
547}