215 lines
7.4 KiB
Tal
215 lines
7.4 KiB
Tal
|
( zenochat.tal )
|
||
|
( )
|
||
|
( uses uxnet protocol, see uxnet.txt for details )
|
||
|
( )
|
||
|
( the server assumes its assigned idxs are exactly 3 bytes long. )
|
||
|
( this is an implementation detail of uxnet.py not currently )
|
||
|
( required by the spec. )
|
||
|
( )
|
||
|
( clients send messages to the server using the > command. )
|
||
|
( each type of messages is identified by its first ASCII )
|
||
|
( character, which is otherwise ignored: )
|
||
|
( )
|
||
|
( CHAR MEANING )
|
||
|
( N sets username to $text )
|
||
|
( B broadcasts a chat message of $text )
|
||
|
( C notifies that user $text has connected )
|
||
|
( D notifies that user $text has disconnected )
|
||
|
( R expects $prev/$curr, user $prev is renamed to $curr )
|
||
|
( )
|
||
|
( nicknames are not allowed to have slashes, i.e. /, and must )
|
||
|
( be 16 bytes or fewer long. messages are not allowed to contain )
|
||
|
( the NULL byte, and are not expected to contain newlines. )
|
||
|
|
||
|
|10 @Console [ &vect $2 &r $1 &pad $4 &type $1 &w $1 &e $1 ]
|
||
|
|
||
|
|0000
|
||
|
@interpret $2
|
||
|
@pos $2
|
||
|
@next-client $2
|
||
|
|
||
|
|0100
|
||
|
;before-start .interpret STZ2
|
||
|
;addr .pos STZ2
|
||
|
;clients .next-client STZ2
|
||
|
;read-args .Console/vect DEO2
|
||
|
BRK
|
||
|
|
||
|
( we don't expect anything )
|
||
|
@before-start ( -> )
|
||
|
#0000 DIV JMP2r
|
||
|
|
||
|
( we expect to read $/ )
|
||
|
@when-starting ( -> )
|
||
|
;input LDAk LIT "$ NEQ ?&error
|
||
|
INC2 LDAk LIT "/ NEQ ?&error
|
||
|
POP2
|
||
|
( TODO: validate input )
|
||
|
;when-listening .interpret STZ2 JMP2r
|
||
|
&error #010f DEO BRK
|
||
|
|
||
|
( we expect to read $ # < )
|
||
|
@when-listening ( -> )
|
||
|
;input LDAk LIT "$ EQU ?connect
|
||
|
LDAk LIT "# EQU ?disconnect
|
||
|
LDAk LIT "< EQU ?receive
|
||
|
POP2 #0000 DIV
|
||
|
|
||
|
@connect ( input* -> )
|
||
|
( TODO: validate input )
|
||
|
INC2 !add-client
|
||
|
|
||
|
@disconnect ( input* -> )
|
||
|
( TODO: validate input )
|
||
|
INC2 !rm-client
|
||
|
|
||
|
( expect: <999/N...\0 )
|
||
|
( or: <999/B...\0 )
|
||
|
@receive ( input* -> )
|
||
|
( TODO: validate input )
|
||
|
INC2 DUP2 find-client ( msg=input+1* maybe* )
|
||
|
ORAk ?&found POP2 emit JMP2r ( )
|
||
|
&found ( msg* client* )
|
||
|
SWP2 #0004 ADD2 ( client* body=msg+4 )
|
||
|
LDAk LIT "N EQU ?rename ( client* body* )
|
||
|
LDAk LIT "B EQU ?broadcast ( client* body* )
|
||
|
POP2 POP2 JMP2r ( )
|
||
|
|
||
|
@broadcast ( client* body* -> )
|
||
|
INC2 SWP2 #0003 ADD2 ( data=body+1* name=client+3* )
|
||
|
start-output copy-dst0 ( data* dst+n* ; copy name into dst+5 )
|
||
|
#3a20 OVR2 STA2 ( data* dst+n-1* ; dst+n-1<-": " )
|
||
|
INC2 INC2 copy ( ; copy data into dst+n+1 )
|
||
|
!send-to-all ( )
|
||
|
|
||
|
( sends ;output to all clients )
|
||
|
( fn should have shape: client* -> )
|
||
|
@send-to-all ( -> )
|
||
|
.next-client LDZ2 ;clients ( limit* start* )
|
||
|
&loop GTH2k ?&ok POP2 POP2 JMP2r ( )
|
||
|
&ok DUP2 send-to #0014 ADD2 !&loop ( limit* pos+20* )
|
||
|
POP2 POP2 JMP2r ( )
|
||
|
|
||
|
( sends ;output to a client )
|
||
|
@send-to ( client* -> )
|
||
|
;output STH2k INC2 ( client* output+1* [output*] )
|
||
|
idx-copy ( [output*] ; copy idx into output+1 )
|
||
|
STH2r emit ( ; emit output )
|
||
|
( #0a18 DEO JMP2r ( need newline for interactive testing ) )
|
||
|
#0018 DEO JMP2r
|
||
|
|
||
|
@rename-msg 20 "is 20 "now 20 "called 20 00
|
||
|
|
||
|
@start-output ( -> dst* )
|
||
|
LIT2r :output
|
||
|
LIT "> STH2kr STA INC2r
|
||
|
LIT2 "00 STH2kr STA2 INC2r INC2r
|
||
|
LIT2 "0/ STH2kr STA2 INC2r INC2r
|
||
|
STH2r JMP2r
|
||
|
|
||
|
@rename ( client* body* -> )
|
||
|
OVR2 #0003 ADD2 ( client* body* name=client+3* )
|
||
|
start-output copy-dst0 ( client* body* dst* ; copy old name to output )
|
||
|
;rename-msg SWP2 copy-dst0 ( client* body* dst2* )
|
||
|
STH2 INC2 STH2k #00 STH2kr ( client* data=body+1* 0^ data* [dst2* data*] )
|
||
|
#0010 ADD2 STA ( client* data* ; data+16<-0 [dst2* data*] )
|
||
|
SWP2 #0003 ADD2 copy ( [dst2* data*] ; copy data into client+3 )
|
||
|
STH2r STH2r ( data* dst2* )
|
||
|
copy !send-to-all ( ; copy data into dst2 and send )
|
||
|
|
||
|
( we expect to read addr then port )
|
||
|
@read-args ( -> BRK )
|
||
|
.Console/type DEI #04 EQU ?start-server
|
||
|
.Console/type DEI #03 EQU ?&end-addr
|
||
|
.Console/r DEI .pos LDZ2 STA ( ; write c into buf )
|
||
|
.pos LDZ2k INC2 ROT STZ2 BRK ( BRK ; increment buf pos )
|
||
|
&end-addr ;port .pos STZ2 BRK ( BRK ; start raeding port port )
|
||
|
|
||
|
@read-stdin
|
||
|
( .Console/r DEI #0a NEQ ?&continue ( ; did we read null? ) )
|
||
|
.Console/r DEI ?&continue ( ; did we read null? )
|
||
|
#00 .pos LDZ2 STA ( ; write null )
|
||
|
.interpret LDZ2 JSR2 ( ; interpret message )
|
||
|
;input .pos STZ2 BRK ( ; reset input buffer )
|
||
|
&continue ( )
|
||
|
.Console/r DEI .pos LDZ2 STA ( ; write c into input buffer )
|
||
|
.pos LDZ2k INC2 ROT STZ2 BRK ( ; increment input buffer )
|
||
|
|
||
|
@start-server ( -> BRK )
|
||
|
LIT "@ .Console/w DEO ;addr emit
|
||
|
LIT "/ .Console/w DEO ;port emit
|
||
|
( #0a .Console/w DEO )
|
||
|
#00 .Console/w DEO
|
||
|
;input .pos STZ2
|
||
|
;when-starting .interpret STZ2
|
||
|
;read-stdin .Console/vect DEO2
|
||
|
BRK
|
||
|
|
||
|
( assumes idxs are 3 bytes long )
|
||
|
@idx-eq ( idx1* idx2* -> bool^ )
|
||
|
STH2 LDAk LDAkr STHr NEQ ?&nope
|
||
|
INC2 INC2r LDAk LDAkr STHr NEQ ?&nope
|
||
|
INC2 INC2r LDA LDAr STHr EQU JMP2r
|
||
|
&nope #00 JMP2r
|
||
|
|
||
|
( assumes idxs are 3 bytes long )
|
||
|
@idx-copy ( idx* dst* -> )
|
||
|
STH2 LDAk STH2kr STA INC2 INC2r ( idx+1* [dst+1*] ; dst<-idx )
|
||
|
LDAk STH2kr STA INC2 INC2r ( idx+2* [dst+2*] ; dst+1<-idx+1 )
|
||
|
LDA STH2r STA JMP2r ( ; dst+2<-idx+2 )
|
||
|
|
||
|
@connect-msg 20 "has 20 "connected 00
|
||
|
|
||
|
@add-client ( idx* -> )
|
||
|
LITr -next-client LDZ2r ( idx* [next*] )
|
||
|
DUP2 STH2kr copy-slash ( idx* [next*] ; copy idx to client-idx )
|
||
|
STH2kr #0003 ADD2 copy-slash ( [next*] ; copy idx to client-name )
|
||
|
#00 STH2kr #0006 ADD2 STA ( [next*] ; null terminate )
|
||
|
STH2kr #0003 ADD2 ( next+3* [next*] )
|
||
|
start-output copy-dst0 ( dst* [next*] ; start message )
|
||
|
;connect-msg SWP2 copy ( [next*] ; finish message )
|
||
|
STH2r #0014 ADD2 .next-client STZ2 ( ; next-client<-next+20 )
|
||
|
!send-to-all ( )
|
||
|
|
||
|
@disconnect-msg 20 "has 20 "disconnected 00
|
||
|
|
||
|
@rm-client ( idx* -> )
|
||
|
find-client ORAk ?&found ( addr* )
|
||
|
POP2 JMP2r ( )
|
||
|
&found ( addr* )
|
||
|
DUP2 #0003 ADD2 start-output ( addr* addr+3* dst* )
|
||
|
copy-dst0 ( addr* dst2* )
|
||
|
;disconnect-msg SWP2 copy ( addr* )
|
||
|
.next-client LDZ2 #0014 SUB2 SWP2 ( limit* addr* )
|
||
|
&loop GTH2k ?&ok !&done ( limit* pos* )
|
||
|
&ok DUP2 #0014 ADD2 LDA2 ( limit* pos* cc* )
|
||
|
OVR2 STA2 INC2 INC2 !&loop ( limit* pos+2* )
|
||
|
&done .next-client STZ2 POP2 ( )
|
||
|
!send-to-all ( )
|
||
|
|
||
|
( returns client addr or 0000 on failure )
|
||
|
( finds client based on 3 byte idx )
|
||
|
@find-client ( idx* -> addr* )
|
||
|
STH2 .next-client LDZ2 ;clients ( limit* clients* [idx*] )
|
||
|
&loop ( limit* c* [idx*] )
|
||
|
DUP2 STH2kr idx-eq ?&found ( limit* c* [idx*] )
|
||
|
#0014 ADD2 GTH2k ?&loop ( limit* c+20* [idx*] )
|
||
|
POP2r POP2 DUP2 EOR2 JMP2r ( 0* )
|
||
|
&found NIP2 POP2r JMP2r ( c* )
|
||
|
|
||
|
~zenoutil.tal
|
||
|
|
||
|
@addr $100
|
||
|
@port $6
|
||
|
@input $1000
|
||
|
@output $1000
|
||
|
|
||
|
( client layout: )
|
||
|
( - bytes 1-3: idx )
|
||
|
( - bytes 4-20: nickname\0 )
|
||
|
( every idx is 3 bytes )
|
||
|
( max username is 16 chars )
|
||
|
( username is null-terminated; idx is not )
|
||
|
( max # of clients is 64 )
|
||
|
@clients $500 &limit
|