File Coverage

blib/lib/Language/RAM.pm
Criterion Covered Total %
statement 146 225 64.8
branch 129 186 69.3
condition 15 30 50.0
subroutine 26 35 74.2
pod 32 32 100.0
total 348 508 68.5


line stmt bran cond sub pod time code
1             package Language::RAM;
2              
3 21     21   442849 use 5.006;
  21         81  
4 21     21   113 use strict;
  21         44  
  21         563  
5 21     21   100 use warnings;
  21         40  
  21         94055  
6              
7             =head1 NAME
8              
9             Language::RAM - A "Random Access Machine" Emulator
10              
11             =head1 VERSION
12              
13             Version 0.012
14              
15             =cut
16              
17             our $VERSION = '0.012';
18              
19              
20             =head1 SYNOPSIS
21              
22             This module provides a library and an interpreter to emulate a basic
23             "Random Access Machine". This computer model uses an assembler-like syntax
24             and can be used to test simple algorithms for their complexity.
25              
26             You can use C to run Random Access Machines and get
27             extensive information on memory usage, memory changes and complexity.
28             See C for details.
29              
30             use Language::RAM;
31              
32             my $input = "INPUT 0\nOUTPUT 1\na <-- s[0]\na <-- a * s[0]\ns[1] <-- a\nHALT";
33              
34             my %machine = Language::RAM::asl($input);
35             die "$machine{'error'}" if ($machine{'error'} ne '');
36              
37             my $ret = Language::RAM::run(\%machine, [8]); # Returns 8^2
38             if($ret) {
39             print STDERR "Error from machine: $machine{'error'}\n";
40             }
41              
42             my %output = Language::RAM::get_output(\%machine);
43             print "OUTPUT FROM MACHINE:\n";
44             foreach (sort { $a <=> $b } keys %output) {
45             printf "%4d=%d\n", $_, $output{$_} // 0;
46             }
47              
48             =head1 EXPORT
49              
50             To use Language::RAM, you only need C, C, C, C,
51             C, C, C, C
52             and C.
53              
54             =head1 GENERAL
55              
56             =head2 asl(input)
57              
58             Return random access machine from input string (one command per line).
59              
60             The returned hash has the following keys/values:
61              
62             =over 4
63              
64             =item B=I
65              
66             Range of input slots defined by INPUT command.
67              
68             =item B=I
69              
70             Range of output slots defined by OUTPUT command.
71              
72             =item B=I
73              
74             Empty string. Not empty on errors.
75              
76             =item B=I
77              
78             Hash of address => AST-Token.
79              
80             =item B=I
81              
82             Hash of slot name => value.
83              
84             =item B=I
85              
86             =item B=I
87              
88             =over 4
89              
90             =item B=I
91              
92             Hash of slot name => [(reads writes)].
93              
94             =item B=I
95              
96             Hash of address => counter.
97              
98             =back
99              
100             =item B=I
101              
102             Memory snapshots of each assignment.
103             Hash of step => [(ip, address, new_value)].
104              
105             =item B=I
106              
107             =back
108              
109             =cut
110              
111             sub asl {
112 0     0 1 0 my %machine = (
113             code => {},
114             lines => {},
115             error => ''
116             );
117 0         0 my $ip = -1;
118 0         0 foreach (split /\n/, $_[0]) {
119 0         0 $_ =~ s/\A\s+|\s+\Z//g;
120 0 0       0 next if $_ eq '';
121 0         0 my ($n_ip, $value) = &ast($_, \%machine, $ip);
122 0 0       0 return %machine unless defined $value;
123 0 0       0 next if $n_ip == -1;
124 0         0 $ip = $n_ip;
125 0         0 $machine{'code'}{$ip} = $value;
126             }
127 0         0 return %machine;
128             }
129              
130             =head2 run(machine, input, limit, snapshots)
131              
132             Run machine until it halts or stepcounter reaches limit.
133              
134             =over 4
135              
136             =item B=I
137              
138             =item B=I
139              
140             Values will be loaded into memory before execution according to paramters given
141             by INPUT.
142              
143             =item B=I
144              
145             =item B=I
146              
147             Set to true to generate detailed memory snapshots of each command.
148              
149             =back
150              
151             Returns empty string on success, error string on error.
152              
153             =cut
154              
155             sub run {
156 0     0 1 0 my $machine = $_[0];
157 0         0 my $limit = -1;
158 0 0       0 $limit = $_[2] if(@_ >= 3);
159              
160 0         0 $machine->{'input'} = $_[1];
161 0         0 $machine->{'error'} = '';
162 0         0 $machine->{'ip'} = 0;
163 0         0 $machine->{'steps'} = 0;
164 0         0 $machine->{'memory'} = {};
165 0 0       0 $machine->{'snaps'} = {} if $_[3];
166              
167 0         0 foreach my $index ( 0 .. $#{$$machine{'input_layout'}}) {
  0         0  
168 0         0 my $id = $$machine{'input_layout'}->[$index];
169 0         0 my $input = $machine->{'input'}[$index];
170 0 0       0 (defined $input) or $input = 0;
171 0         0 $machine->{'memory'}{$id} = $input;
172             }
173              
174 0   0     0 while($limit == -1 || $machine->{'steps'} < $limit) {
175 0         0 my $current = $machine->{'code'}{$machine->{'ip'}};
176              
177 0 0       0 unless($current) {
178 0         0 return $machine->{'error'} = "Reached nocode at $machine->{'ip'}";
179             }
180              
181 0 0       0 unless(exists $machine->{'stats'}{'command_usage'}{$machine->{'ip'}}) {
182 0         0 $machine->{'stats'}{'command_usage'}{$machine->{'ip'}} = 0;
183             }
184 0         0 ++$machine->{'stats'}{'command_usage'}{$machine->{'ip'}};
185              
186 0 0       0 $machine->{'snaps'}{$machine->{'steps'}} = [($machine->{'ip'})] if $_[3];
187              
188 0 0       0 &eval($current, $machine, 0, $_[3]) or return $machine->{'error'};
189              
190 0         0 ++$machine->{'ip'};
191 0         0 ++$machine->{'steps'};
192             }
193              
194 0 0 0     0 if($limit > 0 && $machine->{'steps'} == $limit) {
195 0         0 return $machine->{'error'} = "Readed op limit at $machine->{'ip'}(aborted after $limit ops)";
196             }
197 0         0 return '';
198             }
199              
200             =head2 get_output(machine)
201              
202             Return output from machine.
203              
204             =over 4
205              
206             =item B=I
207              
208             =back
209              
210             Returns a hash of slot => value.
211              
212             =cut
213              
214             sub get_output {
215 0     0 1 0 my %ret;
216 0         0 foreach (@{$_[0]->{'output_layout'}}) {
  0         0  
217 0         0 $ret{$_} = $_[0]->{'memory'}{$_};
218             }
219 0         0 return %ret;
220             }
221              
222             =head2 get_code_stats(machine)
223              
224             Return code statistics from machine.
225              
226             =over 4
227              
228             =item B=I
229              
230             =back
231              
232             Returns a hash of address => counter.
233              
234             =cut
235              
236             sub get_code_stats {
237 0     0 1 0 return %{$_[0]->{'stats'}{'command_usage'}};
  0         0  
238             }
239              
240             =head2 get_mem_stats(machine)
241              
242             Return memory statistics from machine.
243              
244             =over 4
245              
246             =item B=I
247              
248             =back
249              
250             Returns a hash of slot => counter.
251              
252             =cut
253              
254             sub get_mem_stats {
255 0     0 1 0 return %{$_[0]->{'stats'}{'memory_usage'}};
  0         0  
256             }
257              
258             =head2 get_line(machine, id)
259              
260             Return line at id.
261              
262             =over 4
263              
264             =item B=I
265              
266             =item B=I
267              
268             =back
269              
270             Returns line as string.
271              
272             =cut
273              
274             sub get_line {
275 0     0 1 0 return $_[0]->{'lines'}{$_[1]};
276             }
277              
278             =head2 get_first_memory_snapshot(machine)
279              
280             Returns a memory snapshot (a hash) of index => value of the memory at step -1
281             (before the machine starts).
282              
283             =over 4
284              
285             =item B=I
286              
287             =back
288              
289             =cut
290              
291             sub get_first_memory_snapshot {
292 0     0 1 0 my %snapshot = ();
293 0         0 my $machine = $_[0];
294 0         0 my $snapshots = &get_snapshots($machine);
295              
296 0         0 foreach (keys %$snapshots) {
297 0 0       0 next unless exists $$snapshots{$_}->[1];
298 0         0 $snapshot{$$snapshots{$_}->[1]} = 0;
299             }
300              
301 0         0 foreach my $index ( 0 .. $#{$$machine{'input_layout'}}) {
  0         0  
302 0         0 my $id = $$machine{'input_layout'}->[$index];
303 0         0 my $input = $$machine{'input'}->[$index];
304 0 0       0 (defined $input) or $input = 0;
305 0         0 $snapshot{$id} = $input;
306             }
307              
308 0         0 return %snapshot;
309             }
310              
311             =head2 get_snapshots(machine)
312              
313             Returns a hash ref of step => [(ip, addr, value)].
314              
315             =over 4
316              
317             =item B=I
318              
319             =back
320              
321             =cut
322              
323             sub get_snapshots {
324 0     0 1 0 return $_[0]->{'snaps'};
325             }
326              
327             =head2 replay_snapshot(machine, memory, from, to)
328              
329             Replay steps from to to of machine in memory.
330              
331             =over 4
332              
333             =item B=I
334              
335             =item B=I
336              
337             =item B=I
338              
339             =item B=I
340              
341             =back
342              
343             =cut
344              
345             sub replay_snapshot {
346 0     0 1 0 foreach ($_[2]..$_[3]) {
347 0         0 my $step = $_[0]->{'snaps'}{$_};
348 0 0       0 next unless exists $$step[1];
349 0         0 $_[1]->{$$step[1]} = $$step[2];
350             }
351             }
352              
353             =head1 ABSTRACT SYNTAX TREE
354              
355             =head2 ast(line, machine, ip)
356              
357             Return AST of line.
358              
359             =over 4
360              
361             =item B=I
362              
363             =item B=I
364              
365             =item B=I
366              
367             =back
368              
369             Returns (ip, ast).
370              
371             =over 4
372              
373             =item B=
374              
375             Address of line (either generated or read from line, see README).
376              
377             -1 if line is INPUT/OUTPUT statement.
378              
379             =item B=
380              
381             undef on error.
382              
383             =back
384              
385             =cut
386              
387             sub ast {
388 6     6 1 6023 my $l = $_[0];
389 6         14 $l =~ s(//.+\Z)();
390 6         23 $l =~ s/\A\s+|\s+\Z//g;
391              
392 6         11 my $ip = $_[2] + 1;
393 6 100       21 if($l =~ /\A(\d+):\s*(.+)/) {
394 2         19 $ip = $1;
395 2         5 $l = $2;
396             }
397              
398 6         24 while(exists $_[1]->{'code'}{$ip}) {
399 2         7 ++$ip;
400             }
401              
402 6 100       28 if($l =~ /\A(INPUT|OUTPUT)(.*)\Z/) {
403 3 100       14 if($2 eq '') {
404 1         6 $_[1]->{'error'} = "$ip> $1 expects an argument";
405 1         4 return (-1, undef);
406             }
407 2         8 my $ret = &ast_eval_io($2, $_[1]);
408 2 100       7 if($1 eq 'INPUT') {
409 1         3 $_[1]->{'input_layout'} = $ret;
410             } else {
411 1         3 $_[1]->{'output_layout'} = $ret;
412             }
413 2         8 return (-1, $ret);
414             }
415              
416 3         8 $_[1]->{'lines'}{$ip} = $l;
417              
418 3         10 return ($ip, &get_ast($l, $_[1], $ip));
419             }
420              
421             =head2 ast_eval_io(line, machine)
422              
423             Return INPUT/OUTPUT layout.
424              
425             =over 4
426              
427             =item B=I
428              
429             =item B=I
430              
431             =back
432              
433             Returns a reference to a list of indices or undef on error.
434              
435             =cut
436              
437             sub ast_eval_io {
438 10 100   10 1 2217 if(index($_[0], ' ') != -1) {
439 4         19 my @parms = split /\s+/, $_[0];
440 4         9 my $ret = [()];
441              
442 4         12 foreach (@parms) {
443 6 100       20 next if $_ eq '';
444 4         15 my $r = &ast_eval_io($_, $_[1]);
445 4 50       12 return undef unless $r;
446 4         6 push @{$ret}, @{$r};
  4         7  
  4         16  
447             }
448              
449 4 100       6 if(@{$ret} == 0) {
  4         17  
450 1         3 $_[1]->{'error'} = 'Command expects argument';
451             }
452 4         13 return $ret;
453             } else {
454 6 100       36 if($_[0] =~ /\A(\d+)..(\d+)\Z/) {
    100          
455 1         3 my @ret = ();
456 1         8 for(my $i = $1; $i <= $2; $i++) {push @ret, $i;}
  3         12  
457 1         3 return \@ret;
458             } elsif($_[0] =~ /\A(\d+)\Z/) {
459 4         22 return [$1];
460             } else {
461 1         4 $_[1]->{'error'} = "Argument not numeric: $_[0]";
462 1         4 return undef;
463             }
464             }
465             }
466              
467             =head2 get_ast(input, machine, ip)
468              
469             Return AST-Token from line.
470              
471             =over 4
472              
473             =item B=I
474              
475             =item B=I
476              
477             =item B=I
478              
479             =back
480              
481             Return AST-Token or undef.
482              
483             =cut
484              
485             sub get_ast {
486 124 100   124 1 1015 if($_[0] =~ /\A(\-?\d+(?:\.\d+)?)\Z/) {
    100          
    100          
    100          
    100          
    100          
    50          
    50          
    0          
487 30         78 &ast_imm($1);
488             } elsif ($_[0] =~ /\A(a|i|i1|i2|i3)\Z/) {
489 62 50       267 &ast_reg($1 eq 'i' ? 'i1' : $1);
490             } elsif ($_[0] =~ /\As\[\s*(.+?)\s*\]\Z/) {
491 13         42 &ast_mem($1, $_[1], $_[2]);
492             } elsif ($_[0] =~ /\Ajump\s+(.+)\s*\Z/) {
493 3         12 &ast_jump($1, $_[1], $_[2]);
494             } elsif ($_[0] =~ /\A(.+?)\s*<--?\s*(.+?)\s*\Z/) {
495 3         11 &ast_assign($1, $2, $_[1], $_[2]);
496             } elsif ($_[0] =~ /\A(.+?)\s*(<|<=|!?=|>=|>)\s*0\Z/) {
497 2         8 &ast_cond($1, $2, $_[1], $_[2]);
498             } elsif ($_[0] =~ /\Aif\s+(.+?)\s+then\s+(.+)\Z/) {
499 0         0 &ast_cond_jump($1, $2, $_[1], $_[2]);
500             } elsif ($_[0] =~ /\A(.+?)\s*(\+|\-|\*|div|mod)\s*(.+)\Z/) {
501 11         33 &ast_algo($1, $2, $3, $_[1], $_[2]);
502             } elsif ($_[0] eq 'HALT') {
503 0         0 return [('halt')];
504             } else {
505 0         0 $_[1]->{'error'} = "Unknown input: $_[0]";
506 0         0 return undef;
507             }
508             }
509              
510             =head2 ast_imm(value)
511              
512             Returns AST-Token for immutable values.
513              
514             =over 4
515              
516             =item B=I
517              
518             =back
519              
520             Returns C<[('imm', value)]>.
521              
522             =cut
523              
524             sub ast_imm {
525 31     31 1 1241 return [('imm', $_[0])];
526             }
527              
528             =head2 ast_reg(register)
529              
530             Returns AST-Token for a register (a, i1..i3).
531              
532             =over 4
533              
534             =item B=I
535              
536             =back
537              
538             Returns C<[('reg', register)]>.
539              
540             =cut
541              
542             sub ast_reg {
543 63     63 1 630 return [('reg', $_[0])];
544             }
545              
546             =head2 ast_algo(left, op, right, machine, ip)
547              
548             Returns AST-Token for an arithmetic expression.
549              
550             =over 4
551              
552             =item B=I
553              
554             =item B=I
555              
556             =item B=I
557              
558             =item B=I
559              
560             =item B=I
561              
562             =back
563              
564             Returns C<[('algo', type, left, op, right)]> or undef on error.
565              
566             =over 4
567              
568             =item B=I
569              
570             True if left side of expression is register a, otherwise false.
571              
572             =item B=I
573              
574             =item B=I
575              
576             =item B=I
577              
578             =back
579              
580             =cut
581              
582             my %algo_right = qw(imm 0 mem 0 mmem 0);
583             sub ast_algo {
584 18 50   18 1 9746 (my $left = &get_ast($_[0], $_[3], $_[4])) or return undef;
585 18 50       45 (my $right = &get_ast($_[2], $_[3], $_[4])) or return undef;
586              
587 18 100       64 $left->[0] eq 'reg'
588             or return &report($_[3], "$_[4]> Expected reg, got: $left->[0]($_[0])");
589              
590 17 100       59 (exists $algo_right{$right->[0]})
591             or return &report($_[3], "$_[4]> Expected imm, mem or mmem, got: $right->[0]($_[2])");
592              
593 16         30 my $type = $left->[1] eq 'a';
594 16 100 66     98 ($type || ($right->[0] eq 'imm' && ($_[1] eq '+' || $_[1] eq '-')))
      33        
      66        
595             or return &report($_[3], "$_[4]> Index register only allows addition or subtraction with imm ($_[0]$_[1]$_[2])");
596              
597 15         85 return [('algo', $type, $left, $_[1], $right)];
598             }
599              
600             =head2 ast_mem(inner, machine, ip)
601              
602             Returns AST-Token for memory slot.
603              
604             =over 4
605              
606             =item B=I
607              
608             =item B=I
609              
610             =item B=I
611              
612             =back
613              
614             Returns C<[('mem', imm)]>, C<[('mmem', ast)]> or undef on error.
615              
616             =cut
617              
618             sub ast_mem {
619 18 50   18 1 249 (my $inner = &get_ast(@_)) or return undef;
620              
621 18 100       79 ($inner->[0] eq 'imm') and return [('mem', $inner->[1])];
622              
623 12 100 66     43 if($inner->[0] eq 'algo') {
    100          
624 10 100       33 ($inner->[1] == 0)
625             or return &report($_[1], "$_[2]> Cannot use register a in mmem ($_[0])");
626              
627 9         39 return [('mmem', $inner)];
628             } elsif ($inner->[0] eq 'reg' && $inner->[1] ne 'a') {
629 1         6 return [('mmem', $inner)];
630 1         8 } else {return &report($_[1], "$_[2]> Expected imm, algo or index register, got: $inner->[0]($_[0])");}
631             }
632              
633             =head2 ast_cond(cond, op, machine, ip)
634              
635             Returns AST-Token for conditional.
636              
637             =over 4
638              
639             =item B=I
640              
641             =item B=I
642              
643             =item B=I
644              
645             =item B=I
646              
647             =back
648              
649             Returns C<[('cond', reg, op)]> or undef on error.
650              
651             =over 4
652              
653             =item B=I
654              
655             =item B=I
656              
657             =back
658              
659             =cut
660              
661             sub ast_cond {
662 27 50   27 1 284 (my $reg = &get_ast($_[0], $_[2], $_[3])) or return undef;
663              
664 27 100       127 ($reg->[0] eq 'reg')
665             or return &report($_[2], "$_[3]> Expected reg, got: $reg->[0]($_[0])");
666              
667 26         203 return [('cond', $reg, $_[1])];
668             }
669              
670             =head2 ast_jump(imm, machine, ip)
671              
672             Returns AST-Token for jump instruction.
673              
674             =over 4
675              
676             =item B=I
677              
678             =item B=I
679              
680             =item B=I
681              
682             =back
683              
684             Returns C<[('jump', imm)]> or undef on error.
685              
686             =cut
687              
688             sub ast_jump {
689 5 50   5 1 249 (my $dest = &get_ast(@_)) or return undef;
690              
691 5 100       26 ($dest->[0] eq 'imm')
692             or return &report($_[1], "$_[2]> Expected imm, got: $dest->[0]($_[0])");
693              
694 4         19 return [('jump', $dest)];
695             }
696              
697             =head2 ast_cond_jump(cond, jump, machine, ip)
698              
699             Returns AST-Token for if cond then jump k.
700              
701             =over 4
702              
703             =item B=I
704              
705             =item B=I
706              
707             =item B=I
708              
709             =item B=I
710              
711             =back
712              
713             Returns C<[('if', cond, jump)]> or undef on error.
714              
715             =over 4
716              
717             =item B=I
718              
719             =item B=I
720              
721             =back
722              
723             =cut
724              
725             sub ast_cond_jump {
726 3 50   3 1 2175 (my $cond = &get_ast($_[0], $_[2], $_[3])) or return undef;
727              
728 3 100       17 ($cond->[0] eq 'cond')
729             or return &report($_[2], "$_[3]> Expected cond, got: $cond->[0]($_[0])");
730              
731 2 50       5 (my $jump = &get_ast($_[1], $_[2], $_[3])) or return undef;
732 2 100       11 ($jump->[0] eq 'jump')
733             or return &report($_[2], "$_[3]> Expected jump, got: $jump->[0]($_[1])");
734              
735 1         4 return [('if', $cond, $jump)];
736             }
737              
738             =head2 ast_assign(left, right, machine, ip)
739              
740             Returns AST-Token for assignment.
741              
742             =over 4
743              
744             =item B=I
745              
746             =item B=I
747              
748             =item B=I
749              
750             =item B=I
751              
752             =back
753              
754             Returns C<[('assign', left, right)]> or undef on error.
755              
756             =over 4
757              
758             =item B=I
759              
760             =item B=I
761              
762             =back
763              
764             =cut
765              
766             my %assign_right = qw(imm 0 mem 0 reg 0);
767             my %assign_a_right = qw(mmem 0 algo 0);
768             sub ast_assign {
769 15 50   15 1 7733 (my $left = &get_ast($_[0], $_[2], $_[3])) or return undef;
770 15 50       38 (my $right = &get_ast($_[1], $_[2], $_[3])) or return undef;
771              
772 15 100       50 if($left->[0] eq 'reg') {
    100          
    100          
773 10         32 my $rcheck = exists $assign_right{$right->[0]};
774              
775 10 100       27 if($left->[1] eq 'a') {
776 8 100 100     42 ($rcheck || (exists $assign_a_right{$right->[0]}))
777             or return &report($_[2], "$_[3]> Expected imm, reg, mem, mmem or algo, got: $right->[0]($_[1])");
778             } else {
779 2 100 66     18 ($rcheck || $right->[0] eq 'algo')
780             or return &report($_[2], "$_[3]> Expected imm, reg, mem or algo, got: $right->[0]($_[1])");
781              
782 1 50 33     13 (!$right->[1] || $right->[0] ne 'algo') or return &report($_[2], "$_[3]> register a not allowed in i(1|2|3) assignment ($_[1])");
783             }
784             } elsif ($left->[0] eq 'mem') {
785 2 100       13 ($right->[0] eq 'reg')
786             or return &report($_[2], "$_[3]> Expected reg, got: $right->[0]($_[1])");
787             } elsif ($left->[0] eq 'mmem') {
788 2 100 66     18 ($right->[0] eq 'reg' && $right->[1] eq 'a')
789             or return &report($_[2], "$_[3]> Expected register a, got: $right->[0]($_[1])");
790 1         8 } else {return &report($_[2], "$_[3]> Expected reg, mem or mmem, got: $left->[0]($_[0])");}
791 9         41 return [('assign', $left, $right)];
792             }
793              
794             =head1 EVALUATION
795              
796             =head2 eval(ast, machine)
797              
798             Evaluate ast.
799              
800             =over 4
801              
802             =item B=I
803              
804             =item B=I
805              
806             =back
807              
808             Returns undef on error.
809              
810             =cut
811              
812             my %eval_funcs = (
813             imm => \&eval_imm,
814             reg => \&eval_mem,
815             mem => \&eval_mem,
816             mmem => \&eval_mmem,
817             algo => \&eval_algo,
818             cond => \&eval_cond,
819             if => \&eval_if,
820             jump => \&eval_jump,
821             assign => \&eval_assign
822             );
823              
824             sub eval {
825 113 50   113 1 319 if ($_[0]->[0] eq 'halt') {
826 0         0 return undef;
827             }
828 113 50       322 if (exists $eval_funcs{$_[0]->[0]}) {
829 113         326 return $eval_funcs{$_[0]->[0]}->(@_);
830             } else {
831 0         0 $_[1]->{'error'} = "AST Element $_[0][0] not supported";
832 0         0 return undef;
833             }
834             }
835              
836             =head2 eval_imm(ast)
837              
838             my $ast = [qw(imm 2)];
839              
840             Returns immutable value of ast.
841              
842             =over 4
843              
844             =item B=I
845              
846             =back
847              
848             =cut
849              
850             sub eval_imm {
851 22     22 1 280 $_[0]->[1];
852             }
853              
854             =head2 eval_mem(ast, machine, type)
855              
856             my $ast = [qw(mem 2)];
857              
858             Returns value of/reference to/address of memory block.
859              
860             =over 4
861              
862             =item B=I
863              
864             =item B=I
865              
866             =item B=I
867              
868             Returns value of memory block if B is 0.
869              
870             Returns reference to memory block if B is 1.
871              
872             Returns address of memory block if B is 2.
873              
874             =back
875              
876             =cut
877              
878             sub eval_mem {
879 87     87 1 366 my $type = $_[2];
880 87 100       231 (defined $type) or $type = 0;
881 87 100       276 unless(exists $_[1]->{'memory'}{$_[0]->[1]}) {
882 6         19 $_[1]->{'memory'}{$_[0]->[1]} = 0;
883             }
884              
885 87         206 &inc_mem_stat($_[1], $_[0]->[1], $type);
886              
887 87 100       427 return $_[1]->{'memory'}{$_[0]->[1]} unless $type > 0;
888 12 100       64 return \$_[1]->{'memory'}{$_[0]->[1]} if $type == 1;
889 2         11 return $_[0]->[1];
890             }
891              
892             =head2 eval_mmem(ast, machine, type)
893              
894             my $ast = [('mmem', "algo here, see eval_algo")];
895              
896             Same as C, but evaluate inner expression.
897              
898             Returns undef if inner expression could not be evaluated.
899              
900             =cut
901              
902             sub eval_mmem {
903 13 50   13 1 230 return undef unless (defined(my $val = &eval($_[0]->[1], $_[1])));
904 13         51 return &eval_mem([('mem', $val)], $_[1], $_[2]);
905             }
906              
907             =head2 eval_algo(ast, machine)
908              
909             my $ast = [('algo', 1, [qw(reg a)], '+', [qw(mem 2)])];
910              
911             Return result of arithmetic expression.
912              
913             =over 4
914              
915             =item B=I
916              
917             =item B=I
918              
919             =back
920              
921             Returns undef if left side, right side or operation failed to evaluate.
922              
923             =cut
924              
925             sub eval_algo {
926 32 50   32 1 704 return undef unless (defined(my $left = &eval($_[0]->[2], $_[1])));
927 32 50       75 return undef unless (defined(my $right = &eval($_[0]->[4], $_[1])));
928              
929 32 100       120 if($_[0]->[3] eq '+') {
    100          
    100          
    100          
    50          
930 17         74 return $left + $right;
931             } elsif($_[0]->[3] eq '-') {
932 6         29 return $left - $right;
933             } elsif($_[0]->[3] eq '*') {
934 3         15 return $left * $right;
935             } elsif($_[0]->[3] eq 'div') {
936 3         17 return int($left / $right);
937             } elsif($_[0]->[3] eq 'mod') {
938 3         15 return $left % $right;
939             } else {
940 0         0 $_[1]->{'error'} = "Operator not supported: $_[0][3]";
941 0         0 return undef;
942             }
943             }
944              
945             =head2 eval_cond(ast, machine)
946              
947             my $ast = [('cond', [qw(reg a)], '<=')];
948              
949             Return result of conditional (always compares against 0).
950              
951             =over 4
952              
953             =item B=I
954              
955             =item B=I
956              
957             =back
958              
959             Returns undef if left side or operation failed to evaluate.
960              
961             =cut
962              
963             sub eval_cond {
964 20 50   20 1 644 return undef unless (defined(my $val = &eval($_[0]->[1], $_[1])));
965              
966 20 100       118 if($_[0]->[2] eq '<') {
    100          
    100          
    100          
    100          
    50          
967 4         22 return $val < 0;
968             } elsif($_[0]->[2] eq '<=') {
969 3         14 return $val <= 0;
970             } elsif($_[0]->[2] eq '=') {
971 3         16 return $val == 0;
972             } elsif($_[0]->[2] eq '!=') {
973 3         15 return $val != 0;
974             } elsif($_[0]->[2] eq '>=') {
975 3         16 return $val >= 0;
976             } elsif($_[0]->[2] eq '>') {
977 4         20 return $val > 0;
978             } else {
979 0         0 $_[1]->{'error'} = "Operator not supported: $_[0][2]";
980 0         0 return undef;
981             }
982             }
983              
984             =head2 eval_if(ast, machine)
985              
986             my $ast = [('if', "cond here, see eval_cond", "jump here, see eval_jump")];
987              
988             Jump if conditional evaluates to true.
989              
990             =over 4
991              
992             =item B=I
993              
994             =item B=I
995              
996             =back
997              
998             Returns undef if conditional returned an error.
999              
1000             =cut
1001              
1002             sub eval_if {
1003 2 50   2 1 1305 return undef unless (defined(my $cond = &eval($_[0]->[1], $_[1])));
1004 2 100       9 &eval_jump($_[0]->[2], $_[1]) if $cond;
1005 2         6 return 1;
1006             }
1007              
1008             =head2 eval_jump(ast, machine)
1009              
1010             my $ast = [('jump', [qw(imm 2)])];
1011              
1012             Jump to address.
1013              
1014             =over 4
1015              
1016             =item B=I
1017              
1018             =item B=I
1019              
1020             =back
1021              
1022             Returns undef if address could not be evaluated.
1023              
1024             =cut
1025              
1026             sub eval_jump {
1027 2 50   2 1 631 return undef unless (defined(my $val = &eval($_[0]->[1], $_[1])));
1028 2         9 $_[1]->{'ip'} = $val - 1;
1029 2         6 return 1;
1030             }
1031              
1032             =head2 eval_assign(ast, machine)
1033              
1034             my $ast = [('assign', "left side", "right side")];
1035              
1036             Evaluate assignment.
1037              
1038             =over 4
1039              
1040             =item B=I
1041              
1042             =item B=I
1043              
1044             =back
1045              
1046             Returns undef if left or right side could not be evaluated.
1047              
1048             =cut
1049              
1050             sub eval_assign {
1051 6 50   6 1 3515 return undef unless (my $left = &eval($_[0]->[1], $_[1], 1));
1052 6 50       17 return undef unless (defined(my $right = &eval($_[0]->[2], $_[1])));
1053 6         11 $$left = $right;
1054 6 50       17 &add_snapshot($_[1], &eval($_[0]->[1], $_[1], 2), $right) if $_[1]->{'snaps'};
1055 6         15 return 1;
1056             }
1057              
1058             =head1 STATISTICS
1059              
1060             =head2 inc_mem_stat(machine, mem, action)
1061              
1062             Increases access counter of one memory slot. These stats can later be retrieved
1063             with C.
1064              
1065             =over 4
1066              
1067             =item B=I
1068              
1069             =item B=I
1070              
1071             =item B=I
1072              
1073             Add write action to memory slot if B is true.
1074              
1075             Add read action to memory slot if B is false.
1076              
1077             =back
1078              
1079             =cut
1080              
1081             sub inc_mem_stat {
1082 92 100   92 1 422 return if $_[2] == 2; #Bail if used in stat collection.
1083              
1084 90 100       273 unless(exists $_[0]->{'stats'}{'memory_usage'}{$_[1]}) {
1085 17         63 $_[0]->{'stats'}{'memory_usage'}{$_[1]} = [qw(0 0)];
1086             }
1087              
1088 90 100       343 ++$_[0]->{'stats'}{'memory_usage'}{$_[1]}[$_[2] ? 1 : 0];
1089             }
1090              
1091             =head2 add_snapshot(machine, addr, value)
1092              
1093             Add replayable snapshot where memory slot addr changes to value.
1094              
1095             =over 4
1096              
1097             =item B=I
1098              
1099             =item B=I
1100              
1101             =item B=I
1102              
1103             =back
1104              
1105             =cut
1106              
1107             sub add_snapshot {
1108 1     1 1 557 push @{$_[0]->{'snaps'}{$_[0]->{'steps'}}}, ($_[1], $_[2]);
  1         7  
1109             }
1110              
1111             =head2 report(machine, message)
1112              
1113             Set error string of machine to message.
1114              
1115             =over 4
1116              
1117             =item B=I
1118              
1119             =item B=I
1120              
1121             =back
1122              
1123             Returns undef.
1124              
1125             =cut
1126              
1127             sub report {
1128 15     15 1 36 $_[0]->{'error'} = $_[1];
1129 15         67 return undef;
1130             }
1131              
1132             =head1 AUTHOR
1133              
1134             Fabian Stiewitz, C<< >>
1135              
1136             =head1 BUGS
1137              
1138             Please report any bugs or feature requests to C, or through
1139             the web interface at L. I will be notified, and then you'll
1140             automatically be notified of progress on your bug as I make changes.
1141              
1142              
1143              
1144              
1145             =head1 SUPPORT
1146              
1147             You can find documentation for this module with the perldoc command.
1148              
1149             perldoc Language::RAM
1150              
1151              
1152             You can also look for information at:
1153              
1154             =over 4
1155              
1156             =item * RT: CPAN's request tracker (report bugs here)
1157              
1158             L
1159              
1160             =item * AnnoCPAN: Annotated CPAN documentation
1161              
1162             L
1163              
1164             =item * CPAN Ratings
1165              
1166             L
1167              
1168             =item * Search CPAN
1169              
1170             L
1171              
1172             =back
1173              
1174              
1175             =head1 ACKNOWLEDGEMENTS
1176              
1177              
1178             =head1 LICENSE AND COPYRIGHT
1179              
1180             Copyright 2015 Fabian Stiewitz.
1181              
1182             This program is free software; you can redistribute it and/or modify it
1183             under the terms of the the Artistic License (2.0). You may obtain a
1184             copy of the full license at:
1185              
1186             L
1187              
1188             Any use, modification, and distribution of the Standard or Modified
1189             Versions is governed by this Artistic License. By using, modifying or
1190             distributing the Package, you accept this license. Do not use, modify,
1191             or distribute the Package, if you do not accept this license.
1192              
1193             If your Modified Version has been derived from a Modified Version made
1194             by someone other than you, you are nevertheless required to ensure that
1195             your Modified Version complies with the requirements of this license.
1196              
1197             This license does not grant you the right to use any trademark, service
1198             mark, tradename, or logo of the Copyright Holder.
1199              
1200             This license includes the non-exclusive, worldwide, free-of-charge
1201             patent license to make, have made, use, offer to sell, sell, import and
1202             otherwise transfer the Package with respect to any patent claims
1203             licensable by the Copyright Holder that are necessarily infringed by the
1204             Package. If you institute patent litigation (including a cross-claim or
1205             counterclaim) against any party alleging that the Package constitutes
1206             direct or contributory patent infringement, then this Artistic License
1207             to you shall terminate on the date that such litigation is filed.
1208              
1209             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
1210             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
1211             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
1212             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
1213             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
1214             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
1215             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
1216             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1217              
1218              
1219             =cut
1220              
1221             1; # End of Language::RAM