NB. jserver class
NB. js=:conew'jserver' - creates new jserver
NB. destroy__js''      - destroy object and free resources
NB. local__js''        - connects to new local J engine
NB. remote__js'host user password
NB.                    - connects to new remote J engine
NB.                    - host can be name or IP address n.n.n.n
NB. profile__js''      - load profile
NB. run__js sentence   - start and finish sentence
NB. start__js sentence - start sentence
NB. finish__js''       - finish sentence
NB. process__js''      - process available messages - result 1 if finished
NB. break__js''        - request break
NB. in__js text        - input for 1!:1[1 or debug suspension

0 : 0 NB. input lines for a sample session
require'jserver'
js=.conew'jserver'
local__js''   NB. start another J session on this machine
run__js'i.5'
run__js'load' NB. value error as profile not loaded
profile__js'' NB. load profile in other J session
run__js'abc=:i.2 3 4'
xyz=:get__js'abc'   NB. set xyz in this session as value of abc in other
'abc' set__js +/i.5 NB. set abc in other session with value
run__js'6!:3[10'    NB. long running expression ends with timeout
finish__js''        NB. wait for expression to finish
start__js'6!:3[10'  NB. start long running expression
i.5                 NB. do other work
finish__js''        NB. wait for expression to finish
start__js'6!:3[100' NB. start long running expression
break__js''         NB. request break
finish__js''        NB. wait for finish
destroy__js''       NB. destroy object and free resource
)

require'socket'
coclass'jserver'
coextend 'jsocket'

'BUSY CMDDO CMDDOZ CMDIN CMDINZ CMDWD CMDWDZ CMDOUT CMDBRK CMDGET CMDGETZ CMDSETN CMDSET CMDSETZ CMDED CMDFL'=: 16{.a.
'WDZSTR WDZBOX WDZINT WDZMTM WDZERR'=: 0 1 2 4 1000
'OUTFR OUTERR OUTLOG OUTSYS OUTEXIT OUTFILE'=: 1+i.6

sk=:skj=:telnet=:INV=:'x'
RTO=: 6000 NB. read time out in millis
mtm=: i.0 0
null=:<0
error=: 3 : 'y. 13!:8[3'

create=: 3 : 'mtm'

destroy=: 3 : 0
if. sk~:INV do. sdclose sk end.
if. skj~:INV do. sdclose skj end.
if. telnet~:INV do. destroy__telnet'' end.
codestroy''
)

NB. connect to server on localhost
local=: 3 : 0
listen 0
startlocal''
accept''
)

NB. connect to remote server - y. 'remotehost user password'
remote=: 3 : 0
listen 1
startremote y.
accept''
)

NB. listen for connection from J server
NB. y. 0 binds to localhost for localhost server
NB. y. 1 binds to this machine's hostname for remote server
listen=: 3 : 0
sk=:>0{sdcheck sdsocket''
if. 0=y. do.
 host=:>2{sdgethostbyname 'localhost'
else.
 host=:>1{sdcheck sdgethostbyname ,>sdcheck sdgethostname''
end.
sdcheck sdbind sk;AF_INET;host;0
port=: >3{sdgetsockname sk
sdcheck sdlisten sk,0
)

accept=: 3 : 0
k=.>1{sdselect sk;'';'';10000 NB. wait 10 seconds for connection
if. -.k e. sk do. error'no response from J server task' return. end.
skj=:>{.sdcheck sdaccept sk
status=:'connected'
sdclose sk
jstate=:CMDDO
mtm
)

startlocal=: 3 : 0
p=.'"',(jpath '~binary\jconsole'),'" -jconnect ',":port
if. 5=9!:12'' do.
 2!:1 p,' &'
else.
 startupinfo=. 68,16#0
 DETACHED_PROCESS=.8
 t=.null;p;null;null;0;DETACHED_PROCESS;null;null;startupinfo;4#2
 'kernel32 CreateProcessA i *c *c * * i i * *c * *'(15!:0)t
end.
)

startremote=: 3 : 0
require'jtelnet'
telnet=:''conew'jtelnet'
logon__telnet y.
start__telnet 'jc -jconnect ',(":host),':',":port
)

profile=: 3 : 0
run'0!:0<PROFILE_z_=:1!:45'''''
)

NB. data send cmd;type
send=: 4 : 0
b=.(>{.y.),(0 0 0{a.),(ncfromi (>{:y.),#x.),x.
while. #b do. b=.(>sdcheck b sdsend skj,0)}.b end.
)

NB. send new command if ready
sendifrdy=: 4 : 0
if. jstate~:CMDDO do. error'not ready' end.
jstate=:BUSY
x. send y.
)

NB. process available messages - result 1 if ready
process=: 3 : 0
while. jstate=BUSY do.
 if. -.skj e.>1{sdselect skj;'';'';0 do. 0 return. end.
 doz''
end.
1
)

NB. process messages until finished - waits
finish=: 3 : 0
while. jstate=BUSY do. doz'' end.
if. jstate=CMDINZ do. error'input requested' end.
mtm
)

NB. read y. bytes from socket
readn=: 3 : 0
b=.''
while. y.>#b do.
 q=.,>sdcheck sdrecv skj,(y.-#b),0
 if.0=#q do.
  status=:'disconnected'
  error'socket disconnected'
 end.
 b=.b,q
end.
)

NB. read socket message and set mcmd etc globals - RTO timeout
read=: 3 : 0
if. -.skj e.>{.sdcheck sdselect skj;'';'';RTO do. error'read socket timeout' end.
b=.readn 12 NB. read 12 byte header
mcmd=:0{b
mtype=:ifromnc 4 5 6 7{b
mlen=:ifromnc 8 9 10 11{b
mdata=:readn mlen NB. read data
mtm
)

NB. process next message in a do/doz group
doz=: 3 : 0
read''
select. mcmd
 case. CMDDOZ do. jstate=:CMDDO
 case. CMDIN  do. jstate=:CMDINZ
 case. CMDOUT do.
  if. mtype=5 do. status=:'mtyoexit' end. NB. 2!:55 mtyoexit
  smoutput mdata
 case. CMDWD do. '' send CMDWDZ;1003 NB. return domain error
end.
)

ncfromi=: 3 : '(,(4#256)#:y.){a.' NB. net chars from ints
ifromnc=: 3 : '256#.a.i.y.'       NB. int from net chars

NB. start sentence - do not wait for finish
start=: 3 : 0
y. sendifrdy CMDDO;0
mtm
)

NB. start sentence and wait for finish
run=: 3 : 0
y. sendifrdy CMDDO;0
finish''
)

NB. get name
get=: 3 : 0
y. sendifrdy CMDGET;0
read''
if. mcmd~:CMDGETZ do. error'bad socket message - expected CMDGETZ' return. end.
jstate=:CMDDO
if. mtype do. 13!:8 mtype end.
3!:2 mdata
)

NB. 'name' set data
set=: 4 : 0
x. sendifrdy CMDSETN;0
(3!:1 y.) send CMDSET;0
read'' NB. waits for result message
if. mcmd~:CMDSETZ do. error'bad socket message - expected CMDSETZ' return. end.
jstate=:CMDDO
if. mtype do. 13!:8 mtype end.
mtm
)

NB. provide keyboard input to server
in=: 3 : 0
if. jstate~:CMDINZ do. 'wrong state' return. end.
jstate=:BUSY
y. send CMDINZ;0
mtm
)

NB. send break message to server
break=:3 : 'mtm[''''send CMDBRK;0'
