From da807a2056ee1ef18516609486d74de62b45aac3 Mon Sep 17 00:00:00 2001
From: Saf <saf@irkenkitties.com>
Date: Mon, 31 Aug 2020 02:10:35 -0700
Subject: [PATCH] Rewrote symbol table test as rspec

---
 spec/lib/n65/symbol_table_spec.rb | 224 ++++++++++++++++++++++++++++++
 test/test_memory_space.rb         |  77 ----------
 test/test_symbol_table.rb         | 214 ----------------------------
 3 files changed, 224 insertions(+), 291 deletions(-)
 create mode 100644 spec/lib/n65/symbol_table_spec.rb
 delete mode 100644 test/test_memory_space.rb
 delete mode 100644 test/test_symbol_table.rb

diff --git a/spec/lib/n65/symbol_table_spec.rb b/spec/lib/n65/symbol_table_spec.rb
new file mode 100644
index 0000000..99ac6b4
--- /dev/null
+++ b/spec/lib/n65/symbol_table_spec.rb
@@ -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
diff --git a/test/test_memory_space.rb b/test/test_memory_space.rb
deleted file mode 100644
index 53dcdf9..0000000
--- a/test/test_memory_space.rb
+++ /dev/null
@@ -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
diff --git a/test/test_symbol_table.rb b/test/test_symbol_table.rb
deleted file mode 100644
index 4455b56..0000000
--- a/test/test_symbol_table.rb
+++ /dev/null
@@ -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