n65 0.5.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ci.yml +28 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +125 -0
- data/Gemfile +3 -1
- data/README.md +3 -20
- data/Rakefile +15 -1
- data/bin/n65 +2 -0
- data/data/opcodes.yaml +39 -39
- data/everdrive_transfer/everdrive.rb +175 -0
- data/examples/pulse_chord.asm +1 -1
- data/examples/scales.asm +182 -0
- data/lib/n65/directives/ascii.rb +4 -19
- data/lib/n65/directives/bytes.rb +20 -35
- data/lib/n65/directives/dw.rb +22 -36
- data/lib/n65/directives/enter_scope.rb +14 -30
- data/lib/n65/directives/exit_scope.rb +7 -17
- data/lib/n65/directives/inc.rb +14 -30
- data/lib/n65/directives/incbin.rb +6 -24
- data/lib/n65/directives/ines_header.rb +66 -27
- data/lib/n65/directives/label.rb +8 -21
- data/lib/n65/directives/org.rb +9 -19
- data/lib/n65/directives/segment.rb +5 -19
- data/lib/n65/directives/space.rb +6 -18
- data/lib/n65/front_end.rb +36 -39
- data/lib/n65/instruction.rb +123 -159
- data/lib/n65/instruction_base.rb +6 -18
- data/lib/n65/memory_space.rb +51 -71
- data/lib/n65/opcodes.rb +3 -5
- data/lib/n65/parser.rb +20 -38
- data/lib/n65/regexes.rb +20 -21
- data/lib/n65/symbol_table.rb +58 -89
- data/lib/n65/version.rb +3 -1
- data/lib/n65.rb +120 -121
- data/n65.gemspec +17 -12
- data/nes_lib/nes.sym +2 -2
- data/spec/.rubocop.yml +4 -0
- data/spec/assembler_spec.rb +84 -0
- data/spec/lib/n65/memory_space_spec.rb +147 -0
- data/spec/lib/n65/symbol_table_spec.rb +291 -0
- data/utils/opcode_table_to_yaml.rb +65 -67
- data.tar.gz.sig +0 -0
- metadata +84 -41
- metadata.gz.sig +0 -0
- data/examples/music_driver.asm +0 -202
- data/test/test_memory_space.rb +0 -82
- data/test/test_symbol_table.rb +0 -238
- data/utils/midi/Makefile +0 -3
- data/utils/midi/c_scale.mid +0 -0
- data/utils/midi/convert +0 -0
- data/utils/midi/guitar.mid +0 -0
- data/utils/midi/include/event.h +0 -93
- data/utils/midi/include/file.h +0 -57
- data/utils/midi/include/helpers.h +0 -14
- data/utils/midi/include/track.h +0 -45
- data/utils/midi/lil_melody.mid +0 -0
- data/utils/midi/mi_feabhra.mid +0 -0
- data/utils/midi/midi_to_nes.rb +0 -204
- data/utils/midi/source/convert.cpp +0 -16
- data/utils/midi/source/event.cpp +0 -96
- data/utils/midi/source/file.cpp +0 -37
- data/utils/midi/source/helpers.cpp +0 -46
- data/utils/midi/source/track.cpp +0 -37
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'libusb'
|
|
4
|
+
require 'date'
|
|
5
|
+
|
|
6
|
+
class EverdriveIO
|
|
7
|
+
class DeviceNotFound < StandardError; end
|
|
8
|
+
|
|
9
|
+
def initialize(vendor, product)
|
|
10
|
+
@device = find_device(vendor, product)
|
|
11
|
+
@input, @output = find_bulk_endpoints(device)
|
|
12
|
+
@handle = device.open
|
|
13
|
+
handle.claim_interface(input.interface)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def inspect
|
|
17
|
+
"#<#{self.class.name}: #{device.inspect}>"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def close
|
|
21
|
+
handle.close
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def read(length)
|
|
25
|
+
handle.bulk_transfer(endpoint: input, dataIn: length)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def write(data)
|
|
29
|
+
handle.bulk_transfer(endpoint: output, dataOut: data)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def read_u8
|
|
33
|
+
read(1).ord
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def read_u16
|
|
37
|
+
read(2).unpack('S').first
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def read_u32
|
|
41
|
+
read(4).unpack('L').first
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def write_u8(u8)
|
|
45
|
+
write(
|
|
46
|
+
(u8 & 0xff).chr
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def write_u16(u16)
|
|
51
|
+
write(
|
|
52
|
+
[
|
|
53
|
+
(u16 & 0x00ff),
|
|
54
|
+
(u16 & 0xff00) >> 8
|
|
55
|
+
].pack('cc')
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def write_u32(u32)
|
|
60
|
+
write(
|
|
61
|
+
[
|
|
62
|
+
(u32 & 0x000000ff),
|
|
63
|
+
(u32 & 0x0000ff00) >> 8,
|
|
64
|
+
(u32 & 0x00ff0000) >> 32,
|
|
65
|
+
(u32 & 0xff000000) >> 24,
|
|
66
|
+
].pack('cccc')
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def write_string(string)
|
|
71
|
+
write_u16(string.length)
|
|
72
|
+
write(string)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
attr_reader :input, :output, :handle, :device
|
|
78
|
+
|
|
79
|
+
def find_device(vendor, product)
|
|
80
|
+
LIBUSB::Context.new.devices(idVendor: vendor, idProduct: product).first.tap do |device|
|
|
81
|
+
raise(DeviceNotFound) if device.nil?
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def find_bulk_endpoints(device)
|
|
86
|
+
[
|
|
87
|
+
device.endpoints.find { |ep| ep.transfer_type == :bulk && ep.direction == :in },
|
|
88
|
+
device.endpoints.find { |ep| ep.transfer_type == :bulk && ep.direction == :out }
|
|
89
|
+
]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Everdrive
|
|
95
|
+
VENDOR = 0x0483
|
|
96
|
+
PRODUCT = 0x5740
|
|
97
|
+
STATUS_OK = 0xa500
|
|
98
|
+
|
|
99
|
+
FAT_WRITE = 0x02
|
|
100
|
+
FAT_OPEN_ALWAYS = 0x10
|
|
101
|
+
|
|
102
|
+
CMD_F_FCLOSE = 0xCE;
|
|
103
|
+
CMD_F_FOPN = 0xC9
|
|
104
|
+
CMD_F_FWR = 0xCC
|
|
105
|
+
CMD_RTC_GET = 0x14;
|
|
106
|
+
CMD_STATUS = 0x10
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def initialize
|
|
110
|
+
@everdrive = EverdriveIO.new(VENDOR, PRODUCT)
|
|
111
|
+
p status_ok?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def inspect
|
|
115
|
+
"#<#{self.class.name}: #{@everdrive.inspect}>"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def status_ok?
|
|
119
|
+
everdrive.write(make_command(CMD_STATUS))
|
|
120
|
+
everdrive.read_u16 == STATUS_OK
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def get_realtime_clock
|
|
124
|
+
everdrive.write(make_command(CMD_RTC_GET))
|
|
125
|
+
rtc = everdrive.read(6)
|
|
126
|
+
|
|
127
|
+
ary = rtc.split('').map{|c| ('%x' % c.ord).to_i }
|
|
128
|
+
DateTime.new(ary[0] + 2000, ary[1], ary[2], ary[3], ary[4], ary[5])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def test_write_file
|
|
132
|
+
filename = '000-test.nes'
|
|
133
|
+
File.open(filename, 'rb') do |fp|
|
|
134
|
+
contents = fp.read
|
|
135
|
+
p file_open(filename, FAT_OPEN_ALWAYS | FAT_WRITE)
|
|
136
|
+
p file_write(contents, 0, contents.length)
|
|
137
|
+
p file_close
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
attr_reader :everdrive
|
|
144
|
+
|
|
145
|
+
def file_open(filename, mode)
|
|
146
|
+
puts 'file_open'
|
|
147
|
+
everdrive.write(make_command(CMD_F_FOPN))
|
|
148
|
+
everdrive.write_u8(mode)
|
|
149
|
+
everdrive.write_string('000-test.nes')
|
|
150
|
+
status_ok?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def file_write(data, notsure, length)
|
|
154
|
+
puts 'file_write'
|
|
155
|
+
everdrive.write(make_command(CMD_F_FWR))
|
|
156
|
+
everdrive.write_u32(data.length)
|
|
157
|
+
everdrive.write(data)
|
|
158
|
+
# other stuff txdataack instead, pay attention to blocksizes
|
|
159
|
+
status_ok?
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def file_close
|
|
163
|
+
puts 'file_close'
|
|
164
|
+
everdrive.write(make_command(CMD_F_FCLOSE))
|
|
165
|
+
status_ok?
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def make_command(command_code)
|
|
169
|
+
data = ['+', '+'.ord ^ 0xff, command_code, command_code ^ 0xff].map { |b| (b.ord & 0xff).chr }.join
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
everdrive = Everdrive.new
|
|
174
|
+
p everdrive.get_realtime_clock
|
|
175
|
+
# p everdrive.test_write_file
|
data/examples/pulse_chord.asm
CHANGED
data/examples/scales.asm
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0}
|
|
2
|
+
.inc <nes.sym>
|
|
3
|
+
|
|
4
|
+
.segment prog 0
|
|
5
|
+
|
|
6
|
+
;; SRAM Variables
|
|
7
|
+
.org $0000
|
|
8
|
+
.scope audio
|
|
9
|
+
.space timer 1
|
|
10
|
+
.space next_note 1
|
|
11
|
+
.space note_frequency 2
|
|
12
|
+
.
|
|
13
|
+
|
|
14
|
+
;; Interrupt vectors
|
|
15
|
+
.org $FFFA
|
|
16
|
+
.dw vblank
|
|
17
|
+
.dw main
|
|
18
|
+
.dw irq
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
.org $C000
|
|
22
|
+
.scope main
|
|
23
|
+
sei
|
|
24
|
+
cld
|
|
25
|
+
|
|
26
|
+
;; Setup the stack
|
|
27
|
+
ldx #$ff
|
|
28
|
+
txs
|
|
29
|
+
|
|
30
|
+
;; Disable rendering, reset APU
|
|
31
|
+
ldx #$00
|
|
32
|
+
stx nes.ppu.control
|
|
33
|
+
stx nes.ppu.mask
|
|
34
|
+
jsr zero_apu
|
|
35
|
+
|
|
36
|
+
.scope
|
|
37
|
+
wait_vblank:
|
|
38
|
+
bit nes.ppu.status
|
|
39
|
+
bpl wait_vblank
|
|
40
|
+
.
|
|
41
|
+
|
|
42
|
+
clear_ram:
|
|
43
|
+
lda #$00
|
|
44
|
+
sta $00, x
|
|
45
|
+
sta $100, x
|
|
46
|
+
sta $300, x
|
|
47
|
+
sta $400, x
|
|
48
|
+
sta $500, x
|
|
49
|
+
sta $600, x
|
|
50
|
+
sta $700, x
|
|
51
|
+
lda #$ff
|
|
52
|
+
sta $200, x
|
|
53
|
+
inx
|
|
54
|
+
bne clear_ram
|
|
55
|
+
|
|
56
|
+
.scope
|
|
57
|
+
wait_vblank:
|
|
58
|
+
bit nes.ppu.status
|
|
59
|
+
bpl wait_vblank
|
|
60
|
+
.
|
|
61
|
+
|
|
62
|
+
jsr initialize
|
|
63
|
+
|
|
64
|
+
forever:
|
|
65
|
+
jmp forever
|
|
66
|
+
rti
|
|
67
|
+
.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
;; Zero the APU
|
|
71
|
+
.scope zero_apu
|
|
72
|
+
lda #$00
|
|
73
|
+
ldx #$00
|
|
74
|
+
loop:
|
|
75
|
+
sta $4000, x
|
|
76
|
+
inx
|
|
77
|
+
cpx $18
|
|
78
|
+
bne loop
|
|
79
|
+
rts
|
|
80
|
+
.
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
;; Initialize PPU and APU
|
|
84
|
+
.scope initialize
|
|
85
|
+
lda #%00000011
|
|
86
|
+
sta nes.apu.channel_enable
|
|
87
|
+
|
|
88
|
+
; Reenable interrupts, Turn Vblank back on
|
|
89
|
+
lda #%10000000
|
|
90
|
+
sta nes.ppu.control
|
|
91
|
+
|
|
92
|
+
; Initialize the audio structure
|
|
93
|
+
lda #$00
|
|
94
|
+
sta audio.timer zp
|
|
95
|
+
lda #$30
|
|
96
|
+
sta audio.next_note zp
|
|
97
|
+
|
|
98
|
+
cli
|
|
99
|
+
rts
|
|
100
|
+
.
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
;; Keep time via 60fps vblank
|
|
104
|
+
.scope vblank
|
|
105
|
+
; Update the audio timer so it resets every 64 frames
|
|
106
|
+
ldx audio.timer zp
|
|
107
|
+
inx
|
|
108
|
+
txa
|
|
109
|
+
and #%00000011
|
|
110
|
+
sta audio.timer zp
|
|
111
|
+
bne return
|
|
112
|
+
|
|
113
|
+
; Play the next note on reset
|
|
114
|
+
lda audio.next_note zp
|
|
115
|
+
cmp #$80
|
|
116
|
+
bmi continue
|
|
117
|
+
lda #$30
|
|
118
|
+
|
|
119
|
+
continue:
|
|
120
|
+
jsr play_note
|
|
121
|
+
sta audio.next_note zp
|
|
122
|
+
inc audio.next_note zp
|
|
123
|
+
|
|
124
|
+
return:
|
|
125
|
+
rti
|
|
126
|
+
.
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
;; Hi and lo byte tables for note frequencies
|
|
130
|
+
.scope midi_notes
|
|
131
|
+
.scope hi
|
|
132
|
+
.bytes $35, $32, $2f, $2c, $2a, $28, $25, $23, $21, $1f, $1d, $1c, $1a, $19, $17, $16
|
|
133
|
+
.bytes $15, $14, $12, $11, $10, $0f, $0e, $0e, $0d, $0c, $0b, $0b, $0a, $0a, $09, $08
|
|
134
|
+
.bytes $08, $07, $07, $07, $06, $06, $05, $05, $05, $05, $04, $04, $04, $03, $03, $03
|
|
135
|
+
.bytes $03, $03, $02, $02, $02, $02, $02, $02, $02, $01, $01, $01, $01, $01, $01, $01
|
|
136
|
+
.bytes $01, $01, $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
|
137
|
+
.bytes $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
|
138
|
+
.bytes $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
|
139
|
+
.bytes $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
|
140
|
+
.
|
|
141
|
+
.scope lo
|
|
142
|
+
.bytes $71, $71, $9c, $f0, $6a, $09, $ca, $ab, $aa, $c6, $fe, $4f, $b8, $38, $ce, $78
|
|
143
|
+
.bytes $35, $04, $e4, $d5, $d5, $e3, $fe, $27, $5b, $9c, $e6, $3b, $9a, $01, $72, $ea
|
|
144
|
+
.bytes $6a, $f1, $7f, $13, $ad, $4d, $f3, $9d, $4c, $00, $b8, $74, $34, $f8, $bf, $89
|
|
145
|
+
.bytes $56, $26, $f9, $ce, $a6, $80, $5c, $3a, $1a, $fb, $df, $c4, $ab, $93, $7c, $67
|
|
146
|
+
.bytes $52, $3f, $2d, $1c, $0c, $fd, $ef, $e1, $d5, $c9, $bd, $b3, $a9, $9f, $96, $8e
|
|
147
|
+
.bytes $86, $7e, $77, $70, $6a, $64, $5e, $59, $54, $4f, $4b, $46, $42, $3f, $3b, $38
|
|
148
|
+
.bytes $34, $31, $2f, $2c, $29, $27, $25, $23, $21, $1f, $1d, $1b, $1a, $18, $17, $15
|
|
149
|
+
.bytes $14, $13, $12, $11, $10, $0f, $0e, $0d, $0c, $0c, $0b, $0a, $0a, $09, $08, $08
|
|
150
|
+
.
|
|
151
|
+
.
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
;; Play midi note held in A
|
|
155
|
+
.scope play_note
|
|
156
|
+
pha
|
|
157
|
+
tax
|
|
158
|
+
lda #%10011111
|
|
159
|
+
sta nes.apu.pulse1.control
|
|
160
|
+
|
|
161
|
+
; Get the low byte of the timer
|
|
162
|
+
ldy midi_notes.lo, x
|
|
163
|
+
sty nes.apu.pulse1.ft
|
|
164
|
+
sty audio.note_frequency+1 zp
|
|
165
|
+
|
|
166
|
+
; Get the high 3 bits of the timer
|
|
167
|
+
ldy midi_notes.hi, x
|
|
168
|
+
tya
|
|
169
|
+
and #%00000111
|
|
170
|
+
ora #%11111000
|
|
171
|
+
sta nes.apu.pulse1.ct
|
|
172
|
+
sta audio.note_frequency zp
|
|
173
|
+
|
|
174
|
+
pla
|
|
175
|
+
rts
|
|
176
|
+
.
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
;; IRQ, we are not using
|
|
180
|
+
.scope irq
|
|
181
|
+
rti
|
|
182
|
+
.
|
data/lib/n65/directives/ascii.rb
CHANGED
|
@@ -1,42 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../instruction_base'
|
|
2
4
|
|
|
3
5
|
module N65
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
####
|
|
7
|
-
## This directive to include bytes
|
|
6
|
+
# This directive to include bytes
|
|
8
7
|
class ASCII < InstructionBase
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
####
|
|
12
|
-
## Try to parse an incbin directive
|
|
13
8
|
def self.parse(line)
|
|
14
9
|
match_data = line.match(/^\.ascii\s+"([^"]+)"$/)
|
|
15
10
|
return nil if match_data.nil?
|
|
11
|
+
|
|
16
12
|
ASCII.new(match_data[1])
|
|
17
13
|
end
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
####
|
|
21
|
-
## Initialize with filename
|
|
22
15
|
def initialize(string)
|
|
23
16
|
@string = string
|
|
24
17
|
end
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
####
|
|
28
|
-
## Execute on the assembler
|
|
29
19
|
def exec(assembler)
|
|
30
20
|
assembler.write_memory(@string.bytes)
|
|
31
21
|
end
|
|
32
22
|
|
|
33
|
-
|
|
34
|
-
####
|
|
35
|
-
## Display
|
|
36
23
|
def to_s
|
|
37
24
|
".ascii \"#{@string}\""
|
|
38
25
|
end
|
|
39
|
-
|
|
40
26
|
end
|
|
41
|
-
|
|
42
27
|
end
|
data/lib/n65/directives/bytes.rb
CHANGED
|
@@ -1,27 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../instruction_base'
|
|
2
|
-
require_relative '../regexes
|
|
4
|
+
require_relative '../regexes'
|
|
3
5
|
|
|
4
6
|
module N65
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
####
|
|
8
|
-
## This directive to include bytes
|
|
7
|
+
# This directive to include bytes
|
|
9
8
|
class Bytes < InstructionBase
|
|
10
|
-
|
|
11
|
-
#### Custom Exceptions
|
|
12
9
|
class InvalidByteValue < StandardError; end
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
####
|
|
16
|
-
## Try to parse an incbin directive
|
|
11
|
+
# Try to parse an incbin directive
|
|
17
12
|
def self.parse(line)
|
|
18
13
|
match_data = line.match(/^\.bytes\s+(.+)$/)
|
|
19
14
|
return nil if match_data.nil?
|
|
20
15
|
|
|
21
16
|
bytes_array = match_data[1].split(',').map do |byte_string|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## In numeric captures $2 is always binary, $1 is always hex
|
|
17
|
+
# Does byte_string represent a numeric literal, or is it a symbol?
|
|
18
|
+
# In numeric captures $2 is always binary, $1 is always hex
|
|
25
19
|
|
|
26
20
|
case byte_string.strip
|
|
27
21
|
when Regexp.new("^#{Regexes::Num8}$")
|
|
@@ -30,41 +24,36 @@ module N65
|
|
|
30
24
|
when Regexp.new("^#{Regexes::Num16}$")
|
|
31
25
|
value = $2.nil? ? $1.to_i(16) : $2.to_i(2)
|
|
32
26
|
|
|
33
|
-
|
|
27
|
+
# Break value up into two bytes
|
|
34
28
|
high = (0xff00 & value) >> 8
|
|
35
29
|
low = (0x00ff & value)
|
|
36
30
|
[low, high]
|
|
37
31
|
when Regexp.new("^#{Regexes::Sym}$")
|
|
38
32
|
$1
|
|
39
33
|
else
|
|
40
|
-
|
|
34
|
+
raise(InvalidByteValue, byte_string)
|
|
41
35
|
end
|
|
42
36
|
end.flatten
|
|
43
37
|
|
|
44
38
|
Bytes.new(bytes_array)
|
|
45
39
|
end
|
|
46
40
|
|
|
47
|
-
|
|
48
|
-
####
|
|
49
|
-
## Initialize with filename
|
|
41
|
+
# Initialize with a byte array
|
|
50
42
|
def initialize(bytes_array)
|
|
51
43
|
@bytes_array = bytes_array
|
|
52
44
|
end
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
####
|
|
56
|
-
## Execute on the assembler
|
|
46
|
+
# Execute on the assembler
|
|
57
47
|
def exec(assembler)
|
|
58
|
-
|
|
59
48
|
promise = assembler.with_saved_state do |saved_assembler|
|
|
60
49
|
@bytes_array.map! do |byte|
|
|
61
50
|
case byte
|
|
62
|
-
when
|
|
51
|
+
when Integer
|
|
63
52
|
byte
|
|
64
53
|
when String
|
|
65
|
-
|
|
54
|
+
saved_assembler.symbol_table.resolve_symbol(byte)
|
|
66
55
|
else
|
|
67
|
-
|
|
56
|
+
raise(InvalidByteValue, byte)
|
|
68
57
|
end
|
|
69
58
|
end
|
|
70
59
|
saved_assembler.write_memory(@bytes_array)
|
|
@@ -73,30 +62,26 @@ module N65
|
|
|
73
62
|
begin
|
|
74
63
|
promise.call
|
|
75
64
|
rescue SymbolTable::UndefinedSymbol
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
# Write the bytes but assume a zero page address for all symbols
|
|
66
|
+
# And just write 0xDE for a placeholder
|
|
78
67
|
placeholder_bytes = @bytes_array.map do |byte|
|
|
79
68
|
case bytes
|
|
80
|
-
when
|
|
69
|
+
when Integer
|
|
81
70
|
byte
|
|
82
71
|
when String
|
|
83
72
|
0xDE
|
|
84
73
|
else
|
|
85
|
-
|
|
74
|
+
raise(InvalidByteValue, byte)
|
|
86
75
|
end
|
|
87
76
|
end
|
|
88
77
|
assembler.write_memory(placeholder_bytes)
|
|
89
|
-
|
|
78
|
+
promise
|
|
90
79
|
end
|
|
91
80
|
end
|
|
92
81
|
|
|
93
|
-
|
|
94
|
-
####
|
|
95
|
-
## Display, I don't want to write all these out
|
|
82
|
+
# Display, I don't want to write all these out
|
|
96
83
|
def to_s
|
|
97
84
|
".bytes (#{@bytes_array.length})"
|
|
98
85
|
end
|
|
99
|
-
|
|
100
86
|
end
|
|
101
|
-
|
|
102
87
|
end
|
data/lib/n65/directives/dw.rb
CHANGED
|
@@ -1,25 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../instruction_base'
|
|
2
4
|
|
|
3
5
|
module N65
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
####
|
|
7
|
-
## This directive instruction can include a binary file
|
|
6
|
+
# This directive instruction can include a binary file
|
|
8
7
|
class DW < InstructionBase
|
|
9
|
-
|
|
10
|
-
####
|
|
11
|
-
## Try to parse a dw directive
|
|
8
|
+
# Try to parse a dw directive
|
|
12
9
|
def self.parse(line)
|
|
13
|
-
|
|
14
|
-
## Maybe it is a straight up bit of hex
|
|
10
|
+
# Maybe it is a straight up bit of hex
|
|
15
11
|
match_data = line.match(/^\.dw\s+\$([0-9A-F]{1,4})$/)
|
|
16
12
|
unless match_data.nil?
|
|
17
13
|
word = match_data[1].to_i(16)
|
|
18
14
|
return DW.new(word)
|
|
19
15
|
end
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
match_data = line.match(/^\.dw\s+([A-Za-z_][A-Za-z0-9_
|
|
17
|
+
# Or maybe it points to a symbol
|
|
18
|
+
match_data = line.match(/^\.dw\s+([A-Za-z_][A-Za-z0-9_.]+)/)
|
|
23
19
|
unless match_data.nil?
|
|
24
20
|
symbol = match_data[1]
|
|
25
21
|
return DW.new(symbol)
|
|
@@ -27,60 +23,50 @@ module N65
|
|
|
27
23
|
nil
|
|
28
24
|
end
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
####
|
|
32
|
-
## Initialize with filename
|
|
26
|
+
# Initialize with filename
|
|
33
27
|
def initialize(value)
|
|
34
28
|
@value = value
|
|
35
29
|
end
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
## that that symbol will have then be defined
|
|
43
|
-
## This is a little complicated, I admit.
|
|
31
|
+
# Execute on the assembler, now in this case value may
|
|
32
|
+
# be a symbol that needs to be resolved, if so we return
|
|
33
|
+
# a lambda which can be executed later, with the promise
|
|
34
|
+
# that that symbol will have then be defined
|
|
35
|
+
# This is a little complicated, I admit.
|
|
44
36
|
def exec(assembler)
|
|
45
|
-
|
|
46
37
|
promise = assembler.with_saved_state do |saved_assembler|
|
|
47
38
|
value = saved_assembler.symbol_table.resolve_symbol(@value)
|
|
48
39
|
bytes = [value & 0xFFFF].pack('S').bytes
|
|
49
40
|
saved_assembler.write_memory(bytes)
|
|
50
41
|
end
|
|
51
42
|
|
|
52
|
-
|
|
53
|
-
## Try to execute it now, or setup the promise to return
|
|
43
|
+
# Try to execute it now, or setup the promise to return
|
|
54
44
|
case @value
|
|
55
|
-
when
|
|
45
|
+
when Integer
|
|
56
46
|
bytes = [@value & 0xFFFF].pack('S').bytes
|
|
57
47
|
assembler.write_memory(bytes)
|
|
58
48
|
when String
|
|
59
49
|
begin
|
|
60
50
|
promise.call
|
|
61
51
|
rescue SymbolTable::UndefinedSymbol
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
# Must still advance PC before returning promise, so we'll write
|
|
53
|
+
# a place holder value of 0xDEAD
|
|
64
54
|
assembler.write_memory([0xDE, 0xAD])
|
|
65
|
-
|
|
55
|
+
promise
|
|
66
56
|
end
|
|
67
57
|
else
|
|
68
|
-
|
|
58
|
+
raise('Uknown argument in .dw directive')
|
|
69
59
|
end
|
|
70
60
|
end
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
####
|
|
74
|
-
## Display
|
|
62
|
+
# Display
|
|
75
63
|
def to_s
|
|
76
64
|
case @value
|
|
77
65
|
when String
|
|
78
66
|
".dw #{@value}"
|
|
79
|
-
when
|
|
80
|
-
|
|
67
|
+
when Integer
|
|
68
|
+
'.dw $%4.X' % @value
|
|
81
69
|
end
|
|
82
70
|
end
|
|
83
|
-
|
|
84
71
|
end
|
|
85
|
-
|
|
86
72
|
end
|
|
@@ -1,55 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../instruction_base'
|
|
2
4
|
|
|
3
5
|
module N65
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
####
|
|
7
|
-
## This directive to include bytes
|
|
6
|
+
# This directive to include bytes
|
|
8
7
|
class EnterScope < InstructionBase
|
|
9
|
-
|
|
10
|
-
####
|
|
11
|
-
## Try to parse an incbin directive
|
|
12
8
|
def self.parse(line)
|
|
13
|
-
|
|
9
|
+
# Anonymous scope
|
|
14
10
|
match_data = line.match(/^\.scope$/)
|
|
15
|
-
unless match_data.nil?
|
|
16
|
-
return EnterScope.new
|
|
17
|
-
end
|
|
11
|
+
return EnterScope.new unless match_data.nil?
|
|
18
12
|
|
|
19
|
-
|
|
13
|
+
# Named scope
|
|
20
14
|
match_data = line.match(/^\.scope\s+([a-zA-Z][a-zA-Z0-9_]+)$/)
|
|
21
15
|
return nil if match_data.nil?
|
|
16
|
+
|
|
22
17
|
EnterScope.new(match_data[1])
|
|
23
18
|
end
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
####
|
|
27
|
-
## Initialize with filename
|
|
20
|
+
# Initialize with filename
|
|
28
21
|
def initialize(name = nil)
|
|
29
22
|
@name = name
|
|
30
23
|
end
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
## a label to the current PC. If someone tries to use a scope
|
|
38
|
-
## name as a label, it can return the address when the scope opened.
|
|
25
|
+
# Execute on the assembler, also create a symbol referring to
|
|
26
|
+
# the current pc which contains a hyphen, and is impossible for
|
|
27
|
+
# the user to create. This makes a scope simultaneously act as
|
|
28
|
+
# a label to the current PC. If someone tries to use a scope
|
|
29
|
+
# name as a label, it can return the address when the scope opened.
|
|
39
30
|
def exec(assembler)
|
|
40
31
|
assembler.symbol_table.enter_scope(@name)
|
|
41
|
-
unless @name.nil?
|
|
42
|
-
assembler.symbol_table.define_symbol("-#{@name}", assembler.program_counter)
|
|
43
|
-
end
|
|
32
|
+
assembler.symbol_table.define_symbol("-#{@name}", assembler.program_counter) unless @name.nil?
|
|
44
33
|
end
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
####
|
|
48
|
-
## Display
|
|
49
35
|
def to_s
|
|
50
36
|
".scope #{@name}"
|
|
51
37
|
end
|
|
52
|
-
|
|
53
38
|
end
|
|
54
|
-
|
|
55
39
|
end
|