use std::sync::Arc;
use arcstr::ArcStr;
use imbl::Vector;
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use crate::{model::Graph, runtime::{instruction::{Instruction, Instructions}, instructions::{Base, ConsumeStack, PUSH_SYMBOL_SCOPE, TRUTHY}, proc::ProcEnv, Error}};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WhileIns {
pub tag: Option<ArcStr>,
pub test: Arc<dyn Instruction>,
pub ins: Vector<Arc<dyn Instruction>>,
pub declare: Option<Arc<dyn Instruction>>,
pub inc: Option<Arc<dyn Instruction>>,
}
#[typetag::serde(name = "WhileIns")]
impl Instruction for WhileIns {
fn exec(&self, env: &mut ProcEnv, _graph: &mut Graph) -> Result<Option<Instructions>, Error> {
let mut tag: ArcStr = nanoid!(10).into();
if let Some(ctag) = &self.tag {
tag = ctag.clone();
}
let continue_tag: ArcStr = format!("{}_con", &tag).into();
let break_tag: ArcStr = format!("{}_brk", &tag).into();
let scope_count = env.table.scopes.len();
let while_count = env.loop_stack.len();
let mut instructions = Instructions::default();
instructions.push(Arc::new(Base::PushLoop(tag)));
if let Some(declare) = &self.declare {
instructions.push(declare.clone());
}
let top_tag: ArcStr = nanoid!(10).into();
let end_tag: ArcStr = nanoid!(10).into();
instructions.push(Arc::new(Base::Tag(top_tag.clone())));
{
instructions.push(self.test.clone());
instructions.push(TRUTHY.clone());
instructions.push(Arc::new(Base::CtrlForwardToIfNotTruthy(end_tag.clone(), ConsumeStack::Consume)));
instructions.push(PUSH_SYMBOL_SCOPE.clone());
instructions.append(&self.ins);
instructions.push(Arc::new(Base::Tag(continue_tag.clone())));
instructions.push(Arc::new(Base::PopLoopUntilDepth(while_count + 1)));
instructions.push(Arc::new(Base::PopSymbolScopeUntilDepth(scope_count + 1)));
if let Some(inc) = &self.inc {
instructions.push(inc.clone());
}
instructions.push(Arc::new(Base::CtrlLoopBackTo {
top_tag,
test: self.test.clone(),
end_tag: end_tag.clone(),
ins: self.ins.clone(),
continue_tag,
loop_count: while_count,
scope_count,
inc: self.inc.clone()
}));
}
instructions.push(Arc::new(Base::Tag(break_tag)));
instructions.push(Arc::new(Base::Tag(end_tag)));
instructions.push(Arc::new(Base::PopLoopUntilDepth(while_count)));
instructions.push(Arc::new(Base::PopSymbolScopeUntilDepth(scope_count)));
Ok(Some(instructions))
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use arcstr::literal;
use crate::{model::Graph, runtime::{instructions::{block::Block, ops::{Op, OpIns}, whiles::WhileIns, Base}, Num, Runtime, Type, Val}};
#[test]
fn for_range_loop() {
let mut outer_block = Block::default();
outer_block.ins.push_back(Arc::new(Base::Literal(Val::Num(Num::Int(0)))));
outer_block.ins.push_back(Arc::new(Base::DeclareVar(literal!("total"), Type::Void)));
let mut declare = Block::default();
declare.ins.push_back(Arc::new(Base::Literal(Val::Num(Num::Int(0)))));
declare.ins.push_back(Arc::new(Base::DeclareVar(literal!("i"), Type::Void)));
let mut test_block = Block::default();
test_block.ins.push_back(Arc::new(OpIns {
lhs: Arc::new(Base::LoadVariable(literal!("i"), false, false)),
op: Op::Less,
rhs: Arc::new(Base::Literal(Val::Num(Num::Int(100)))),
}));
let mut inc_block = Block::default();
inc_block.ins.push_back(Arc::new(OpIns {
lhs: Arc::new(Base::LoadVariable(literal!("i"), false, false)),
op: Op::Add,
rhs: Arc::new(Base::Literal(Val::Num(Num::Int(1)))),
}));
inc_block.ins.push_back(Arc::new(Base::SetVariable(literal!("i"))));
let mut while_loop = WhileIns {
tag: None,
test: Arc::new(test_block),
ins: Default::default(),
declare: Some(Arc::new(declare)),
inc: Some(Arc::new(inc_block)),
};
while_loop.ins.push_back(Arc::new(OpIns {
lhs: Arc::new(Base::LoadVariable(literal!("total"), false, false)),
op: Op::Add,
rhs: Arc::new(Base::Literal(Val::Num(Num::Int(1)))),
}));
while_loop.ins.push_back(Arc::new(Base::SetVariable(literal!("total"))));
outer_block.ins.push_back(Arc::new(while_loop));
outer_block.ins.push_back(Arc::new(Base::LoadVariable(literal!("total"), false, false)));
let mut graph = Graph::default();
let res = Runtime::eval(&mut graph, Arc::new(outer_block)).expect("expected pass");
assert_eq!(res, 100.into());
}
}