Abstract
This is a simulator of a simple stack machine. It can be programmed using a language which resembles RPN and RPL. Currently only the following commands are provided:
Operations on the data stack
Command | Name | Stack Diagram |
---|---|---|
nnn |
Number |
( - - n ) |
+ |
Add |
( x y - - x + y ) |
- |
Subtract |
( x y - - x - y ) |
* |
Multiply |
( x y - - x * y ) |
/ |
Divide |
( x y - - x / y ) |
STO n |
Storage |
( x - - ) |
RCL n |
Recall |
( - - x ) |
DUP |
Dup |
( x - - x x ) |
DROP |
Drop |
( x - - ) |
SWAP |
Swap |
( x y - - y x ) |
Controll structure
Command | Name |
---|---|
LBL n |
Label |
GTO n |
GoTo |
x=0? |
EqualsZero |
x>0? |
GreaterZero |
GSB n |
GoToSubroutine |
RTN |
Return |
But it’s very easy to extend the language with new commands like MOD or OVER.
Code
Processor
The processor has an unlimited stack and 10 registers plus a few functions to manipulate them:
-
push: pushes the value onto the top of the stack
-
pop: pops and returns the last value of the stack
-
peek: reads and returns the last value of the stack
-
storage: stores the value in the register using the index
-
recall: recalls the register using the index
package Processor; my @stack = (); my @register = (0) x 10; sub push($) { my ($value) = @_; CORE::push @stack, $value; } sub pop() { my $value = CORE::pop @stack; return $value; } sub peek() { my $index = $#stack; my $value = $stack[$index]; return $value; } sub storage($$) { my ($index, $value) = @_; $register[$index] = $value; } sub recall($) { my ($index) = @_; my $value = $register[$index]; return $value; } sub toString() { return <<EOF; Register: @register Stack : @stack EOF } return 1;
Controller
The controller has an unlimited return stack, a list of labels that keeps track of the corresponding progam line and a program counter pc.
-
label: stores the program line for a label
-
goTo: jumps to the program line registered with that label
-
incPc: increments the program counter by 1
-
enter: pushes the current adress on the return stack and jumps to the label
-
leave: sets the program counter to the last value of the return stack
-
run: executes each command of the program
package Controller; my @stack = (); my @label = (); my $pc = 0; sub label($$) { my ($index, $value) = @_; $label[$index] = $value; } sub goTo($) { my ($index) = @_; $pc = $label[$index]; } sub incPc() { $pc++; } sub enter($) { my ($index) = @_; push @stack, $pc; goTo($index); } sub leave() { $pc = @stack ? pop @stack : 1_000_000_000; } sub run(@) { my @program = @_; @stack = (); for ($pc = 0; $pc < @program; $pc++) { $program[$pc]->(); } } return 1;
Command
A command is a closure, which is a function together with a referencing environment. It may operate both on the processor and the controller. Part of the environment are the variables name and value which are passed when calling the constructor. The name is used to select the command from an anonymous hash-map. This command is then returned within a wrapper-function that prints the command and both the registers and the stack of the processor.
package Command; use Processor; use Controller; sub new($$) { my ($class, $name, $value) = @_; my $command = { Number => sub { Processor::push($value); }, '+' => sub { my $x = Processor::pop(); my $y = Processor::pop(); Processor::push($y + $x); }, '-' => sub { my $x = Processor::pop(); my $y = Processor::pop(); Processor::push($y - $x); }, '*' => sub { my $x = Processor::pop(); my $y = Processor::pop(); Processor::push($y * $x); }, '/' => sub { my $x = Processor::pop(); my $y = Processor::pop(); Processor::push($y / $x); }, 'MOD' => sub { my $x = Processor::pop(); my $y = Processor::pop(); Processor::push($y % $x); }, 'DUP' => sub { my $x = Processor::peek(); Processor::push($x); }, 'DROP' => sub { Processor::pop(); }, 'SWAP' => sub { my $x = Processor::pop(); my $y = Processor::pop(); Processor::push($x); Processor::push($y); }, 'OVER' => sub { my $x = Processor::pop(); my $y = Processor::peek(); Processor::push($x); Processor::push($y); }, 'STO' => sub { my $x = Processor::pop(); Processor::storage($value, $x); }, 'RCL' => sub { my $x = Processor::recall($value); Processor::push($x); }, 'x>0?' => sub { my $x = Processor::pop(); unless ($x > 0) { Controller::incPc(); } }, 'x=0?' => sub { my $x = Processor::pop(); unless ($x == 0) { Controller::incPc(); } }, 'LBL' => sub { }, 'GTO' => sub { Controller::goTo($value); }, 'GSB' => sub { Controller::enter($value); }, 'RTN' => sub { Controller::leave(); }, }->{$name}; return sub { $command->(); printf <<EOF, Processor::toString(); $name $value %s EOF } } return 1;
Running a program
The simulator reads the DATA line by line and creates a list of commands — the program — which is passed to the controller to run it. Each line is split into a name and its value but the latter may be missing.
There are two cases that have to be considered:
-
numbers: the name "Number" is used
-
labels: a label will register the current program line within the controller
The example program will calculate the sum of the natural numbers from 0 to 10.
#!/usr/bin/perl use strict; use Scalar::Util qw(looks_like_number); use Processor; use Command; use Controller; my @program = (); while (<DATA>) { my ($name, $value) = split; if (looks_like_number($name)) { ($name, $value) = ('Number', $name); } elsif ($name eq 'LBL') { Controller::label($value, $#program); } push @program, new Command($name, $value); } Processor::push(10); Controller::run(@program); __DATA__ STO 0 0 LBL 0 RCL 0 + RCL 0 1 - STO 0 RCL 0 x>0? GTO 0
Recursion
Thank’s to the unlimited return- and data-stack recursion is possible. Here’s a recursive variant of the program above:
LBL 0 DUP x=0? RTN DUP 1 - GSB 0 + RTN
Examples
Greatest Common Divisor
LBL 0 SWAP OVER MOD DUP x>0? GTO 0 DROP
Factoring Numbers
STO 0 2 STO 1 LBL 0 RCL 1 DUP * RCL 0 - x>0? GTO 9 RCL 0 RCL 1 MOD x=0? GTO 1 RCL 1 1 + STO 1 GTO 0 LBL 1 RCL 0 RCL 1 / STO 0 RCL 1 GTO 0 LBL 9 RCL 0
We could add the following commands to make the program above a little shorter:
'x<y?' => sub { my $x = Processor::pop(); my $y = Processor::pop(); unless ($x < $y) { Controller::incPc(); } }, 'INC' => sub { my $x = Processor::recall($value); Processor::storage($value, $x + 1); },
Feel free to add all the stuff that is still missing.
Download
Just unzip this file in a new directory:
# unzip simulator.zip Archive: simulator.zip inflating: simulator.pl inflating: Command.pm inflating: Controller.pm inflating: Processor.pm
Here’s how to run the simulator:
# perl simulator.pl 00: STO 0 Register: 10 0 0 0 0 0 0 0 0 0 Stack : 01: Number 0 Register: 10 0 0 0 0 0 0 0 0 0 Stack : 0 02: LBL 0 Register: 10 0 0 0 0 0 0 0 0 0 Stack : 0 (...) 09: RCL 0 Register: 0 0 0 0 0 0 0 0 0 0 Stack : 55 0 10: x>0? Register: 0 0 0 0 0 0 0 0 0 0 Stack : 55