nomo/src/eval/mod.rs
Marcel Müller 59f92e31fe Allow trimming of whitespace
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-03-09 13:24:40 +01:00

115 lines
3.3 KiB
Rust

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<String, EvaluationError> {
let mut output = String::new();
let mut scopes: HashMap<crate::emit::VariableSlot, serde_json::Value> = 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",
)
"#);
}
}