use crate::script_engine::compiler::CompiledScript;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use super::compiler::{Compiler, CompilerConfig};
use super::instruction::ScriptError;
use super::parser::{Parser, ParserConfig};
use super::{InstructionRegistry, VM, VMConfig};
#[derive(Clone)]
pub struct InterruptController {
flag: Arc<AtomicBool>,
}
impl InterruptController {
pub fn new() -> Self {
Self {
flag: Arc::new(AtomicBool::new(false)),
}
}
#[inline]
pub fn request_interrupt(&self) {
self.flag.store(true, Ordering::SeqCst);
}
#[inline]
pub fn is_interrupted(&self) -> bool {
self.flag.load(Ordering::SeqCst)
}
pub(crate) fn get_flag(&self) -> Arc<AtomicBool> {
self.flag.clone()
}
#[inline]
pub fn reset(&self) {
self.flag.store(false, Ordering::SeqCst);
}
}
impl Default for InterruptController {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct ScriptConfig {
pub parser: ParserConfig,
pub compiler: CompilerConfig,
pub vm: VMConfig,
}
pub struct ScriptEngine {
registry: InstructionRegistry,
config: ScriptConfig,
}
impl ScriptEngine {
pub fn new() -> Self {
Self {
registry: InstructionRegistry::new(),
config: ScriptConfig::default(),
}
}
pub fn with_config(config: ScriptConfig) -> Self {
Self {
registry: InstructionRegistry::new(),
config,
}
}
#[cfg(feature = "scripts_builtin")]
pub fn with_builtin() -> Self {
let mut registry = InstructionRegistry::new();
crate::scripts_builtin::register_all(&mut registry);
Self::with_registry(registry)
}
pub fn with_registry(registry: InstructionRegistry) -> Self {
Self::with_registry_and_config(registry, ScriptConfig::default())
}
pub fn with_registry_and_config(registry: InstructionRegistry, config: ScriptConfig) -> Self {
Self { registry, config }
}
pub fn compile(&self, text: &str) -> Result<CompiledScript, ScriptError> {
let parser = Parser::new(self.config.parser.clone(), &self.registry);
let compiler = Compiler::new(self.config.compiler.clone(), &self.registry);
let ast = parser.parse(text)?;
let script = compiler.compile(&ast)?;
Ok(script)
}
pub fn execute(&self, script: &CompiledScript) -> Result<(), ScriptError> {
let mut vm = VM::new(self.config.vm.clone(), &self.registry);
vm.execute(script)
}
pub fn execute_with_interrupt(
&self,
script: &CompiledScript,
interrupt: &InterruptController,
) -> Result<(), ScriptError> {
let mut vm =
VM::new_with_interrupt(self.config.vm.clone(), &self.registry, interrupt.get_flag());
vm.execute(script)
}
pub fn execute_with_context<F>(
&self,
script: &CompiledScript,
setup_fn: F,
) -> Result<(), ScriptError>
where
F: FnOnce(&mut super::vm::VMContext),
{
let mut vm = VM::new(self.config.vm.clone(), &self.registry);
setup_fn(vm.get_context_mut());
vm.execute(script)
}
pub fn execute_with_context_and_interrupt<F>(
&self,
script: &CompiledScript,
setup_fn: F,
interrupt: &InterruptController,
) -> Result<(), ScriptError>
where
F: FnOnce(&mut super::vm::VMContext),
{
let mut vm =
VM::new_with_interrupt(self.config.vm.clone(), &self.registry, interrupt.get_flag());
setup_fn(vm.get_context_mut());
vm.execute(script)
}
pub fn compile_and_execute(&self, text: &str) -> Result<(), ScriptError> {
let script = self.compile(text)?;
self.execute(&script)
}
pub fn compile_and_execute_with_interrupt(
&self,
text: &str,
interrupt: &InterruptController,
) -> Result<(), ScriptError> {
let script = self.compile(text)?;
self.execute_with_interrupt(&script, interrupt)
}
pub fn compile_and_execute_with_context<F>(
&self,
text: &str,
setup_fn: F,
) -> Result<(), ScriptError>
where
F: FnOnce(&mut super::vm::VMContext),
{
let script = self.compile(text)?;
self.execute_with_context(&script, setup_fn)
}
pub fn compile_and_execute_with_context_and_interrupt<F>(
&self,
text: &str,
setup_fn: F,
interrupt: &InterruptController,
) -> Result<(), ScriptError>
where
F: FnOnce(&mut super::vm::VMContext),
{
let script = self.compile(text)?;
self.execute_with_context_and_interrupt(&script, setup_fn, interrupt)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::script_engine::instruction::{
BlockStructure, InstructionData, InstructionHandler, InstructionMetadata,
};
use crate::script_engine::vm::VMContext;
#[derive(Debug, Clone, Default)]
struct TestLoopState {
remaining: u32,
start_ip: usize,
#[allow(dead_code)] end_ip: usize,
}
struct EndHandler;
impl InstructionHandler for EndHandler {
fn name(&self) -> &str {
"end"
}
fn parse(&self, _args: &[&str]) -> Result<InstructionData, ScriptError> {
Ok(InstructionData::None)
}
fn execute(
&self,
vm: &mut VMContext,
_data: &InstructionData,
_metadata: Option<&InstructionMetadata>,
) -> Result<(), ScriptError> {
let next_action = {
let loop_stack =
vm.get_or_create_execution_state::<Vec<TestLoopState>>("__test_loop_stack");
if let Some(mut state) = loop_stack.pop() {
state.remaining -= 1;
if state.remaining > 0 {
Some((state.start_ip + 1, state)) } else {
None
}
} else {
None
}
};
if let Some((jump_target, state)) = next_action {
vm.ip = jump_target;
let loop_stack =
vm.get_or_create_execution_state::<Vec<TestLoopState>>("__test_loop_stack");
loop_stack.push(state);
}
Ok(())
}
}
struct TestLoopHandler;
impl InstructionHandler for TestLoopHandler {
fn name(&self) -> &str {
"loop"
}
fn parse(&self, args: &[&str]) -> Result<InstructionData, ScriptError> {
if args.is_empty() {
return Err(ScriptError::ParseError("Missing loop iterations".into()));
}
let n = args[0]
.parse::<u32>()
.map_err(|e| ScriptError::ParseError(format!("Invalid loop count: {}", e)))?;
if n == 0 {
return Err(ScriptError::ParseError(
"Loop iterations must be greater than 0".into(),
));
}
Ok(InstructionData::U32(n))
}
fn execute(
&self,
vm: &mut VMContext,
data: &InstructionData,
metadata: Option<&InstructionMetadata>,
) -> Result<(), ScriptError> {
use crate::script_engine::compiler::GenericBlockMetadata;
let iterations = match data {
InstructionData::U32(n) => *n,
_ => return Err(ScriptError::ExecutionError("Invalid data type".into())),
};
let end_ip = metadata
.and_then(|m| m.get::<GenericBlockMetadata>())
.map(|m| m.end_ip)
.ok_or_else(|| {
ScriptError::ExecutionError("Loop instruction missing metadata".into())
})?;
let start_ip = vm.ip;
let loop_stack =
vm.get_or_create_execution_state::<Vec<TestLoopState>>("__test_loop_stack");
loop_stack.push(TestLoopState {
remaining: iterations,
start_ip,
end_ip,
});
Ok(())
}
fn declare_block_structure(&self) -> Option<BlockStructure> {
Some(BlockStructure::SimplePair {
start_name: "loop",
end_name: "end",
})
}
}
#[test]
fn test_interrupt_controller_basic() {
let interrupt = InterruptController::new();
assert!(!interrupt.is_interrupted());
interrupt.request_interrupt();
assert!(interrupt.is_interrupted());
interrupt.reset();
assert!(!interrupt.is_interrupted());
}
#[test]
fn test_interrupt_controller_clone_shares_state() {
let interrupt = InterruptController::new();
let clone = interrupt.clone();
assert!(!interrupt.is_interrupted());
assert!(!clone.is_interrupted());
clone.request_interrupt();
assert!(interrupt.is_interrupted());
assert!(clone.is_interrupted());
}
#[test]
fn test_script_engine_execute_without_interrupt() {
let mut engine = ScriptEngine::new();
engine.registry.register(TestLoopHandler).unwrap();
engine.registry.register(EndHandler).unwrap();
let script_text = "loop 2\nend";
let result = engine.compile_and_execute(script_text);
assert!(
result.is_ok(),
"Failed to compile and execute simple loop: {:?}",
result.err()
);
}
#[test]
fn test_script_engine_execute_with_interrupt_not_triggered() {
let mut engine = ScriptEngine::new();
engine.registry.register(TestLoopHandler).unwrap();
engine.registry.register(EndHandler).unwrap();
let script_text = "loop 2\nend";
let interrupt = InterruptController::new();
let result = engine.compile_and_execute_with_interrupt(script_text, &interrupt);
assert!(
result.is_ok(),
"Failed to compile and execute with interrupt: {:?}",
result.err()
);
assert!(
!interrupt.is_interrupted(),
"Interrupt should not be triggered"
);
}
#[test]
fn test_script_engine_execute_with_interrupt_triggered() {
let mut engine = ScriptEngine::new();
engine.registry.register(TestLoopHandler).unwrap();
engine.registry.register(EndHandler).unwrap();
let script_text = "loop 1000\nend";
let interrupt = InterruptController::new();
let result = engine.compile_and_execute_with_interrupt(script_text, &interrupt);
assert!(
result.is_ok(),
"Normal execution should succeed: {:?}",
result.err()
);
assert!(
!interrupt.is_interrupted(),
"Interrupt flag should remain false after normal execution"
);
interrupt.request_interrupt();
assert!(interrupt.is_interrupted());
interrupt.reset();
assert!(!interrupt.is_interrupted());
}
}