instruct 0.1.0

A language to write general purpose 'makefile like' tasks which are powerful and reuseable
Documentation
use std::str;

use anyhow::Context;
use log::{error, info};
use thiserror::Error;

use crate::interpreter::context::ContextRef;
use crate::interpreter::stack::StackRef;
use crate::interpreter::variables::Variables;
use crate::parse::ast::{Executeable, ExecuteableType};

use super::{get_executor, DynExecutor, Executor, ExecutorError, Stack};

#[derive(Error, Debug)]
pub enum TaskError {
    #[error("Task {0} is missing it's main")]
    MissingMain(String),
}

struct Executeables {
    pre: Option<Executeable>,
    main: Executeable,
    post: Option<Executeable>,
}

struct Executors {
    pre: Option<DynExecutor>,
    main: DynExecutor,
    post: Option<DynExecutor>,
    stack: StackRef,
}

pub struct TaskExecutor {
    name: String,
    variables: Variables,
    arguments: Variables,
    executeables: Option<Executeables>,
    executors: Option<Executors>,
}

impl TaskExecutor {
    pub fn new(input: Executeable) -> anyhow::Result<TaskExecutor> {
        if let ExecuteableType::Task { executeables } = input.executeable_type {
            let pre_executeable = TaskExecutor::find_executeable(&executeables, "pre");
            let main_executeable = match TaskExecutor::find_executeable(&executeables, "main") {
                Some(executeable) => executeable,
                None => return Err(TaskError::MissingMain(input.name).into()),
            };
            let post_executeable = TaskExecutor::find_executeable(&executeables, "post");
            Ok(TaskExecutor {
                name: input.name,
                variables: Variables::new(input.output_variables),
                arguments: Variables::new(input.options),
                executeables: Some(Executeables {
                    pre: pre_executeable,
                    main: main_executeable,
                    post: post_executeable,
                }),
                executors: None,
            })
        } else {
            Err(ExecutorError::WrongExecutorType(input.executeable_type).into())
        }
    }

    fn find_executeable(executeables: &[Executeable], name: &str) -> Option<Executeable> {
        for executeable in executeables {
            if executeable.name == name {
                return Some(executeable.clone());
            }
        }

        None
    }

    pub fn error_context(&self, stage: &'static str) -> String {
        format!("executing task '{}' at stage '{}'", self.name, stage)
    }

    fn convert_and_init_executeable(
        stack: &mut StackRef,
        executeable: Option<Executeable>,
        ctx: ContextRef,
    ) -> anyhow::Result<Option<DynExecutor>> {
        let mut executor = match executeable {
            Some(executeable) => get_executor(executeable, stack.clone())?,
            None => return Ok(None),
        };

        executor.init(stack.clone(), ctx.clone())?;

        Ok(Some(executor))
    }
}

impl Executor for TaskExecutor {
    fn init(&mut self, mut parent_stack: StackRef, ctx: ContextRef) -> anyhow::Result<()> {
        if let Some(executeables) = self.executeables.take() {
            let mut child_stack: StackRef = Stack::new().into();

            self.arguments
                .allocate_and_check_all(&mut child_stack, &mut parent_stack)
                .with_context(|| self.error_context("check_args"))?;

            let pre = Self::convert_and_init_executeable(
                &mut child_stack,
                executeables.pre,
                ctx.clone(),
            )?;
            let mut main = get_executor(executeables.main, child_stack.clone())?;
            main.init(child_stack.clone(), ctx.clone())?;
            let post = Self::convert_and_init_executeable(
                &mut child_stack,
                executeables.post,
                ctx.clone(),
            )?;

            self.variables
                .allocate_and_check_all(&mut parent_stack, &mut child_stack)
                .with_context(|| self.error_context("check_vars"))?;

            self.executors = Some(Executors {
                pre,
                main,
                post,
                stack: child_stack,
            });

            Ok(())
        } else {
            Err(ExecutorError::NotInitialized.into())
        }
    }

    fn execute(&mut self, mut parent_stack: StackRef, ctx: ContextRef) -> anyhow::Result<()> {
        if let Some(mut executors) = self.executors.take() {
            self.arguments
                .carry_over(&mut executors.stack, &mut parent_stack)
                .with_context(|| self.error_context("get_args"))?;

            info!("-> {}", &self.name);
            if let Some(mut pre) = executors.pre {
                pre.execute(executors.stack.clone(), ctx.clone())
                    .with_context(|| self.error_context("executing_pre"))?;
            }
            executors
                .main
                .execute(executors.stack.clone(), ctx.clone())
                .with_context(|| self.error_context("executing_main"))?;

            if let Some(mut post) = executors.post {
                post.execute(executors.stack.clone(), ctx.clone())
                    .with_context(|| self.error_context("executing_post"))?;
            }

            self.variables
                .carry_over(&mut parent_stack, &mut executors.stack)?;
            Ok(())
        } else {
            Err(ExecutorError::NotInitialized.into())
        }
    }
}