Add perlcritic test and make code compliant
[app-scheme79asm.git] / lib / App / Scheme79asm.pm
CommitLineData
509643aa
MG
1package App::Scheme79asm;
2
3use 5.014000;
4use strict;
5use warnings;
1756f229
MG
6use re '/s';
7use Carp qw/croak/;
509643aa 8
4a9c3fa9 9use Data::Dumper qw/Dumper/;
509643aa
MG
10use Data::SExpression qw/consp scalarp/;
11use Scalar::Util qw/looks_like_number/;
12
3fff77b7 13our $VERSION = '0.004';
509643aa
MG
14
15our %TYPES = (
16 LIST => 0,
17 SYMBOL => 1,
4a9c3fa9 18 NUMBER => 1,
509643aa
MG
19 VAR => 2,
20 VARIABLE => 2,
21 CLOSURE => 3,
22 PROC => 4,
23 PROCEDURE => 4,
4a9c3fa9 24 IF => 5,
509643aa
MG
25 COND => 5,
26 CONDITIONAL => 5,
27 CALL => 6,
509643aa
MG
28 QUOTE => 7,
29 QUOTED => 7,
4a9c3fa9
MG
30
31 MORE => 0,
32 CAR => 1,
33 CDR => 2,
34 CONS => 3,
35 ATOM => 4,
36 PROGN => 5,
f0289d3d 37 'REVERSE-LIST' => 6,
4a9c3fa9 38 FUNCALL => 7,
509643aa
MG
39);
40
41*consp = *Data::SExpression::consp;
42*scalarp = *Data::SExpression::scalarp;
43
44sub process {
4a9c3fa9
MG
45 my ($self, $sexp, $location) = @_;
46 die 'Toplevel is not a list: ', Dumper($sexp), "\n" unless ref $sexp eq 'ARRAY';
47 my ($type, @addrs) = @$sexp;
48 my $addr;
49
50 die 'Type of toplevel is not atom: '. Dumper($type), "\n" unless scalarp($type);
51
52 if (@addrs > 1) {
53 $addr = $self->{freeptr} + 1;
54 $self->{freeptr} += @addrs;
55 $self->process($addrs[$_], $addr + $_) for 0 .. $#addrs;
56 } else {
57 $addr = $addrs[0];
58 }
59
60 $addr = $self->process($addr) if ref $addr eq 'ARRAY';
61 die 'Addr of toplevel is not atom: ', Dumper($addr), "\n" unless scalarp($addr);
34073a4a 62 my ($comment_type, $comment_addr) = ($type, $addr);
4a9c3fa9 63 die 'Computed addr is not a number: ', Dumper($addr), "\n" unless looks_like_number $addr;
509643aa 64
ab8f838f 65 if (!looks_like_number $type) {
509643aa
MG
66 die "No such type: $type\n" unless exists $TYPES{$type};
67 $type = $TYPES{$type};
509643aa
MG
68 }
69
f0289d3d 70 $addr += (1 << $self->{addr_bits}) if $addr < 0;
1756f229
MG
71 die "Type too large: $type\n" if $type >= (1 << $self->{type_bits});
72 die "Addr too large: $addr\n" if $addr >= (1 << $self->{addr_bits});
509643aa 73 my $result = ($type << $self->{addr_bits}) + $addr;
4a9c3fa9
MG
74 unless ($location) {
75 $self->{freeptr}++;
76 $location = $self->{freeptr}
77 }
78 $self->{memory}[$location] = $result;
34073a4a 79 $self->{comment}[$location] = "$comment_type $comment_addr";
4a9c3fa9 80 $location
509643aa
MG
81}
82
83sub parse {
84 my ($self, $string) = @_;
4a9c3fa9 85 my $ds = Data::SExpression->new({symbol_case => 'up', use_symbol_class => 1, fold_lists => 1});
509643aa
MG
86
87 my $sexp;
88 while () {
89 last if $string =~ /^\s*$/;
90 ($sexp, $string) = $ds->read($string);
91 $self->process($sexp)
92 }
93}
94
95sub finish {
96 my ($self) = @_;
97 $self->{memory}[5] = $self->{memory}[$self->{freeptr}];
34073a4a 98 $self->{comment}[5] = $self->{comment}[$self->{freeptr}];
509643aa
MG
99 $self->{memory}[4] = $self->{freeptr};
100 delete $self->{memory}[$self->{freeptr}]
101}
102
103sub new {
104 my ($class, %args) = @_;
105 $args{type_bits} //= 3;
106 $args{addr_bits} //= 8;
107 $args{freeptr} //= 6;
108 $args{memory} //= [0, 0, (1<<$args{addr_bits}), (1<<$args{addr_bits}), 0, 0, 0];
5ff3fe85
MG
109 my @default_comments = ('(cdr part of NIL)', '(car part of NIL)', '(cdr part of T)', '(car part of T)', '(free storage pointer)', '', '(result of computation)');
110 for (0 .. $#default_comments) {
111 $args{comment}[$_] = $default_comments[$_]
112 }
509643aa
MG
113 bless \%args, $class
114}
115
f0289d3d
MG
116sub print_binary16 {
117 my ($self, $fh) = @_;
744f5c2a 118 $fh //= \*STDOUT; # uncoverable condition right
f0289d3d 119
26bb1344
MG
120 die "addr_bits + type_bits >= 16\n"if $self->{addr_bits} + $self->{type_bits} > 16;
121
f0289d3d 122 my $length = @{$self->{memory}};
1756f229 123 print $fh pack 'n', $length or croak "Failed to print memory size: $!";
f0289d3d 124 for (@{$self->{memory}}) {
1756f229 125 print $fh pack 'n', $_ or croak "Failed to print memory: $!"
f0289d3d
MG
126 }
127}
128
129sub print_verilog {
509643aa 130 my ($self, $fh) = @_;
744f5c2a 131 $fh //= \*STDOUT; # uncoverable condition right
509643aa
MG
132
133 my $bits = $self->{type_bits} + $self->{addr_bits};
34073a4a
MG
134 my $index_length = length $#{$self->{memory}};
135 my $index_format = '%' . $index_length . 'd';
509643aa
MG
136 for my $index (0 .. $#{$self->{memory}}) {
137 my $val = $self->{memory}[$index];
34073a4a 138 my $comment = $self->{comment}[$index];
509643aa
MG
139 if ($index == 4) {
140 $val = "${bits}'d$val"
141 } else {
142 $val = $val ? sprintf "%d'b%0${bits}b", $bits, $val : '0';
143 }
34073a4a
MG
144 my $spaces = ' ' x ($bits + 5 - (length $val));
145 $index = sprintf $index_format, $index;
744f5c2a 146
1756f229
MG
147 my $string = "mem[$index] <= $val;";
148 $string .= "$spaces // $comment" if defined $comment;
149 say $fh $string or croak "Failed to print verilog: $!";
509643aa 150 }
f0289d3d
MG
151
152}
153sub parse_and_print_binary16 {
154 my ($self, $string, $fh) = @_;
155 $self->parse($string);
156 $self->finish;
157 $self->print_binary16($fh);
509643aa
MG
158}
159
f0289d3d 160sub parse_and_print_verilog {
509643aa
MG
161 my ($self, $string, $fh) = @_;
162 $self->parse($string);
163 $self->finish;
f0289d3d 164 $self->print_verilog($fh);
509643aa
MG
165}
166
1671;
168__END__
169
170=encoding utf-8
171
172=head1 NAME
173
174App::Scheme79asm - assemble sexp to Verilog ROM for SIMPLE processor
175
176=head1 SYNOPSIS
177
178 use App::Scheme79asm;
179 my $asm = App::Scheme79asm->new(type_bits => 3, addr_bits => 5);
f0289d3d 180 $asm->parse_and_print_verilog('(number 70)');
509643aa
MG
181
182=head1 DESCRIPTION
183
509643aa
MG
184SIMPLE is a LISP processor defined in the 1979
185B<Design of LISP-Based Processors> paper by Steele and Sussman.
186
187The SIMPLE processor expects input in a particular tagged-pointer
188format. This module takes a string containing a sequence of
9924aa7b
MG
189S-expressions. Each S-expression is a list of one of three types:
190
744f5c2a 191C<(tag value)>, for example C<(symbol 2)>, represents a value to be
9924aa7b 192put in memory (for example a number, or a symbol, or a variable
5ff3fe85 193reference). The value must be a number.
9924aa7b
MG
194
195C<(tag list)>, where C<list> is of one of these three types,
196represents a tagged pointer. In this case, C<list> is (recursively)
197laid out in memory as per these rules, and a pointer to that location
198(and tagged C<tag>) is put somewhere in memory.
199
200C<(tag list1 list2)>, where C<list1> and C<list2> are of one of these
201three types (not necessarily the same type). In this case, C<list1>
202and C<list2> are (recursively) laid out in memory such that C<list1>
203is at position X and C<list2> is at position X+1, and a pointer of
204type tag and value X is put somewhere in memory.
205
206After this process the very last pointer placed in memory is moved to
207the special location 5 (which is where SIMPLE expects to find the
208expression to be evaluated).
209
210In normal use a single S-expression will be supplied, representing an
211entire program.
212
68367057
MG
213The C<tag> is either a number, a type, or a primitive.
214The available types are:
509643aa 215
68367057
MG
216=over
217
218=item LIST
219
220=item SYMBOL (syn. NUMBER)
221
222=item VAR (syn. VARIABLE)
223
224=item CLOSURE
225
226=item PROC (syn. PROCEDURE)
227
228=item IF (syn. COND, CONDITIONAL)
229
230=item CALL
231
232=item QUOTE (syn. QUOTED)
233
234=back
235
236The available primitives are:
237
238=over
239
240=item MORE
241
242=item CAR
243
244=item CDR
245
246=item CONS
247
248=item ATOM
249
250=item PROGN
251
f0289d3d 252=item REVERSE-LIST
68367057
MG
253
254=item FUNCALL
255
256=back
257
258The following methods are available:
259
260=over
261
262=item App::Scheme79asm->B<new>([key => value, key => value, ...])
263
264Create a new assembler object. Takes a list of keys and values, here
265are the possible keys:
266
267=over
268
269=item type_bits
270
271=item address_bits
272
273A word is made of a type and an address, with the type occupying the
274most significant C<type_bits> (default 3) bits, and the address
275occupying the least significant C<address_bits> (default 8) bits.
5ff3fe85 276Therefore the word size is C<type_bits + address_bits> (default 11).
68367057
MG
277
278=item freeptr
279
280A pointer to the last used byte in memory (default 6). The program
281will be laid out starting with location C<freeptr + 1>.
282
283=item memory
284
285The initial contents of the memory. Note that locations 4, 5, 6 will
286be overwritten, as will every location larger than the value of
287C<freeptr>.
288
289=item comment
290
291The initial comments for memory entries. C<< $comment->[$i] >> is the
5ff3fe85
MG
292comment for C<< $memory->[$i] >>. Note that the first 7 entries of
293this array will be overwritten with the default comments. This is
294useful when using custom initial memory contents and freeptr, because
295this key can be used to provide comments for the extra reserved
296locations in memory.
68367057 297
68367057
MG
298=back
299
300=item $asm->B<parse>(I<$string>)
301
302Parse a sequence of S-expressions and lay it out in memory.
303Can be called multiple times to lay out multiple sequences of
304S-expressions one after another.
305
8ff4c670
MG
306=item $asm->B<process>(I<$sexp>)
307
308Given an already-parsed sexp (meaning a
309L<Data::SExpression> object), lay it out in memory.
310Can be called multiple times to lay out multiple sequences of
311S-expressions one after another.
312
68367057
MG
313=item $asm->B<finish>
314
315Move the last pointer to position 5, and put the free pointer at
316position 4. After all sequences of S-expressions have been given to
317B<parse>, this method should be called.
318
f0289d3d
MG
319=item $asm->B<print_binary16>([I<$fh>])
320
321Print the length of the memory (as a big-endian 16-bit value),
322followed by the memory contents as a sequence of big-endian 16-bit
323values to the given filehandle (default STDOUT). Dies if
324C<addr_bits + type_bits> is more than 16.
325
326Big-endian 16-bit values can be decoded with C<unpack 'n', $value>.
327
328=item $asm->B<print_verilog>([I<$fh>])
68367057
MG
329
330Print a block of Verilog code assigning the memory contents to an
331array named C<mem> to the given filehandle (default STDOUT).
332
f0289d3d
MG
333=item $asm->B<parse_and_print_binary16>(I<$string>[, I<$fh>])
334
335Convenience method that calls B<parse>($string), B<finish>, and then
336B<print_binary16>($fh).
337
338=item $asm->B<parse_and_print_verilog>(I<$string>[, I<$fh>])
68367057
MG
339
340Convenience method that calls B<parse>($string), B<finish>, and then
f0289d3d 341B<print_verilog>($fh).
68367057
MG
342
343=back
509643aa
MG
344
345=head1 SEE ALSO
346
347L<http://repository.readscheme.org/ftp/papers/ai-lab-pubs/AIM-514.pdf>
348
349=head1 AUTHOR
350
351Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
352
353=head1 COPYRIGHT AND LICENSE
354
355Copyright (C) 2018 by Marius Gavrilescu
356
357This library is free software; you can redistribute it and/or modify
358it under the same terms as Perl itself, either Perl version 5.24.3 or,
359at your option, any later version of Perl 5 you may have available.
360
361
362=cut
This page took 0.032529 seconds and 4 git commands to generate.