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;
    }
}