forked from JuliaLang/julia
/
julia_web_base.jl
297 lines (253 loc) · 9.63 KB
/
julia_web_base.jl
1
2
3
4
5
6
7
8
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
import Base.*
##########################################
# protocol
###########################################
###### the julia<-->server protocol #######
# the message type is sent as a byte
# the next byte indicates how many arguments there are
# each argument is four bytes indicating the size of the argument, then the data for that argument
###### the server<-->browser protocol #####
# messages are sent as arrays of arrays (json)
# the outer array is an "array of messages"
# each message is itself an array:
# [message_type::number, arg0::string, arg1::string, ...]
# import the message types
load("julia_message_types_h.jl")
#macro debug_only(x); x; end
macro debug_only(x); end
###########################################
# set up the socket connection
###########################################
# open a socket on any port
function connect_cb(server::AsyncStream,status::Int32)
@debug_only println(STDERR,"Julia: Client instance connected")
global __client
if(status == -1)
error("An error occured during the creation of the server")
end
client = TcpSocket()
__client = client
err = accept(server,client)
if err!=0
print("accept error: ", Base._uv_lasterror(Base.globalEventLoop()), "\n")
else
p=__PartialMessageBuffer()
client.readcb = (args...)->__socket_callback(client,p,args...)
Base.start_reading(client)
end
end
(port,sock) = Base.open_any_tcp_port(4444,connect_cb)
# print the socket number so the server knows what it is
println(STDOUT,int16(port))
@debug_only println(STDERR,"Julia Instance Started")
###########################################
# protocol implementation
###########################################
# a message
type __Message
msg_type::Uint8
args::Array{Any, 1}
__Message(msg_type::Uint8,args::Array{Any,1})=new(msg_type,args)
__Message() = new(255,cell(0))
end
type __PartialMessageBuffer
current::__Message
num_args::Uint8
curArg::ASCIIString
curArgLength::Int32
curArgHeaderByteNum::Uint8
curArgPos::Int32
__PartialMessageBuffer()=new(__Message(),255,"",0,0,1)
end
# send a message
function __write_message(client::TcpSocket,msg)
@debug_only __print_message(msg)
write(client, uint8(msg.msg_type))
write(client, uint8(length(msg.args)))
for arg=msg.args
write(client, uint32(length(arg)))
write(client, arg)
end
end
__write_message(msg) = __write_message(__client,msg)
# print a message (useful for debugging)
function __print_message(msg)
println(STDERR,"Writing message: ",msg.msg_type)
println(STDERR,"Number of arguments: ",length(msg.args))
show(STDERR,msg.args)
for arg=msg.args
println(STDERR,"Argument Length: ",length(arg))
end
end
###########################################
# standard web library
###########################################
# load the special functions available to the web repl
load("julia_web.jl")
###########################################
# input event handler
###########################################
# store the result of the previous input
ans = nothing
# callback for that event handler
function __socket_callback(client::TcpSocket,p::__PartialMessageBuffer,stream::TcpSocket)
@debug_only println(STDERR,"received")
arr = stream.buffer.data
@debug_only println(STDERR,"Callback: ",arr)
pos = 0
nread = stream.buffer.ptr-1
while(pos<nread)
pos+=1
@debug_only println(STDERR,"loop at pos ",pos," of ",length(arr)," with message type ",p.current.msg_type)
b=arr[pos]
if(p.current.msg_type == 255)
p.current.msg_type = b
if(b==255)
error("Message type must not exceed 254")
end
@debug_only println(STDERR,"Message type: ",b)
continue
elseif(p.num_args == 255)
if(b==255)
error("Number of arguments for a message must not exceed 254")
end
p.num_args = b
@debug_only println(STDERR,"Number of arguments: ",b)
continue
elseif(p.curArgHeaderByteNum<4)
p.curArgLength|=int32(b)<<8*p.curArgHeaderByteNum
p.curArgHeaderByteNum += 1
@debug_only println(STDERR,"received header: ",b," ",p.curArgHeaderByteNum)
if(p.curArgHeaderByteNum != 4 || p.curArgLength != 0)
continue
end
elseif(nread-pos<p.curArgLength-p.curArgPos)
append!(p.curArg.data,arr[pos:nread])
@debug_only begin
println(STDERR,"message body incomplete")
println(STDERR,nread)
println(STDERR,pos)
println(STDERR,p.curArgLength)
println(STDERR,p.curArgPos)
println(STDERR,p.current.msg_type)
println(STDERR,p.num_args)
end
p.curArgPos=nread-pos
break
else
append!(p.curArg.data,arr[pos:(pos+p.curArgLength-p.curArgPos)])
end
@debug_only println(STDERR,"argument of length ",p.curArgLength," at pos ",p.curArgPos," complete");
pos+=p.curArgLength-p.curArgPos;
push(p.current.args,p.curArg)
p.curArg=ASCIIString(Array(Uint8,0))
@debug_only begin
println(STDERR,"message body complete")
println(STDERR,p.num_args)
println(STDERR,p.current.args)
end
p.curArgLength=0
p.curArgHeaderByteNum=0
p.curArgPos=1
if(numel(p.current.args)>=p.num_args)
__msg=p.current
p.current=__Message()
p.num_args=255
# MSG_INPUT_EVAL
if __msg.msg_type == __MSG_INPUT_EVAL && length(__msg.args) == 3
@debug_only println(STDERR,"Evaluating input")
# parse the arguments
__user_name = __msg.args[1]
__user_id = __msg.args[2]
__input = __msg.args[3]
# split the input into lines
__lines = split(__input, '\n')
# try to parse each line incrementally
__parsed_exprs = {}
__input_so_far = ""
__all_nothing = true
for i=1:length(__lines)
# add the next line of input
__input_so_far = strcat(__input_so_far, __lines[i], "\n")
# try to parse it
__expr = parse_input_line(__input_so_far)
# if there was nothing to parse, just keep going
if __expr == nothing
continue
end
__all_nothing = false
__expr_multitoken = isa(__expr, Expr)
# stop now if there was a parsing error
if __expr_multitoken && __expr.head == :error
# send everyone the input
__write_message(client,__Message(__MSG_OUTPUT_EVAL_INPUT, {__user_id, __user_name, __input}))
__write_message(client,__Message(__MSG_OUTPUT_EVAL_ERROR, {__user_id, __expr.args[1]}))
stream.buffer.ptr = 1
return true
end
# if the expression was incomplete, just keep going
if __expr_multitoken && __expr.head == :continue
continue
end
# add the parsed expression to the list
__input_so_far = ""
__parsed_exprs = [__parsed_exprs, {(__user_id, __expr)}]
end
# if the input was empty, stop early
if __all_nothing
# send everyone the input
__write_message(client,__Message(__MSG_OUTPUT_EVAL_INPUT, {__user_id, __user_name, __input}))
__write_message(client,__Message(__MSG_OUTPUT_EVAL_RESULT, {__user_id, ""}))
stream.buffer.ptr = 1
return true
end
# tell the browser if we didn't get a complete expression
if length(__parsed_exprs) == 0
__write_message(client,__Message(__MSG_OUTPUT_EVAL_INCOMPLETE, {__user_id}))
stream.buffer.ptr = 1
return true
end
# send everyone the input
__write_message(client,__Message(__MSG_OUTPUT_EVAL_INPUT, {__user_id, __user_name, __input}))
__eval_exprs(client, __parsed_exprs)
end
end
end
stream.buffer.ptr=1
return true
end
function __eval_exprs(client,__parsed_exprs)
global ans
user_id = ""
# try to evaluate the expressions
for i=1:length(__parsed_exprs)
# evaluate the expression and stop if any exceptions happen
user_id = __parsed_exprs[i][1]
try
ans = eval(__parsed_exprs[i][2])
catch __error
return __write_message(client,__Message(__MSG_OUTPUT_EVAL_ERROR, {user_id,sprint(repl_show, __error)}))
end
end
# send the result of the last expression
if isa(ans,Nothing)
return __write_message(client,__Message(__MSG_OUTPUT_EVAL_RESULT, {user_id,""}))
else
return __write_message(client,__Message(__MSG_OUTPUT_EVAL_RESULT, {user_id,sprint(repl_show, ans)}))
end
end
# print version info
println("Julia ", Base._jl_version_string)
println(Base._jl_commit_string, "\n")
# work around bug displaying "\n "
#print(" ",replace(Base._jl_banner_plain, "\n", "\n "))
###########################################
# wait forever while asynchronous processing happens
###########################################
while true
try
Base.run_event_loop()
catch(err)
print(err)
end
end