mirror of
https://github.com/safiire/n65.git
synced 2024-12-31 14:30:17 +00:00
Fixed an issue with referencing outer scoped symbols
This commit is contained in:
parent
411e8b9873
commit
a48d10474e
@ -1,55 +0,0 @@
|
||||
; Create an iNES header
|
||||
.ines {"prog": 1, "char": 1, "mapper": 0, "mirror": 1}
|
||||
|
||||
|
||||
; Main code segment
|
||||
.org $C000
|
||||
start:
|
||||
SEI ; disable IRQs
|
||||
CLD ; disable decimal mode
|
||||
LDX #$40
|
||||
STX $4017 ; disable APU frame IRQ
|
||||
LDX #$FF
|
||||
TXS ; Set up stack
|
||||
INX ; now X = 0
|
||||
STX $2000 ; disable NMI
|
||||
STX $2001 ; disable rendering
|
||||
STX $4010 ; disable DMC IRQs
|
||||
|
||||
vblankwait1: ; First wait for vblank to make sure PPU is ready
|
||||
BIT $2002
|
||||
BPL vblankwait1
|
||||
|
||||
clrmem:
|
||||
LDA #$00
|
||||
STA $0000, X
|
||||
STA $0100, X
|
||||
STA $0200, X
|
||||
STA $0400, X
|
||||
STA $0500, X
|
||||
STA $0600, X
|
||||
STA $0700, X
|
||||
LDA #$FE
|
||||
STA $0300, X
|
||||
INX
|
||||
BNE clrmem
|
||||
|
||||
vblankwait2: ; Second wait for vblank, PPU is ready after this
|
||||
BIT $2002
|
||||
BPL vblankwait2
|
||||
|
||||
|
||||
LDA #$60 ;intensify blues
|
||||
STA $2001
|
||||
|
||||
forever:
|
||||
JMP forever ;jump back to Forever, infinite loop
|
||||
|
||||
nmi:
|
||||
RTI
|
||||
|
||||
|
||||
.org $FFFA ;first of the three vectors starts here
|
||||
.dw nmi ;when an NMI happens (once per frame if enabled) the processor will jump to the label NMI:
|
||||
.dw start ;when the processor first turns on or is reset, it will jump to the label RESET:
|
||||
.dw $0 ;external interrupt IRQ is not used in this tutorial
|
@ -1,9 +1,9 @@
|
||||
; Create an iNES header
|
||||
.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 1}
|
||||
; Create an iNES header
|
||||
.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 1}
|
||||
|
||||
; Here is the start of our code
|
||||
.org $C000
|
||||
start:
|
||||
.org $C000
|
||||
.scope main
|
||||
LDA #$01 ; square 1
|
||||
STA $4015
|
||||
LDA #$F8 ; period low
|
||||
@ -12,13 +12,13 @@ start:
|
||||
STA $4003
|
||||
LDA #$BF ; volume
|
||||
STA $4000
|
||||
forever:
|
||||
JMP forever
|
||||
forever:
|
||||
JMP forever
|
||||
|
||||
nmi:
|
||||
nothing:
|
||||
RTI
|
||||
|
||||
.org $FFFA ; Here are the three interrupt vectors
|
||||
.dw nmi ; VBlank non-maskable interrupt
|
||||
.dw start ; When the processor is reset or powers on
|
||||
.dw $0 ; External interrupt IRQ
|
||||
.dw nothing ; VBlank non-maskable interrupt
|
||||
.dw main ; When the processor is reset or powers on
|
||||
.dw nothing ; External interrupt IRQ
|
||||
|
@ -239,7 +239,7 @@
|
||||
|
||||
;;;;
|
||||
; Update the sprite, I don't exactly understand the DMA call yet.
|
||||
update_sprite:
|
||||
.scope update_sprite
|
||||
lda #>sprite
|
||||
sta nes.sprite.dma ; Jam page $200-$2FF into SPR-RAM, how do we get these numbers?
|
||||
lda sprite.x
|
||||
@ -262,11 +262,12 @@ update_sprite:
|
||||
adc dx zp
|
||||
sta sprite.x
|
||||
rts
|
||||
.
|
||||
|
||||
|
||||
;;;;
|
||||
; Read the first controller, and handle input
|
||||
react_to_input:
|
||||
.scope react_to_input
|
||||
lda #$01 ; strobe joypad
|
||||
sta nes.controller1
|
||||
lda #$00
|
||||
@ -306,11 +307,12 @@ react_to_input:
|
||||
stx sprite.y
|
||||
not_dn:
|
||||
rts ; Ignore left and right
|
||||
.
|
||||
|
||||
|
||||
;;;;
|
||||
; XORing with $ff toggles between 0x1 and 0xfe (-1)
|
||||
reverse_dx:
|
||||
.scope reverse_dx
|
||||
lda #$FF
|
||||
eor dx zp
|
||||
clc
|
||||
@ -318,30 +320,32 @@ reverse_dx:
|
||||
sta dx zp
|
||||
jsr low_c ; Play the reverse low C note
|
||||
rts
|
||||
.
|
||||
|
||||
|
||||
;;;;
|
||||
; Scroll the screen if we have to
|
||||
scroll_screen:
|
||||
.scope scroll_screen
|
||||
ldx #$00 ; Reset VRAM Address to $0000
|
||||
stx nes.vram.address
|
||||
stx nes.vram.address
|
||||
|
||||
ldx scroll zp ; Do we need to scroll at all?
|
||||
beq no_scroll
|
||||
beq return
|
||||
dex
|
||||
stx scroll zp
|
||||
lda #$00
|
||||
sta nes.ppu.scroll ; Write 0 for Horiz. Scroll value
|
||||
stx nes.ppu.scroll ; Write the value of 'scroll' for Vert. Scroll value
|
||||
|
||||
no_scroll:
|
||||
return:
|
||||
rts
|
||||
.
|
||||
|
||||
|
||||
;;;;
|
||||
; Play a low C note on square 1
|
||||
low_c:
|
||||
.scope low_c
|
||||
pha
|
||||
lda #$84
|
||||
sta nes.apu.pulse1.control
|
||||
@ -351,11 +355,12 @@ low_c:
|
||||
sta nes.apu.pulse1.ct
|
||||
pla
|
||||
rts
|
||||
.
|
||||
|
||||
|
||||
;;;;
|
||||
; Play a high C note on square 1
|
||||
high_c:
|
||||
.scope high_c
|
||||
pha
|
||||
lda #$86
|
||||
sta nes.apu.pulse1.control
|
||||
@ -365,15 +370,17 @@ high_c:
|
||||
sta nes.apu.pulse1.ct
|
||||
pla
|
||||
rts
|
||||
.
|
||||
|
||||
|
||||
;;;;
|
||||
; Update everything on every vblank
|
||||
vblank:
|
||||
.scope vblank
|
||||
jsr scroll_screen
|
||||
jsr update_sprite
|
||||
jsr react_to_input
|
||||
rti
|
||||
.
|
||||
|
||||
|
||||
;;;;
|
||||
|
@ -9,13 +9,6 @@
|
||||
; Create an iNES header
|
||||
.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0}
|
||||
|
||||
;;;;
|
||||
; Here is a good spot to associate zero page memory addresses
|
||||
; with quick access variables in the program.
|
||||
|
||||
.org $0200
|
||||
sprite:
|
||||
|
||||
|
||||
;;;;
|
||||
; Setup the interrupt vectors
|
||||
@ -28,7 +21,7 @@ sprite:
|
||||
.org $C000
|
||||
;;;;
|
||||
; Here is our code entry point, which we'll call main.
|
||||
main:
|
||||
.scope main
|
||||
; Disable interrupts and decimal flag
|
||||
sei
|
||||
cld
|
||||
@ -85,15 +78,16 @@ main:
|
||||
cli
|
||||
forever:
|
||||
jmp forever
|
||||
.
|
||||
|
||||
|
||||
;;;;
|
||||
; Update everything on every vblank
|
||||
vblank:
|
||||
vblank:
|
||||
rti
|
||||
|
||||
|
||||
;;;;
|
||||
; Don't do anything on IRQ
|
||||
irq:
|
||||
irq:
|
||||
rti
|
||||
|
@ -1,30 +0,0 @@
|
||||
;;;;
|
||||
; Create an iNES header
|
||||
.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0}
|
||||
|
||||
;;;;
|
||||
;; Start a prog segment number 0
|
||||
.segment prog 0
|
||||
.org $8000
|
||||
|
||||
.scope main
|
||||
sei
|
||||
cld
|
||||
loop:
|
||||
ldx $00
|
||||
inx
|
||||
stx $00
|
||||
jmp loop
|
||||
.
|
||||
|
||||
vblank:
|
||||
irq:
|
||||
rti
|
||||
|
||||
;;;;
|
||||
;; Vector table
|
||||
.org $FFFA
|
||||
|
||||
.dw vblank
|
||||
.dw main
|
||||
.dw irq
|
@ -67,6 +67,23 @@ module Assembler6502
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
## Return an object that contains the assembler's current state
|
||||
def get_current_state
|
||||
saved_program_counter, saved_segment, saved_bank = @program_counter, @current_segment, @current_bank
|
||||
saved_scope = symbol_table.scope_stack.dup
|
||||
OpenStruct.new(program_counter: saved_program_counter, segment: saved_segment, bank: saved_bank, scope: saved_scope)
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
## Set the current state from an OpenStruct
|
||||
def set_current_state(struct)
|
||||
@program_counter, @current_segment, @current_bank = struct.program_counter, struct.segment, struct.bank
|
||||
symbol_table.scope_stack = struct.scope.dup
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
## This is the main assemble method, it parses one line into an object
|
||||
## which when given a reference to this assembler, controls the assembler
|
||||
@ -95,6 +112,23 @@ module Assembler6502
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
## This rewinds the state of the assembler, so a promise can be
|
||||
## executed with a previous state, for example if we can't resolve
|
||||
## a symbol right now, and want to try during the second pass
|
||||
def with_saved_state(&block)
|
||||
## Save the current state of the assembler
|
||||
old_state = get_current_state
|
||||
|
||||
lambda do
|
||||
|
||||
## Set the assembler state back to the old state and run the block like that
|
||||
set_current_state(old_state)
|
||||
block.call(self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
## Write to memory space. Typically, we are going to want to write
|
||||
## to the location of the current PC, current segment, and current bank.
|
||||
|
@ -43,18 +43,13 @@ module Assembler6502
|
||||
## This is a little complicated, I admit.
|
||||
def exec(assembler)
|
||||
|
||||
## Save these current values into the closure
|
||||
pc = assembler.program_counter
|
||||
segment = assembler.current_segment
|
||||
bank = assembler.current_bank
|
||||
|
||||
## Create a promise, if this symbol is not defined yet.
|
||||
promise = lambda do
|
||||
value = assembler.symbol_table.resolve_symbol(@value)
|
||||
promise = assembler.with_saved_state do |saved_assembler|
|
||||
value = saved_assembler.symbol_table.resolve_symbol(@value)
|
||||
bytes = [value & 0xFFFF].pack('S').bytes
|
||||
assembler.write_memory(bytes, pc, segment, bank)
|
||||
saved_assembler.write_memory(bytes)
|
||||
end
|
||||
|
||||
|
||||
## Try to execute it now, or setup the promise to return
|
||||
case @value
|
||||
when Fixnum
|
||||
@ -66,7 +61,7 @@ module Assembler6502
|
||||
rescue SymbolTable::UndefinedSymbol
|
||||
## Must still advance PC before returning promise, so we'll write
|
||||
## a place holder value of 0xDEAD
|
||||
assembler.write_memory([0xDE, 0xAD], pc, segment, bank)
|
||||
assembler.write_memory([0xDE, 0xAD])
|
||||
return promise
|
||||
end
|
||||
else
|
||||
|
@ -147,7 +147,6 @@ module Assembler6502
|
||||
if match_array.size == 4
|
||||
_, op, byte_selector, arg = match_array
|
||||
return Instruction.new(op, arg, mode, byte_selector.to_sym)
|
||||
puts "I found one with #{byte_selector} #{arg}"
|
||||
else
|
||||
_, op, arg = match_array
|
||||
return Instruction.new(op, arg, mode)
|
||||
@ -204,22 +203,16 @@ module Assembler6502
|
||||
## promise to resolve it later.
|
||||
def exec(assembler)
|
||||
|
||||
## Save these current values into the closure/promise
|
||||
pc = assembler.program_counter
|
||||
segment = assembler.current_segment
|
||||
bank = assembler.current_bank
|
||||
|
||||
## Create a promise if this symbol is not defined yet.
|
||||
promise = lambda do
|
||||
@arg = assembler.symbol_table.resolve_symbol(@arg)
|
||||
promise = assembler.with_saved_state do |saved_assembler|
|
||||
@arg = saved_assembler.symbol_table.resolve_symbol(@arg)
|
||||
|
||||
## If the instruction uses a byte selector, we need to apply that.
|
||||
@arg = apply_byte_selector(@byte_selector, @arg)
|
||||
|
||||
## If the instruction is relative we need to work out how far away it is
|
||||
@arg = @arg - pc - 2 if @mode == :relative
|
||||
@arg = @arg - saved_assembler.program_counter - 2 if @mode == :relative
|
||||
|
||||
assembler.write_memory(emit_bytes, pc, segment, bank)
|
||||
saved_assembler.write_memory(emit_bytes)
|
||||
end
|
||||
|
||||
case @arg
|
||||
@ -234,7 +227,7 @@ module Assembler6502
|
||||
placeholder = [@hex, 0xDE, 0xAD][0...@length]
|
||||
## I still have to write a placeholder instruction of the right
|
||||
## length. The promise will come back and resolve the address.
|
||||
assembler.write_memory(placeholder, pc, segment, bank)
|
||||
assembler.write_memory(placeholder)
|
||||
return promise
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,9 @@
|
||||
require 'pry-byebug'
|
||||
|
||||
module Assembler6502
|
||||
|
||||
class SymbolTable
|
||||
attr_accessor :scope_stack
|
||||
|
||||
##### Custom Exceptions
|
||||
class InvalidScope < StandardError; end
|
||||
@ -54,6 +56,7 @@ module Assembler6502
|
||||
end
|
||||
|
||||
|
||||
=begin
|
||||
####
|
||||
## Resolve symbol to a value, for example:
|
||||
## scope1.scope2.variable
|
||||
@ -61,7 +64,7 @@ module Assembler6502
|
||||
## You can just address anything by name in the current scope
|
||||
## To go backwards in scope you need to write the full path
|
||||
## like global.sprite.x or whatever
|
||||
def resolve_symbol(name)
|
||||
def resolve_symbol_old(name)
|
||||
|
||||
value = if name.include?('.')
|
||||
path_ary = name.split('.').map(&:to_sym)
|
||||
@ -85,6 +88,57 @@ module Assembler6502
|
||||
end
|
||||
value
|
||||
end
|
||||
=end
|
||||
|
||||
|
||||
####
|
||||
##
|
||||
def resolve_symbol(name)
|
||||
method = name.include?('.') ? :resolve_symbol_dot_syntax : :resolve_symbol_scoped
|
||||
value = self.send(method, name)
|
||||
|
||||
fail(UndefinedSymbol, name) if value.nil?
|
||||
value
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
## Resolve symbol by working backwards through each
|
||||
## containing scope. Similarly named scopes shadow outer scopes
|
||||
def resolve_symbol_scoped(name)
|
||||
root = "-#{name}".to_sym
|
||||
stack = @scope_stack.dup
|
||||
loop do
|
||||
scope = retreive_scope(stack)
|
||||
|
||||
## We see if there is a key either under this name, or root
|
||||
v = scope[name.to_sym] || scope[root]
|
||||
v = v.kind_of?(Hash) ? v[root] : v
|
||||
|
||||
return v unless v.nil?
|
||||
|
||||
## Pop the stack so we can decend to the parent scope, if any
|
||||
stack.pop
|
||||
return nil if stack.empty?
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
## Dot syntax means to check an absolute path to the symbol
|
||||
## :global is ignored if it is provided as part of the path
|
||||
def resolve_symbol_dot_syntax(name)
|
||||
path_ary = name.split('.').map(&:to_sym)
|
||||
symbol = path_ary.pop
|
||||
root = "-#{symbol}".to_sym
|
||||
path_ary.shift if path_ary.first == :global
|
||||
|
||||
scope = retreive_scope(path_ary)
|
||||
|
||||
## We see if there is a key either under this name, or root
|
||||
v = scope[symbol]
|
||||
v.kind_of?(Hash) ? v[root] : v
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
|
@ -28,6 +28,34 @@ class TestSymbolTable < MiniTest::Test
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
## Access something from an outer scope without dot syntax
|
||||
def test_outer_scope
|
||||
st = SymbolTable.new
|
||||
st.enter_scope('outer')
|
||||
st.define_symbol('dog', 'woof')
|
||||
st.enter_scope('inner')
|
||||
st.define_symbol('pig', 'oink')
|
||||
assert_equal('woof', st.resolve_symbol('dog'))
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
## Access something from an outer scope without dot syntax
|
||||
def test_shadow
|
||||
st = SymbolTable.new
|
||||
st.enter_scope('outer')
|
||||
st.define_symbol('dog', 'woof')
|
||||
st.enter_scope('inner')
|
||||
st.define_symbol('dog', 'bark')
|
||||
assert_equal('bark', st.resolve_symbol('dog'))
|
||||
assert_equal('woof', st.resolve_symbol('outer.dog'))
|
||||
st.exit_scope
|
||||
st.exit_scope
|
||||
assert_equal('bark', st.resolve_symbol('outer.inner.dog'))
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
## Test exiting a sub scope, and seeing that the variable is unavailable by simple name
|
||||
def test_exit_scope
|
||||
@ -153,5 +181,58 @@ class TestSymbolTable < MiniTest::Test
|
||||
assert_equal(0x8000, assembler.symbol_table.resolve_symbol('global.main'))
|
||||
end
|
||||
|
||||
|
||||
####
|
||||
## Fix a bug where we can't see a forward declared symbol in a scope
|
||||
def test_foward_declaration_in_scope
|
||||
program = <<-ASM
|
||||
;;;;
|
||||
; Create an iNES header
|
||||
.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0}
|
||||
|
||||
;;;;
|
||||
; Try to expose a problem we have with scopes
|
||||
; We don't seem to be able to branch to a forward
|
||||
; declared symbol within a scope
|
||||
.org $8000
|
||||
.scope main
|
||||
sei
|
||||
cld
|
||||
lda #\$00
|
||||
bne forward_symbol
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
forward_symbol:
|
||||
rts
|
||||
.
|
||||
ASM
|
||||
|
||||
#### There really should be an evaluate string method
|
||||
assembler = Assembler.new
|
||||
program.split(/\n/).each do |line|
|
||||
assembler.assemble_one_line(line)
|
||||
end
|
||||
puts YAML.dump(assembler.symbol_table)
|
||||
assembler.fulfill_promises
|
||||
|
||||
#### The forward symbol should have been resolved to +3, and the ROM should look like this:
|
||||
correct_rom = [0x4e, 0x45, 0x53, 0x1a, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x78, # SEI
|
||||
0xd8, # CLD
|
||||
0xa9, 0x0, # LDA immediate 0
|
||||
0xd0, 0x3, # BNE +3
|
||||
0xea, # NOP
|
||||
0xea, # NOP
|
||||
0xea, # NOP
|
||||
0x60 # RTS forward_symbol
|
||||
]
|
||||
|
||||
#### Grab the first 26 bytes of the rom and make sure they assemble to the above
|
||||
emitted_rom = assembler.emit_binary_rom.bytes[0...26]
|
||||
assert_equal(correct_rom, emitted_rom)
|
||||
#### Yup it is fixed now.
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user