module Natalie
  class VM
    class Block
      def initialize(vm:)
        @vm = vm
        @ip = vm.ip
        @captured_block = vm.block
        @captured_self = vm.self
        @parent_scope = vm.scope
      end

      attr_reader :vm

      def inspect
        "#<#{self.class.name}:0x#{object_id.to_s(16)}>"
      end

      def call(*args, **kwargs)
        to_proc.call(*args, **kwargs)
      end

      def [](*args, **kwargs)
        to_proc.call(*args, **kwargs)
      end

      def to_proc
        @lambda ||=
          lambda do |*args, **kwargs|
            vm.with_self(@captured_self) do
              scope = { vars: {}, parent: @parent_scope }
              args << kwargs if kwargs.any?
              vm.push_call(name: nil, return_ip: vm.ip, args:, kwargs:, scope:, block: @captured_block)
              vm.ip = @ip
              begin
                vm.run
              ensure
                vm.ip = vm.pop_call.fetch(:return_ip)
              end
              vm.pop # result must be returned from proc
            end
          end
      end
    end
  end
end
