use std::{io::Read, ops::Deref, sync::Arc};
use crate::{
compilers::{CompiledCode, Compiler},
runtimes::{CodeRuntime, ExecutionResult},
};
use super::{
compiler::{CompilationError, CompilationResult},
preprocessor::{Preprocessor, PreprocessorBundle},
};
pub struct RuntimeBuilder<C: Compiler<R>, R: CodeRuntime> {
preprocessors: Vec<Arc<dyn Preprocessor>>,
compiler: Option<C>,
runtime: Option<R>,
compiler_config: Option<C::Config>,
runtime_config: Option<R::Config>,
}
#[derive(Debug, Clone)]
pub enum RuntimeBuilderError {
CompilerNotSet,
RuntimeNotSet,
}
type RuntimeBuilderResult<T> = Result<T, RuntimeBuilderError>;
impl<C: Compiler<R> + 'static, R: CodeRuntime + 'static> RuntimeBuilder<C, R> {
pub const fn new() -> Self {
Self {
preprocessors: Vec::new(),
compiler: None,
runtime: None,
compiler_config: None,
runtime_config: None,
}
}
pub fn preprocessor(mut self, preprocessor: impl Preprocessor + 'static) -> Self {
self.preprocessors.push(Arc::new(preprocessor));
self
}
pub fn preprocessor_bundle(mut self, bundle: &PreprocessorBundle) -> Self {
self.preprocessors
.extend(bundle.preprocessors.iter().cloned());
self
}
pub fn compiler(mut self, compiler: C, config: Option<C::Config>) -> Self {
self.compiler = Some(compiler);
self.compiler_config = config;
self
}
pub fn runtime(mut self, runtime: R, config: Option<R::Config>) -> Self {
self.runtime = Some(runtime);
self.runtime_config = config;
self
}
pub fn build(mut self) -> RuntimeBuilderResult<CustomRuntime<R>> {
let compiler = self
.compiler
.take()
.ok_or(RuntimeBuilderError::CompilerNotSet)?;
let runtime = self
.runtime
.take()
.ok_or(RuntimeBuilderError::RuntimeNotSet)?;
let compiler_config = self.compiler_config.take().unwrap_or_default();
let runtime_config = self.runtime_config.take().unwrap_or_default();
let cf = move |code: &mut dyn std::io::Read| -> CompilationResult<CompiledCode<R>> {
let mut code = std::io::BufReader::new(code);
let mut code_str = String::new();
code.read_to_string(&mut code_str).unwrap();
let mut code = code_str;
for preprocessor in self.preprocessors.iter() {
code = preprocessor.preprocess(&code)?;
}
let compiled_code = compiler.compile(&mut code.as_bytes(), compiler_config.clone())?;
Ok(compiled_code)
};
let rf = move |compiled_code: &CompiledCode<R>| -> Result<ExecutionResult, R::Error> {
runtime.run(compiled_code, runtime_config.clone())
};
Ok(CustomRuntime::new(cf, rf))
}
}
pub struct CustomRuntime<R: CodeRuntime> {
#[allow(clippy::type_complexity)]
crf: Box<dyn Fn(&mut dyn std::io::Read) -> Result<ExecutionResult, CustomRuntimeError<R>>>,
}
impl<R: CodeRuntime> CustomRuntime<R> {
#[allow(clippy::type_complexity)]
pub(crate) fn new(
cf: impl Fn(&mut dyn std::io::Read) -> CompilationResult<CompiledCode<R>> + 'static,
rf: impl Fn(&CompiledCode<R>) -> Result<ExecutionResult, R::Error> + 'static,
) -> Self {
Self {
crf: Box::new(move |code| {
let compiled_code =
cf(code).map_err(|e| CustomRuntimeError::CompilationError(e))?;
(rf)(&compiled_code).map_err(|e| CustomRuntimeError::RuntimeError(e))
}),
}
}
pub fn run(
&self,
code: &mut dyn std::io::Read,
) -> Result<ExecutionResult, CustomRuntimeError<R>> {
(self.crf)(code)
}
}
#[allow(clippy::type_complexity)]
impl<R: CodeRuntime + 'static> Deref for CustomRuntime<R> {
type Target = dyn Fn(&mut dyn std::io::Read) -> Result<ExecutionResult, CustomRuntimeError<R>>;
fn deref(&self) -> &Self::Target {
&self.crf
}
}
#[derive(Debug)]
pub enum CustomRuntimeError<R: CodeRuntime> {
CompilationError(CompilationError),
RuntimeError(R::Error),
}
impl<R: CodeRuntime> From<CompilationError> for CustomRuntimeError<R> {
fn from(error: CompilationError) -> Self {
Self::CompilationError(error)
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "wasm")]
use crate::{compilers::rust_compiler::RustCompiler, runtimes::wasm_runtime::WasmRuntime};
use super::RuntimeBuilder;
#[test]
#[cfg(feature = "wasm")]
fn test_builder_rust_wasm() {
let rust_wasm_runtime = RuntimeBuilder::new()
.compiler(RustCompiler, None)
.runtime(WasmRuntime, None)
.build()
.unwrap();
let code = r#"
fn main() {
println!("Hello, world!");
}
"#;
assert_eq!(
rust_wasm_runtime(&mut code.as_bytes()).unwrap().stdout,
Some("Hello, world!\n".to_string())
);
let code = r#"
fn main() {
println!("Hello, world!");
println!("Hello, world!");
}
"#;
assert_eq!(
rust_wasm_runtime(&mut code.as_bytes()).unwrap().stdout,
Some("Hello, world!\nHello, world!\n".to_string())
);
}
}