From e3cb2841ac2e8282a6f3ecc54ceccf51c9127b3b Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Mon, 8 Apr 2024 18:13:16 -0700 Subject: [PATCH] Partial docs --- Cargo.toml | 1 + README.md | 5 + amuse/src/main.rs | 8 +- examples/async.rs | 6 +- examples/fib-vm.rs | 12 +- examples/fib.rs | 8 + examples/invoke.rs | 7 +- examples/rust-function.rs | 5 +- muse-ui/src/lib.rs | 8 +- src/compiler.rs | 16 +- src/{ => compiler}/syntax.rs | 6 +- src/{ => compiler}/syntax/token.rs | 2 +- src/lib.rs | 21 +- src/main.rs | 9 +- src/runtime.rs | 9 + src/{ => runtime}/exception.rs | 24 +- src/{ => runtime}/list.rs | 59 +++- src/{ => runtime}/map.rs | 51 +++- src/{ => runtime}/regex.rs | 25 +- src/{ => runtime}/string.rs | 15 +- src/{ => runtime}/symbol.rs | 68 ++++- src/{ => runtime}/value.rs | 6 +- src/tests.rs | 10 +- src/vm.rs | 454 +++++++++++++++++++++-------- src/vm/bitcode.rs | 8 +- src/vm/dispatched.rs | 4 +- tests/harness.rs | 22 +- tests/hosted.rs | 14 +- 28 files changed, 655 insertions(+), 228 deletions(-) rename src/{ => compiler}/syntax.rs (99%) rename src/{ => compiler}/syntax/token.rs (99%) create mode 100644 src/runtime.rs rename src/{ => runtime}/exception.rs (80%) rename src/{ => runtime}/list.rs (62%) rename src/{ => runtime}/map.rs (75%) rename src/{ => runtime}/regex.rs (91%) rename src/{ => runtime}/string.rs (94%) rename src/{ => runtime}/symbol.rs (77%) rename src/{ => runtime}/value.rs (99%) diff --git a/Cargo.toml b/Cargo.toml index 37405af..87e71a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ missing_panics_doc = "allow" [lints.rust] unsafe_code = "forbid" +missing_docs = "warn" [[test]] diff --git a/README.md b/README.md index 98e9c67..86b659e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # muse +![Muse is considered experimental and unsupported](https://img.shields.io/badge/status-experimental-purple) +[![crate version](https://img.shields.io/crates/v/muse.svg)](https://crates.io/crates/muse) +[![Documentation for `main`](https://img.shields.io/badge/docs-main-informational)](https://khonsu.dev/muse/main/docs/muse/) +[![Muse User's Guide](https://img.shields.io/badge/user%27s%20guide-main-informational)][guide] + A thoughtful, designed-to-be-embedded programming language. ## What is Muse? diff --git a/amuse/src/main.rs b/amuse/src/main.rs index 4b08f26..e255905 100644 --- a/amuse/src/main.rs +++ b/amuse/src/main.rs @@ -7,10 +7,10 @@ use cushy::widgets::input::InputValue; use cushy::widgets::list::ListStyle; use cushy::widgets::Label; use cushy::{Open, PendingApp}; +use muse::compiler::syntax::{SourceId, Sources}; use muse::compiler::Compiler; use muse::refuse::CollectionGuard; -use muse::syntax::{SourceId, Sources}; -use muse::value::Value as MuseValue; +use muse::runtime::value::Value as MuseValue; use muse::vm::{ExecutionError, Vm, VmContext}; use muse_ui::VmUi; @@ -33,8 +33,8 @@ fn main() { }); let input = Dynamic::::default(); - let parsed = - input.map_each(|source| muse::syntax::parse(source).map_err(|err| format!("{err:?}"))); + let parsed = input + .map_each(|source| muse::compiler::syntax::parse(source).map_err(|err| format!("{err:?}"))); let expression = parsed.map_each(|parsed| { parsed .as_ref() diff --git a/examples/async.rs b/examples/async.rs index 8266e99..8b1d663 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -1,6 +1,8 @@ +//! An example showing how to use Muse in async. + use muse::compiler::Compiler; -use muse::symbol::Symbol; -use muse::value::{AsyncFunction, Value}; +use muse::runtime::symbol::Symbol; +use muse::runtime::value::{AsyncFunction, Value}; use muse::vm::{Arity, Fault, Register, Vm, VmContext}; use refuse::CollectionGuard; diff --git a/examples/fib-vm.rs b/examples/fib-vm.rs index cbd07c9..bc66cdb 100644 --- a/examples/fib-vm.rs +++ b/examples/fib-vm.rs @@ -1,5 +1,13 @@ -use muse::symbol::Symbol; -use muse::syntax::CompareKind::LessThanOrEqual; +//! An example using virtual machine instructions that calculates fibonacci +//! using a naive recursive algorithm. +//! +//! This is not how someone should implement fibonacci, but it provides a way to +//! test calling *a lot* of functions while also doing some other operations +//! between function calls. This ends up being a pretty good way to profile the +//! virtual machine to see overall bottlenecks in code execution. + +use muse::compiler::syntax::CompareKind::LessThanOrEqual; +use muse::runtime::symbol::Symbol; use muse::vm::bitcode::BitcodeBlock; use muse::vm::{Function, Register as R, Vm, VmContext}; use refuse::CollectionGuard; diff --git a/examples/fib.rs b/examples/fib.rs index c6c5645..565d369 100644 --- a/examples/fib.rs +++ b/examples/fib.rs @@ -1,3 +1,11 @@ +//! An example using Muse that calculates fibonacci using a naive recursive +//! algorithm. +//! +//! This is not how someone should implement fibonacci, but it provides a way to +//! test calling *a lot* of functions while also doing some other operations +//! between function calls. This ends up being a pretty good way to profile the +//! virtual machine to see overall bottlenecks in code execution. + use muse::vm::Vm; use refuse::CollectionGuard; diff --git a/examples/invoke.rs b/examples/invoke.rs index 90cfa49..8549d67 100644 --- a/examples/invoke.rs +++ b/examples/invoke.rs @@ -1,4 +1,9 @@ -use muse::syntax::Sources; +//! An example demonstrating how to invoke a Muse-written function from Rust. +//! +//! Rust code can only access public declarations. Attempting to invoke a +//! private function will result in an error. + +use muse::compiler::syntax::Sources; use muse::vm::Vm; use refuse::CollectionGuard; diff --git a/examples/rust-function.rs b/examples/rust-function.rs index 4c33d37..ea95639 100644 --- a/examples/rust-function.rs +++ b/examples/rust-function.rs @@ -1,6 +1,7 @@ +//! An example demonstrating how to call a Rust-written function from Muse. use muse::compiler::Compiler; -use muse::symbol::Symbol; -use muse::value::{RustFunction, Value}; +use muse::runtime::symbol::Symbol; +use muse::runtime::value::{RustFunction, Value}; use muse::vm::{Fault, Register, Vm, VmContext}; use refuse::CollectionGuard; diff --git a/muse-ui/src/lib.rs b/muse-ui/src/lib.rs index 1398bd9..8690f18 100644 --- a/muse-ui/src/lib.rs +++ b/muse-ui/src/lib.rs @@ -8,8 +8,8 @@ use cushy::widgets::{Expand, Slider}; use cushy::window::WindowHandle; use cushy::{App, Application, Open, PendingApp}; use muse::refuse::{self, CollectionGuard, SimpleType, Trace}; -use muse::symbol::Symbol; -use muse::value::{ContextOrGuard, CustomType, RustFunction, RustType, TypeRef, Value}; +use muse::runtime::symbol::Symbol; +use muse::runtime::value::{ContextOrGuard, CustomType, RustFunction, RustType, TypeRef, Value}; use muse::vm::{Fault, Register, Vm, VmContext}; pub fn install(vm: &Vm, guard: &mut CollectionGuard<'_>) { @@ -165,7 +165,9 @@ impl CustomType for DynamicValue { |this, guard| { this.0 .map_ref(|value| value.deep_clone(guard)) - .map(|value| muse::value::AnyDynamic::new(Self(Dynamic::new(value)), guard)) + .map(|value| { + muse::runtime::value::AnyDynamic::new(Self(Dynamic::new(value)), guard) + }) } }) }); diff --git a/src/compiler.rs b/src/compiler.rs index 55f70c7..ef78e26 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] use std::collections::VecDeque; use std::fmt::{Debug, Display}; use std::ops::{BitOr, BitOrAssign, Range}; @@ -9,14 +10,17 @@ use refuse::CollectionGuard; use regex::Regex; use serde::{Deserialize, Serialize}; -use crate::symbol::Symbol; -use crate::syntax::token::Token; -use crate::syntax::{ - self, AssignTarget, BinaryExpression, BreakExpression, CompareKind, ContinueExpression, - Delimited, DelimitedIter, EntryKeyPattern, Expression, FunctionCall, FunctionDefinition, Index, - Literal, LogicalKind, Lookup, LoopExpression, LoopKind, MatchExpression, MatchPattern, Matches, +pub mod syntax; + +use syntax::token::Token; +use syntax::{ + AssignTarget, BinaryExpression, BreakExpression, CompareKind, ContinueExpression, Delimited, + DelimitedIter, EntryKeyPattern, Expression, FunctionCall, FunctionDefinition, Index, Literal, + LogicalKind, Lookup, LoopExpression, LoopKind, MatchExpression, MatchPattern, Matches, PatternKind, Ranged, SourceCode, SourceRange, TryExpression, UnaryExpression, Variable, }; + +use crate::runtime::symbol::Symbol; use crate::vm::bitcode::{ BinaryKind, BitcodeBlock, BitcodeFunction, FaultKind, Label, Op, OpDestination, ValueOrSource, }; diff --git a/src/syntax.rs b/src/compiler/syntax.rs similarity index 99% rename from src/syntax.rs rename to src/compiler/syntax.rs index 7b357f9..d0d3bde 100644 --- a/src/syntax.rs +++ b/src/compiler/syntax.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + use core::slice; use std::collections::VecDeque; use std::fmt::{self, Debug, Display}; @@ -9,8 +11,8 @@ use ahash::AHashMap; use serde::{Deserialize, Serialize}; use self::token::{FormatString, FormatStringPart, Paired, RegexLiteral, Token, Tokens}; -use crate::exception::Exception; -use crate::symbol::Symbol; +use crate::runtime::exception::Exception; +use crate::runtime::symbol::Symbol; use crate::vm::{ExecutionError, VmContext}; pub mod token; diff --git a/src/syntax/token.rs b/src/compiler/syntax/token.rs similarity index 99% rename from src/syntax/token.rs rename to src/compiler/syntax/token.rs index 48626b0..ef7b0e6 100644 --- a/src/syntax/token.rs +++ b/src/compiler/syntax/token.rs @@ -8,7 +8,7 @@ use std::str::CharIndices; use serde::{Deserialize, Serialize}; use super::{Ranged, SourceCode, SourceId}; -use crate::symbol::Symbol; +use crate::runtime::symbol::Symbol; #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Token { diff --git a/src/lib.rs b/src/lib.rs index feb128f..524d86a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +//! A Safe, familiar, embeddable programming language. + macro_rules! impl_from { ($on:ty, $from:ty, $variant:ident) => { impl From<$from> for $on { @@ -9,29 +11,28 @@ macro_rules! impl_from { } pub mod compiler; -pub mod map; -pub mod string; -pub mod symbol; -pub mod syntax; -pub mod value; pub mod vm; -pub mod exception; -pub mod list; -pub mod regex; +pub mod runtime; + #[cfg(test)] mod tests; +use compiler::syntax::Ranged; pub use refuse; -use syntax::Ranged; -pub trait ErrorKind: std::error::Error { +/// Summarizes an error's kind. +pub trait ErrorKind { + /// Returns the summary of the error being raised. fn kind(&self) -> &'static str; } +/// One or more errors raised during compilation or execution. #[derive(Debug, Clone, PartialEq)] pub enum Error { + /// A list of compilation errors. Compilation(Vec>), + /// An execution error. Execution(vm::ExecutionError), } diff --git a/src/main.rs b/src/main.rs index 90521f7..8eef401 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,13 @@ +//! The interactive Muse REPL (Read-Eval-Print-Loop). + use std::collections::HashMap; use std::num::NonZeroUsize; use std::path::PathBuf; use ariadne::{Cache, Label, Span}; +use muse::compiler::syntax::{parse, Ranged, SourceId, SourceRange, Sources}; use muse::compiler::Compiler; -use muse::exception::Exception; -use muse::syntax::{parse, Ranged, SourceId, SourceRange, Sources}; +use muse::runtime::exception::Exception; use muse::vm::{ExecutionError, StackFrame, Vm, VmContext}; use muse::ErrorKind; use refuse::CollectionGuard; @@ -182,7 +184,8 @@ impl Validator for Muse { match parse(source) { Ok(_) => Ok(ValidationResult::Valid(None)), Err(Ranged( - muse::syntax::Error::UnexpectedEof | muse::syntax::Error::MissingEnd(_), + muse::compiler::syntax::Error::UnexpectedEof + | muse::compiler::syntax::Error::MissingEnd(_), _, )) => Ok(ValidationResult::Incomplete), Err(err) => { diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..8981d3e --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,9 @@ +//! Types that are used within the Muse language. + +pub mod exception; +pub mod list; +pub mod map; +pub mod regex; +pub mod string; +pub mod symbol; +pub mod value; diff --git a/src/exception.rs b/src/runtime/exception.rs similarity index 80% rename from src/exception.rs rename to src/runtime/exception.rs index 574ec85..d7ae816 100644 --- a/src/exception.rs +++ b/src/runtime/exception.rs @@ -1,11 +1,19 @@ +//! Types used in exception handling. + use std::fmt::{self, Debug}; use refuse::Trace; -use crate::syntax::Sources; -use crate::value::{AnyDynamic, CustomType, RustType, TypeRef, Value}; +use crate::compiler::syntax::Sources; +use crate::runtime::value::{AnyDynamic, CustomType, RustType, TypeRef, Value}; use crate::vm::{Code, StackFrame, VmContext}; +/// A thrown exception. +/// +/// An exception contains two pieces of data: +/// +/// - a [`Value`]. +/// - a [backtrace](Self::backtrace) #[derive(Debug)] pub struct Exception { value: Value, @@ -13,21 +21,27 @@ pub struct Exception { } impl Exception { + /// Returns a new exception with the current backtrace of the virtual + /// machine. pub fn new(value: Value, vm: &mut VmContext<'_, '_>) -> Self { - let stack_trace = vm.stack_trace(); + let stack_trace = vm.backtrace(); Self { value, stack_trace } } + /// Returns the thrown value. #[must_use] - pub const fn value(&self) -> &Value { - &self.value + pub const fn value(&self) -> Value { + self.value } + /// Returns the list of recorded stack frames when this exception was + /// thrown. #[must_use] pub fn backtrace(&self) -> &[StackFrame] { &self.stack_trace } + /// Displays this exception with its backtrace into `f`. pub fn format( &self, sources: &Sources, diff --git a/src/list.rs b/src/runtime/list.rs similarity index 62% rename from src/list.rs rename to src/runtime/list.rs index 715ad8b..eed6a6c 100644 --- a/src/list.rs +++ b/src/runtime/list.rs @@ -1,10 +1,18 @@ +//! Types used for lists/tuples. + use parking_lot::Mutex; use refuse::Trace; -use crate::symbol::Symbol; -use crate::value::{CustomType, Dynamic, Rooted, RustFunctionTable, RustType, TypeRef, Value}; +use crate::runtime::symbol::Symbol; +use crate::runtime::value::{ + CustomType, Dynamic, Rooted, RustFunctionTable, RustType, TypeRef, Value, +}; use crate::vm::{Fault, Register, VmContext}; +/// The type definition that the [`List`] type uses. +/// +/// In general, developers will not need this. However, if you are building your +/// own `core` module, this type can be used to populate `$.core.List`. pub static LIST_TYPE: RustType = RustType::new("List", |t| { t.with_construct(|_| { |vm, arity| { @@ -42,15 +50,23 @@ pub static LIST_TYPE: RustType = RustType::new("List", |t| { ) }); +/// A list of [`Value`]s. #[derive(Debug)] pub struct List(Mutex>); impl List { + /// Returns an empty list. #[must_use] pub const fn new() -> Self { Self(Mutex::new(Vec::new())) } + /// Returns the value at `index`. + /// + /// # Errors + /// + /// Returns [`Fault::OutOfBounds`] if `index` cannot be converted to a + /// `usize` or is out of bounds of this list. pub fn get(&self, index: &Value) -> Result { let Some(index) = index.as_usize() else { return Err(Fault::OutOfBounds); @@ -60,39 +76,64 @@ impl List { contents.get(index).copied().ok_or(Fault::OutOfBounds) } + /// Inserts `value` at `index`. + /// + /// # Errors + /// + /// Returns [`Fault::OutOfBounds`] if `index` cannot be converted to a + /// `usize` or is greater than the length of this list. pub fn insert(&self, index: &Value, value: Value) -> Result<(), Fault> { - let Some(index) = index.as_usize() else { - return Err(Fault::OutOfBounds); - }; let mut contents = self.0.lock(); - contents.insert(index, value); - Ok(()) + contents.try_reserve(1).map_err(|_| Fault::OutOfMemory)?; + match index.as_usize() { + Some(index) if index <= contents.len() => { + contents.insert(index, value); + + Ok(()) + } + _ => Err(Fault::OutOfBounds), + } } + /// Pushes `value` to the end of the list. pub fn push(&self, value: Value) -> Result<(), Fault> { let mut contents = self.0.lock(); + contents.try_reserve(1).map_err(|_| Fault::OutOfMemory)?; contents.push(value); Ok(()) } - pub fn set(&self, index: &Value, value: Value) -> Result, Fault> { + /// Replaces the value at `index` with `value`. Returns the previous value. + /// + /// # Errors + /// + /// Returns [`Fault::OutOfBounds`] if `index` cannot be converted to a + /// `usize` or is out of bounds of this list. + pub fn set(&self, index: &Value, value: Value) -> Result { let Some(index) = index.as_usize() else { return Err(Fault::OutOfBounds); }; let mut contents = self.0.lock(); if let Some(entry) = contents.get_mut(index) { - Ok(Some(std::mem::replace(entry, value))) + Ok(std::mem::replace(entry, value)) } else { Err(Fault::OutOfBounds) } } + /// Converts this list into a Vec. pub fn to_vec(&self) -> Vec { self.0.lock().clone() } } +impl Default for List { + fn default() -> Self { + Self::new() + } +} + impl From> for List { fn from(value: Vec) -> Self { Self(Mutex::new(value)) diff --git a/src/map.rs b/src/runtime/map.rs similarity index 75% rename from src/map.rs rename to src/runtime/map.rs index f11e346..8435f2f 100644 --- a/src/map.rs +++ b/src/runtime/map.rs @@ -1,18 +1,25 @@ +//! Types used for maps/dictionaries. + use std::cmp::Ordering; use parking_lot::Mutex; use refuse::{CollectionGuard, Trace}; -use crate::list::List; -use crate::symbol::Symbol; -use crate::value::{ +use crate::runtime::list::List; +use crate::runtime::symbol::Symbol; +use crate::runtime::value::{ ContextOrGuard, CustomType, Dynamic, RustFunctionTable, RustType, TypeRef, Value, }; use crate::vm::{Fault, Register, VmContext}; +/// A collection of key-value pairs. #[derive(Debug)] pub struct Map(Mutex>); +/// The type definition that the [`Map`] type uses. +/// +/// In general, developers will not need this. However, if you are building your +/// own `core` module, this type can be used to populate `$.core.Map`. pub static MAP_TYPE: RustType = RustType::new("Map", |t| { t.with_construct(|_| { |vm, arity| { @@ -65,11 +72,13 @@ impl Trace for Map { } impl Map { + /// Returns an empty map. #[must_use] pub const fn new() -> Self { Self(Mutex::new(Vec::new())) } + /// Creates a map from an iterator of key-value pairs. pub fn from_iterator( vm: &mut VmContext<'_, '_>, iter: impl IntoIterator, @@ -82,9 +91,18 @@ impl Map { }) .collect(); fields.sort_unstable_by(|a, b| a.key.hash.cmp(&b.key.hash)); + // TODO need to de-dup keys. Maybe there's a sorting algorithm that + // supports deduping we can use here? Self(Mutex::new(fields)) } + /// Returns the value contained for `key`, or `None` if this map does not + /// contain key. + /// + /// # Errors + /// + /// This function does not directly return any errors, but comparing values + /// can result in runtime errors. pub fn get(&self, vm: &mut VmContext<'_, '_>, key: &Value) -> Result, Fault> { let hash = key.hash(vm); let contents = self.0.lock(); @@ -103,6 +121,12 @@ impl Map { Ok(None) } + /// Returns the value contained at `index`. + /// + /// # Errors + /// + /// Returns [`Fault::OutOfBounds`] if `index` is out of bounds of this + /// collection. pub fn nth(&self, index: &Value, guard: &CollectionGuard) -> Result { let Some(index) = index.as_usize() else { return Err(Fault::OutOfBounds); @@ -114,6 +138,9 @@ impl Map { .ok_or(Fault::OutOfBounds) } + /// Inserts a key-value pair into this map. + /// + /// If an existing value is stored for `key`, it will be returned. pub fn insert( &self, vm: &mut VmContext<'_, '_>, @@ -141,11 +168,13 @@ impl Map { } } + contents.try_reserve(1).map_err(|_| Fault::OutOfMemory)?; contents.insert(insert_at, Field { key, value }); Ok(None) } + /// Returns this map as a vec of fields. pub fn to_vec(&self) -> Vec { self.0.lock().clone() } @@ -157,6 +186,7 @@ impl CustomType for Map { } } +/// A key-value pair. #[derive(Debug, Clone)] pub struct Field { key: MapKey, @@ -164,6 +194,19 @@ pub struct Field { } impl Field { + /// Returns the value of this field. + #[must_use] + pub const fn value(&self) -> Value { + self.value + } + + #[must_use] + /// Returns the key of this field. + pub const fn key(&self) -> Value { + self.key.value + } + + /// Splits this value into its key and value. #[must_use] pub fn into_parts(self) -> (Value, Value) { (self.key.value, self.value) @@ -177,7 +220,7 @@ struct MapKey { } impl MapKey { - pub fn new(vm: &mut VmContext<'_, '_>, key: Value) -> Self { + fn new(vm: &mut VmContext<'_, '_>, key: Value) -> Self { Self { hash: key.hash(vm), value: key, diff --git a/src/regex.rs b/src/runtime/regex.rs similarity index 91% rename from src/regex.rs rename to src/runtime/regex.rs index 2b0c6fe..4da8ead 100644 --- a/src/regex.rs +++ b/src/runtime/regex.rs @@ -1,3 +1,5 @@ +//! Types used for regular expressions. + use std::fmt::Display; use std::hash::Hash; use std::ops::Deref; @@ -6,12 +8,15 @@ use kempt::Map; use refuse::{CollectionGuard, ContainsNoRefs, Trace}; use regex::{Captures, Regex}; -use crate::string::MuseString; -use crate::symbol::{Symbol, SymbolRef}; -use crate::syntax::token::RegexLiteral; -use crate::value::{AnyDynamic, CustomType, Rooted, RustFunctionTable, RustType, TypeRef, Value}; +use crate::compiler::syntax::token::RegexLiteral; +use crate::runtime::string::MuseString; +use crate::runtime::symbol::{Symbol, SymbolRef}; +use crate::runtime::value::{ + AnyDynamic, CustomType, Rooted, RustFunctionTable, RustType, TypeRef, Value, +}; use crate::vm::{Fault, Register, VmContext}; +/// A compiled [`RegexLiteral`]. #[derive(Debug, Clone)] pub struct MuseRegex { expr: Regex, @@ -19,6 +24,12 @@ pub struct MuseRegex { } impl MuseRegex { + /// Returns a compiled regular expression. + /// + /// # Errors + /// + /// All errors returned from this function are directly from the [`regex`] + /// crate. pub fn new(literal: &RegexLiteral) -> Result { let expr = regex::RegexBuilder::new(&literal.pattern) .ignore_whitespace(literal.expanded) @@ -35,11 +46,13 @@ impl MuseRegex { }) } + /// Returns the literal used to compile this regular expression. #[must_use] pub const fn literal(&self) -> &RegexLiteral { &self.literal } + /// Returns the captures when searching in `haystack`. #[must_use] pub fn captures(&self, haystack: &str, guard: &CollectionGuard) -> Option { self.expr @@ -143,6 +156,7 @@ impl Display for MuseRegex { } } +/// A set of captures from a regular expression search. #[derive(Debug, Clone, Trace)] pub struct MuseCaptures { matches: Vec, @@ -227,9 +241,12 @@ impl CustomType for MuseCaptures { } } +/// A regular expression match. #[derive(Clone, Debug, Trace)] pub struct MuseMatch { + /// The content of the match. pub content: AnyDynamic, + /// The offset within the haystack that this match occurred. pub start: usize, } diff --git a/src/string.rs b/src/runtime/string.rs similarity index 94% rename from src/string.rs rename to src/runtime/string.rs index cc15ec3..76d53cb 100644 --- a/src/string.rs +++ b/src/runtime/string.rs @@ -1,17 +1,20 @@ +//! Types used for strings/text. + use std::fmt::Display; use std::hash::Hash; use parking_lot::{Mutex, MutexGuard}; use refuse::ContainsNoRefs; -use crate::list::List; -use crate::regex::MuseRegex; -use crate::symbol::{Symbol, SymbolRef}; -use crate::value::{ +use crate::runtime::list::List; +use crate::runtime::regex::MuseRegex; +use crate::runtime::symbol::{Symbol, SymbolRef}; +use crate::runtime::value::{ AnyDynamic, CustomType, Dynamic, Rooted, RustFunctionTable, RustType, TypeRef, Value, }; use crate::vm::{Fault, Register, VmContext}; +/// The [`String`] type for Muse. #[derive(Debug)] pub struct MuseString(Mutex); @@ -35,6 +38,10 @@ impl From<&'_ str> for MuseString { impl ContainsNoRefs for MuseString {} +/// The type definition that the [`MuseString`] type uses. +/// +/// In general, developers will not need this. However, if you are building your +/// own `core` module, this type can be used to populate `$.core.String`. pub static STRING_TYPE: RustType = RustType::new("String", |t| { t.with_construct(|_| { |vm, arity| { diff --git a/src/symbol.rs b/src/runtime/symbol.rs similarity index 77% rename from src/symbol.rs rename to src/runtime/symbol.rs index e7d9851..070d294 100644 --- a/src/symbol.rs +++ b/src/runtime/symbol.rs @@ -1,3 +1,6 @@ +//! Types for "symbols", an optimized string-like type that ensures only one +//! underlying copy of each unique string exists. + use std::fmt::{Debug, Display}; use std::ops::{Add, Deref}; use std::sync::OnceLock; @@ -8,16 +11,28 @@ use refuse_pool::{RefString, RootString}; use serde::de::Visitor; use serde::{Deserialize, Serialize}; -use crate::value::ValueFreed; +use crate::runtime::value::ValueFreed; +/// A garbage-collected weak reference to a [`Symbol`]. #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Trace)] pub struct SymbolRef(RefString); + impl SymbolRef { + /// Loads the underlying string value of this symbol. + /// + /// Returns `None` if the underlying symbol has been freed by the garbage + /// collector. #[must_use] pub fn load<'guard>(&self, guard: &'guard CollectionGuard<'_>) -> Option<&'guard str> { self.0.load(guard) } + /// Tries to loads the underlying string value of this symbol. + /// + /// # Errors + /// + /// Returns [`ValueFreed`] if the underlying symbol has been freed by the + /// garbage collector. pub fn try_load<'guard>( &self, guard: &'guard CollectionGuard<'_>, @@ -25,10 +40,22 @@ impl SymbolRef { self.load(guard).ok_or(ValueFreed) } + /// Upgrades this weak reference to a reference-counted [`Symbol`]. + /// + /// + /// Returns `None` if the underlying symbol has been freed by the garbage + /// collector. + #[must_use] pub fn upgrade(&self, guard: &CollectionGuard<'_>) -> Option { self.0.as_root(guard).map(Symbol) } + /// Tries to upgrade this weak reference to a reference-counted [`Symbol`]. + /// + /// # Errors + /// + /// Returns [`ValueFreed`] if the underlying symbol has been freed by the + /// garbage collector. pub fn try_upgrade(&self, guard: &CollectionGuard<'_>) -> Result { self.upgrade(guard).ok_or(ValueFreed) } @@ -40,10 +67,16 @@ impl kempt::Sort for Symbol { } } +/// A reference-counted, cheap-to-compare String type. +/// +/// Symbols are optimized to be able to cheaply compare and hash without needing +/// to analyze the underlying string contents. This is done by ensuring that all +/// instances of the same underlying string data point to the same [`Symbol`]. #[derive(Clone, Trace)] pub struct Symbol(RootString); impl Symbol { + /// Returns a weak reference to this symbol. #[must_use] pub const fn downgrade(&self) -> SymbolRef { SymbolRef(self.0.downgrade()) @@ -79,7 +112,9 @@ impl std::hash::Hash for Symbol { macro_rules! static_symbols { ($($name:ident => $string:literal),+ $(,)?) => { impl Symbol { - $(pub fn $name() -> &'static Self { + $( + #[doc = concat!("Returns the symbol for \"", $string, "\".")] + pub fn $name() -> &'static Self { static S: OnceLock = OnceLock::new(); S.get_or_init(|| Symbol::from($string)) })+ @@ -169,7 +204,9 @@ impl From for SymbolRef { } } +/// A type that can be optionally be converted to a [`Symbol`]. pub trait IntoOptionSymbol { + /// Returns this type as an optional symbol. fn into_symbol(self) -> Option; } @@ -332,9 +369,15 @@ impl<'de> Visitor<'de> for SymbolVisitor { } } +/// A [`Symbol`] that is initialized once and retains a reference to the +/// [`Symbol`]. +/// +/// This type is designed to be used as a `static`, allowing usages of common +/// symbols to never be garbage collected. pub struct StaticSymbol(OnceLock, &'static str); impl StaticSymbol { + /// Returns a new static symbol from a static string. #[must_use] pub const fn new(symbol: &'static str) -> Self { Self(OnceLock::new(), symbol) @@ -361,24 +404,31 @@ impl Deref for StaticSymbol { } } +/// A type that contains a list of symbols. pub trait SymbolList { + /// The iterator used for [`into_symbols`](Self::into_symbols). type Iterator: Iterator; + + /// Returns `self` as an iterator over its contained symbols. fn into_symbols(self) -> Self::Iterator; } -impl SymbolList for [Symbol; N] { - type Iterator = array::IntoIter; +/// An iterator over an array of types that implement [`Into`]. +pub struct ArraySymbolsIntoIter, const N: usize>(array::IntoIter); - fn into_symbols(self) -> Self::Iterator { - self.into_iter() +impl, const N: usize> Iterator for ArraySymbolsIntoIter { + type Item = Symbol; + + fn next(&mut self) -> Option { + self.0.next().map(T::into) } } -impl<'a, const N: usize> SymbolList for [&'a Symbol; N] { - type Iterator = iter::Cloned>; +impl, const N: usize> SymbolList for [T; N] { + type Iterator = ArraySymbolsIntoIter; fn into_symbols(self) -> Self::Iterator { - self.into_iter().cloned() + ArraySymbolsIntoIter(self.into_iter()) } } diff --git a/src/value.rs b/src/runtime/value.rs similarity index 99% rename from src/value.rs rename to src/runtime/value.rs index 5d64a56..3a09d4c 100644 --- a/src/value.rs +++ b/src/runtime/value.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + use std::any::Any; use std::cmp::Ordering; use std::fmt::{self, Debug}; @@ -15,8 +17,8 @@ use kempt::Map; use parking_lot::Mutex; use refuse::{AnyRef, AnyRoot, CollectionGuard, ContainsNoRefs, MapAs, Ref, Root, Trace}; -use crate::string::MuseString; -use crate::symbol::{Symbol, SymbolList, SymbolRef}; +use crate::runtime::string::MuseString; +use crate::runtime::symbol::{Symbol, SymbolList, SymbolRef}; use crate::vm::{Arity, ExecutionError, Fault, VmContext}; #[derive(Default, Clone, Copy, Debug)] diff --git a/src/tests.rs b/src/tests.rs index fce6091..c523333 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -2,12 +2,12 @@ use std::collections::VecDeque; use refuse::CollectionGuard; +use crate::compiler::syntax::token::{Paired, Token}; +use crate::compiler::syntax::{Expression, Ranged, SourceRange}; use crate::compiler::Compiler; -use crate::exception::Exception; -use crate::symbol::Symbol; -use crate::syntax::token::{Paired, Token}; -use crate::syntax::{Expression, Ranged, SourceRange}; -use crate::value::Value; +use crate::runtime::exception::Exception; +use crate::runtime::symbol::Symbol; +use crate::runtime::value::Value; use crate::vm::bitcode::BitcodeBlock; use crate::vm::{ExecutionError, Register, Vm}; diff --git a/src/vm.rs b/src/vm.rs index ca60d98..b16b5f7 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,3 +1,5 @@ +//! Virtual Machine types for executing Muse code. + use std::cmp::Ordering; use std::fmt::{Debug, Write}; use std::future::Future; @@ -23,16 +25,18 @@ use self::bitcode::trusted_loaded_source_to_value; use self::bitcode::{ BinaryKind, BitcodeFunction, FaultKind, Label, Op, OpDestination, ValueOrSource, }; +use crate::compiler::syntax::token::RegexLiteral; +use crate::compiler::syntax::{BitwiseKind, CompareKind, SourceCode, SourceRange}; use crate::compiler::{BitcodeModule, BlockDeclaration, Compiler, SourceMap, UnaryKind}; -use crate::exception::Exception; -use crate::regex::MuseRegex; -use crate::string::MuseString; -use crate::symbol::{IntoOptionSymbol, Symbol, SymbolRef}; -use crate::syntax::token::RegexLiteral; -use crate::syntax::{BitwiseKind, CompareKind, SourceCode, SourceRange}; +use crate::runtime::exception::Exception; +use crate::runtime::regex::MuseRegex; +use crate::runtime::string::MuseString; +use crate::runtime::symbol::{IntoOptionSymbol, Symbol, SymbolRef}; #[cfg(not(feature = "dispatched"))] -use crate::value::ContextOrGuard; -use crate::value::{CustomType, Dynamic, Rooted, RustType, StaticRustFunctionTable, Value}; +use crate::runtime::value::ContextOrGuard; +use crate::runtime::value::{ + CustomType, Dynamic, Rooted, RustType, StaticRustFunctionTable, Value, +}; pub mod bitcode; @@ -54,12 +58,18 @@ macro_rules! try_all { #[cfg(feature = "dispatched")] mod dispatched; +/// A virtual machine that executes compiled Muse [`Code`]. #[derive(Clone)] pub struct Vm { memory: Root, } impl Vm { + /// Returns a new virtual machine. + /// + /// Virtual machines are allocated within the [`refuse`] garbage collector. + /// Each virtual machine acts as a "root" reference to all of the values + /// contained within its stack and registers. #[must_use] pub fn new(guard: &CollectionGuard) -> Self { let parker = Parker::new(); @@ -89,6 +99,11 @@ impl Vm { } } + /// Compiles `source`, executes it, and returns the result. + /// + /// When building an interactive environment like a REPL, reusing a + /// [`Compiler`] and calling [`execute`](Self::execute) enables the compiler + /// to remember declarations from previous compilations. pub fn compile_and_execute<'a>( &self, source: impl Into>, @@ -100,6 +115,7 @@ impl Vm { Ok(self.execute(&code, guard)?) } + /// Executes `code` and returns the result. pub fn execute( &self, code: &Code, @@ -108,6 +124,7 @@ impl Vm { self.context(guard).execute(code) } + /// Executes `code` for at most `duration` before returning a timout. pub fn execute_for( &self, code: &Code, @@ -117,6 +134,7 @@ impl Vm { self.context(guard).execute_for(code, duration) } + /// Executes `code` for until `instant` before returning a timout. pub fn execute_until( &self, code: &Code, @@ -126,6 +144,16 @@ impl Vm { self.context(guard).execute_until(code, instant) } + /// Resumes executing the current code. + /// + /// This should only be called if an [`ExecutionError::Waiting`], + /// [`ExecutionError::NoBudget`], or [`ExecutionError::Timeout`] was + /// returned when executing code. + pub fn resume(&self, guard: &mut CollectionGuard) -> Result { + self.context(guard).resume() + } + + /// Returns a future that executes `code` asynchronously. pub fn execute_async<'context, 'guard>( &'context self, code: &Code, @@ -134,6 +162,11 @@ impl Vm { self.context(guard).execute_async(code) } + /// Resumes executing the current code asynchronously. + /// + /// This should only be called if an [`ExecutionError::Waiting`], + /// [`ExecutionError::NoBudget`], or [`ExecutionError::Timeout`] was + /// returned when executing code. pub fn resume_async<'context, 'guard>( &'context self, guard: &'context mut CollectionGuard<'guard>, @@ -141,14 +174,15 @@ impl Vm { self.context(guard).resume_async() } + /// Increases the current budget by `amount`. + /// + /// If the virtual machine currently is unbudgeted, calling this function + /// enables budgeting. pub fn increase_budget(&self, amount: usize) { self.memory.0.lock().budget.allocate(amount); } - pub fn resume(&self, guard: &mut CollectionGuard) -> Result { - self.context(guard).resume() - } - + /// Invokes a public function at path `name` with the given parameters. pub fn invoke( &self, name: impl Into, @@ -158,6 +192,8 @@ impl Vm { self.context(guard).invoke(name, params) } + /// Returns an execution context that synchronizes with the garbage + /// collector. pub fn context<'context, 'guard>( &'context self, guard: &'context mut CollectionGuard<'guard>, @@ -168,30 +204,51 @@ impl Vm { } } + /// Sets the number of virtual machine steps to take per budget being + /// charged. + /// + /// This also affects how often the virtual machine checks if it should + /// yield to the garbage collector. pub fn set_steps_per_charge(&self, steps: u16) { self.memory.0.lock().set_steps_per_charge(steps); } + /// Returns the value contained in `register`. #[must_use] pub fn register(&self, register: Register) -> Value { self.memory.0.lock()[register] } + /// Replaces the current value in `register` with `value`. #[allow(clippy::must_use_candidate)] pub fn set_register(&self, register: Register, value: Value) -> Value { std::mem::replace(&mut self.memory.0.lock()[register], value) } + /// Returns the value contained at `index` on the stack. + /// + /// # Panics + /// + /// This function panics if `index` is out of bounds of the stack. #[must_use] - pub fn stack(&self, index: usize) -> Value { - self.memory.0.lock()[index] + pub fn stack(&self, index: Stack) -> Value { + self.memory.0.lock()[index.0] } + /// Replaces the current value at `index` on the stack with `value`. + /// + /// # Panics + /// + /// This function panics if `index` is out of bounds of the stack. #[must_use] - pub fn set_stack(&self, index: usize, value: Value) -> Value { - std::mem::replace(&mut self.memory.0.lock()[index], value) + pub fn set_stack(&self, index: Stack, value: Value) -> Value { + std::mem::replace(&mut self.memory.0.lock()[index.0], value) } + /// Allocates a variable declaration. + /// + /// Returns a stack index that has been allocated. The Muse virtual machine + /// ensures the stack is Nil-initialized. pub fn declare_variable( &self, name: SymbolRef, @@ -201,6 +258,7 @@ impl Vm { VmContext::new(self, guard).declare_variable(name, mutable) } + /// Declares an immutable variable with `name` containing `value`. pub fn declare( &self, name: impl Into, @@ -210,15 +268,20 @@ impl Vm { VmContext::new(self, guard).declare_inner(name, value, false) } + /// Declares an mutable variable with `name` containing `value`. pub fn declare_mut( &self, name: impl Into, value: Value, guard: &mut CollectionGuard<'_>, ) -> Result, Fault> { - VmContext::new(self, guard).declare_inner(name, value, true) + VmContext::new(self, guard).declare_mut(name, value) } + /// Declares a compiled function. + /// + /// Returns a reference to the function, or `None` if the function could not + /// be declared because it has no name. pub fn declare_function( &self, function: Function, @@ -267,6 +330,11 @@ impl refuse::Trace for VmMemory { impl NoMapping for VmMemory {} +/// The state of a [`Vm`]. +/// +/// Virtual machines are garbage collected types, which requires interior +/// mutability so that the garbage collector can trace what every allocated +/// virtual machine is currently referencing. pub struct VmState { registers: [Value; 256], stack: Vec, @@ -287,13 +355,103 @@ pub struct VmState { } impl VmState { + /// Sets the number of virtual machine steps to take per budget being + /// charged. + /// + /// This also affects how often the virtual machine checks if it should + /// yield to the garbage collector. pub fn set_steps_per_charge(&mut self, steps: u16) { self.steps_per_charge = steps; self.counter = self.counter.min(steps); } } +impl Index for VmState { + type Output = Value; + + fn index(&self, index: Register) -> &Self::Output { + &self.registers[usize::from(index)] + } +} + +impl IndexMut for VmState { + fn index_mut(&mut self, index: Register) -> &mut Self::Output { + &mut self.registers[usize::from(index)] + } +} + +impl Index for VmState { + type Output = Value; + + fn index(&self, index: usize) -> &Self::Output { + &self.stack[index] + } +} + +impl IndexMut for VmState { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.stack[index] + } +} + +/// A virtual machine execution context. +/// +/// While a [`VmContext`] is held and not executing code, the garbage collector +/// cannot run and the virtual machine is exclusivly accessible by the current +/// thread. +pub struct VmContext<'a, 'guard> { + guard: &'a mut CollectionGuard<'guard>, + vm: MutexGuard<'a, VmState>, +} + impl<'context, 'guard> VmContext<'context, 'guard> { + /// Returns a new execution context for `vm` using `guard`. + pub fn new(vm: &'context Vm, guard: &'context mut CollectionGuard<'guard>) -> Self { + Self { + guard, + vm: vm.memory.0.lock(), + } + } + + /// Returns an exclusive reference to the collection guard. + #[must_use] + pub fn guard_mut(&mut self) -> &mut CollectionGuard<'guard> { + self.guard + } + + /// Returns a reference to the collection guard. + #[must_use] + pub fn guard(&self) -> &CollectionGuard<'guard> { + self.guard + } + + /// Returns a reference to the virtual machine. + pub fn vm(&mut self) -> &mut VmState { + &mut self.vm + } + + fn budget_and_yield(&mut self) -> Result<(), Fault> { + let next_count = self.counter - 1; + if next_count > 0 { + self.counter = next_count; + Ok(()) + } else { + self.counter = self.steps_per_charge; + self.budget.charge()?; + self.guard + .coordinated_yield(|yielder| MutexGuard::unlocked(&mut self.vm, || yielder.wait())); + self.check_timeout() + } + } + + /// Executes `func` while the virtual machine is unlocked. + /// + /// This can be used in combination with [`CollectionGuard::while_unlocked`] + /// to perform code while the garbage collector is free to execute. + pub fn while_unlocked(&mut self, func: impl FnOnce(&mut CollectionGuard<'_>) -> R) -> R { + MutexGuard::unlocked(&mut self.vm, || func(self.guard)) + } + fn push_code(&mut self, code: &Code, owner: Option<&Rooted>) -> CodeIndex { let vm = self.vm(); *vm.code_map @@ -308,11 +466,13 @@ impl<'context, 'guard> VmContext<'context, 'guard> { }) } + /// Executes `code` and returns the result. pub fn execute(&mut self, code: &Code) -> Result { let code = self.push_code(code, None); self.execute_owned(code) } + /// Executes `code` for at most `duration` before returning a timout. pub fn execute_for( &mut self, code: &Code, @@ -326,6 +486,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { ) } + /// Executes `code` for until `instant` before returning a timout. pub fn execute_until( &mut self, code: &Code, @@ -336,6 +497,26 @@ impl<'context, 'guard> VmContext<'context, 'guard> { self.execute_owned(code) } + /// Resumes executing the current code. + /// + /// This should only be called if an [`ExecutionError::Waiting`], + /// [`ExecutionError::NoBudget`], or [`ExecutionError::Timeout`] was + /// returned when executing code. + pub fn resume(&mut self) -> Result { + loop { + match self.resume_async_inner(0) { + Err(Fault::Waiting) => { + self.check_timeout() + .map_err(|err| ExecutionError::new(err, self))?; + + self.parker.park(); + } + Err(other) => return Err(ExecutionError::new(other, self)), + Ok(value) => return Ok(value), + } + } + } + fn prepare_owned(&mut self, code: CodeIndex) -> Result<(), ExecutionError> { let vm = self.vm(); vm.frames[vm.current_frame].code = Some(code); @@ -352,6 +533,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { self.resume() } + /// Returns a future that executes `code` asynchronously. pub fn execute_async( mut self, code: &Code, @@ -362,29 +544,24 @@ impl<'context, 'guard> VmContext<'context, 'guard> { Ok(ExecuteAsync(self)) } + /// Resumes executing the current code asynchronously. + /// + /// This should only be called if an [`ExecutionError::Waiting`], + /// [`ExecutionError::NoBudget`], or [`ExecutionError::Timeout`] was + /// returned when executing code. pub fn resume_async(self) -> Result, ExecutionError> { Ok(ExecuteAsync(self)) } + /// Increases the current budget by `amount`. + /// + /// If the virtual machine currently is unbudgeted, calling this function + /// enables budgeting. pub fn increase_budget(&mut self, amount: usize) { self.budget.allocate(amount); } - pub fn resume(&mut self) -> Result { - loop { - match self.resume_async_inner(0) { - Err(Fault::Waiting) => { - self.check_timeout() - .map_err(|err| ExecutionError::new(err, self))?; - - self.parker.park(); - } - Err(other) => return Err(ExecutionError::new(other, self)), - Ok(value) => return Ok(value), - } - } - } - + /// Invokes a public function at path `name` with the given parameters. pub fn invoke( &mut self, name: impl Into, @@ -446,7 +623,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } #[must_use] - pub fn waker(&self) -> &Waker { + pub(crate) fn waker(&self) -> &Waker { &self.waker } @@ -528,7 +705,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } } - pub fn enter_anonymous_frame(&mut self) -> Result<(), Fault> { + pub(crate) fn enter_anonymous_frame(&mut self) -> Result<(), Fault> { if self.has_anonymous_frame { self.current_frame += 1; Ok(()) @@ -554,7 +731,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { Err(Fault::FrameChanged) } - pub fn recurse_current_function(&mut self, arity: Arity) -> Result { + pub(crate) fn recurse_current_function(&mut self, arity: Arity) -> Result { let current_function = self.code[self.frames[self.current_frame] .code .expect("missing function") @@ -566,6 +743,10 @@ impl<'context, 'guard> VmContext<'context, 'guard> { current_function.call(self, arity) } + /// Allocates a variable declaration. + /// + /// Returns a stack index that has been allocated. The Muse virtual machine + /// ensures the stack is Nil-initialized. pub fn declare_variable(&mut self, name: SymbolRef, mutable: bool) -> Result { let vm = self.vm(); let current_frame = &mut vm.frames[vm.current_frame]; @@ -587,6 +768,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } } + /// Declares an immutable variable with `name` containing `value`. pub fn declare( &mut self, name: impl Into, @@ -595,6 +777,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { self.declare_inner(name, value, false) } + /// Declares an mutable variable with `name` containing `value`. pub fn declare_mut( &mut self, name: impl Into, @@ -626,6 +809,10 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } } + /// Declares a compiled function. + /// + /// Returns a reference to the function, or `None` if the function could not + /// be declared because it has no name. pub fn declare_function(&mut self, mut function: Function) -> Result, Fault> { let Some(name) = function.name().clone() else { return Ok(None); @@ -635,6 +822,9 @@ impl<'context, 'guard> VmContext<'context, 'guard> { self.declare_inner(name, Value::dynamic(function, &self), true) } + /// Resolves the value at `path`. + // TODO write better documentation, but I'm not sure this function is "done" + // with it's implementation yet. pub fn resolve(&mut self, name: &Symbol) -> Result { if let Some(path) = name.strip_prefix('$') { let mut module_dynamic = self.modules[0]; @@ -656,7 +846,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { .ok_or(Fault::UnknownSymbol)? .value; let Some(inner) = value.as_dynamic::() else { - return Err(Fault::MissingModule); + return Err(Fault::NotAModule); }; drop(declarations); module_dynamic = inner; @@ -703,7 +893,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } } - pub fn assign(&mut self, name: &SymbolRef, value: Value) -> Result<(), Fault> { + pub(crate) fn assign(&mut self, name: &SymbolRef, value: Value) -> Result<(), Fault> { let vm = &mut *self.vm; let current_frame = &mut vm.frames[vm.current_frame]; if let Some(decl) = current_frame.variables.get_mut(name) { @@ -764,7 +954,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } } - pub fn exit_frame(&mut self) -> Result<(), Fault> { + pub(crate) fn exit_frame(&mut self) -> Result<(), Fault> { let vm = self.vm(); if vm.current_frame >= 1 { vm.has_anonymous_frame = false; @@ -777,6 +967,8 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } } + /// Allocates `count` entries on the stack. Returns the first allocated + /// index. pub fn allocate(&mut self, count: usize) -> Result { let vm = self.vm(); let current_frame = &mut vm.frames[vm.current_frame]; @@ -793,6 +985,7 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } } + /// Returns a reference to the currently executing code. #[must_use] pub fn current_code(&self) -> Option<&Code> { self.frames[self.current_frame] @@ -800,32 +993,42 @@ impl<'context, 'guard> VmContext<'context, 'guard> { .map(|index| &self.code[index.0].code) } + /// Returns the instruction offset in the current frame. #[must_use] pub fn current_instruction(&self) -> usize { self.frames[self.current_frame].instruction } - pub fn jump_to(&mut self, instruction: usize) { + /// Jumps execution to `instruction`. + pub(crate) fn jump_to(&mut self, instruction: usize) { let vm = self.vm(); vm.frames[vm.current_frame].instruction = instruction; } + /// Returns a slice of the current stack frame. #[must_use] pub fn current_frame(&self) -> &[Value] { &self.stack[self.frames[self.current_frame].start..self.frames[self.current_frame].end] } + /// Returns an exclusive reference to the slice of the current stack frame. #[must_use] pub fn current_frame_mut(&mut self) -> &mut [Value] { let vm = self.vm(); &mut vm.stack[vm.frames[vm.current_frame].start..vm.frames[vm.current_frame].end] } + /// Returns the size of the current stack frame. #[must_use] pub fn current_frame_size(&self) -> usize { self.frames[self.current_frame].end - self.frames[self.current_frame].start } + /// Resets the stack and registers to their initial state and clears any + /// executing code. + /// + /// All declarations and modules will still be loaded in the virtual + /// machine. pub fn reset(&mut self) { self.current_frame = 0; for frame in &mut self.frames { @@ -835,8 +1038,9 @@ impl<'context, 'guard> VmContext<'context, 'guard> { self.stack.fill_with(|| Value::Nil); } + /// Generates a backtrace for the current virtual machine state. #[must_use] - pub fn stack_trace(&self) -> Vec { + pub fn backtrace(&self) -> Vec { self.frames[..=self.current_frame] .iter() .filter_map(|f| { @@ -876,80 +1080,6 @@ impl<'context, 'guard> VmContext<'context, 'guard> { } } -impl Index for VmState { - type Output = Value; - - fn index(&self, index: Register) -> &Self::Output { - &self.registers[usize::from(index)] - } -} - -impl IndexMut for VmState { - fn index_mut(&mut self, index: Register) -> &mut Self::Output { - &mut self.registers[usize::from(index)] - } -} - -impl Index for VmState { - type Output = Value; - - fn index(&self, index: usize) -> &Self::Output { - &self.stack[index] - } -} - -impl IndexMut for VmState { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.stack[index] - } -} - -pub struct VmContext<'a, 'guard> { - guard: &'a mut CollectionGuard<'guard>, - vm: MutexGuard<'a, VmState>, -} - -impl<'a, 'guard> VmContext<'a, 'guard> { - pub fn new(vm: &'a Vm, guard: &'a mut CollectionGuard<'guard>) -> Self { - Self { - guard, - vm: vm.memory.0.lock(), - } - } - - #[must_use] - pub fn guard_mut(&mut self) -> &mut CollectionGuard<'guard> { - self.guard - } - - #[must_use] - pub fn guard(&self) -> &CollectionGuard<'guard> { - self.guard - } - - pub fn vm(&mut self) -> &mut VmState { - &mut self.vm - } - - fn budget_and_yield(&mut self) -> Result<(), Fault> { - let next_count = self.counter - 1; - if next_count > 0 { - self.counter = next_count; - Ok(()) - } else { - self.counter = self.steps_per_charge; - self.budget.charge()?; - self.guard - .coordinated_yield(|yielder| MutexGuard::unlocked(&mut self.vm, || yielder.wait())); - self.check_timeout() - } - } - - pub fn while_unlocked(&mut self, func: impl FnOnce(&mut CollectionGuard<'_>) -> R) -> R { - MutexGuard::unlocked(&mut self.vm, || func(self.guard)) - } -} - impl<'guard> AsRef> for VmContext<'_, 'guard> { fn as_ref(&self) -> &CollectionGuard<'guard> { self.guard() @@ -1635,8 +1765,8 @@ impl From for MaybeOwnedValue<'_> { } } +/// A virtual machine register index. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] - pub struct Register(pub u8); impl From for Register { @@ -1659,6 +1789,7 @@ impl From for usize { } } +/// A value was given that is not a valid register. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct InvalidRegister; @@ -1685,11 +1816,16 @@ impl Frame { } } +/// An error that arises from executing Muse code. #[derive(Debug, PartialEq, Clone, Trace)] pub enum ExecutionError { + /// The budget for the virtual machine has been exhausted. NoBudget, + /// The virtual machine is waiting for an async task. Waiting, + /// Execution did not complete before a timeout occurred. Timeout, + /// A thrown exception was not handled. Exception(Value), } @@ -1705,34 +1841,68 @@ impl ExecutionError { } } +/// A virtual machine error. #[derive(Debug, PartialEq, Clone)] +#[non_exhaustive] pub enum Fault { + /// The budget for the virtual machine has been exhausted. + NoBudget, + /// Execution did not complete before a timeout occurred. + Timeout, + /// The virtual machine is waiting for an async task. + Waiting, + /// A thrown exception was not handled. + Exception(Value), + /// The execution frame has changed. + FrameChanged, + /// A symbol could not be resolved to a declaration or function. UnknownSymbol, + /// A function was invoked with an unsupported number of arguments. IncorrectNumberOfArguments, + /// A pattern could not be matched. PatternMismatch, + /// An unsupported operation was performed on [`Value::Nil`]. OperationOnNil, + /// A value was freed. + /// + /// This, in general, should not happen, but it can happen through + /// intentional usage or if a type does not implement [`Trace`] correctly. ValueFreed, - MissingModule, + /// A value was expected to be a module, but was not. + NotAModule, + /// A value was invoked but is not invokable. NotAFunction, + /// An allocation failed because the stack is full. StackOverflow, + /// An attempt to return from the execution root. StackUnderflow, + /// A general error indicating an unsupported operation. UnsupportedOperation, + /// An allocation could not be performed due to memory constraints. OutOfMemory, + /// An operation attempted to access something outside of its bounds. OutOfBounds, + /// An assignment was attempted to an immutable declaration. NotMutable, + /// A value was divided by zero. DivideByZero, + /// Execution jumped to an invalid instruction address. InvalidInstructionAddress, + /// An operation expected a symbol. ExpectedSymbol, + /// An operation expected an integer. ExpectedInteger, + /// An operation expected a string. ExpectedString, + /// An invalid value was provided for the [`Arity`] of a function. InvalidArity, + /// An invalid label was encountered. InvalidLabel, + /// An instruction referenced an invalid index. + /// + /// This is differen than a [`Fault::OutOfBounds`] because this fault + /// indicates invalid code rather than a logic error. InvalidOpcode, - NoBudget, - Timeout, - Waiting, - FrameChanged, - Exception(Value), } impl Fault { @@ -1740,6 +1910,7 @@ impl Fault { matches!(self, Fault::NoBudget | Fault::Waiting | Fault::Timeout) } + /// Converts this fault into an exception. #[must_use] pub fn as_exception(&self, vm: &mut VmContext<'_, '_>) -> Value { let exception = match self { @@ -1748,7 +1919,7 @@ impl Fault { Fault::OperationOnNil => Symbol::from("nil").into(), Fault::ValueFreed => Symbol::from("out-of-scope").into(), Fault::NotAFunction => Symbol::from("not_invokable").into(), - Fault::MissingModule => Symbol::from("internal").into(), + Fault::NotAModule => Symbol::from("not_a_module").into(), Fault::StackOverflow => Symbol::from("overflow").into(), Fault::StackUnderflow => Symbol::from("underflow").into(), Fault::UnsupportedOperation => Symbol::from("unsupported").into(), @@ -1782,6 +1953,7 @@ impl Fault { } } +/// A Muse function ready for execution. #[derive(Debug, Clone)] pub struct Function { module: Option, @@ -1791,6 +1963,7 @@ pub struct Function { } impl Function { + /// Returns a function with the given name and no bodies. #[must_use] pub fn new(name: impl IntoOptionSymbol) -> Self { Self { @@ -1806,16 +1979,21 @@ impl Function { self } + /// Inserts `body` to be executed when this function is invoked with `arity` + /// number of arguments. pub fn insert_arity(&mut self, arity: impl Into, body: Code) { self.bodies.insert(arity.into(), body); } + /// Adds `body` to be executed when this function is invoked with `arity` + /// number of arguments, and returns the updated function. #[must_use] pub fn when(mut self, arity: impl Into, body: Code) -> Self { self.insert_arity(arity, body); self } + /// Returns the name of this function. #[must_use] pub const fn name(&self) -> &Option { &self.name @@ -1823,7 +2001,7 @@ impl Function { } impl CustomType for Function { - fn muse_type(&self) -> &crate::value::TypeRef { + fn muse_type(&self) -> &crate::runtime::value::TypeRef { static TYPE: RustType = RustType::new("Function", |t| { t.with_call(|_| { |this, vm, arity| { @@ -1833,7 +2011,7 @@ impl CustomType for Function { .rev() .find_map(|va| (va.key() <= &arity).then_some(&va.value)) }) { - let module = this.module.ok_or(Fault::MissingModule)?; + let module = this.module.ok_or(Fault::NotAModule)?; vm.execute_function(body, &this, module) } else { Err(Fault::IncorrectNumberOfArguments) @@ -1848,6 +2026,7 @@ impl CustomType for Function { impl ContainsNoRefs for Function {} +/// The number of arguments provided to a function. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)] pub struct Arity(pub u8); @@ -1877,12 +2056,14 @@ impl TryFrom for Arity { } } +/// A series of instructions that are ready to execute. #[derive(Debug, Clone)] pub struct Code { data: Arc, } impl Code { + /// Pushes another operation to this code block. pub fn push(&mut self, op: &Op, range: SourceRange, guard: &CollectionGuard) { Arc::make_mut(&mut self.data).push(op, range, guard); } @@ -2148,6 +2329,9 @@ impl From> for StepResult { } } +/// A Muse module. +/// +/// A module enables encapsulating and namespacing code and declarations. #[derive(Default, Debug)] pub struct Module { parent: Option>, @@ -2155,6 +2339,7 @@ pub struct Module { } impl Module { + /// Returns a new module with the built-in `core` module loaded. #[must_use] pub fn with_core(guard: &CollectionGuard) -> Dynamic { let module = Dynamic::new(Self::default(), guard); @@ -2179,6 +2364,7 @@ impl Module { module } + /// Returns the default `core` module. pub fn core() -> Self { let core = Self::default(); @@ -2187,21 +2373,21 @@ impl Module { SymbolRef::from("Map"), ModuleDeclaration { mutable: false, - value: Value::Dynamic(crate::map::MAP_TYPE.as_any_dynamic()), + value: Value::Dynamic(crate::runtime::map::MAP_TYPE.as_any_dynamic()), }, ); declarations.insert( SymbolRef::from("List"), ModuleDeclaration { mutable: false, - value: Value::Dynamic(crate::list::LIST_TYPE.as_any_dynamic()), + value: Value::Dynamic(crate::runtime::list::LIST_TYPE.as_any_dynamic()), }, ); declarations.insert( SymbolRef::from("String"), ModuleDeclaration { mutable: false, - value: Value::Dynamic(crate::string::STRING_TYPE.as_any_dynamic()), + value: Value::Dynamic(crate::runtime::string::STRING_TYPE.as_any_dynamic()), }, ); drop(declarations); @@ -2215,7 +2401,7 @@ impl Module { } impl CustomType for Module { - fn muse_type(&self) -> &crate::value::TypeRef { + fn muse_type(&self) -> &crate::runtime::value::TypeRef { static TYPE: RustType = RustType::new("Module", |t| { t.with_invoke(|_| { |this, vm, name, arity| { @@ -2301,6 +2487,8 @@ impl Budget { } } +/// An asynchronous code execution. +#[must_use = "futures must be awaited to be exected"] pub struct ExecuteAsync<'context, 'guard>(VmContext<'context, 'guard>); impl Future for ExecuteAsync<'_, '_> { @@ -2451,10 +2639,12 @@ fn precompiled_regex(regex: &RegexLiteral, guard: &CollectionGuard) -> Precompil } } +/// An offset into a virtual machine stack. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(transparent)] pub struct Stack(pub usize); +/// Information about an executing stack frame. #[derive(PartialEq, Clone)] pub struct StackFrame { code: Code, @@ -2463,20 +2653,23 @@ pub struct StackFrame { impl StackFrame { #[must_use] - pub fn new(code: Code, instruction: usize) -> Self { + fn new(code: Code, instruction: usize) -> Self { Self { code, instruction } } + /// Returns the code executing in this frame. #[must_use] pub const fn code(&self) -> &Code { &self.code } + /// Returns the instruction offset of the frame. #[must_use] pub const fn instruction(&self) -> usize { self.instruction } + /// Returns the source range for this instruction, if available. #[must_use] pub fn source_range(&self) -> Option { self.code.data.map.get(self.instruction) @@ -2492,7 +2685,10 @@ impl Debug for StackFrame { } } +/// A set of arguments that can be loaded into a virtual machine when invoking a +/// function. pub trait InvokeArgs { + /// Loads the arguments into `vm`. fn load(self, vm: &mut VmContext<'_, '_>) -> Result; } diff --git a/src/vm/bitcode.rs b/src/vm/bitcode.rs index 23aa56f..6ef0b43 100644 --- a/src/vm/bitcode.rs +++ b/src/vm/bitcode.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + use std::ops::{Deref, DerefMut}; use std::str; @@ -8,10 +10,10 @@ use serde::{Deserialize, Serialize}; #[cfg(not(feature = "dispatched"))] use super::LoadedOp; use super::{Arity, Code, CodeData, Function, LoadedSource, Register}; +use crate::compiler::syntax::token::RegexLiteral; +use crate::compiler::syntax::{BitwiseKind, CompareKind, Literal, SourceRange}; use crate::compiler::{BitcodeModule, SourceMap, UnaryKind}; -use crate::symbol::Symbol; -use crate::syntax::token::RegexLiteral; -use crate::syntax::{BitwiseKind, CompareKind, Literal, SourceRange}; +use crate::runtime::symbol::Symbol; use crate::vm::Stack; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/vm/dispatched.rs b/src/vm/dispatched.rs index ceeb50f..0cfded5 100644 --- a/src/vm/dispatched.rs +++ b/src/vm/dispatched.rs @@ -15,9 +15,9 @@ use super::{ VmContext, }; use crate::compiler::{BitcodeModule, UnaryKind}; -use crate::symbol::Symbol; +use crate::runtime::symbol::Symbol; +use crate::runtime::value::{ContextOrGuard, Dynamic, Value}; use crate::syntax::{BitwiseKind, CompareKind}; -use crate::value::{ContextOrGuard, Dynamic, Value}; use crate::vm::{Fault, SourceRange}; impl CodeData { diff --git a/tests/harness.rs b/tests/harness.rs index baa7b9f..1b83de2 100644 --- a/tests/harness.rs +++ b/tests/harness.rs @@ -1,17 +1,19 @@ +//! A test harness that executes `.rsn` tests in the `cases` directory. + use std::collections::BTreeMap; use std::path::Path; use std::process::exit; +use muse::compiler::syntax::token::RegexLiteral; +use muse::compiler::syntax::{Ranged, SourceRange}; use muse::compiler::{Compiler, Error}; -use muse::exception::Exception; -use muse::list::List; -use muse::map::Map; -use muse::regex::{MuseMatch, MuseRegex}; -use muse::string::MuseString; -use muse::symbol::Symbol; -use muse::syntax::token::RegexLiteral; -use muse::syntax::{Ranged, SourceRange}; -use muse::value::Value; +use muse::runtime::exception::Exception; +use muse::runtime::list::List; +use muse::runtime::map::Map; +use muse::runtime::regex::{MuseMatch, MuseRegex}; +use muse::runtime::string::MuseString; +use muse::runtime::symbol::Symbol; +use muse::runtime::value::Value; use muse::vm::{Code, ExecutionError, StackFrame, Vm}; use refuse::CollectionGuard; use serde::de::Visitor; @@ -152,7 +154,7 @@ impl From for TestOutput { } } else if let Some(m) = v.downcast_ref::(&guard) { TestOutput::Exception { - value: Box::new(Self::from(*m.value())), + value: Box::new(Self::from(m.value())), backtrace: m .backtrace() .iter() diff --git a/tests/hosted.rs b/tests/hosted.rs index 4da4165..032a18f 100644 --- a/tests/hosted.rs +++ b/tests/hosted.rs @@ -1,13 +1,15 @@ +//! A test harness for Muse-defined tests in the `cases` directory. + use std::collections::VecDeque; use std::path::Path; use std::process::exit; +use muse::compiler::syntax::token::{Paired, Token}; +use muse::compiler::syntax::Ranged; use muse::compiler::Compiler; -use muse::exception::Exception; -use muse::symbol::Symbol; -use muse::syntax::token::{Paired, Token}; -use muse::syntax::Ranged; -use muse::value::{CustomType, RustFunction, RustType, Value}; +use muse::runtime::exception::Exception; +use muse::runtime::symbol::Symbol; +use muse::runtime::value::{CustomType, RustFunction, RustType, Value}; use muse::vm::{ExecutionError, Fault, Register, Vm, VmContext}; use refuse::{CollectionGuard, Trace}; @@ -205,7 +207,7 @@ struct TestError { } impl CustomType for TestError { - fn muse_type(&self) -> &muse::value::TypeRef { + fn muse_type(&self) -> &muse::runtime::value::TypeRef { static TYPE: RustType = RustType::new("TestError", |t| t); &TYPE }