lashlang 0.1.0-alpha.93

Lashlang: compact CodeAct language for model-authored REPL blocks in the lash agent runtime.
Documentation
use std::time::Instant;

use super::super::{
    COOPERATIVE_YIELD_INSTRUCTION_BUDGET, ExecutionHost, ExecutionMode, ExecutionOutcome,
    RuntimeError, RuntimeFailure, Value,
};
use super::effects::VmEffect;
use super::{Vm, VmRunOutcome};
use crate::lexer::Span;

pub(super) enum VmStep {
    Continue,
    Effect(VmEffect),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum VmMode {
    Foreground,
    Process,
}

impl From<ExecutionMode> for VmMode {
    fn from(mode: ExecutionMode) -> Self {
        match mode {
            ExecutionMode::Foreground => Self::Foreground,
            ExecutionMode::Process => Self::Process,
        }
    }
}

impl From<VmMode> for ExecutionMode {
    fn from(mode: VmMode) -> Self {
        match mode {
            VmMode::Foreground => Self::Foreground,
            VmMode::Process => Self::Process,
        }
    }
}

pub(super) enum VmOutcome {
    Continued,
    EffectCompleted,
    Finished(Value),
    ProcessFinished(Value),
    ProcessFailed(Value),
    #[cfg(test)]
    Suspended,
}

struct VmTrap {
    error: RuntimeError,
    instruction_ip: usize,
    span: Option<Span>,
}

impl<H: ExecutionHost> Vm<'_, H> {
    pub(crate) async fn run(&mut self) -> Result<ExecutionOutcome, RuntimeError> {
        match self.run_raw().await? {
            VmOutcome::Continued => Ok(ExecutionOutcome::Continued),
            VmOutcome::EffectCompleted => Ok(ExecutionOutcome::Continued),
            VmOutcome::Finished(value) => Ok(ExecutionOutcome::Finished(value)),
            #[cfg(test)]
            VmOutcome::Suspended => Ok(ExecutionOutcome::Continued),
            VmOutcome::ProcessFinished(_) => {
                Err(RuntimeError::SessionProcessAdminOutsideProcess { keyword: "finish" })
            }
            VmOutcome::ProcessFailed(_) => {
                Err(RuntimeError::SessionProcessAdminOutsideProcess { keyword: "fail" })
            }
        }
    }

    pub(crate) async fn run_process(&mut self) -> Result<ExecutionOutcome, RuntimeError> {
        match self.run_raw().await? {
            VmOutcome::Continued => Ok(ExecutionOutcome::Finished(Value::Null)),
            VmOutcome::EffectCompleted => Ok(ExecutionOutcome::Continued),
            VmOutcome::ProcessFinished(value) => Ok(ExecutionOutcome::Finished(value)),
            VmOutcome::ProcessFailed(value) => Ok(ExecutionOutcome::Failed(value)),
            VmOutcome::Finished(value) => Ok(ExecutionOutcome::Finished(value)),
            #[cfg(test)]
            VmOutcome::Suspended => Ok(ExecutionOutcome::Continued),
        }
    }

    pub async fn run_for_mode(&mut self) -> Result<ExecutionOutcome, RuntimeError> {
        match self.mode {
            VmMode::Foreground => self.run().await,
            VmMode::Process => self.run_process().await,
        }
    }

    pub async fn run_process_until_effect(&mut self) -> Result<VmRunOutcome, RuntimeError> {
        match self.run_raw_until_effect().await? {
            VmOutcome::EffectCompleted => Ok(VmRunOutcome::EffectCompleted),
            VmOutcome::Continued => Ok(VmRunOutcome::Complete(ExecutionOutcome::Finished(
                Value::Null,
            ))),
            VmOutcome::ProcessFinished(value) | VmOutcome::Finished(value) => {
                Ok(VmRunOutcome::Complete(ExecutionOutcome::Finished(value)))
            }
            VmOutcome::ProcessFailed(value) => {
                Ok(VmRunOutcome::Complete(ExecutionOutcome::Failed(value)))
            }
            #[cfg(test)]
            VmOutcome::Suspended => Ok(VmRunOutcome::EffectCompleted),
        }
    }

    pub async fn run_process_traced_until_effect(
        &mut self,
    ) -> Result<VmRunOutcome, RuntimeFailure> {
        match self.run_raw_traced_until_effect().await? {
            VmOutcome::EffectCompleted => Ok(VmRunOutcome::EffectCompleted),
            VmOutcome::Continued => Ok(VmRunOutcome::Complete(ExecutionOutcome::Finished(
                Value::Null,
            ))),
            VmOutcome::ProcessFinished(value) | VmOutcome::Finished(value) => {
                Ok(VmRunOutcome::Complete(ExecutionOutcome::Finished(value)))
            }
            VmOutcome::ProcessFailed(value) => {
                Ok(VmRunOutcome::Complete(ExecutionOutcome::Failed(value)))
            }
            #[cfg(test)]
            VmOutcome::Suspended => Ok(VmRunOutcome::EffectCompleted),
        }
    }

