244 lines
7.4 KiB
Rust
244 lines
7.4 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use displaydoc::Display;
|
|
use thiserror::Error;
|
|
|
|
use crate::Context;
|
|
use crate::emit::Instruction;
|
|
use crate::emit::VMInstructions;
|
|
use crate::emit::VariableSlot;
|
|
use crate::input::NomoInput;
|
|
use crate::value::NomoValue;
|
|
|
|
#[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
|
|
*/
|
|
LabelNotFound,
|
|
}
|
|
|
|
struct Scope {
|
|
stack: Vec<HashMap<String, NomoValue>>,
|
|
slots: HashMap<VariableSlot, NomoValue>,
|
|
}
|
|
impl Scope {
|
|
fn insert_into_slot(&mut self, slot: VariableSlot, value: NomoValue) {
|
|
self.slots.insert(slot, value);
|
|
}
|
|
|
|
fn get(&self, slot: &VariableSlot) -> &NomoValue {
|
|
self.slots.get(slot).expect("All slot loads must be valid")
|
|
}
|
|
|
|
fn get_mut(&mut self, slot: &VariableSlot) -> &mut NomoValue {
|
|
self.slots
|
|
.get_mut(slot)
|
|
.expect("All slot loads must be valid")
|
|
}
|
|
|
|
fn push_scope(&mut self) {
|
|
self.stack.push(Default::default());
|
|
}
|
|
|
|
fn pop_scope(&mut self) {
|
|
self.stack.pop();
|
|
}
|
|
|
|
fn insert_into_scope(&mut self, ident: &NomoInput, value: NomoValue) {
|
|
self.stack
|
|
.last_mut()
|
|
.unwrap()
|
|
.insert(ident.to_string(), value);
|
|
}
|
|
|
|
fn get_scoped(&self, name: &NomoInput) -> Option<&NomoValue> {
|
|
self.stack
|
|
.iter()
|
|
.rev()
|
|
.find_map(|scope| scope.get(name.as_str()))
|
|
}
|
|
}
|
|
|
|
#[allow(
|
|
clippy::unnecessary_to_owned,
|
|
reason = "We cannot do the suggested way as the lifetimes would not match up"
|
|
)]
|
|
pub fn execute(vm: &VMInstructions, global_context: &Context) -> Result<String, EvaluationError> {
|
|
let mut output = String::new();
|
|
|
|
let mut scopes = Scope {
|
|
stack: vec![global_context.values().clone()],
|
|
slots: HashMap::new(),
|
|
};
|
|
|
|
let mut ip = 0;
|
|
loop {
|
|
if ip >= vm.instructions.len() {
|
|
break;
|
|
}
|
|
|
|
let instr = vm.instructions.get(ip).unwrap();
|
|
|
|
match instr {
|
|
Instruction::NoOp => (),
|
|
Instruction::AppendContent { content } => output.push_str(content),
|
|
Instruction::LoadFromContextToSlot { name, slot } => {
|
|
let value = scopes
|
|
.get_scoped(name)
|
|
.ok_or(EvaluationError::UnknownVariable(name.clone()))?;
|
|
|
|
scopes.insert_into_slot(*slot, value.clone());
|
|
}
|
|
Instruction::EmitFromSlot { slot } => {
|
|
let value = scopes.get(slot).try_to_string().unwrap();
|
|
|
|
output.push_str(&value);
|
|
}
|
|
Instruction::PushScope { inherit_parent: _ } => {
|
|
scopes.push_scope();
|
|
}
|
|
Instruction::Abort => return Err(EvaluationError::ExplicitAbort),
|
|
Instruction::JumpIfNotTrue { emit_slot, jump } => {
|
|
let dont_jump = scopes.get(emit_slot).as_bool().unwrap();
|
|
if dont_jump {
|
|
// We are done
|
|
} else {
|
|
let Some(new_ip) = vm.labels.get(jump) else {
|
|
return Err(EvaluationError::LabelNotFound);
|
|
};
|
|
|
|
ip = *new_ip;
|
|
continue;
|
|
}
|
|
}
|
|
Instruction::Jump { jump } => {
|
|
let Some(new_ip) = vm.labels.get(jump) else {
|
|
return Err(EvaluationError::LabelNotFound);
|
|
};
|
|
|
|
ip = *new_ip;
|
|
continue;
|
|
}
|
|
Instruction::CreateIteratorFromSlotToSlot {
|
|
iterator_slot,
|
|
iterator_source_slot,
|
|
} => {
|
|
let value = scopes.get(iterator_source_slot).as_array().unwrap();
|
|
scopes.insert_into_slot(
|
|
*iterator_slot,
|
|
NomoValue::Iterator {
|
|
value: Box::new(value.to_vec().into_iter()),
|
|
},
|
|
);
|
|
}
|
|
Instruction::AdvanceIteratorOrJump {
|
|
iterator_slot,
|
|
value_slot,
|
|
jump,
|
|
} => {
|
|
let iterator = scopes.get_mut(iterator_slot).as_iterator_mut().unwrap();
|
|
|
|
if let Some(value) = iterator.next() {
|
|
scopes.insert_into_slot(*value_slot, value);
|
|
} else {
|
|
let Some(new_ip) = vm.labels.get(jump) else {
|
|
return Err(EvaluationError::LabelNotFound);
|
|
};
|
|
|
|
ip = *new_ip;
|
|
continue;
|
|
}
|
|
}
|
|
Instruction::GetIteratorEmptyOrJump {
|
|
iterator_slot,
|
|
jump,
|
|
} => {
|
|
let iterator = scopes.get(iterator_slot).as_iterator().unwrap();
|
|
let (min, _) = iterator.size_hint();
|
|
|
|
if min == 0 {
|
|
let Some(new_ip) = vm.labels.get(jump) else {
|
|
return Err(EvaluationError::LabelNotFound);
|
|
};
|
|
|
|
ip = *new_ip;
|
|
continue;
|
|
}
|
|
}
|
|
Instruction::PopScope => scopes.pop_scope(),
|
|
Instruction::LoadFromSlotToContext {
|
|
value_ident,
|
|
value_slot,
|
|
} => {
|
|
let value = scopes.get(value_slot).clone();
|
|
|
|
scopes.insert_into_scope(value_ident, value);
|
|
}
|
|
Instruction::LoadLiteralToSlot {
|
|
source: _,
|
|
value,
|
|
slot,
|
|
} => {
|
|
scopes.insert_into_slot(*slot, value.clone());
|
|
}
|
|
Instruction::MathOperate {
|
|
op,
|
|
left_slot,
|
|
right_slot,
|
|
result_slot,
|
|
} => {
|
|
let left_value = scopes.get(left_slot);
|
|
let right_value = scopes.get(right_slot);
|
|
|
|
let result = match op {
|
|
crate::parser::TokenOperator::Plus => left_value.try_add(right_value),
|
|
crate::parser::TokenOperator::Minus => left_value.try_sub(right_value),
|
|
crate::parser::TokenOperator::Times => left_value.try_mul(right_value),
|
|
crate::parser::TokenOperator::Divide => left_value.try_div(right_value),
|
|
crate::parser::TokenOperator::And => left_value.try_and(right_value),
|
|
crate::parser::TokenOperator::Or => left_value.try_or(right_value),
|
|
_ => todo!(),
|
|
};
|
|
|
|
scopes.insert_into_slot(*result_slot, result.unwrap());
|
|
}
|
|
}
|
|
|
|
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",
|
|
)
|
|
"#);
|
|
}
|
|
}
|