1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
// VM.rs
// by Lut99
//
// Created:
// 12 Sep 2022, 17:41:33
// Last edited:
// 12 Dec 2023, 17:20:22
// Auto updated?
// Yes
//
// Description:
//! Implements the VM trait, which is a simple trait for defining VMs
//! that use threads.
//
use std::sync::{Arc, RwLock};
use async_trait::async_trait;
use brane_ast::{SymTable, Workflow};
use specifications::profiling::ProfileScopeHandle;
use crate::errors::VmError;
use crate::spec::{CustomGlobalState, CustomLocalState, RunState, VmPlugin};
use crate::thread::Thread;
use crate::value::FullValue;
/***** LIBRARY *****/
/// Defines a common interface (and some code) for virtual machines.
#[async_trait]
pub trait Vm {
/// The type of the thread-global extension to the runtime state.
type GlobalState: CustomGlobalState;
/// The type of the thread-local extension to the runtime state.
type LocalState: CustomLocalState;
// Child-specific
/// A function that stores the given runtime state information in the parent struct.
///
/// This is important and will be used later.
///
/// # Arguments
/// - `state`: The current state of the workflow we have executed.
///
/// # Returns
/// Nothing, but should change the internals to return this state later upon request.
///
/// # Errors
/// This function may error for its own reasons.
fn store_state(this: &Arc<RwLock<Self>>, state: RunState<Self::GlobalState>) -> Result<(), VmError>;
/// A function that returns the VM's runtime state in the parent struct.
///
/// This is important and will be used later.
///
/// # Returns
/// The RunState of this application if it exists, or else None.
///
/// # Errors
/// This function may error for its own reasons.
fn load_state(this: &Arc<RwLock<Self>>) -> Result<RunState<Self::GlobalState>, VmError>;
// Global
/// Initializes a new global state based on the given custom part.
///
/// # Arguments
/// - `pindex`: The package index which we can use for resolving packages.
/// - `dindex`: The data index which we can use for resolving datasets.
/// - `custom`: The custom part of the global state with which we will initialize it.
///
/// # Returns
/// A new RunState instance.
#[inline]
fn new_state(custom: Self::GlobalState) -> RunState<Self::GlobalState> { RunState::new(Arc::new(SymTable::new()), Arc::new(RwLock::new(custom))) }
/// Runs the given workflow, possibly asynchronously (if a parallel is encountered / there are external functions calls and the given closure runs this asynchronously.)
///
/// # Generic arguments
/// - `P`: The "VM plugin" that will fill in the blanks with respect to interacting with the outside world.
///
/// # Arguments
/// - `snippet`: The snippet to compile. This is either the entire workflow, or a snippet of it. In the case of the latter, the internal state will be used (and updated).
/// - `prof`: The ProfileScope that can be used to provide additional information about the timings of the VM (framework-wise, not user-wise).
///
/// # Returns
/// The result if the Workflow returned any.
async fn run<P: VmPlugin<GlobalState = Self::GlobalState, LocalState = Self::LocalState>>(
this: Arc<RwLock<Self>>,
snippet: Workflow,
prof: ProfileScopeHandle<'_>,
) -> Result<FullValue, VmError>
where
Self: Sync,
{
// Fetch the previous state (if any)
let mut state: RunState<Self::GlobalState> = Self::load_state(&this)?;
state.fstack.update_table(snippet.table.clone());
// Create a new thread with (a copy of) the internal state, if any.
let main: Thread<Self::GlobalState, Self::LocalState> = Thread::from_state(&snippet, state);
// Run the workflow
match main.run_snippet::<P>(prof.into()).await {
Ok((res, state)) => {
// Convert the value into a full value (if any)
let res: FullValue = res.into_full(state.fstack.table());
// Store the state
Self::store_state(&this, state)?;
// Done, return
Ok(res)
},
Err(err) => Err(err),
}
}
}
/***** TESTS *****/
#[cfg(test)]
pub mod tests {
use brane_ast::fetcher::SnippetFetcher;
use brane_ast::state::CompileState;
use brane_ast::traversals::print::ast;
use brane_ast::{CompileResult, ParserOptions, compile_snippet};
use brane_shr::utilities::{create_data_index, create_package_index, test_on_dsl_files_async};
use specifications::data::DataIndex;
use specifications::package::PackageIndex;
use super::*;
use crate::dummy::DummyVm;
/// Tests the traversal by generating symbol tables for every file.
#[tokio::test]
async fn test_snippets() {
// Run the tests on all the files
test_on_dsl_files_async("BraneScript", |path, code| {
async move {
// Start by the name to always know which file this is
println!("{}", (0..80).map(|_| '-').collect::<String>());
println!("File '{}' gave us:", path.display());
// Load the package index
let pindex: PackageIndex = create_package_index();
let dindex: DataIndex = create_data_index();
// Run the program but now line-by-line (to test the snippet function)
let mut source: String = String::new();
let mut state: CompileState = CompileState::new();
let mut vm: DummyVm = DummyVm::new();
let mut iter = code.split('\n');
for (offset, l) in SnippetFetcher::new(|| Ok(iter.next().map(|l| l.into()))) {
// Append the source (for errors only)
source.push_str(&l);
// Compile the workflow
let workflow: Workflow = match compile_snippet(&mut state, l.as_bytes(), &pindex, &dindex, &ParserOptions::bscript()) {
CompileResult::Workflow(wf, warns) => {
// Print warnings if any
for w in warns {
w.prettyprint(path.to_string_lossy(), &source);
}
wf
},
CompileResult::Eof(err) => {
// Fetch more data instead
err.prettyprint(path.to_string_lossy(), &source);
panic!("Failed to compile to workflow (see output above)");
},
CompileResult::Err(errs) => {
// Print the errors
for e in errs {
e.prettyprint(path.to_string_lossy(), &source);
}
panic!("Failed to compile to workflow (see output above)");
},
_ => {
unreachable!();
},
};
// Print the file itself
ast::do_traversal(&workflow, std::io::stdout()).unwrap();
println!("{}", (0..40).map(|_| "- ").collect::<String>());
// Run the VM on this snippet
vm = match vm.exec(workflow).await {
(vm, Ok(value)) => {
println!("Workflow stdout:");
vm.flush_stdout();
println!();
println!("Workflow returned: {value:?}");
vm
},
(_, Err(err)) => {
eprintln!("{err}");
panic!("Failed to execute workflow (snippet) (see output above)");
},
};
// Increment the state offset
state.offset += offset.line;
}
println!("{}\n\n", (0..80).map(|_| '-').collect::<String>());
}
})
.await;
}
}