    pub(crate) async fn run_traced(&mut self) -> Result<ExecutionOutcome, RuntimeFailure> {
        let result = self.run_raw_traced().await?;
        match result {
            VmOutcome::Continued => Ok(ExecutionOutcome::Continued),
            VmOutcome::EffectCompleted => Ok(ExecutionOutcome::Continued),
            VmOutcome::Finished(value) => Ok(ExecutionOutcome::Finished(value)),
            #[cfg(test)]
            VmOutcome::Suspended => Ok(ExecutionOutcome::Continued),
            VmOutcome::ProcessFinished(_) => Err(RuntimeFailure {
                error: RuntimeError::SessionProcessAdminOutsideProcess { keyword: "finish" },
                span: None,
            }),
            VmOutcome::ProcessFailed(_) => Err(RuntimeFailure {
                error: RuntimeError::SessionProcessAdminOutsideProcess { keyword: "fail" },
                span: None,
            }),
        }
    }

    pub(crate) async fn run_process_traced(&mut self) -> Result<ExecutionOutcome, RuntimeFailure> {
        let result = self.run_raw_traced().await?;
        match result {
            VmOutcome::Continued => Ok(ExecutionOutcome::Finished(Value::Null)),
            VmOutcome::EffectCompleted => Ok(ExecutionOutcome::Continued),
            VmOutcome::ProcessFinished(value) => Ok(ExecutionOutcome::Finished(value)),
            VmOutcome::ProcessFailed(value) => Ok(ExecutionOutcome::Failed(value)),
            VmOutcome::Finished(value) => Ok(ExecutionOutcome::Finished(value)),
            #[cfg(test)]
            VmOutcome::Suspended => Ok(ExecutionOutcome::Continued),
        }
    }

    pub(crate) async fn run_traced_for_mode(&mut self) -> Result<ExecutionOutcome, RuntimeFailure> {
        match self.mode {
            VmMode::Foreground => self.run_traced().await,
            VmMode::Process => self.run_process_traced().await,
        }
    }

    async fn run_raw(&mut self) -> Result<VmOutcome, RuntimeError> {
        let result = self.run_loop(false).await.map_err(|trap| trap.error);
        #[cfg(test)]
        let suspended = matches!(result, Ok(VmOutcome::Suspended));
        #[cfg(not(test))]
        let suspended = false;
        if !suspended {
            self.unwind_iterators();
        }
        result
    }

    async fn run_raw_until_effect(&mut self) -> Result<VmOutcome, RuntimeError> {
        self.run_loop(true).await.map_err(|trap| trap.error)
    }

    async fn run_raw_traced(&mut self) -> Result<VmOutcome, RuntimeFailure> {
        let result = self.run_loop(false).await.map_err(|trap| RuntimeFailure {
            error: trap.error,
            span: trap
                .span
                .or_else(|| self.chunk.spans.get(trap.instruction_ip).copied().flatten()),
        });
        #[cfg(test)]
        let suspended = matches!(result, Ok(VmOutcome::Suspended));
        #[cfg(not(test))]
        let suspended = false;
        if !suspended {
            self.unwind_iterators();
        }
        result
    }

    async fn run_raw_traced_until_effect(&mut self) -> Result<VmOutcome, RuntimeFailure> {
        self.run_loop(true).await.map_err(|trap| RuntimeFailure {
            error: trap.error,
            span: trap
                .span
                .or_else(|| self.chunk.spans.get(trap.instruction_ip).copied().flatten()),
        })
    }

    async fn run_loop(&mut self, stop_after_effect: bool) -> Result<VmOutcome, VmTrap> {
        let mut budget = COOPERATIVE_YIELD_INSTRUCTION_BUDGET;
        while let Some(instruction) = self.chunk.code.get(self.ip).copied() {
            let instruction_ip = self.ip;
            self.ip += 1;
            let profile = self
                .profile
                .as_ref()
                .map(|_| (instruction.profile_tag(), Instant::now()));
            let step = match self.step_instruction_fast(instruction) {
                Ok(Some(step)) => Ok(step),
                Ok(None) => Box::pin(self.step_instruction(instruction)).await,
                Err(error) => Err(error),
            };
            let completed_effect = matches!(&step, Ok(VmStep::Effect(_)));
            let result = match step {
                Ok(VmStep::Continue) => Ok(None),
                Ok(VmStep::Effect(effect)) => self.resolve_effect(effect, instruction_ip).await,
                Err(error) => Err(error),
            };
            if let Some((tag, start)) = profile {
                self.record_instruction_profile(tag, start.elapsed().as_nanos());
            }
            match result {
                Ok(Some(outcome)) => return Ok(outcome),
                Ok(None) => {}
                Err(error) => {
                    let span = self.pending_error_span.take();
                    return Err(VmTrap {
                        error,
                        instruction_ip,
                        span,
                    });
                }
            }
            if stop_after_effect && completed_effect {
                return Ok(VmOutcome::EffectCompleted);
            }
            #[cfg(test)]
            if self.test_suspension.should_suspend(completed_effect) {
                return Ok(VmOutcome::Suspended);
            }
            budget -= 1;
            if budget == 0 {
                self.host.yield_now().await;
                budget = COOPERATIVE_YIELD_INSTRUCTION_BUDGET;
            }
        }
        Ok(VmOutcome::Continued)
    }
}