# frozen_string_literal: true module MutualRecursion class TailCall attr_reader :value, :block def initialize(value = nil, &block) @value = value @block = block end ## # Invoke this tail call. Only the returned value from the initial call to # the recursive function should be invoked. # # @return [Object] the terminal_value of this tail call def invoke self.then do |tail| while tail.block tail = tail.block.call raise MissingTailCallError unless tail.is_a?(TailCall) end tail.value end end end class MissingTailCallError < StandardError def initialize(msg = 'expected a tail call') super end end module_function ## # Make a direct or indirect recursive call in tail position. # # @yieldreturn [MutualRecursion::TailCall] a tail call # @return [MutualRecursion::TailCall] a non terminal tail call def tail_call TailCall.new { yield } end ## # Indicates that the recursion has ended with the provided value. # # @param [Object] value the terminal value # @return [MutualRecursion::TailCall] a terminal tail call that will return the given value def terminal_value(value) TailCall.new(value) end end