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; |