File Coverage

blib/lib/Language/RAM.pm
Criterion Covered Total %
statement 143 220 65.0
branch 127 182 69.7
condition 19 44 43.1
subroutine 26 35 74.2
pod 32 32 100.0
total 347 513 67.6


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