Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/perl
- use strict;
- use warnings;
- use Curses;
- use Time::HiRes qw(sleep);
- my $DELAY = 0.01;
- # Read code file
- my @code = map { chomp; [split] } <>;
- # Init curses - draw initial display
- my $cur = new Curses;
- Curses::initscr();
- $cur->clear();
- $cur->addstr( 1, 10, "Part 1: " );
- $cur->addstr( 3, 10, "Part 2: " );
- foreach (my $i = 0; $i * 50 < $#code; $i++) {
- $cur->addstr( 5 + $i, 5, sprintf( "%3s", $i * 50 ) );
- $cur->addstr( 5 + $i, 10, '.' x ((($i + 1) * 50 > $#code) ? $#code % 50
- : 50) );
- }
- $cur->refresh();
- sub mark_code {
- my ($inst, $char) = @_;
- $cur->addch( 5 + int( $inst / 50 ), 10 + $inst % 50, $char );
- $cur->refresh();
- sleep( $DELAY );
- }
- #
- # Virtual Machine
- #
- # Registers:
- my ($acc, $ip);
- my @ran;
- my %op_codes = (
- 'acc' => sub { $acc += shift; $ip++ },
- 'jmp' => sub { $ip += shift; },
- 'nop' => sub { $ip++ },
- );
- sub run_vm {
- my $line = shift;
- $acc = 0;
- $ip = 0;
- @ran = map { 0 } (0 .. $#code);
- while (1) {
- last if ($ip == $#code);
- last if (++$ran[$ip] == 2);
- &mark_code( $ip, ($line == 1) ? '#' : '+' );
- &{$op_codes{ $code[$ip][0] }}( $code[$ip][1] );
- $cur->addstr( $line, 19, "$acc " );
- $cur->refresh();
- sleep( $DELAY );
- }
- $cur->addstr( $line, 25,
- sprintf( "%35s", (($ip == $#code) ? "Stopped"
- : "Looped") . " at $ip" ));
- return ($acc);
- }
- $cur->addstr( 1, 19, &run_vm(1) . " " );
- $cur->refresh();
- #
- # Analyse code
- #
- # Find start of block leading up to a jump
- sub find_block_start {
- my $inst = shift;
- do {
- $inst--;
- } while ($inst > 0 && ($code[$inst][0] ne 'jmp' || $code[$inst][1] == 1));
- return ($inst + 1);
- }
- # Returns a list of jumps that jump into a block
- sub backtrack {
- my ($start, $end) = @_;
- my @ret = ();
- for (my $i = 0; $i < $#code; $i++) {
- next if ($i >= $start && $i <= $end);
- if ($code[$i][0] eq 'jmp') {
- my $next = $i + $code[$i][1];
- if ($next >= $start && $next <= $end) {
- push( @ret, $i );
- }
- }
- }
- return (@ret);
- }
- # Find the magic sequence of code locations whose execution leads to the end.
- my @seq = map { 0 } (0 .. $#code);
- my @jobs = ($#code);
- $cur->addstr( 2, 10, "Analysing..." );
- while (my $targ = shift @jobs) {
- my $start = &find_block_start( $targ );
- foreach my $i ($start .. $targ) {
- $seq[$i] = 1;
- &mark_code( $i, '>' );
- }
- $cur->refresh();
- sleep( $DELAY );
- push( @jobs, &backtrack( $start, $targ ) );
- }
- # Look for a jmp/nop that can be toggled to get onto the magic sequence
- foreach my $i (0 .. $#code) {
- next if (!$ran[$i] || $code[$i][0] eq 'acc');
- if ($code[$i][0] eq 'jmp' && $seq[$i + 1]) {
- $code[$i][0] = 'nop';
- $cur->addstr( 2, 25, sprintf( "%35s", "Changing $i to nop" ) );
- &mark_code( $i, '*' );
- last;
- } elsif ($code[$i][0] eq 'nop' && $seq[$i + $code[$i][1]]) {
- $code[$i][0] = 'jmp';
- $cur->addstr( 2, 25, sprintf( "%35s", "Changing $i to jmp" ) );
- &mark_code( $i, '*' );
- last;
- }
- }
- $cur->addstr( 3, 19, &run_vm(3) . " " );
- $cur->refresh();
- Curses::endwin();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement