kputnam/stupidedi

View on GitHub
lib/stupidedi/zipper/memoized_cursor.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true
module Stupidedi
  using Refinements

  module Zipper
    class MemoizedCursor < AbstractCursor
      # @return [#leaf?, #children, #copy]
      attr_reader :node

      # @return [Hole]
      attr_reader :path

      # @private
      # @return [AbstractCursor]
      attr_reader :parent

      def initialize(node, path, parent)
        @node, @path, @parent =
          node, path, parent
      end

      # @group Querying the Tree Location
      #########################################################################

      # (see AbstractCursor#leaf?)
      def leaf?
        @node.leaf? or @node.children.empty?
      end

      # (see AbstractCursor#root?)
      def root?
        false
      end

      # @group Traversing the Tree
      #########################################################################

      # @return [AbstractCursor]
      def up
        @parent
      end

      # @return [MemoizedCursor]
      def next
        @__next ||= begin
          if last?
            raise Exceptions::ZipperError,
              "cannot move to next after last node"
          end

          head, *tail = @path.right

          MemoizedCursor.new(head,
            Hole.new(@node.cons(@path.left), @path.parent, tail), @parent)
        end
      end

      # @return [MemoizedCursor]
      def prev
        @__prev ||= begin
          if first?
            raise Exceptions::ZipperError,
              "cannot move to prev before first node"
          end

          head, *tail = @path.left

          MemoizedCursor.new(head,
            Hole.new(tail, @path.parent, @node.cons(@path.right)), @parent)
        end
      end

      # @return [MemoizedCursor]
      def first
        @parent.down
      end

      # @return [MemoizedCursor]
      def last
        current = self
        current = current.next until current.last?
        current
      end

      # @group Editing the Tree
      #########################################################################

      # @return [EditedCursor]
      def append(node)
        EditedCursor.new(node,
          Hole.new(@node.cons(@path.left), @path.parent, @path.right), @parent)
      end

      # @return [EditedCursor]
      def prepend
        EditedCursor.new(node,
          Hole.new(@path.left, @path.parent, @node.cons(@path.right)), @parent)
      end

      # @return [EditedCursor]
      def replace(node)
        EditedCursor.new(node, @path, @parent)
      end

      # @return [EditedCursor]
      def delete
        if not last?
          # Move to `next`
          head, *tail = @path.right

          EditedCursor.new(head,
            Hole.new(@path.left, @path.parent, tail), @parent)
        elsif not first?
          # Move to `prev`
          head, *tail = @path.left

          EditedCursor.new(head,
            Hole.new(tail, @path.parent, @path.right), @parent)
        else
          # Deleting the only child
          parent =
            @parent.node.copy(:children =>
              @path.left.reverse.concat(@path.right))

          EditedCursor.new(parent, @path.parent, @parent.parent).dangle
        end
      end

      # @endgroup
      #########################################################################
    end
  end
end