use std::collections::VecDeque;
use std::io::{self, Write};
use std::{error::Error, fmt};
use crate::{
datamodel::SystemVariables,
event::Event,
interpreter::{Interpreter, InterpreterError},
};
#[derive(Clone, Debug, PartialEq)]
pub enum ExecutableContent {
Assign(
String,
String,
),
Cancel,
ForEach,
BranchTable(
Vec<BranchTableEntry>,
),
Log(
String,
String,
),
Raise(Event ),
Script,
Send,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BranchTableEntry {
cond: String,
exec: Vec<ExecutableContent>,
}
#[derive(Debug, PartialEq)]
pub enum ExecutableContentError {
InterpreterError(InterpreterError),
IoError(io::ErrorKind),
}
impl ExecutableContent {
pub fn execute<W>(
&self,
sys_vars: &mut SystemVariables,
internal_queue: &mut VecDeque<Event>,
writer: &mut W,
) -> Result<(), ExecutableContentError>
where
W: Write,
{
match self {
Self::Assign(location, expr) => Self::execute_assign(location, expr, sys_vars),
Self::BranchTable(branch_table) => {
Self::execute_if(branch_table, sys_vars, internal_queue, writer)
}
Self::Log(label, expr) => Self::execute_log(label, expr, sys_vars, writer),
Self::Raise(event) => {
Self::execute_raise(event, internal_queue);
Ok(())
}
_ => todo!(
"Attempted to execute unimplemented ExecutableContent '{:?}'",
self
),
}
}
fn execute_assign(
location: &str,
expr: &str,
sys_vars: &mut SystemVariables,
) -> Result<(), ExecutableContentError> {
let interpreter = Interpreter::new(expr);
let value = interpreter.interpret(sys_vars)?;
sys_vars.set_data_member(location, value);
Ok(())
}
fn execute_if<W>(
branch_table: &[BranchTableEntry],
sys_vars: &mut SystemVariables,
internal_queue: &mut VecDeque<Event>,
writer: &mut W,
) -> Result<(), ExecutableContentError>
where
W: Write,
{
for branch in branch_table {
if Interpreter::new(branch.cond()).interpret_as_bool(sys_vars)? {
for exec_content_element in branch.exec_content() {
exec_content_element.execute(sys_vars, internal_queue, writer)?;
}
break;
}
}
Ok(())
}
fn execute_log<W>(
label: &str,
expr: &str,
sys_vars: &mut SystemVariables,
writer: &mut W,
) -> Result<(), ExecutableContentError>
where
W: Write,
{
let mut has_content = false;
let interpreter = Interpreter::new(expr);
let value = interpreter.interpret(sys_vars)?;
if !label.is_empty() {
write!(writer, "{}: ", label)?;
has_content = true;
}
if !expr.is_empty() {
write!(writer, "{}", value)?;
has_content = true;
}
if has_content {
writeln!(writer)?;
}
Ok(())
}
fn execute_raise(event: &Event, internal_queue: &mut VecDeque<Event>) {
internal_queue.push_back(event.clone());
}
}
impl BranchTableEntry {
pub fn new(cond: String, exec: Vec<ExecutableContent>) -> Self {
Self { cond, exec }
}
pub fn cond(&self) -> &String {
&self.cond
}
pub fn exec_content(&self) -> &Vec<ExecutableContent> {
&self.exec
}
pub fn set_cond(&mut self, cond: String) {
self.cond = cond;
}
pub fn push_exec_content(&mut self, exec: ExecutableContent) {
self.exec.push(exec);
}
}
impl Error for ExecutableContentError {}
impl fmt::Display for ExecutableContentError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::InterpreterError(interp_err) => {
write!(
f,
"InterpreterError '{:?}' encountered within Executable Content",
interp_err
)
}
Self::IoError(io_err) => {
write!(
f,
"IO Error '{:?}' encountered within Executable Content",
io_err
)
}
}
}
}
impl From<InterpreterError> for ExecutableContentError {
fn from(src: InterpreterError) -> Self {
Self::InterpreterError(src)
}
}
impl From<std::io::Error> for ExecutableContentError {
fn from(src: std::io::Error) -> Self {
Self::IoError(src.kind())
}
}
#[cfg(test)]
mod tests {
use std::collections::VecDeque;
use std::error::Error;
use std::io;
use crate::{
datamodel::SystemVariables, executable_content::ExecutableContent,
interpreter::EcmaScriptValue,
};
type TestResult = Result<(), Box<dyn Error>>;
mod assign {
use super::*;
#[test]
fn basic_assignment() -> TestResult {
let location = "loc".to_string();
let initial_val = EcmaScriptValue::Boolean(false);
let expr = "true".to_string();
let mut internal_queue = VecDeque::new();
let assignment = ExecutableContent::Assign(location.clone(), expr.clone());
let mut sys_vars = SystemVariables::default();
sys_vars.set_data_member(&location, initial_val);
eprintln!("=== Initial Data Model:\n{:?}", sys_vars._x());
assignment.execute(&mut sys_vars, &mut internal_queue, &mut io::stdout())?;
eprintln!("=== Final Data Model:\n{:?}", sys_vars._x());
assert_eq!(
sys_vars.get_data_member(&location),
Some(&EcmaScriptValue::Boolean(true))
);
Ok(())
}
#[test]
fn meta_assignment() -> TestResult {
let location = "loc".to_string();
let initial_val = EcmaScriptValue::Number(0.0);
let expr = "loc + 1".to_string();
let mut internal_queue = VecDeque::new();
let assignment = ExecutableContent::Assign(location.clone(), expr.clone());
let mut sys_vars = SystemVariables::default();
sys_vars.set_data_member(&location, initial_val.clone());
eprintln!("=== Initial Data Model:\n{:?}", sys_vars._x());
let executions = 4;
for _ in 0..executions {
assignment.execute(&mut sys_vars, &mut internal_queue, &mut io::stdout())?;
}
eprintln!("=== Final Data Model:\n{:?}", sys_vars._x());
assert_eq!(
sys_vars.get_data_member(&location),
Some(&(initial_val + EcmaScriptValue::Number(executions as f64)))
);
Ok(())
}
}
mod log {
use super::*;
use crate::interpreter::Interpreter;
#[test]
fn basic_logging() -> TestResult {
let label = "LABEL".to_string();
let expr = "\'This is a log message.\'".to_string();
let mut internal_queue = VecDeque::new();
let mut buffer = Vec::new();
let log_action = ExecutableContent::Log(label.clone(), expr.clone());
let mut sys_vars = SystemVariables::default();
log_action.execute(&mut sys_vars, &mut internal_queue, &mut buffer)?;
let output = String::from_utf8(buffer)?;
assert_eq!(
output,
format!(
"{}: {}\n",
label,
Interpreter::new(&expr).interpret(&sys_vars)?
)
);
Ok(())
}
#[test]
fn empty_logging() -> TestResult {
let label = "".to_string();
let expr = "".to_string();
let mut internal_queue = VecDeque::new();
let mut buffer = Vec::new();
let log_action = ExecutableContent::Log(label, expr);
let mut sys_vars = SystemVariables::default();
log_action.execute(&mut sys_vars, &mut internal_queue, &mut buffer)?;
let output = String::from_utf8(buffer)?;
assert_eq!(output, String::default());
Ok(())
}
#[test]
fn parsed_logging() -> TestResult {
let label = "RESULT".to_string();
let expr = "5.5 + 3".to_string();
let mut internal_queue = VecDeque::new();
let mut buffer = Vec::new();
let log_action = ExecutableContent::Log(label.clone(), expr.clone());
let mut sys_vars = SystemVariables::default();
log_action.execute(&mut sys_vars, &mut internal_queue, &mut buffer)?;
let output = String::from_utf8(buffer)?;
assert_eq!(
output,
format!(
"{}: {}\n",
label,
Interpreter::new(&expr).interpret(&sys_vars)?
)
);
Ok(())
}
}
}