File Coverage

blib/lib/CPU/Emulator/DCPU16/Assembler.pm
Criterion Covered Total %
statement 79 85 92.9
branch 46 62 74.1
condition 7 15 46.6
subroutine 6 6 100.0
pod 1 1 100.0
total 139 169 82.2


line stmt bran cond sub pod time code
1             package CPU::Emulator::DCPU16::Assembler;
2 5     5   36897 use strict;
  5         11  
  5         9329  
3              
4             =head1 NAME
5              
6             CPU::Emulator::DCPU16::Assembler - assemble DCPU-16 bytecode
7              
8             =head1 SYNOPSIS
9            
10             # Assemble a program
11             my $bytes = CPU::Emulator::DCPU16::Assembler->assemble($asm);
12              
13             # Then either run it ...
14             my $cpu = CPU::Emulator::DCPU16->new();
15             $cpu->load($bytes);
16             $cpu-run;
17            
18             # ... or disassemble it
19             my $asm = CPU::Emulator::DCPU16::Disassembler->dump($bytes);
20              
21             =head1 METHODS
22            
23             =cut
24              
25             =head2 assemble
26              
27             Return bytes representing an assembled program
28              
29             =cut
30             sub assemble {
31 9     9 1 82 my $class = shift;
32 9         21 my $asm = shift;
33 9         20 my $bytes = "";
34 9         26 my %labels = ();
35 9         21 my %unres = ();
36 9         15 my $idx = 1;
37 9         56 for my $line (split /\n/, $asm) {
38 46         163 $class->_parse_line($line, $idx++, \$bytes, \%labels, \%unres);
39             }
40 9         51 $class->_resolve_references(\$bytes, \%labels, \%unres);
41 9         76 return $bytes;
42             }
43              
44              
45             our %_EXTENDED_OPS = (JSR => 0x01);
46             our %_OPS = (SET => 0x01,
47             ADD => 0x02,
48             SUB => 0x03,
49             MUL => 0x04,
50             DIV => 0x05,
51             MOD => 0x06,
52             SHL => 0x07,
53             SHR => 0x08,
54             AND => 0x09,
55             BOR => 0x0a,
56             XOR => 0x0b,
57             IFE => 0x0c,
58             IFN => 0x0d,
59             IFG => 0x0e,
60             IFB => 0x0f);
61              
62             sub _parse_line {
63 46     46   87 my $class = shift;
64 46         63 my $line = shift;
65 46         58 my $idx = shift;
66 46         80 my $bytes = shift;
67 46         60 my $labels = shift;
68 46         54 my $unres = shift;
69 46         72 my $off = length($$bytes)/2;
70 46         50 my $oc;
71              
72             # trim and clean the line
73 46         532 $line =~ s!(^\s*|\s*$|;.*$)!!g;
74 46 100       131 return unless length($line);
75              
76 38         245 my ($label, $op, $a, $b) = $line =~ m!
77             ^
78             (?::(\w+) \s+)? # optional label
79             ([A-Za-z]+) \s+ # opcode
80             ([^,\s]+) (?:, \s+ # operand
81             ([^,\s]+))? \s* # optional second opcode
82             $
83             !x;
84            
85 38 50       94 die "Couldn't parse line $idx: $line\n" unless defined $op;
86            
87 38 100       105 $labels->{$label} = $off if defined $label;
88            
89 38         66 $op = uc $op;
90 38 100       154 if ($oc = $_EXTENDED_OPS{$op}) {
    50          
91 2 50 33     18 die "$op takes one operand at line $idx: $line\n" unless defined $a && !defined $b;
92 2         7 my ($val, $next_word, $label) = _parse_operand($a);
93 2 50       9 die "Can't parse operand '$a' at line $idx: $line\n" unless defined $val;
94              
95 2         4 $oc <<= 4;
96 2         6 $oc |= $val << 10;
97            
98 2 50       14 $unres->{$off} = [$label] if defined $label;
99 2         9 $$bytes .= pack("S>", $oc);
100 2 50       17 $$bytes .= pack("S>", $next_word) if defined $next_word;
101              
102             } elsif ($oc = $_OPS{$op}) {
103 36 50 33     164 die "$op takes two operands at line $idx: $line\n" unless defined $a && defined $b;
104            
105 36         80 my ($val_a, $next_word_a, $label_a) = _parse_operand($a);
106 36 50       91 die "Can't parse operand '$a' at line $idx: $line\n" unless defined $val_a;
107 36         59 my ($val_b, $next_word_b, $label_b) = _parse_operand($b);
108 36 50       147 die "Can't parse operand '$b' at line $idx: $line\n" unless defined $val_b;
109              
110 36         55 $oc |= $val_a << 4;
111 36         46 $oc |= $val_b << 10;
112 36 100 66     228 $unres->{$off} = [$label_a, $label_b] if defined $label_a || defined $label_b;
113            
114 36         105 $$bytes .= pack("S>", $oc);
115 36 100       80 $$bytes .= pack("S>", $next_word_a) if defined $next_word_a;
116 36 100       163 $$bytes .= pack("S>", $next_word_b) if defined $next_word_b;
117             } else {
118 0         0 die "Unknown opcode $op at line $idx: $line\n";
119             }
120            
121            
122             }
123              
124             sub _parse_num {
125 25     25   60 my $num = shift;
126 25 100       91 $num = oct($num) if $num =~ /^0x/i;
127 25         66 $num;
128             }
129              
130             sub _parse_operand {
131 74     74   113 my $op = shift;
132 74         94 my $regs = "ABCXYZIJ";
133 74         338 my $nums = qr/(?:0x[0-9A-F]+|[0-9]+)/i;
134              
135 74 100 66     1609 if (0<=index $regs, $op) {
    100 33        
    100          
    100          
    50          
    50          
    50          
    100          
    50          
    100          
    100          
    50          
136 26         96 return (index $regs, $op);
137             } elsif ($op =~ /^\[\s*([$regs])\s*\]$/) {
138 3         16 return (0x08 + index $regs, uc($1));
139             } elsif ($op =~ /^\[\s*($nums)\s*\+\s*([$regs])\s*\]$/) {
140 2         12 return (0x10 + index($regs, uc($2)), _parse_num($1));
141             } elsif ($op eq 'POP' || $op =~ /^\[\s*SP\+\+\s*\]$/) {
142 1         4 return (0x18);
143             } elsif ($op eq 'PEEK' || $op =~ /^\[\s*\-\-SP\s*\]$/) {
144 0         0 return (0x19);
145             } elsif ($op eq 'PUSH') {
146 0         0 return (0x1a);
147             } elsif ($op eq 'SP') {
148 0         0 return (0x1b);
149             } elsif ($op eq 'PC') {
150 9         34 return (0x1c);
151             } elsif ($op eq 'O') {
152 0         0 return (0x1d);
153             } elsif ($op =~ /^\[\s*($nums)\s*\]$/) {
154 3         8 return (0x1e, _parse_num($1));
155             } elsif ($op =~ /^($nums)$/) {
156 20         44 my $num = _parse_num($1);
157 20 100       103 return ($num < 0x20) ? (0x20 + $num) : (0x1f, $num);
158             } elsif ($op =~ /\w+/) {
159 10         41 return (0x1f, 0x00, $op);
160             } else {
161 0         0 return ();
162             }
163             }
164              
165             sub _resolve_references {
166 9     9   17 my $class = shift;
167 9         26 my $bytes = shift;
168 9         15 my $labels = shift;
169 9         16 my $unres = shift;
170            
171 9         52 foreach my $pos (reverse sort { $a <=> $b } keys %$unres) {
  9         14  
172 10         13 my @labels = grep { defined } @{ delete $unres->{$pos} };
  18         61  
  10         28  
173 10 50       586 next unless @labels;
174              
175 10         131 my $offset = 2;
176 10         18 for my $label (@labels) {
177 10         26 my $resolved = $labels->{$label};
178 10 50       27 die "Can't resolve label $label" unless defined $resolved;
179 10         47 substr($$bytes, $pos * 2 + $offset, 2, pack("S>", $resolved));
180 10         40 $offset += 2;
181             }
182             }
183             }
184              
185              
186              
187             1;