1
0
mirror of https://github.com/safiire/n65.git synced 2025-01-08 19:29:24 +00:00

Linted symbol_table.rb

This commit is contained in:
Saf 2020-08-30 13:20:17 -07:00
parent d3ed4e81ec
commit 34cc52c6a8

View File

@ -1,128 +1,108 @@
# frozen_string_literal: true
module N65 module N65
class SymbolTable class SymbolTable
attr_accessor :scope_stack attr_accessor :scope_stack
##### Custom Exceptions # Custom Exceptions
class InvalidScope < StandardError; end class InvalidScope < StandardError; end
class UndefinedSymbol < StandardError; end class UndefinedSymbol < StandardError; end
class CantExitScope < StandardError; end class CantExitScope < StandardError; end
# Initialize a symbol table that begins in global scope
####
## Initialize a symbol table that begins in global scope
def initialize def initialize
@symbols = { @symbols = {
:global => {} global: {}
} }
@anonymous_scope_number = 0 @anonymous_scope_number = 0
@scope_stack = [:global] @scope_stack = [:global]
@subroutine_cycles = {} @subroutine_cycles = {}
end end
# Add a running cycle count to current top level scopes (ie subroutines)
####
## Add a running cycle count to current top level scopes (ie subroutines)
def add_cycles(cycles) def add_cycles(cycles)
cycles ||= 0 cycles ||= 0
top_level_subroutine = @scope_stack[1] top_level_subroutine = @scope_stack[1]
unless top_level_subroutine.nil? return if top_level_subroutine.nil?
@subroutine_cycles[top_level_subroutine] ||= 0
@subroutine_cycles[top_level_subroutine] += cycles @subroutine_cycles[top_level_subroutine] ||= 0
end @subroutine_cycles[top_level_subroutine] += cycles
end end
# Define a new scope, which can be anonymous or named
#### # and switch into that scope
## Define a new scope, which can be anonymous or named
## and switch into that scope
def enter_scope(name = nil) def enter_scope(name = nil)
name = generate_name if name.nil? name = generate_name if name.nil?
name = name.to_sym name = name.to_sym
scope = current_scope scope = current_scope
if scope.has_key?(name) raise(InvalidScope, "Scope: #{name} already exists") if scope.key?(name)
fail(InvalidScope, "Scope: #{name} already exists")
end
scope[name] = {} scope[name] = {}
@scope_stack.push(name) @scope_stack.push(name)
end end
# Exit the current scope
####
## Exit the current scope
def exit_scope def exit_scope
if @scope_stack.size == 1 raise(CantExitScope, 'You cannot exit global scope') if @scope_stack.size == 1
fail(CantExitScope, "You cannot exit global scope")
end
@scope_stack.pop @scope_stack.pop
end end
# Define a symbol in the current scope
####
## Define a symbol in the current scope
def define_symbol(symbol, value) def define_symbol(symbol, value)
scope = current_scope scope = current_scope
scope[symbol.to_sym] = value scope[symbol.to_sym] = value
end end
# Separate arithmetic from scope name
####
## Separate arithmetic from scope name
def find_arithmetic(name) def find_arithmetic(name)
last_name = name.split('.').last last_name = name.split('.').last
md = last_name.match(/([\+\-\*\/])(\d+)$/) md = last_name.match(%r{([+\-*/])(\d+)$})
f = lambda{|v| v} f = ->(v) { v }
unless md.nil? unless md.nil?
full_match, operator, argument = md.to_a full_match, operator, argument = md.to_a
name.gsub!(full_match, '') name.gsub!(full_match, '')
f = lambda {|value| value.send(operator.to_sym, argument.to_i) } f = ->(value) { value.send(operator.to_sym, argument.to_i) }
end end
[name, f] [name, f]
end end
# Resolve a symbol to its value
####
## Resolve a symbol to its value
def resolve_symbol(name) def resolve_symbol(name)
name, arithmetic = find_arithmetic(name) name, arithmetic = find_arithmetic(name)
method = name.include?('.') ? :resolve_symbol_dot_syntax : :resolve_symbol_scoped method = name.include?('.') ? :resolve_symbol_dot_syntax : :resolve_symbol_scoped
value = self.send(method, name) value = send(method, name)
value = arithmetic.call(value) value = arithmetic.call(value)
raise(UndefinedSymbol, name) if value.nil?
fail(UndefinedSymbol, name) if value.nil?
value value
end end
# Resolve symbol by working backwards through each
#### # containing scope. Similarly named scopes shadow outer scopes
## Resolve symbol by working backwards through each
## containing scope. Similarly named scopes shadow outer scopes
def resolve_symbol_scoped(name) def resolve_symbol_scoped(name)
root = "-#{name}".to_sym root = "-#{name}".to_sym
stack = @scope_stack.dup stack = @scope_stack.dup
loop do loop do
scope = retreive_scope(stack) scope = retreive_scope(stack)
## We see if there is a key either under this name, or root # We see if there is a key either under this name, or root
v = scope[name.to_sym] || scope[root] v = scope[name.to_sym] || scope[root]
v = v.kind_of?(Hash) ? v[root] : v v = v.is_a?(Hash) ? v[root] : v
return v unless v.nil? return v unless v.nil?
## Pop the stack so we can decend to the parent scope, if any # Pop the stack so we can decend to the parent scope, if any
stack.pop stack.pop
return nil if stack.empty? return nil if stack.empty?
end end
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
## 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) def resolve_symbol_dot_syntax(name)
path_ary = name.split('.').map(&:to_sym) path_ary = name.split('.').map(&:to_sym)
symbol = path_ary.pop symbol = path_ary.pop
@ -131,40 +111,32 @@ module N65
scope = retreive_scope(path_ary) scope = retreive_scope(path_ary)
## We see if there is a key either under this name, or root # We see if there is a key either under this name, or root
v = scope[symbol] v = scope[symbol]
v.kind_of?(Hash) ? v[root] : v v.is_a?(Hash) ? v[root] : v
end end
# Export the symbol table as YAML
####
## Export the symbol table as YAML
def export_to_yaml def export_to_yaml
@symbols.to_yaml.gsub(/(\d+)$/) do |match| @symbols.to_yaml.gsub(/(\d+)$/) do |match|
integer = match.to_i integer = match.to_i
sprintf("0x%.4X", integer) format('0x%.4X', integer)
end end
end end
# Export a cycle count for top level subroutines
####
## Export a cycle count for top level subroutines
def export_cycle_count_yaml def export_cycle_count_yaml
@subroutine_cycles.to_yaml @subroutine_cycles.to_yaml
end end
private private
#### # A bit more clearly states to get the current scope
## A bit more clearly states to get the current scope
def current_scope def current_scope
retreive_scope retreive_scope
end end
# Retrieve a reference to a scope, current scope by default
####
## Retrieve a reference to a scope, current scope by default
def retreive_scope(path_ary = @scope_stack) def retreive_scope(path_ary = @scope_stack)
path_ary = path_ary.dup path_ary = path_ary.dup
path_ary.unshift(:global) unless path_ary.first == :global path_ary.unshift(:global) unless path_ary.first == :global
@ -175,28 +147,22 @@ module N65
if new_scope.nil? if new_scope.nil?
path_string = generate_scope_path(path_ary) path_string = generate_scope_path(path_ary)
message = "Resolving scope: #{path_string} failed at #{path_component}" message = "Resolving scope: #{path_string} failed at #{path_component}"
fail(InvalidScope, message) if new_scope.nil? raise(InvalidScope, message) if new_scope.nil?
end end
new_scope new_scope
end end
end end
# Generate a scope path from an array
####
## Generate a scope path from an array
def generate_scope_path(path_ary) def generate_scope_path(path_ary)
path_ary.join('.') path_ary.join('.')
end end
# Generate an anonymous scope name
####
## Generate an anonymous scope name
def generate_name def generate_name
@anonymous_scope_number += 1 @anonymous_scope_number += 1
"anonymous_#{@anonymous_scope_number}" "anonymous_#{@anonymous_scope_number}"
end end
end end
end end