lib/stupidedi/reader/segment_dict.rb
# frozen_string_literal: true
module Stupidedi
using Refinements
module Reader
class SegmentDict
include Inspect
# Return the top element from the stack. This will throw an exception
# if the stack is {#empty?}
abstract :top
# Return the remainder of the stack. This will throw an exception if
# the stack is {#empty?}
#
# @return [SegmentDict]
abstract :pop
# Returns a new SegmentDict with +top+ pushed to the top of the stack
#
# @return [SegmentDict]
abstract :push, :args => %w(top)
# Searches the stack for the definition of the given segment identifier
abstract :at, :args => %w(segment_id)
# Returns true if a node in the stack has a definition for the given
# segment identifier
abstract :defined_at?, :args => %w(segment_id)
# True if the stack of dictionaries is empty
abstract :empty?
# @private
class NonEmpty < SegmentDict
attr_reader :top
# @return [SegmentDict]
attr_reader :pop
def initialize(top, pop = SegmentDict.empty)
@top, @pop = top, pop
end
# @return [SegmentDict]
def copy(changes = {})
NonEmpty.new \
changes.fetch(:top, @top),
changes.fetch(:pop, @pop)
end
def push(top)
if top.is_a?(Module)
copy(:top => Constants.new(top), :pop => self)
else
copy(:top => top, :pop => self)
end
end
def at(segment_id)
if @top.defined_at?(segment_id)
@top.at(segment_id)
else
@pop.at(segment_id)
end
end
def defined_at?(segment_id)
@top.defined_at?(segment_id) or
@pop.defined_at?(segment_id)
end
def empty?
false
end
# @return [void]
def pretty_print(q)
q.text "SegmentDict"
q.group(2, "(", ")") do
q.breakable ""
node = self
until node.empty?
unless q.current_group.first?
q.text ","
q.breakable
end
q.pp node.top
node = node.pop
end
end
end
end
# @private
Empty = Class.new(SegmentDict) do
def top
raise TypeError, "empty stack"
end
def pop
raise TypeError, "stack underflow"
end
def push(top)
if top.is_a?(Module)
NonEmpty.new(Constants.new(top), self)
else
NonEmpty.new(top, self)
end
end
def defined_at?(segment_id)
false
end
def at(segment_id)
raise TypeError, "empty stack"
end
def empty?
true
end
# @return [void]
def pretty_print(q)
q.text "SegmentDict.empty"
end
end.new
# @private
class Constants
def initialize(namespace)
@namespace = namespace
@constants = Hash.new
namespace.constants.each{|c| @constants[c.to_sym] = true }
end
def at(segment_id)
@namespace.const_get(segment_id)
end
def defined_at?(segment_id)
@constants.include?(segment_id.to_sym)
end
# @return [void]
def pretty_print(q)
q.text("#{@namespace.name}.constants")
end
end
end
class << SegmentDict
# @group Constructors
#########################################################################
# @return [SegmentDict::Empty]
def empty
SegmentDict::Empty
end
# @return [SegmentDict::NonEmpty]
def build(top)
SegmentDict::Empty.push(top)
end
# @endgroup
#########################################################################
end
SegmentDict.eigenclass.send(:protected, :new)
SegmentDict::NonEmpty.eigenclass.send(:public, :new)
end
end