whoward/cadenza

View on GitHub
lib/cadenza/context/stack.rb

Summary

Maintainability
A
0 mins
Test Coverage

# frozen_string_literal: true

module Cadenza
  class Context
    # This module contains all methods related to the variable stack
    module Stack
      # @!attribute [r] stack
      # @return [Array] the variable stack
      def stack
        @stack ||= []
      end

      # retrieves the value of the given identifier by inspecting the variable
      # stack from top to bottom.  Identifiers with dots in them are separated
      # on that dot character and looked up as a path.  If no value could be
      # found then nil is returned.
      #
      # @return [Object] the object matching the identifier or nil if not found
      def lookup(identifier)
        sym_identifier = identifier.to_sym

        # if a function is defined matching the identifier name then return that
        return functions[sym_identifier] if functions.key?(sym_identifier)

        stack.reverse_each do |scope|
          value = lookup_identifier(scope, identifier)

          return value unless value.nil?
        end

        nil
      end

      # assigns the given value to the given identifier at the highest current
      # scope of the stack.
      #
      # @param [String] identifier the name of the variable to store
      # @param [Object] value the value to assign to the given name
      def assign(identifier, value)
        stack.last[identifier.to_sym] = value
      end

      # creates a new scope on the variable stack and assigns the given hash
      # to it.
      #
      # @param [Hash] scope the mapping of names to values for the new scope
      # @return nil
      def push(scope)
        # TODO: symbolizing strings is slow so consider symbolizing here to improve
        # the speed of the lookup method (its more important than push)

        # TODO: since you can assign with the #assign method then make the scope
        # variable optional (assigns an empty hash)
        stack.push(scope)

        nil
      end

      # removes the highest scope from the variable stack
      # @return [Hash] the removed scope
      def pop
        stack.pop
      end

      private

      def lookup_identifier(scope, identifier)
        if identifier.index('.')
          lookup_path(scope, identifier.split('.'))
        else
          self.class.lookup_on_object(identifier, scope)
        end
      end

      def lookup_path(scope, path)
        loop do
          component = path.shift

          scope = self.class.lookup_on_object(component, scope)

          return scope if path.empty?
        end
      end
    end
  end
end