Skip to content

Commit

Permalink
chore: convert terminate_working_block macro to fn (#79)
Browse files Browse the repository at this point in the history
* chore: convert terminate_working_block macro to fn

* chore: clippy fixes

* chore: fix mutability on fn param to remove regression

* chore: update readme to document testing instructions

* chore: use early return instead of if/else and document fn

* chore: remove needless assignment

* chore: update docs

Co-authored-by: Kalan <22137047+kalzoo@users.noreply.github.com>

Co-authored-by: kalzoo <22137047+kalzoo@users.noreply.github.com>
  • Loading branch information
nilslice and kalzoo authored Jun 9, 2022
1 parent 9f60bc2 commit 618aa5e
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 62 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,12 @@ It serves three purposes:
3. Construct a dependency graph among program instructions

It should be considered unstable until the release of v1.0.


## Testing

When testing this crate, you should run with the `--all-features` flag to ensure all tests are executed.

```sh
cargo test --all-features
```
133 changes: 71 additions & 62 deletions src/program/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,33 +452,44 @@ pub struct ScheduledProgram {
pub blocks: IndexMap<String, InstructionBlock>,
}

macro_rules! terminate_working_block {
($terminator:expr, $working_instructions:ident, $blocks:ident, $working_label:ident, $program: ident, $instruction_index: ident) => {{
// If this "block" has no instructions and no terminator, it's not worth storing - skip it
if $working_instructions.is_empty() && $terminator.is_none() && $working_label.is_none() {
$working_label = None
} else {
let block = InstructionBlock::build(
$working_instructions.iter().map(|el| el.clone()).collect(),
$terminator,
$program,
)?;
let label =
$working_label.unwrap_or_else(|| Self::generate_autoincremented_label(&$blocks));

match $blocks.insert(label.clone(), block) {
Some(_) => Err(ScheduleError {
instruction_index: $instruction_index,
instruction: Instruction::Label(Label(label.clone())),
variant: ScheduleErrorVariant::DuplicateLabel,
}), // Duplicate label
None => Ok(()),
}?;
$working_instructions = vec![];
$working_label = None
}
Ok(())
}};
/// Builds an [`InstructionBlock`] from provided instructions, terminator, and program, then tracks
/// the block and its label, and resets the instruction list and label for a future block to use.
///
/// The "working block" is that being traversed within the program, accumulating instructions located
/// between control instructions (such as `LABEL` and 'JUMP`). When such a control instruction is reached,
/// this function performs the work to close out and store the instructions in the current "working block"
/// and then reset the state to prepare for the next block.
fn terminate_working_block(
terminator: Option<BlockTerminator>,
working_instructions: &mut Vec<Instruction>,
blocks: &mut IndexMap<String, InstructionBlock>,
working_label: &mut Option<String>,
program: &Program,
instruction_index: Option<usize>,
) -> ScheduleResult<()> {
// If this "block" has no instructions and no terminator, it's not worth storing - skip it
if working_instructions.is_empty() && terminator.is_none() && working_label.is_none() {
return Ok(());
}

let block = InstructionBlock::build(working_instructions.to_vec(), terminator, program)?;
let label = working_label
.clone()
.unwrap_or_else(|| ScheduledProgram::generate_autoincremented_label(blocks));

match blocks.insert(label.clone(), block) {
Some(_) => Err(ScheduleError {
instruction_index,
instruction: Instruction::Label(Label(label)),
variant: ScheduleErrorVariant::DuplicateLabel,
}),
None => Ok(()),
}?;

working_instructions.drain(..);
*working_label = None;

Ok(())
}

impl ScheduledProgram {
Expand Down Expand Up @@ -536,80 +547,78 @@ impl ScheduledProgram {
Ok(())
}
Instruction::Label(Label(value)) => {
terminate_working_block!(
terminate_working_block(
None as Option<BlockTerminator>,
working_instructions,
blocks,
working_label,
&mut working_instructions,
&mut blocks,
&mut working_label,
program,
instruction_index
instruction_index,
)?;

working_label = Some(value.clone());
Ok(())
}
Instruction::Jump(Jump { target }) => {
terminate_working_block!(
terminate_working_block(
Some(BlockTerminator::Unconditional {
target: target.clone(),
}),
working_instructions,
blocks,
working_label,
&mut working_instructions,
&mut blocks,
&mut working_label,
program,
instruction_index
instruction_index,
)?;
Ok(())
}
Instruction::JumpWhen(JumpWhen { target, condition }) => {
terminate_working_block!(
terminate_working_block(
Some(BlockTerminator::Conditional {
target: target.clone(),
condition: condition.clone(),
jump_if_condition_true: true,
}),
working_instructions,
blocks,
working_label,
&mut working_instructions,
&mut blocks,
&mut working_label,
program,
instruction_index
instruction_index,
)?;
Ok(())
}
Instruction::JumpUnless(JumpUnless { target, condition }) => {
terminate_working_block!(
terminate_working_block(
Some(BlockTerminator::Conditional {
target: target.clone(),
condition: condition.clone(),
jump_if_condition_true: false,
}),
working_instructions,
blocks,
working_label,
program,
instruction_index
)
}
Instruction::Halt => {
terminate_working_block!(
Some(BlockTerminator::Halt),
working_instructions,
blocks,
working_label,
&mut working_instructions,
&mut blocks,
&mut working_label,
program,
instruction_index
instruction_index,
)
}
Instruction::Halt => terminate_working_block(
Some(BlockTerminator::Halt),
&mut working_instructions,
&mut blocks,
&mut working_label,
program,
instruction_index,
),
}?;
}

terminate_working_block!(
terminate_working_block(
None as Option<BlockTerminator>,
working_instructions,
blocks,
working_label,
&mut working_instructions,
&mut blocks,
&mut working_label,
program,
None
None,
)?;

Ok(ScheduledProgram { blocks })
Expand Down

0 comments on commit 618aa5e

Please sign in to comment.