Merge branch 'master' of git://factorcode.org/git/factor
commit
0091558ff6
|
@ -13,9 +13,8 @@ SYMBOL: local-node
|
||||||
[ first2 get-process send ] [ stop-this-server ] if* ;
|
[ first2 get-process send ] [ stop-this-server ] if* ;
|
||||||
|
|
||||||
: <node-server> ( addrspec -- threaded-server )
|
: <node-server> ( addrspec -- threaded-server )
|
||||||
<threaded-server>
|
binary <threaded-server>
|
||||||
swap >>insecure
|
swap >>insecure
|
||||||
binary >>encoding
|
|
||||||
"concurrency.distributed" >>name
|
"concurrency.distributed" >>name
|
||||||
[ handle-node-client ] >>handler ;
|
[ handle-node-client ] >>handler ;
|
||||||
|
|
||||||
|
|
|
@ -341,12 +341,11 @@ M: ftp-server handle-client* ( server -- )
|
||||||
] with-destructors ;
|
] with-destructors ;
|
||||||
|
|
||||||
: <ftp-server> ( directory port -- server )
|
: <ftp-server> ( directory port -- server )
|
||||||
ftp-server new-threaded-server
|
latin1 ftp-server new-threaded-server
|
||||||
swap >>insecure
|
swap >>insecure
|
||||||
swap canonicalize-path >>serving-directory
|
swap canonicalize-path >>serving-directory
|
||||||
"ftp.server" >>name
|
"ftp.server" >>name
|
||||||
5 minutes >>timeout
|
5 minutes >>timeout ;
|
||||||
latin1 >>encoding ;
|
|
||||||
|
|
||||||
: ftpd ( directory port -- )
|
: ftpd ( directory port -- )
|
||||||
<ftp-server> start-server ;
|
<ftp-server> start-server ;
|
||||||
|
|
|
@ -269,7 +269,7 @@ M: http-server handle-client*
|
||||||
] with-destructors ;
|
] with-destructors ;
|
||||||
|
|
||||||
: <http-server> ( -- server )
|
: <http-server> ( -- server )
|
||||||
http-server new-threaded-server
|
ascii http-server new-threaded-server
|
||||||
"http.server" >>name
|
"http.server" >>name
|
||||||
"http" protocol-port >>insecure
|
"http" protocol-port >>insecure
|
||||||
"https" protocol-port >>secure ;
|
"https" protocol-port >>secure ;
|
||||||
|
|
|
@ -79,12 +79,12 @@ HELP: threaded-server
|
||||||
{ $class-description "The class of threaded servers. New instances are created with " { $link <threaded-server> } ". This class may be subclassed, and instances of subclasses should be created with " { $link new-threaded-server } ". See " { $link "server-config" } " for slot documentation." } ;
|
{ $class-description "The class of threaded servers. New instances are created with " { $link <threaded-server> } ". This class may be subclassed, and instances of subclasses should be created with " { $link new-threaded-server } ". See " { $link "server-config" } " for slot documentation." } ;
|
||||||
|
|
||||||
HELP: new-threaded-server
|
HELP: new-threaded-server
|
||||||
{ $values { "class" class } { "threaded-server" threaded-server } }
|
{ $values { "encoding" "an encoding descriptor" } { "class" class } { "threaded-server" threaded-server } }
|
||||||
{ $description "Creates a new instance of a subclass of " { $link threaded-server } ". Subclasses can implement the " { $link handle-client* } " generic word." } ;
|
{ $description "Creates a new instance of a subclass of " { $link threaded-server } ". Subclasses can implement the " { $link handle-client* } " generic word." } ;
|
||||||
|
|
||||||
HELP: <threaded-server>
|
HELP: <threaded-server>
|
||||||
{ $values { "threaded-server" threaded-server } }
|
{ $values { "encoding" "an encoding descriptor" } { "threaded-server" threaded-server } }
|
||||||
{ $description "Creates a new threaded server. Its slots should be filled in as per " { $link "server-config" } ", before " { $link start-server } " is called to begin waiting for connections." } ;
|
{ $description "Creates a new threaded server with streams encoded " { $snippet "encoding" } ". Its slots should be filled in as per " { $link "server-config" } ", before " { $link start-server } " is called to begin waiting for connections." } ;
|
||||||
|
|
||||||
HELP: remote-address
|
HELP: remote-address
|
||||||
{ $var-description "Variable holding the address specifier of the current client connection. See " { $link "network-addressing" } "." } ;
|
{ $var-description "Variable holding the address specifier of the current client connection. See " { $link "network-addressing" } "." } ;
|
||||||
|
|
|
@ -3,10 +3,10 @@ USING: tools.test io.servers.connection io.sockets namespaces
|
||||||
io.servers.connection.private kernel accessors sequences
|
io.servers.connection.private kernel accessors sequences
|
||||||
concurrency.promises io.encodings.ascii io threads calendar ;
|
concurrency.promises io.encodings.ascii io threads calendar ;
|
||||||
|
|
||||||
[ t ] [ <threaded-server> listen-on empty? ] unit-test
|
[ t ] [ ascii <threaded-server> listen-on empty? ] unit-test
|
||||||
|
|
||||||
[ f ] [
|
[ f ] [
|
||||||
<threaded-server>
|
ascii <threaded-server>
|
||||||
25 internet-server >>insecure
|
25 internet-server >>insecure
|
||||||
listen-on
|
listen-on
|
||||||
empty?
|
empty?
|
||||||
|
@ -19,16 +19,16 @@ concurrency.promises io.encodings.ascii io threads calendar ;
|
||||||
and
|
and
|
||||||
] unit-test
|
] unit-test
|
||||||
|
|
||||||
[ ] [ <threaded-server> init-server drop ] unit-test
|
[ ] [ ascii <threaded-server> init-server drop ] unit-test
|
||||||
|
|
||||||
[ 10 ] [
|
[ 10 ] [
|
||||||
<threaded-server>
|
ascii <threaded-server>
|
||||||
10 >>max-connections
|
10 >>max-connections
|
||||||
init-server semaphore>> count>>
|
init-server semaphore>> count>>
|
||||||
] unit-test
|
] unit-test
|
||||||
|
|
||||||
[ ] [
|
[ ] [
|
||||||
<threaded-server>
|
ascii <threaded-server>
|
||||||
5 >>max-connections
|
5 >>max-connections
|
||||||
0 >>insecure
|
0 >>insecure
|
||||||
[ "Hello world." write stop-this-server ] >>handler
|
[ "Hello world." write stop-this-server ] >>handler
|
||||||
|
|
|
@ -27,18 +27,18 @@ ready ;
|
||||||
|
|
||||||
: internet-server ( port -- addrspec ) f swap <inet> ;
|
: internet-server ( port -- addrspec ) f swap <inet> ;
|
||||||
|
|
||||||
: new-threaded-server ( class -- threaded-server )
|
: new-threaded-server ( encoding class -- threaded-server )
|
||||||
new
|
new
|
||||||
|
swap >>encoding
|
||||||
"server" >>name
|
"server" >>name
|
||||||
DEBUG >>log-level
|
DEBUG >>log-level
|
||||||
ascii >>encoding
|
|
||||||
1 minutes >>timeout
|
1 minutes >>timeout
|
||||||
V{ } clone >>sockets
|
V{ } clone >>sockets
|
||||||
<secure-config> >>secure-config
|
<secure-config> >>secure-config
|
||||||
[ "No handler quotation" throw ] >>handler
|
[ "No handler quotation" throw ] >>handler
|
||||||
<flag> >>ready ; inline
|
<flag> >>ready ; inline
|
||||||
|
|
||||||
: <threaded-server> ( -- threaded-server )
|
: <threaded-server> ( encoding -- threaded-server )
|
||||||
threaded-server new-threaded-server ;
|
threaded-server new-threaded-server ;
|
||||||
|
|
||||||
GENERIC: handle-client* ( threaded-server -- )
|
GENERIC: handle-client* ( threaded-server -- )
|
||||||
|
|
|
@ -11,9 +11,8 @@ IN: fuel.remote
|
||||||
[ [ print-error-and-restarts ] error-hook set listener ] with-scope ;
|
[ [ print-error-and-restarts ] error-hook set listener ] with-scope ;
|
||||||
|
|
||||||
: server ( port -- server )
|
: server ( port -- server )
|
||||||
<threaded-server>
|
utf8 <threaded-server>
|
||||||
"tty-server" >>name
|
"tty-server" >>name
|
||||||
utf8 >>encoding
|
|
||||||
swap local-server >>insecure
|
swap local-server >>insecure
|
||||||
[ start-listener ] >>handler
|
[ start-listener ] >>handler
|
||||||
f >>timeout ;
|
f >>timeout ;
|
||||||
|
|
|
@ -4,7 +4,8 @@ USING: accessors assocs combinators combinators.smart
|
||||||
destructors fry io io.encodings.utf8 kernel managed-server
|
destructors fry io io.encodings.utf8 kernel managed-server
|
||||||
namespaces parser sequences sorting splitting strings.parser
|
namespaces parser sequences sorting splitting strings.parser
|
||||||
unicode.case unicode.categories calendar calendar.format
|
unicode.case unicode.categories calendar calendar.format
|
||||||
locals multiline ;
|
locals multiline io.encodings.binary io.encodings.string
|
||||||
|
prettyprint ;
|
||||||
IN: managed-server.chat
|
IN: managed-server.chat
|
||||||
|
|
||||||
TUPLE: chat-server < managed-server ;
|
TUPLE: chat-server < managed-server ;
|
||||||
|
@ -35,6 +36,31 @@ CONSTANT: line-beginning "-!- "
|
||||||
[ "Unknown command: " prepend print flush ] if
|
[ "Unknown command: " prepend print flush ] if
|
||||||
] if-empty ;
|
] if-empty ;
|
||||||
|
|
||||||
|
: usage ( string -- )
|
||||||
|
chat-docs get at print flush ;
|
||||||
|
|
||||||
|
: username-taken-string ( username -- string )
|
||||||
|
"The username ``" "'' is already in use; try again." surround ;
|
||||||
|
|
||||||
|
: warn-name-changed ( old new -- )
|
||||||
|
[
|
||||||
|
[ line-beginning "``" ] 2dip
|
||||||
|
[ "'' is now known as ``" ] dip "''"
|
||||||
|
] "" append-outputs-as send-everyone ;
|
||||||
|
|
||||||
|
: handle-nick ( string -- )
|
||||||
|
[
|
||||||
|
"nick" usage
|
||||||
|
] [
|
||||||
|
dup clients key? [
|
||||||
|
username-taken-string print flush
|
||||||
|
] [
|
||||||
|
[ username swap warn-name-changed ]
|
||||||
|
[ username clients rename-at ]
|
||||||
|
[ client (>>username) ] tri
|
||||||
|
] if
|
||||||
|
] if-empty ;
|
||||||
|
|
||||||
:: add-command ( quot docs key -- )
|
:: add-command ( quot docs key -- )
|
||||||
quot key commands get set-at
|
quot key commands get set-at
|
||||||
docs key chat-docs get set-at ;
|
docs key chat-docs get set-at ;
|
||||||
|
@ -44,7 +70,7 @@ CONSTANT: line-beginning "-!- "
|
||||||
Displays the documentation for a command.">
|
Displays the documentation for a command.">
|
||||||
"help" add-command
|
"help" add-command
|
||||||
|
|
||||||
[ drop clients keys ", " join print flush ]
|
[ drop clients keys [ "``" "''" surround ] map ", " join print flush ]
|
||||||
<" Syntax: /who
|
<" Syntax: /who
|
||||||
Shows the list of connected users.">
|
Shows the list of connected users.">
|
||||||
"who" add-command
|
"who" add-command
|
||||||
|
@ -53,6 +79,11 @@ Shows the list of connected users.">
|
||||||
<" Syntax: /time
|
<" Syntax: /time
|
||||||
Returns the current GMT time."> "time" add-command
|
Returns the current GMT time."> "time" add-command
|
||||||
|
|
||||||
|
[ handle-nick ]
|
||||||
|
<" Syntax: /nick nickname
|
||||||
|
Changes your nickname.">
|
||||||
|
"nick" add-command
|
||||||
|
|
||||||
[ handle-me ]
|
[ handle-me ]
|
||||||
<" Syntax: /me action">
|
<" Syntax: /me action">
|
||||||
"me" add-command
|
"me" add-command
|
||||||
|
@ -69,8 +100,7 @@ Disconnects a user from the chat server."> "quit" add-command
|
||||||
] if ;
|
] if ;
|
||||||
|
|
||||||
: <chat-server> ( port -- managed-server )
|
: <chat-server> ( port -- managed-server )
|
||||||
"chat-server" chat-server new-managed-server
|
"chat-server" utf8 chat-server new-managed-server ;
|
||||||
utf8 >>encoding ;
|
|
||||||
|
|
||||||
: handle-chat ( string -- )
|
: handle-chat ( string -- )
|
||||||
[
|
[
|
||||||
|
@ -93,8 +123,7 @@ M: chat-server handle-client-disconnect
|
||||||
] "" append-outputs-as send-everyone ;
|
] "" append-outputs-as send-everyone ;
|
||||||
|
|
||||||
M: chat-server handle-already-logged-in
|
M: chat-server handle-already-logged-in
|
||||||
"The username ``" username "'' is already in use; try again."
|
username username-taken-string print flush ;
|
||||||
3append print flush ;
|
|
||||||
|
|
||||||
M: chat-server handle-managed-client*
|
M: chat-server handle-managed-client*
|
||||||
readln dup f = [ t client (>>quit?) ] when
|
readln dup f = [ t client (>>quit?) ] when
|
||||||
|
|
|
@ -14,28 +14,43 @@ input-stream output-stream local-address remote-address
|
||||||
username object quit? ;
|
username object quit? ;
|
||||||
|
|
||||||
HOOK: handle-login threaded-server ( -- username )
|
HOOK: handle-login threaded-server ( -- username )
|
||||||
|
HOOK: handle-managed-client* managed-server ( -- )
|
||||||
HOOK: handle-already-logged-in managed-server ( -- )
|
HOOK: handle-already-logged-in managed-server ( -- )
|
||||||
HOOK: handle-client-join managed-server ( -- )
|
HOOK: handle-client-join managed-server ( -- )
|
||||||
HOOK: handle-client-disconnect managed-server ( -- )
|
HOOK: handle-client-disconnect managed-server ( -- )
|
||||||
HOOK: handle-managed-client* managed-server ( -- )
|
|
||||||
|
|
||||||
M: managed-server handle-already-logged-in ;
|
ERROR: already-logged-in username ;
|
||||||
|
|
||||||
|
M: managed-server handle-already-logged-in already-logged-in ;
|
||||||
M: managed-server handle-client-join ;
|
M: managed-server handle-client-join ;
|
||||||
M: managed-server handle-client-disconnect ;
|
M: managed-server handle-client-disconnect ;
|
||||||
M: managed-server handle-managed-client* ;
|
|
||||||
|
|
||||||
: server ( -- managed-client ) managed-server get ;
|
: server ( -- managed-client ) managed-server get ;
|
||||||
: client ( -- managed-client ) managed-client get ;
|
: client ( -- managed-client ) managed-client get ;
|
||||||
: clients ( -- assoc ) server clients>> ;
|
: clients ( -- assoc ) server clients>> ;
|
||||||
: client-streams ( -- assoc ) clients values ;
|
: client-streams ( -- assoc ) clients values ;
|
||||||
: username ( -- string ) client username>> ;
|
: username ( -- string ) client username>> ;
|
||||||
|
: everyone-else ( -- assoc )
|
||||||
|
clients [ drop username = not ] assoc-filter ;
|
||||||
|
: everyone-else-streams ( -- assoc ) everyone-else values ;
|
||||||
|
|
||||||
|
ERROR: no-such-client username ;
|
||||||
|
|
||||||
|
<PRIVATE
|
||||||
|
|
||||||
|
: (send-client) ( managed-client seq -- )
|
||||||
|
[ output-stream>> ] dip '[ _ print flush ] with-output-stream* ;
|
||||||
|
|
||||||
|
PRIVATE>
|
||||||
|
|
||||||
|
: send-client ( seq username -- )
|
||||||
|
clients ?at [ no-such-client ] [ (send-client) ] if ;
|
||||||
|
|
||||||
: send-everyone ( seq -- )
|
: send-everyone ( seq -- )
|
||||||
[ client-streams ] dip '[
|
[ client-streams ] dip '[ _ (send-client) ] each ;
|
||||||
output-stream>> [ _ print flush ] with-output-stream*
|
|
||||||
] each ;
|
|
||||||
|
|
||||||
ERROR: already-logged-in username ;
|
: send-everyone-else ( seq -- )
|
||||||
|
[ everyone-else-streams ] dip '[ _ (send-client) ] each ;
|
||||||
|
|
||||||
<PRIVATE
|
<PRIVATE
|
||||||
|
|
||||||
|
@ -48,10 +63,7 @@ ERROR: already-logged-in username ;
|
||||||
remote-address get >>remote-address ;
|
remote-address get >>remote-address ;
|
||||||
|
|
||||||
: check-logged-in ( username -- username )
|
: check-logged-in ( username -- username )
|
||||||
dup server clients>> key? [
|
dup clients key? [ handle-already-logged-in ] when ;
|
||||||
[ server ] dip
|
|
||||||
[ handle-already-logged-in ] [ already-logged-in ] bi
|
|
||||||
] when ;
|
|
||||||
|
|
||||||
: add-managed-client ( -- )
|
: add-managed-client ( -- )
|
||||||
client username check-logged-in clients set-at ;
|
client username check-logged-in clients set-at ;
|
||||||
|
@ -60,19 +72,19 @@ ERROR: already-logged-in username ;
|
||||||
username server clients>> delete-at ;
|
username server clients>> delete-at ;
|
||||||
|
|
||||||
: handle-managed-client ( -- )
|
: handle-managed-client ( -- )
|
||||||
[ [ handle-managed-client* client quit?>> not ] loop ]
|
handle-login <managed-client> managed-client set
|
||||||
[ delete-managed-client handle-client-disconnect ]
|
add-managed-client handle-client-join
|
||||||
[ ] cleanup ;
|
[ handle-managed-client* client quit?>> not ] loop ;
|
||||||
|
|
||||||
PRIVATE>
|
PRIVATE>
|
||||||
|
|
||||||
M: managed-server handle-client*
|
M: managed-server handle-client*
|
||||||
managed-server set
|
managed-server set
|
||||||
handle-login <managed-client> managed-client set
|
[ handle-managed-client ]
|
||||||
add-managed-client
|
[ delete-managed-client handle-client-disconnect ]
|
||||||
handle-client-join handle-managed-client ;
|
[ ] cleanup ;
|
||||||
|
|
||||||
: new-managed-server ( port name class -- server )
|
: new-managed-server ( port name encoding class -- server )
|
||||||
new-threaded-server
|
new-threaded-server
|
||||||
swap >>name
|
swap >>name
|
||||||
swap >>insecure
|
swap >>insecure
|
||||||
|
|
|
@ -89,9 +89,8 @@ M: mdb-msg dump-message ( message -- )
|
||||||
|
|
||||||
: start-mmm-server ( -- )
|
: start-mmm-server ( -- )
|
||||||
output-stream get mmm-dump-output set
|
output-stream get mmm-dump-output set
|
||||||
<threaded-server> [ mmm-t-srv set ] keep
|
binary <threaded-server> [ mmm-t-srv set ] keep
|
||||||
"127.0.0.1" mmm-port get <inet4> >>insecure
|
"127.0.0.1" mmm-port get <inet4> >>insecure
|
||||||
binary >>encoding
|
|
||||||
[ handle-mmm-connection ] >>handler
|
[ handle-mmm-connection ] >>handler
|
||||||
start-server* ;
|
start-server* ;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
! Copyright (C) 2008 Slava Pestov.
|
! Copyright (C) 2008 Slava Pestov.
|
||||||
! See http://factorcode.org/license.txt for BSD license.
|
! See http://factorcode.org/license.txt for BSD license.
|
||||||
USING: io io.servers.connection accessors threads
|
USING: accessors calendar calendar.format io io.encodings.ascii
|
||||||
calendar calendar.format ;
|
io.servers.connection threads ;
|
||||||
IN: time-server
|
IN: time-server
|
||||||
|
|
||||||
: handle-time-client ( -- )
|
: handle-time-client ( -- )
|
||||||
now timestamp>rfc822 print ;
|
now timestamp>rfc822 print ;
|
||||||
|
|
||||||
: <time-server> ( -- threaded-server )
|
: <time-server> ( -- threaded-server )
|
||||||
<threaded-server>
|
ascii <threaded-server>
|
||||||
"time-server" >>name
|
"time-server" >>name
|
||||||
1234 >>insecure
|
1234 >>insecure
|
||||||
[ handle-time-client ] >>handler ;
|
[ handle-time-client ] >>handler ;
|
||||||
|
|
|
@ -3,9 +3,8 @@ accessors kernel ;
|
||||||
IN: tty-server
|
IN: tty-server
|
||||||
|
|
||||||
: <tty-server> ( port -- )
|
: <tty-server> ( port -- )
|
||||||
<threaded-server>
|
utf8 <threaded-server>
|
||||||
"tty-server" >>name
|
"tty-server" >>name
|
||||||
utf8 >>encoding
|
|
||||||
swap local-server >>insecure
|
swap local-server >>insecure
|
||||||
[ listener ] >>handler
|
[ listener ] >>handler
|
||||||
start-server ;
|
start-server ;
|
||||||
|
|
|
@ -2,7 +2,6 @@ USING: accessors assocs continuations effects io
|
||||||
io.encodings.binary io.servers.connection kernel
|
io.encodings.binary io.servers.connection kernel
|
||||||
memoize namespaces parser sets sequences serialize
|
memoize namespaces parser sets sequences serialize
|
||||||
threads vocabs vocabs.parser words ;
|
threads vocabs vocabs.parser words ;
|
||||||
|
|
||||||
IN: modules.rpc-server
|
IN: modules.rpc-server
|
||||||
|
|
||||||
SYMBOL: serving-vocabs V{ } clone serving-vocabs set-global
|
SYMBOL: serving-vocabs V{ } clone serving-vocabs set-global
|
||||||
|
@ -12,17 +11,24 @@ SYMBOL: serving-vocabs V{ } clone serving-vocabs set-global
|
||||||
|
|
||||||
MEMO: mem-do-rpc ( args word -- bytes ) do-rpc ; inline
|
MEMO: mem-do-rpc ( args word -- bytes ) do-rpc ; inline
|
||||||
|
|
||||||
: process ( vocabspec -- ) vocab-words [ deserialize ] dip deserialize
|
: process ( vocabspec -- )
|
||||||
|
vocab-words [ deserialize ] dip deserialize
|
||||||
swap at "executer" get execute( args word -- bytes ) write flush ;
|
swap at "executer" get execute( args word -- bytes ) write flush ;
|
||||||
|
|
||||||
: (serve) ( -- ) deserialize dup serving-vocabs get-global index
|
: (serve) ( -- )
|
||||||
|
deserialize dup serving-vocabs get-global index
|
||||||
[ process ] [ drop ] if ;
|
[ process ] [ drop ] if ;
|
||||||
|
|
||||||
: start-serving-vocabs ( -- ) [
|
: start-serving-vocabs ( -- )
|
||||||
<threaded-server> 5000 >>insecure binary >>encoding [ (serve) ] >>handler
|
[
|
||||||
start-server ] in-thread ;
|
binary <threaded-server>
|
||||||
|
5000 >>insecure
|
||||||
|
[ (serve) ] >>handler
|
||||||
|
start-server
|
||||||
|
] in-thread ;
|
||||||
|
|
||||||
: (service) ( -- ) serving-vocabs get-global empty? [ start-serving-vocabs ] when
|
: (service) ( -- )
|
||||||
|
serving-vocabs get-global empty? [ start-serving-vocabs ] when
|
||||||
current-vocab serving-vocabs get-global adjoin
|
current-vocab serving-vocabs get-global adjoin
|
||||||
"get-words" create-in
|
"get-words" create-in
|
||||||
in get [ vocab vocab-words [ stack-effect ] { } assoc-map-as ] curry
|
in get [ vocab vocab-words [ stack-effect ] { } assoc-map-as ] curry
|
||||||
|
@ -32,6 +38,8 @@ SYNTAX: service \ do-rpc "executer" set (service) ;
|
||||||
SYNTAX: mem-service \ mem-do-rpc "executer" set (service) ;
|
SYNTAX: mem-service \ mem-do-rpc "executer" set (service) ;
|
||||||
|
|
||||||
load-vocab-hook [
|
load-vocab-hook [
|
||||||
[ dup words>> values
|
[
|
||||||
\ mem-do-rpc "memoize" word-prop [ delete-at ] curry each ]
|
dup words>> values
|
||||||
append ] change-global
|
\ mem-do-rpc "memoize" word-prop [ delete-at ] curry each
|
||||||
|
] append
|
||||||
|
] change-global
|
||||||
|
|
Loading…
Reference in New Issue