File Coverage

blib/lib/App/Scheme79asm/Compiler.pm
Criterion Covered Total %
statement 115 116 99.1
branch 43 44 97.7
condition n/a
subroutine 27 27 100.0
pod 0 15 0.0
total 185 202 91.5


line stmt bran cond sub pod time code
1             package App::Scheme79asm::Compiler;
2              
3 1     1   116316 use 5.014000;
  1         15  
4 1     1   8 use strict;
  1         2  
  1         44  
5 1     1   7 use warnings;
  1         3  
  1         70  
6              
7             our $VERSION = '0.005001';
8              
9 1     1   9 use Carp qw/croak/;
  1         2  
  1         74  
10 1     1   722 use Data::Dumper qw/Dumper/;
  1         9024  
  1         84  
11 1     1   8 use Scalar::Util qw/looks_like_number/;
  1         3  
  1         60  
12              
13 1     1   9 use Data::SExpression qw/cons consp scalarp/;
  1         3  
  1         9  
14 1     1   691 use List::MoreUtils qw/firstidx/;
  1         16458  
  1         11  
15              
16             our @PRIMOPS = qw/car cdr cons atom progn reverse-list/;
17              
18 20     20 0 868 sub make_symbol { Data::SExpression::Symbol->new(shift) }
19              
20             # types
21             our $LIST = make_symbol 'LIST';
22             our $SYMBOL = make_symbol 'SYMBOL';
23             our $VAR = make_symbol 'VAR';
24             # no need for closures
25             our $PROC = make_symbol 'PROC';
26             our $IF = make_symbol 'IF';
27             our $CALL = make_symbol 'CALL';
28             # no need for quoted constants
29              
30             # primitives
31             our $MORE = make_symbol 'MORE';
32             our $FUNCALL = make_symbol 'FUNCALL';
33              
34             BEGIN {
35 1     1   1576 *cons = *Data::SExpression::cons;
36 1         5 *consp = *Data::SExpression::consp;
37 1         1916 *scalarp = *Data::SExpression::scalarp;
38             }
39              
40             # list processing routines
41             sub append {
42 30     30 0 927 my ($expr, $rest) = @_;
43 30 100       89 if (defined $expr) {
44 20         552 cons $expr->car, append($expr->cdr, $rest)
45             } else {
46 10         43 $rest
47             }
48             }
49              
50             sub mapcar (&@);
51              
52             sub mapcar (&@) {
53 41     41 0 273 my ($block, $expr) = @_;
54 41 100       124 if (defined $expr) {
55 24         47 my $result;
56 24         44 do {
57 24         642 local $_ = $expr->car;
58 24         191 $result = $block->()
59             };
60 24     10   892 cons $result, mapcar { $block->($_) } $expr->cdr
  10         29  
61             } else {
62             undef
63 17         74 }
64             }
65              
66             sub revacc {
67 14     14 0 426 my ($expr, $acc) = @_;
68 14 100       45 if (defined $expr) {
69 8         221 revacc ($expr->cdr, cons($expr->car, $acc))
70             } else {
71 6         29 $acc
72             }
73             }
74              
75             sub rev {
76 6     6 0 19 my ($expr) = @_;
77 6         23 revacc $expr, undef;
78             }
79              
80             sub positionacc {
81 37     37 0 224 my ($expr, $list, $acc) = @_;
82 37 100       980 if (!defined $list) {
    100          
83             undef
84 1         4 } elsif ($list->car eq $expr) {
85 16         1872 $acc
86             } else {
87 20         2787 positionacc($expr, $list->cdr, $acc + 1)
88             }
89             }
90              
91             sub position {
92 17     17 0 54 my ($expr, $list) = @_;
93 17         51 positionacc $expr, $list, 0
94             }
95             # end list processing routines
96              
97             sub new {
98 27     27 0 202 my ($class) = @_;
99 27         190 my %self = (
100             symbols => ['', '', 'T'],
101             nsymbols => 3,
102             symbol_map => {T => 2},
103             );
104 27         168 bless \%self, $class;
105             }
106              
107             sub process_quoted {
108 47     47 0 5641 my ($self, $expr) = @_;
109 47 100       206 if (!defined $expr) { # nil
    100          
    100          
110 9         240 [$LIST => 0]
111             } elsif (scalarp $expr) {
112 24         218 $expr = uc $expr;
113 24 50       697 if ($expr eq 'NIL') {
114 0         0 return [$LIST => 0]
115             }
116 24 100       92 if (!exists $self->{symbol_map}{$expr}) {
117 22         76 $self->{symbol_map}{$expr} = $self->{nsymbols};
118 22         47 $self->{nsymbols}++;
119 22         48 push @{$self->{symbols}}, $expr;
  22         103  
120             }
121 24         340 [$SYMBOL => $self->{symbol_map}{$expr}]
122             } elsif (consp $expr) {
123 13         583 [$LIST => $self->process_quoted($expr->cdr), $self->process_quoted($expr->car)]
124             } else {
125 1         26 croak 'argument to process_quoted is not a scalar, cons, or nil: ', Dumper($expr);
126             }
127             }
128              
129             sub process_proc {
130 6     6 0 23 my ($self, $func_name, $func_args, $func_body, $env) = @_;
131 6         25 my $new_env = append cons($func_name, rev $func_args), $env;
132 6         248 $self->process_toplevel($func_body, $new_env)
133             }
134              
135             sub rest_of_funcall {
136 28     28 0 1096 my ($self, $func, $args) = @_;
137 28 100       88 if (!defined $args) {
138 16         442 $func
139             } else {
140 12         378 [$MORE => $self->rest_of_funcall($func, $args->cdr), $args->car]
141             }
142             }
143              
144             sub process_funcall {
145 17     17 0 661 my ($self, $func_name, $func_args, $env) = @_;
146 17     59   167 my $prim_idx = firstidx { uc $_ eq uc $func_name } @PRIMOPS;
  59         2207  
147             my $processed_args =
148 17     24   1055 mapcar { $self->process_toplevel($_, $env) } $func_args;
  24         98  
149 17 100       514 if ($prim_idx > -1) {
150 13 100       48 if (!defined $processed_args) {
151 1         7 croak "Cannot call primitive $func_name with no arguments";
152             }
153 12         54 [$CALL => $self->rest_of_funcall([make_symbol(uc $func_name), 0], $processed_args->cdr), $processed_args->car]
154             } else {
155 4         21 my $final_args = append $processed_args, cons ($self->process_toplevel($func_name, $env), undef);
156 4         221 [$CALL => $self->rest_of_funcall([$FUNCALL => 0], $final_args->cdr), $final_args->car]
157             }
158             }
159              
160             sub process_toplevel {
161 68     68 0 30563 my ($self, $expr, $env) = @_;
162 68 100       365 if (!defined $expr) {
    100          
163 2         44 [$LIST => 0]
164             } elsif (scalarp $expr) {
165 25 100       318 if (looks_like_number $expr) {
    100          
    100          
166 2         10 $self->process_quoted($expr);
167             } elsif (uc $expr eq 'T') {
168 4         580 [$SYMBOL => 2]
169             } elsif (uc $expr eq 'NIL') {
170 2         434 [$LIST => 0]
171             } else {
172 17         3240 my $position = position $expr, $env;
173 17 100       50 if (defined $position) {
174 16         149 [$VAR => -1 - $position]
175             } else {
176 1         6 croak "Variable $expr not in environment";
177             }
178             }
179             } else {
180 41         1463 my $func = uc $expr->car;
181 41 100       2784 if ($func eq 'QUOTE') {
    100          
    100          
182 14         366 $self->process_quoted($expr->cdr->car)
183             } elsif ($func eq 'LAMBDA') {
184 6         210 my $func_name = $expr->cdr->car;
185 6         353 my $func_args = $expr->cdr->cdr->car;
186 6         364 my $func_body = $expr->cdr->cdr->cdr->car; ## no critic (ProhibitLongChainsOfMethodCalls)
187 6         244 [$PROC => $self->process_proc($func_name, $func_args, $func_body, $env)]
188             } elsif ($func eq 'IF') {
189             my ($if_cond, $if_then, $if_else) =
190 4         115 map { $self->process_toplevel($_, $env) }
  12         634  
191             ($expr->cdr->car, $expr->cdr->cdr->car, $expr->cdr->cdr->cdr->car); ## no critic (ProhibitLongChainsOfMethodCalls)
192 4         180 [$IF => [$LIST => $if_else, $if_then], $if_cond]
193             } else {
194 17         455 $self->process_funcall($expr->car, $expr->cdr, $env)
195             }
196             }
197             }
198              
199             sub compile_sexp {
200 10     10 0 39 my ($self, $expr) = @_;
201 10         38 $self->process_toplevel($expr, undef)
202             }
203              
204             sub compile_string {
205 10     10 0 40 my ($self, $string) = @_;
206 10         113 my $sexp = Data::SExpression->new(
207             {fold_lists => 0, use_symbol_class => 1}
208             );
209 10         2219 my $expr = $sexp->read($string);
210 10         28132 $self->compile_sexp($expr)
211             }
212              
213             1;
214             __END__