103 lines
2.9 KiB
Rust
103 lines
2.9 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 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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",
|
|
)
|
|
"#);
|
|
}
|
|
}
|