debugger: Added Websocket compatible implementation of the debugger.
[nit.git] / src / websocket_debugger.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Lucas Bajolet <r4pass@hotmail.com>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Network debugger for a nit program, based on the original debugger
18 # Replaces access to stdin/stdout to send data on the network to the client concerned
19 module websocket_debugger
20
21 import websocket
22 intrude import debugger
23
24 redef class ToolContext
25
26 var opt_debug_port: OptionInt = new OptionInt("Sets the debug port (Defaults to 22125) - Must be contained between 0 and 65535", 22125, "--port")
27
28 redef init
29 do
30 super
31 self.option_context.add_option(self.opt_debug_port)
32 end
33
34 end
35
36 redef class ModelBuilder
37 fun run_debugger_network_mode(mainmodule: MModule, arguments: Array[String], port: Int)
38 do
39 var time0 = get_time
40 self.toolcontext.info("*** START INTERPRETING ***", 1)
41
42 var interpreter = new NetworkDebugger(self, mainmodule, arguments, port)
43
44 init_naive_interpreter(interpreter, mainmodule)
45
46 var time1 = get_time
47 self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2)
48
49 interpreter.ns.stop_server
50 end
51 end
52
53 # Extends the debugger by adding new network capabilities for remote debugging
54 class NetworkDebugger
55 super Debugger
56
57 # Represents the connexion with a particular client (Actually the only one accepted at the moment)
58 private var ns: WebSocket
59
60 # Initializes the debugger, waits for a client to connect
61 # Then starts debugging as usual
62 init(modelbuilder: ModelBuilder, mainmodule: MModule, arguments: Array[String], port: Int)
63 do
64 print "Listening on port {port}"
65
66 ns = new WebSocket(port, 1)
67
68 ns.accept
69
70 print "Client connected"
71
72 stdin.connection = ns
73
74 if stdout isa Stdout then
75 (stdout.as(Stdout)).connection = ns
76 else
77 ns.stop_server
78 abort
79 end
80
81 super
82 end
83
84 # Checks on every call if the client has sent a command before continuing debugging
85 redef fun stmt(n)
86 do
87 if stdin.poll_in then
88 var command = gets
89 if command == "stop" then
90 n.debug("Stopped by client")
91 while process_debug_command(gets) do end
92 else
93 process_debug_command(command)
94 end
95 end
96
97 super(n)
98 end
99
100 end
101
102 redef class ANode
103
104 # Breaks automatically when encountering an error
105 # Permits the injunction of commands before crashing
106 # Disconnects from the client before crashing
107 redef private fun fatal(v: NaiveInterpreter, message: String)
108 do
109 if v isa Debugger then
110 print "An error was encountered, the program will stop now."
111 self.debug(message)
112 while v.process_debug_command(gets) do end
113 end
114
115 if v isa NetworkDebugger then
116 stdin.connection.stop_server
117 end
118
119 super
120 end
121 end
122
123 # Replaces Stdin to read on the network
124 redef class Stdin
125
126 # Represents the connection with a client
127 var connection: nullable WebSocket = null
128
129 # Used to store data that has been read from the connection
130 var buf: Buffer = new FlatBuffer
131 var buf_pos: Int = 0
132
133 # Checks if data is available for reading
134 redef fun poll_in
135 do
136 return connection.can_read(0)
137 end
138
139 # Reads the whole content of the buffer
140 # Blocking if the buffer is empty
141 redef fun read_all
142 do
143 var loc_buf = new FlatBuffer
144 if connection.can_read(0) then buf.append(connection.read)
145 for i in [buf_pos .. buf.length-1] do loc_buf.add(buf.chars[i])
146 buf.clear
147 buf_pos = 0
148 return loc_buf.to_s
149 end
150
151 # Reads a single char on the incoming buffer
152 # If the buffer is empty, the call is blocking
153 redef fun read_char
154 do
155 if connection.can_read(0) then buf.append(connection.read)
156 if buf_pos >= buf.length then
157 buf.clear
158 buf_pos = 0
159 #Blocking call
160 buf.append(connection.read)
161 end
162 buf_pos += 1
163 return buf.chars[buf_pos-1].ascii
164 end
165
166 # Reads a line on the network if available
167 # Stops at the first encounter of a \n character
168 # If the buffer is empty, the read_line call is blocking
169 redef fun read_line
170 do
171 var line_buf = new FlatBuffer
172 if connection.can_read(0) then
173 buf.append(connection.read)
174 end
175 var has_found_eol: Bool = false
176 loop
177 if buf_pos >= buf.length then
178 buf.clear
179 buf_pos = 0
180 # Blocking call
181 buf.append(connection.read)
182 end
183 buf_pos += 1
184 if buf.chars[buf_pos-1] == '\n' then break
185 line_buf.add(buf.chars[buf_pos-1])
186 end
187 return line_buf.to_s
188 end
189 end
190
191 # Replaces Stdout to write on the network
192 redef class Stdout
193
194 # Connection with the client object
195 var connection: nullable WebSocket = null
196
197 # Writes a string on the network if available, else
198 # it is written in the standard output (Terminal)
199 redef fun write(s)
200 do
201 if connection != null then
202 connection.write(s)
203 else
204 super
205 end
206 end
207
208 end
209