use std::collections::HashMap; use displaydoc::Display; use thiserror::Error; use crate::Context; use crate::emit::Instruction; use crate::input::NomoInput; #[derive(Debug, Error, Display)] pub enum EvaluationError { /// An unknown variable was encountered: .0 UnknownVariable(NomoInput), /// An explicit abort was requested ExplicitAbort, /** The instruction pointer overflowed ** ** This is an internal error and is a bug that should be reported */ InstructionPointerOverflow, } pub fn execute( instructions: &[Instruction], global_context: &Context, ) -> Result { let mut output = String::new(); let mut scopes: HashMap = HashMap::new(); let mut ip = 0; loop { if ip >= instructions.len() { break; } let instr = instructions.get(ip).unwrap(); match instr { Instruction::AppendContent { content } => output.push_str(content), Instruction::LoadFromContextToSlot { name, slot } => { let value = global_context .values .get(name.as_str()) .ok_or(EvaluationError::UnknownVariable(name.clone()))?; scopes.insert(*slot, value.clone()); } Instruction::EmitFromSlot { slot } => { let value = scopes.get(slot).unwrap().as_str().unwrap(); output.push_str(value); } Instruction::PushScope { inherit_parent: _ } => todo!(), Instruction::Abort => return Err(EvaluationError::ExplicitAbort), Instruction::JumpIfNotTrue { emit_slot, jump } => { let jump = if *jump == 0 { 1 } else { *jump }; let dont_jump = scopes.get(emit_slot).unwrap().as_bool().unwrap(); if dont_jump { // We are done } else { let (new_ip, overflow) = ip.overflowing_add_signed(jump); if overflow { return Err(EvaluationError::InstructionPointerOverflow); } else { ip = new_ip; continue; } } } Instruction::Jump { jump } => { let jump = if *jump == 0 { 1 } else { *jump }; let (new_ip, overflow) = ip.overflowing_add_signed(jump); if overflow { return Err(EvaluationError::InstructionPointerOverflow); } else { ip = new_ip; continue; } } } ip += 1; } Ok(output) } #[cfg(test)] mod tests { use crate::Context; use crate::eval::execute; #[test] fn check_simple_variable_interpolation() { let input = "Hello {{= world }}"; let parsed = crate::parser::parse(input.into()).unwrap(); let ast = crate::ast::parse(parsed.tokens()).unwrap(); let emit = crate::emit::emit_machine(ast); let mut context = Context::new(); context.insert("world", "World"); let output = execute(&emit, &context); insta::assert_debug_snapshot!(output, @r#" Ok( "Hello World", ) "#); } }