mirror of
https://github.com/safiire/n65.git
synced 2024-06-11 07:29:27 +00:00
Rewrote symbol table test as rspec
This commit is contained in:
parent
9f012a159c
commit
da807a2056
224
spec/lib/n65/symbol_table_spec.rb
Normal file
224
spec/lib/n65/symbol_table_spec.rb
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative '../../../lib/n65/symbol_table'
|
||||||
|
require_relative '../../../lib/n65'
|
||||||
|
|
||||||
|
RSpec.describe(N65::SymbolTable) do
|
||||||
|
subject { described_class.new }
|
||||||
|
|
||||||
|
context 'when defining a global symbol' do
|
||||||
|
before { subject.define_symbol('dog', 'woof') }
|
||||||
|
|
||||||
|
it 'can resolve the value' do
|
||||||
|
expect(subject.resolve_symbol('dog')).to eq('woof')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when entering a sub-scope' do
|
||||||
|
before do
|
||||||
|
subject.enter_scope('animals')
|
||||||
|
subject.define_symbol('dog', 'woof')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can resolve the value' do
|
||||||
|
expect(subject.resolve_symbol('dog')).to eq('woof')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can resolve the value with full dot syntax' do
|
||||||
|
expect(subject.resolve_symbol('animals.dog')).to eq('woof')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when accessing a symbol at higher scope' do
|
||||||
|
before do
|
||||||
|
subject.enter_scope('outer')
|
||||||
|
subject.define_symbol('dog', 'woof')
|
||||||
|
subject.enter_scope('inner')
|
||||||
|
subject.define_symbol('pig', 'oink')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can resolve the outer value without dot syntax' do
|
||||||
|
expect(subject.resolve_symbol('dog')).to eq('woof')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can resolve the value in current scope' do
|
||||||
|
expect(subject.resolve_symbol('pig')).to eq('oink')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a symbol from an outer scope is shadowed' do
|
||||||
|
before do
|
||||||
|
subject.enter_scope('outer')
|
||||||
|
subject.define_symbol('dog', 'woof')
|
||||||
|
subject.enter_scope('inner')
|
||||||
|
subject.define_symbol('dog', 'bark')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'the inner scope shadows the outer' do
|
||||||
|
expect(subject.resolve_symbol('dog')).to eq('bark')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not shadow it when we leave the inner scope' do
|
||||||
|
subject.exit_scope
|
||||||
|
expect(subject.resolve_symbol('dog')).to eq('woof')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can access inner via dot syntax if we exit both scopes' do
|
||||||
|
subject.exit_scope
|
||||||
|
subject.exit_scope
|
||||||
|
expect(subject.resolve_symbol('outer.inner.dog')).to eq('bark')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when trying to access a symbol not in scope' do
|
||||||
|
before do
|
||||||
|
subject.enter_scope('animals')
|
||||||
|
subject.define_symbol('dog', 'woof')
|
||||||
|
subject.exit_scope
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is undefined' do
|
||||||
|
expect { subject.resolve_symbol('dog') }.to raise_error(described_class::UndefinedSymbol)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when trying to access a symbol not in scope' do
|
||||||
|
before do
|
||||||
|
subject.enter_scope('animals')
|
||||||
|
subject.define_symbol('dog', 'woof')
|
||||||
|
subject.exit_scope
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can be accessed by full path' do
|
||||||
|
expect(subject.resolve_symbol('animals.dog')).to eq('woof')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when we have two symbols with the same name in different scopes' do
|
||||||
|
before do
|
||||||
|
subject.define_symbol('dog', 'woof')
|
||||||
|
subject.enter_scope('animals')
|
||||||
|
subject.define_symbol('dog', 'woof woof')
|
||||||
|
subject.exit_scope
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can access each by full path' do
|
||||||
|
expect(subject.resolve_symbol('dog')).to eq('woof')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can access each by full path' do
|
||||||
|
expect(subject.resolve_symbol('animals.dog')).to eq('woof woof')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when trying to access symbols at top scope' do
|
||||||
|
before do
|
||||||
|
subject.define_symbol('dog', 'woof')
|
||||||
|
subject.enter_scope('animals')
|
||||||
|
subject.define_symbol('dog', 'woof woof')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can use the global prefix' do
|
||||||
|
expect(subject.resolve_symbol('global.dog')).to eq('woof')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when creating an anonymous scope' do
|
||||||
|
before do
|
||||||
|
subject.define_symbol('dog', 'woof')
|
||||||
|
subject.enter_scope
|
||||||
|
subject.define_symbol('dog', 'woof woof')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'gets the value in the current anonymous scope' do
|
||||||
|
expect(subject.resolve_symbol('dog')).to eq('woof woof')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can get the outer dog by dot syntax' do
|
||||||
|
expect(subject.resolve_symbol('global.dog')).to eq('woof')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can get the outer dog by exiting anonymous scope and resolving' do
|
||||||
|
subject.exit_scope
|
||||||
|
expect(subject.resolve_symbol('dog')).to eq('woof')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when trying to exit the top most scope' do
|
||||||
|
it 'raises an error' do
|
||||||
|
expect { subject.exit_scope }.to raise_error(described_class::CantExitScope)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when checking the address value of a scope' do
|
||||||
|
let(:assembler) { N65::Assembler.new }
|
||||||
|
let(:program) do
|
||||||
|
<<~'ASM'
|
||||||
|
.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0}
|
||||||
|
.org $8000
|
||||||
|
.segment prog 0
|
||||||
|
.scope main
|
||||||
|
sei
|
||||||
|
cld
|
||||||
|
lda #$00
|
||||||
|
jmp main
|
||||||
|
.
|
||||||
|
jmp main
|
||||||
|
jmp global.main
|
||||||
|
ASM
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
program.split(/\n/).each { |line| assembler.assemble_one_line(line) }
|
||||||
|
assembler.fulfill_promises
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'assigns the value of the scope main to the program counter value' do
|
||||||
|
expect(assembler.symbol_table.resolve_symbol('global.main')).to eq(0x8000)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when we try to jump to a forward declared symbol' do
|
||||||
|
let(:assembler) { N65::Assembler.new }
|
||||||
|
let(:program) do
|
||||||
|
<<~'ASM'
|
||||||
|
.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0}
|
||||||
|
.org $8000
|
||||||
|
.scope main
|
||||||
|
sei
|
||||||
|
cld
|
||||||
|
lda #$00
|
||||||
|
bne forward_symbol
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
forward_symbol:
|
||||||
|
rts
|
||||||
|
.
|
||||||
|
ASM
|
||||||
|
end
|
||||||
|
let(:correct_binary) do
|
||||||
|
[
|
||||||
|
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
|
||||||
|
]
|
||||||
|
end
|
||||||
|
let(:emitted_rom) { assembler.emit_binary_rom.bytes[0...26] }
|
||||||
|
|
||||||
|
before do
|
||||||
|
program.split(/\n/).each { |line| assembler.assemble_one_line(line) }
|
||||||
|
assembler.fulfill_promises
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'assembles the branch to forward_symbol correctly' do
|
||||||
|
expect(emitted_rom).to eq(correct_binary)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,77 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
gem 'minitest'
|
|
||||||
require 'minitest/autorun'
|
|
||||||
require 'minitest/unit'
|
|
||||||
|
|
||||||
require_relative '../lib/n65/memory_space'
|
|
||||||
|
|
||||||
class TestMemorySpace < MiniTest::Test
|
|
||||||
include N65
|
|
||||||
|
|
||||||
def test_create_prog_rom
|
|
||||||
# First just try to read alll of it
|
|
||||||
space = MemorySpace.create_prog_rom
|
|
||||||
contents = space.read(0x8000, 0x4000)
|
|
||||||
assert_equal(contents.size, 0x4000)
|
|
||||||
assert(contents.all?(&:zero?))
|
|
||||||
|
|
||||||
# It is mirrored so this should also work
|
|
||||||
space = MemorySpace.create_prog_rom
|
|
||||||
contents = space.read(0xC000, 0x4000)
|
|
||||||
assert_equal(contents.size, 0x4000)
|
|
||||||
assert(contents.all?(&:zero?))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_writing
|
|
||||||
# Write some bytes into prog 2 area
|
|
||||||
space = MemorySpace.create_prog_rom
|
|
||||||
space.write(0xC000, 'hi there'.bytes)
|
|
||||||
|
|
||||||
# Read them back..
|
|
||||||
contents = space.read(0xC000, 8)
|
|
||||||
assert_equal('hi there', contents.pack('C*'))
|
|
||||||
|
|
||||||
# Should be mirrored in prog 1
|
|
||||||
contents = space.read(0x8000, 8)
|
|
||||||
assert_equal('hi there', contents.pack('C*'))
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_reading_out_of_bounds
|
|
||||||
space = MemorySpace.create_prog_rom
|
|
||||||
assert_raises(MemorySpace::AccessOutsideProgRom) do
|
|
||||||
space.read(0x200, 10)
|
|
||||||
end
|
|
||||||
|
|
||||||
# But that is valid char rom area, so no explody
|
|
||||||
space = MemorySpace.create_char_rom
|
|
||||||
space.read(0x200, 10)
|
|
||||||
|
|
||||||
# But something like this should explode
|
|
||||||
space = MemorySpace.create_char_rom
|
|
||||||
assert_raises(MemorySpace::AccessOutsideCharRom) do
|
|
||||||
space.read(0x8001, 10)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# There seem to be problems writing bytes right to
|
|
||||||
# the end of the memory map, specifically where the
|
|
||||||
# vector table is in prog rom, so let's test that.
|
|
||||||
def test_writing_to_end
|
|
||||||
space = MemorySpace.create_prog_rom
|
|
||||||
bytes = [0xDE, 0xAD]
|
|
||||||
|
|
||||||
# Write the NMI address to FFFA
|
|
||||||
space.write(0xFFFA, bytes)
|
|
||||||
|
|
||||||
# Write the entry point to FFFC
|
|
||||||
space.write(0xFFFC, bytes)
|
|
||||||
|
|
||||||
# Write the irq to FFFE, and this fails, saying
|
|
||||||
# I'm trying to write to $10000 for some reason.
|
|
||||||
space.write(0xFFFE, bytes)
|
|
||||||
|
|
||||||
# Write to the very first
|
|
||||||
space.write(0x8000, bytes)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,214 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
gem 'minitest'
|
|
||||||
require 'minitest/autorun'
|
|
||||||
require 'minitest/unit'
|
|
||||||
|
|
||||||
require_relative '../lib/n65/symbol_table'
|
|
||||||
require_relative '../lib/n65'
|
|
||||||
|
|
||||||
class TestSymbolTable < MiniTest::Test
|
|
||||||
include N65
|
|
||||||
|
|
||||||
# Test that we can make simple global symbols
|
|
||||||
def test_define_global_symbols
|
|
||||||
st = SymbolTable.new
|
|
||||||
st.define_symbol('dog', 'woof')
|
|
||||||
assert_equal('woof', st.resolve_symbol('dog'))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Test entering into a sub scope, and setting and retrieving values
|
|
||||||
def test_enter_scope
|
|
||||||
st = SymbolTable.new
|
|
||||||
st.enter_scope('animals')
|
|
||||||
st.define_symbol('dog', 'woof')
|
|
||||||
assert_equal('woof', st.resolve_symbol('dog'))
|
|
||||||
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
|
|
||||||
st = SymbolTable.new
|
|
||||||
st.enter_scope('animals')
|
|
||||||
st.define_symbol('dog', 'woof')
|
|
||||||
assert_equal('woof', st.resolve_symbol('dog'))
|
|
||||||
|
|
||||||
st.exit_scope
|
|
||||||
|
|
||||||
assert_raises(SymbolTable::UndefinedSymbol) do
|
|
||||||
assert_equal('woof', st.resolve_symbol('dog'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Test exiting a sub scope, and being able to access a symbol through a full path
|
|
||||||
def test_exit_scope_full_path
|
|
||||||
st = SymbolTable.new
|
|
||||||
st.enter_scope('animals')
|
|
||||||
st.define_symbol('dog', 'woof')
|
|
||||||
assert_equal('woof', st.resolve_symbol('dog'))
|
|
||||||
|
|
||||||
st.exit_scope
|
|
||||||
|
|
||||||
assert_equal('woof', st.resolve_symbol('animals.dog'))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Have two symbols that are the same but are in different scopes
|
|
||||||
def test_two_scopes_same_symbol
|
|
||||||
st = SymbolTable.new
|
|
||||||
st.define_symbol('dog', 'woof')
|
|
||||||
assert_equal('woof', st.resolve_symbol('dog'))
|
|
||||||
|
|
||||||
st.enter_scope('animals')
|
|
||||||
|
|
||||||
st.define_symbol('dog', 'woofwoof')
|
|
||||||
assert_equal('woofwoof', st.resolve_symbol('dog'))
|
|
||||||
|
|
||||||
st.exit_scope
|
|
||||||
|
|
||||||
assert_equal('woof', st.resolve_symbol('dog'))
|
|
||||||
assert_equal('woofwoof', st.resolve_symbol('animals.dog'))
|
|
||||||
end
|
|
||||||
|
|
||||||
# How do you get stuff out of the global scope when you are in a sub scope?
|
|
||||||
def test_access_global_scope
|
|
||||||
st = SymbolTable.new
|
|
||||||
st.define_symbol('dog', 'woof')
|
|
||||||
assert_equal('woof', st.resolve_symbol('dog'))
|
|
||||||
|
|
||||||
st.enter_scope('animals')
|
|
||||||
st.define_symbol('pig', 'oink')
|
|
||||||
assert_equal('oink', st.resolve_symbol('pig'))
|
|
||||||
|
|
||||||
# Ok, now I want to access global.dog basically from the previous scope
|
|
||||||
assert_equal('woof', st.resolve_symbol('global.dog'))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Now I want to just test making an anonymous scope
|
|
||||||
def test_anonymous_scope
|
|
||||||
st = SymbolTable.new
|
|
||||||
st.define_symbol('dog', 'woof')
|
|
||||||
assert_equal('woof', st.resolve_symbol('dog'))
|
|
||||||
|
|
||||||
st.enter_scope
|
|
||||||
st.define_symbol('pig', 'oink')
|
|
||||||
assert_equal('oink', st.resolve_symbol('pig'))
|
|
||||||
|
|
||||||
# Ok, now I want to access global.dog basically from the previous scope
|
|
||||||
assert_equal('woof', st.resolve_symbol('global.dog'))
|
|
||||||
|
|
||||||
# Now exit the anonymous scope and get dog
|
|
||||||
st.exit_scope
|
|
||||||
assert_equal('woof', st.resolve_symbol('global.dog'))
|
|
||||||
assert_equal('woof', st.resolve_symbol('dog'))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Now I want to test that I cannot exist the outer-most
|
|
||||||
# global scope by mistake
|
|
||||||
def test_cant_exit_global
|
|
||||||
st = SymbolTable.new
|
|
||||||
assert_raises(SymbolTable::CantExitScope) do
|
|
||||||
st.exit_scope
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# I would like the name of the scope to take on the
|
|
||||||
# value of the program counter at that location.
|
|
||||||
def test_scope_as_symbol
|
|
||||||
program = <<-ASM
|
|
||||||
.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0}
|
|
||||||
.org $8000
|
|
||||||
.segment prog 0
|
|
||||||
.scope main
|
|
||||||
sei
|
|
||||||
cld
|
|
||||||
lda \#$00
|
|
||||||
jmp main
|
|
||||||
.
|
|
||||||
jmp main
|
|
||||||
jmp global.main
|
|
||||||
ASM
|
|
||||||
|
|
||||||
# There really should be an evaluate string method
|
|
||||||
assembler = Assembler.new
|
|
||||||
program.split(/\n/).each do |line|
|
|
||||||
assembler.assemble_one_line(line)
|
|
||||||
end
|
|
||||||
assembler.fulfill_promises
|
|
||||||
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