use crate::internal::as2_pcode::{Action, Actions, PushValue};
#[derive(Debug)]
pub(crate) struct CodeBuilder {
actions: Actions,
stack_size: u32,
}
impl CodeBuilder {
pub fn new() -> Self {
Self {
actions: Actions::empty(),
stack_size: 0,
}
}
pub fn into_actions(self) -> Actions {
self.actions
}
pub fn stack_size(&self) -> u32 {
self.stack_size
}
pub fn truncate_stack(&mut self, stack_size: u32) {
let delta = self.stack_size.saturating_sub(stack_size);
for _ in 0..delta {
self.action(Action::Pop);
}
}
pub fn assume_stack_delta(&mut self, delta: i32) {
let stack_size = ((self.stack_size as i32) + delta).max(0);
self.stack_size = stack_size as u32;
}
pub fn mark_label(&mut self, label: String) {
if self.actions.label_positions().contains_key(&label) {
panic!("Label already exists");
}
self.actions.push_label(label);
}
pub fn action(&mut self, action: Action) {
let delta = action.stack_delta();
self.action_with_stack_delta(action, delta);
}
pub fn action_with_stack_delta(&mut self, action: Action, stack_delta: i32) {
self.assume_stack_delta(stack_delta);
self.actions.push(action);
}
pub fn push<V>(&mut self, value: V)
where
V: Into<PushValue>,
{
if !self.actions.has_dangling_label()
&& let Some(Action::Push(values)) = self.actions.last_mut()
{
values.push(value.into());
self.assume_stack_delta(1);
} else {
self.action(Action::Push(vec![value.into()]));
}
}
pub fn append(&mut self, actions: Actions) {
self.actions.append(actions);
}
}
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_snapshot;
#[test]
fn test_multiple_pushes() {
let mut builder = CodeBuilder::new();
builder.push(PushValue::True);
builder.push(PushValue::False);
assert_snapshot!(builder.actions, @"Push true, false");
}
#[test]
fn test_multiple_pushes_split_by_label() {
let mut builder = CodeBuilder::new();
builder.push(PushValue::True);
builder.mark_label("loc0001".to_string());
builder.push(PushValue::False);
assert_snapshot!(builder.actions, @r"
Push true
loc0001:Push false
");
}
}