Class: Musa::REPL::REPL

Inherits:
Object
  • Object
show all
Defined in:
lib/musa-dsl/repl/repl.rb

Constant Summary collapse

@@repl_mutex =
Mutex.new

Instance Method Summary collapse

Constructor Details

#initialize(bind = nil, port: nil, after_eval: nil, logger: nil, highlight_exception: true) ⇒ REPL

Returns a new instance of REPL.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/musa-dsl/repl/repl.rb', line 9

def initialize(bind = nil, port: nil, after_eval: nil, logger: nil, highlight_exception: true)

  self.bind = bind

  port ||= 1327

  @logger = logger || Musa::Logger::Logger.new
  @highlight_exception = highlight_exception

  @block_source = nil

  @client_threads = []
  @run = true

  @main_thread = Thread.new do
    @server = TCPServer.new(port)
    begin
      while (@connection = @server.accept) && @run
        @client_threads << Thread.new do
          buffer = nil

          begin
            while (line = @connection.gets) && @run

              @logger.warn('REPL') { 'input line is nil; will close connection...' } if line.nil?

              line.chomp!
              case line
              when '#path'
                buffer = StringIO.new

              when '#begin'
                user_path = buffer&.string
                @bind.receiver.instance_variable_set(:@user_pathname, Pathname.new(user_path)) if user_path

                buffer = StringIO.new

              when '#end'
                @@repl_mutex.synchronize do
                  @block_source = buffer.string

                  begin
                    send_echo @block_source, output: @connection
                    @bind.receiver.execute @block_source, '(repl)', 1

                  rescue StandardError, ScriptError => e
                    @logger.warn('REPL') { 'code execution error' }
                    @logger.warn('REPL') { e.full_message(highlight: @highlight_exception, order: :top) }

                    send_exception e, output: @connection
                  else
                    after_eval.call @block_source if after_eval
                  end
                end
              else
                buffer.puts line
              end
            end

          rescue IOError, Errno::ECONNRESET, Errno::EPIPE => e
            @logger.warn('REPL') { 'lost connection' }
            @logger.warn('REPL') { e.full_message(highlight: @highlight_exception, order: :top) }

          ensure
            @logger.debug('REPL') { "closing connection (running #{@run})" }
            @connection.close
          end

        end
      end
    rescue Errno::ECONNRESET, Errno::EPIPE => e
      @logger.warn('REPL') { 'connection failure while getting server port; will retry...' }
      @logger.warn('REPL') { e.full_message(highlight: @highlight_exception, order: :top) }
      retry

    end
  end
end

Instance Method Details

#bind=(bind) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/musa-dsl/repl/repl.rb', line 88

def bind=(bind)
  raise 'Already binded' if @bind

  @bind = bind

  return unless @bind

  if @bind.receiver.respond_to?(:sequencer) &&
     @bind.receiver.sequencer.respond_to?(:on_error)

    @bind.receiver.sequencer.on_error do |e|
      send_exception e, output: @connection
    end
  end
end

#puts(*messages) ⇒ Object



116
117
118
119
120
121
122
123
124
# File 'lib/musa-dsl/repl/repl.rb', line 116

def puts(*messages)
  if @connection
    messages.each do |message|
      send output: @connection, content: message&.to_s
    end
  else
    @logger.warn('REPL') { "trying to print a message in Atom client but the client is not connected. Ignoring message \'#{message} \'." }
  end
end

#stopObject



104
105
106
107
108
109
110
111
112
113
114
# File 'lib/musa-dsl/repl/repl.rb', line 104

def stop
  @run = false

  @main_thread.terminate
  Thread.pass

  @main_thread = nil

  @client_threads.each { |t| t.terminate; Thread.pass }
  @client_threads.clear
end