#!/usr/bin/perl # me@ys.lc (c) use 5.010; use strict; use warnings; use Data::Dumper; ### Global our $addr_map = { }; our $curr_value = 0; our $debug = 1; ###################### sub dd { $debug and say @_; } sub make_op_code { my ($name, $cb) = @_; return { name => $name, cb => $cb }; } sub get_val_by_addr { my ($addr) = @_; my $v = $addr_map->{$addr}; die "ERROR: value at $addr is not defined!" unless defined $v; return $v; }; sub op_ld { my ($addr) = @_; dd("Loading value at ${addr} to current reg ..."); $curr_value = get_val_by_addr($addr); } sub op_st { my ($addr) = @_; dd("Saving value at ${addr} to map ..."); $addr_map->{$addr} = $curr_value; } sub op_add { my ($addr) = @_; my $v = get_val_by_addr($addr); dd("ADD: $curr_value + $v"); $curr_value += $v; } sub op_sub { my ($addr) = @_; my $v = get_val_by_addr($addr); dd("SUB: $curr_value - $v"); $curr_value -= $v; } sub op_mul { my ($addr) = @_; my $v = get_val_by_addr($addr); dd("MUL: $curr_value * $v"); $curr_value *= $v; } sub op_div { my ($addr) = @_; my $v = get_val_by_addr($addr); die "ERROR: division by zero detected at line $..\n" unless $v; dd("DIV: $curr_value / $v"); $curr_value = int($curr_value / $v); } sub op_in { my ($addr) = @_; my $v; while (not defined $v) { print "Enter: "; chomp(($v = )); } die "ERROR: '$v' it is not a number!" unless $v =~ m/^-?\d+$/; dd("IN: Saving '$v' to $addr ..."); $addr_map->{$addr} = $v; } sub op_out { my ($addr) = @_; my $v = get_val_by_addr($addr); say "OUT: ${v}"; } ### Jump address my $op_jmp_addr = 0; sub op_jmp { $op_jmp_addr = shift; } sub op_jz { $op_jmp_addr = shift unless $curr_value; } sub op_js { $op_jmp_addr = shift if $curr_value < 0; } my $op_codes = { 0 => make_op_code("exit", sub { exit 0 }), # Выход 1 => make_op_code("ld", \&op_ld), # Загрузить аккумулятор 2 => make_op_code("st", \&op_st), # Сохранить аккумулятор 3 => make_op_code("add", \&op_add), # Сложить с аккумулятором 4 => make_op_code("sub", \&op_sub), # Вычесть из аккумулятора 5 => make_op_code("mul", \&op_mul), # Умножить аккумулятор 6 => make_op_code("div", \&op_div), # Разделить аккумулятор 7 => make_op_code("in", \&op_in), # Ввести данные 8 => make_op_code("out", \&op_out), # Вывести данные 9 => make_op_code("jmp", \&op_jmp), # Перейти по адресу 10 => make_op_code("jz", \&op_jz), # Перейти по адресу, если аккумулятор == 0 11 => make_op_code("js", \&op_js), # Перейти по адресу, если аккумулятор < 0 }; sub do_program { my ($prog, $at) = @_; $op_jmp_addr = 0; for ( sort { $a <=> $b } grep { $_ >= $at } keys $prog) { my ($cmd, $addr) = @{$prog->{$_}}{qw(cmd addr)}; my $f = $op_codes->{$cmd}; dd("Do command '", $f->{name}, "' from address ", $_); # Execute it! $f->{cb}($addr); # Check jump address if ($op_jmp_addr) { die "ERROR: unknwon address: $op_jmp_addr\n" unless exists $prog->{$op_jmp_addr}; dd("Go to addr (line) $op_jmp_addr ..."); return do_program($prog, $op_jmp_addr); } } } ######################################## ### START ######################################## die "Usage: $0 " unless $ARGV[0]; open my $fh, "<", $ARGV[0] or die "$0: Can't open '$ARGV[0]' for reading: $!\n"; my $prog; dd("Loading program ..."); while (<$fh>) { chomp; next unless $_; next if m/^#/; die "ERROR: unknwon code/command: '$_' at line '$.' in file '$ARGV[0]'\n" unless m/^\d+$/; my $code = substr $_, 0, 2; my $addr = substr $_, 2; die "ERROR: unknwon code: '$code' at line '$.' in file '$ARGV[0]'\n" unless exists $op_codes->{int($code)}; dd("Line $.: $_"); $prog->{$.} = { cmd => int($code), addr => int($addr) }; } close $fh; do_program($prog, 0);