File Coverage

blib/lib/Language/RAM.pm
Criterion Covered Total %
statement 144 217 66.3
branch 127 180 70.5
condition 19 38 50.0
subroutine 26 35 74.2
pod 32 32 100.0
total 348 502 69.3


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