-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsludge.clw
More file actions
1135 lines (967 loc) · 45.2 KB
/
sludge.clw
File metadata and controls
1135 lines (967 loc) · 45.2 KB
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
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
% -*- mode: CLWEB; package: SLUDGE; -*-
\font\sc=cmcsc10
\def\<#1>{\leavevmode\hbox{$\mkern-2mu\langle${\it #1\/}$\rangle$}}
\def\etc.{{\it \char`&c.\spacefactor1000}}
\def\eof{{\sc eof}}
\def\repl{{\sc repl}}
\def\sludge{{\sc sludge}}
\def\man#1(#2){the Unix manual page {\bf #1(#2)}}
@*\sludge. The Simple Lisp Usage and Documentation Gathering Engine allows
Emacs (or any other interested client) to request various pieces of
information from a running Common Lisp process: e.g., function argument
lists, documentation strings, possible symbol completions, \etc. A server
written in Common Lisp (this file) listens for connections from a client
written in Emacs Lisp ({\tt sludge.el}), then reads and responds to
requests. The system thus behaves essentially as a remote procedure call
mechanism, with Emacs calling down to a set of pre-defined procedures on
the Lisp side. The protocol that governs the communication is a simple
sexp-based stream of Unicode characters, encoded as a stream of octets,
and may utilize any of several suitable network transports.
The server currently runs only on multi-threaded builds of SBCL, but should
be simple enough to port to other implementations.
@l
(provide "SLUDGE")
@e
(eval-when (:compile-toplevel :load-toplevel :execute)
(require "SB-BSD-SOCKETS")
(require "SB-INTROSPECT")
(require "SB-POSIX"))
@e
(defpackage "SLUDGE"
(:use "COMMON-LISP" "SB-BSD-SOCKETS" "SB-THREAD")
(:import-from "SB-INTROSPECT" "FUNCTION-LAMBDA-LIST")
(:import-from "SB-POSIX" "MKTEMP" "UMASK")
(:export "START-SERVER" "STOP-SERVER" "SERVER-LOG"))
@e
(in-package "SLUDGE")
@t*Test suite. The test suite for this system uses Richard Waters's
{\sc rt} library. For more information on {\sc rt}, see Richard C.~Waters,
``Supporting the Regression Testing of Lisp Programs,''
{\it SIGPLAN Lisp Pointers}~4, no.~2 (1991): 47--53.
We use the sleazy trick of manually importing the external symbols of
the {\sc rt} package instead of the more sensible |(use-package "RT")|
because many compilers issue warnings when the use-list of a package
changes, which would occur if the |defpackage| form above were evaluated
after the tests have been loaded.
@l
(in-package "SLUDGE")
@e
(eval-when (:compile-toplevel :load-toplevel :execute)
(require 'sb-rt)
(do-external-symbols (symbol (find-package "SB-RT"))
(import symbol)))
@ We'll define our global variables and condition classes as we need them,
but we'd like them to appear near the top of the tangled output.
@l
@<Global variables@>
@<Condition classes@>
@1*Serving network clients. We'll start with the low-level guts of the
server. This implementation is specific to the |sb-bsd-sockets| module, but
should be easily portable to any standard {\sc bsd}-style socket {\sc api}.
@ What's a server without logging? We'll use a subclass of |warning| so that
they can be easily muffled.
@l
(defun server-log (format-control &rest args)
(warn 'server-log :format-control format-control :format-arguments args))
@ @<Condition classes@>=
(define-condition server-log (simple-warning) ())
@ When we make a socket for the server, we'll bind it to an address and have
it listen there for connections. The function |make-server-socket| thus
returns a new socket ready to accept connections, or else signals an error.
The domain of the constructed socket is determined automatically from the
type of address given, with slightly hairy defaulting behavior.
@l
@<Define |with-umask| macro@>
(deftype inet-addr () '(or (simple-vector 4) (vector (unsigned-byte 8) 4)))
(deftype inet-sock-addr () '(cons inet-addr (cons integer null)))
(defun make-server-socket (address &key (backlog *default-backlog*)
(port *default-port*) ;
(protocol :tcp) ;
(umask #O77) &aux
(address @<Resolve |address|...@>))
(let ((socket (etypecase address
(inet-sock-addr (make-instance 'inet-socket ;
:type :stream ;
:protocol protocol))
(pathname (make-instance 'local-socket :type :stream)))))
@<Bind |socket| to |address| and listen for connections@>
(assert (socket-open-p socket))
(server-log "Server listening on ~{~A~^ port ~D~}."
(multiple-value-list (socket-name socket)))
socket))
@ In SBCL's socket library, local (Unix) domain socket addresses are
represented by filenames, and {\sc inet} domain socket addresses are
represented by lists of the form |(ipv4-address port)|. In an effort
to be both slightly more accommodating to the user and more Lisp-like,
we accept a slightly different set of socket address designators, which
are easier to express in Lisp than prose. Note the defaulting for address
and port, and that strings are ambiguous: we first try to parse them as
dotted-quads, and only if that fails do we treat them as filenames.
@<Resolve |address| as a socket address designator@>=
(etypecase address
(null (list *localhost* port))
(inet-addr (list address port))
(inet-sock-addr address)
(pathname address)
(string (or (ignore-errors (list (make-inet-address address) port))
(pathname address))))
@t We'll test both filenames and pathnames as designators for local domain
socket addresses. We assume that {\tt /tmp} is an acceptable place to put
temporary socket files.
@l
(defun make-temp-socket-name ()
(mktemp (make-pathname :name "socket"
:type "XXXXXX"
:directory '(:absolute "tmp"))))
(defmacro with-open-server-socket ((socket address &optional args) &body body)
`(let ((,socket
(handler-bind ((server-log #'muffle-warning))
(apply #'make-server-socket ,address ,args))))
(unwind-protect (progn ,@body)
(socket-close ,socket))))
(defun test-make-local-server-socket (address)
(with-open-server-socket (socket address)
(unwind-protect
(and (socket-open-p socket)
(string= (socket-name socket) (namestring address))
(probe-file address)
t)
(delete-file address))))
(deftest (make-server-socket local)
(values (test-make-local-server-socket (make-temp-socket-name))
(test-make-local-server-socket (pathname (make-temp-socket-name))))
t t)
@t We have three kinds of address specifications to test for {\sc inet}
sockets: dotted quads, host \& port, and~(\<host> \<port>). We also verify
that an attempt to bind to the same socket address while a socket is still
open signals an address-in-use error.
We assume that |*default-port*| on the local host is available for binding.
@l
(defun test-make-inet-server-socket (address &rest args)
(with-open-server-socket (socket address args)
(and (socket-open-p socket)
(typep (multiple-value-list (socket-name socket)) 'inet-sock-addr)
(handler-case
(handler-bind ((server-log #'muffle-warning))
(apply #'make-server-socket address args))
(address-in-use-error () t)))))
(deftest (make-server-socket inet)
(values (test-make-inet-server-socket "127.0.0.1")
(test-make-inet-server-socket *localhost* :port *default-port*)
(test-make-inet-server-socket (list *localhost* *default-port*)))
t t t)
@ If we're binding to an {\sc inet} socket, we'll use the loopback address
and a pseudo-random (but fixed) unprivledged port as defaults.
@<Global variables@>=
(defparameter *localhost* #(127 0 0 1)
"The loopback address.")
(defparameter *default-port* 31415
"Default port on which to listen for connections.")
@ The |backlog| parameter to |socket-listen| controls the maximum queue
length for new connections: if there are more than this many outstanding
connection requests, new connection attemps will be refused. See \man
listen(2) for more information. A value of~5 is traditional, and since
this server is expected to handle only low traffic volume, it should be
more than sufficient.
@<Global variables@>=
(defparameter *default-backlog* 5
"Maximum length of pending connections queue.")
@ Both binding a socket to an address and attempting to listen on it can
fail for many reasons. If the listen fails, there's usually not much that
can be usefully done with the socket, so we'll offer an |abort| restart
that closes it down gracefully.
@<Bind |socket| to |address| and listen for connections@>=
(restart-case (progn
@<Bind |socket| to |address|@>
(socket-listen socket backlog))
(abort ()
:report "Abort and close the socket."
(socket-close socket)
(abort)))
@ But with binding, transient failures are more common, so we'll provide a
|retry| restart to allow the user the possibility of correcting the error.
The most common cause of transient binding failure is probably the address
being already in use (e.g., by another server).
When binding to a local domain socket, we'll also give the option to remove
any file that already exists at that address.
@<Bind |socket| to |address|@>=
(tagbody
bind
(restart-case
(etypecase address
(inet-sock-addr (apply #'socket-bind socket address))
(pathname (with-umask umask
(restart-case (socket-bind socket (namestring address))
(delete-file ()
:report "Delete the socket file and retry binding."
(ignore-errors (delete-file address))
(go bind))))))
(retry ()
:report "Retry binding the socket."
(go bind))))
@t First, we'll open a local socket |a| at some address (filename). Then
we'll try to open a socket~|b| at the same address; this will fail, since
the address is already in use (by~|a|). So we close |a| and invoke the
|delete-file| restart; |b| should then open.
@l
(deftest (make-server-socket retry)
(let ((address (make-temp-socket-name)))
(with-open-server-socket (a address)
(let* ((retried nil)
(b (handler-bind ((address-in-use-error
(lambda (condition)
(declare (ignore condition))
(setq retried t)
(invoke-restart 'delete-file)))
(server-log #'muffle-warning))
(make-server-socket address))))
(unwind-protect (values (socket-open-p a) (socket-open-p b) retried)
(socket-close b)
(delete-file address)))))
t t t)
@ @<Define |with-umask|...@>=
(defmacro with-umask (umask &body body)
(let ((old-umask (make-symbol "OLD-UMASK")))
`(let ((,old-umask (umask ,umask)))
(unwind-protect (progn ,@body)
(umask ,old-umask)))))
@ Once a socket is bound and listening, it is ready to accept connections.
As soon as we accept a connection, we'll enter what we call, for lack of
a better term, a \repl: a loop, implemented as a function of no arguments,
that reads messages from standard input and responds on standard output.
Error output is {\it not\/} rebound, so that the \repl\ has a stream on
which to write error messages that might reach the user directly.
By default, we'll spawn a new thread for each \repl\ so that it can operate
in the background. During debugging, however, it can be useful to run in
the foreground, so we'll support a |spawn| keyword argument which can be
used to override the default behavior.
@l
(defun serve-client (client repl &key (external-format :default))
(unwind-protect
(let* ((stream (socket-make-stream client
:input t :output t
:buffering :none
:external-format external-format))
(*standard-input* stream)
(*standard-output* stream))
(server-log "Entering ~A."
(etypecase repl
(symbol repl)
(function (nth-value 2 ; name
(function-lambda-expression ;
repl)))))
(funcall repl))
(prog1 (socket-close client)
(server-log "Client connection closed."))))
(defun server-accept (socket repl &key (spawn t))
(let ((client (socket-accept socket)))
(server-log "Accepted connection on ~{~A~^ port ~D~}~
~{ from ~:[local client~;client at ~:*~A~^ port ~D~]~}."
(multiple-value-list (socket-name socket))
(multiple-value-list (socket-peername client)))
(if spawn
(make-thread 'serve-client ;
:arguments (list client repl) ;
:name "SLUDGE client")
(serve-client client repl))))
(defun server-loop (server repl &key (spawn t) once-only)
(unwind-protect
(loop
(server-accept server repl :spawn spawn)
(when once-only (return)))
(when (typep server 'local-socket)
(ignore-errors (delete-file (socket-name server))))
(prog1 (socket-close server)
(server-log "Server socket closed."))))
@ With the above machinery in place, we come now to the primary public
interface of the whole system: a pair of functions which start and stop,
respectively, a server loop thread. If the |spawn| argument to |start-server|
is false, however, the server loop will run in the current thread; this is
for debugging purposes only. Note that running client threads are currently
{\it not\/} aborted when the server is stopped; only the server loop itself
is halted, so no new client connections will be accepted.
@l
(defun start-server (&rest args &key address (repl 'main-loop) (spawn t) ;
once-only &allow-other-keys)
@<Offer to shut down an already-running server@>
(let ((server (apply #'make-server-socket address :allow-other-keys t args)))
(flet ((serve () ;
(server-loop server repl :spawn spawn :once-only once-only)))
(if spawn
(setq *server* (make-thread #'serve :name "SLUDGE server"))
(let ((*server* *current-thread*))
(serve))))))
@ @<Global variables@>=
(defvar *server* nil
"The current SLUDGE server.")
@ One could run more than one instance of the server per Lisp process, but
there's no particular reason to do so, and it isn't expected to be a common
practice.
@<Offer to shut down...@>=
(when (and spawn *server* (ignore-errors (thread-alive-p *server*)))
(restart-case (cerror "Ignore it." 'server-already-running :thread *server*)
(shutdown (&optional (server *server*))
:report "Shut it down."
(stop-server server))))
@ @<Condition classes@>=
(define-condition server-already-running ()
((thread :reader server-thread :initarg :thread))
(:report (lambda (condition stream)
(format stream "A server is already running: ~A." ;
(server-thread condition)))))
@ This somewhat violent and crude implementation of |stop-server| at least
has the advantage of simplicity. Less harsh solutions that do not add undue
complexity would be welcome.
@l
(defun stop-server (&optional (server *server*))
(interrupt-thread server #'abort))
@t Here's a teeny-tiny little top-level \repl, just for testing the server.
Most of this implementation was cribbed from SBCL's \repl.
@l
(defun toplevel-eval (form &key (eval #'eval))
"Evaluate FORM, returning whatever it returns and adjusting the
variables ***, **, *, +++, ++, +, ///, //, /, and -."
(setq - form)
(unwind-protect
(let ((values (multiple-value-list (funcall eval form))))
(shiftf /// // / values)
(shiftf *** ** ** * (first values)))
(shiftf +++ ++ + -))
(values-list /))
(defun simple-repl (&key (prompt (lambda (stream) (format stream "~&* "))))
(with-standard-io-syntax
(loop
(with-simple-restart (abort "~@<Return to REPL.~@:>")
(funcall prompt *standard-output*)
(force-output)
(let* ((form (handler-case (read) (end-of-file () (return))))
(values (multiple-value-list (toplevel-eval form))))
(if values
(mapc (lambda (value) (format t "~S~&" value)) values)
(fresh-line)))))))
@ Since we haven't yet even described our protocol, we can only sketch
our main loop at this time. We'll fill in the details later, after we've
defined our protocol interface.
@l
(defun main-loop ()
(let ((*read-eval* nil))
(loop @<Read and handle a request@>)))
@1*Protocol definition. The \sludge\ protocol may be informally described
as follows. Sequences of octets (8-bit bytes) are interpreted as representing
Unicode characters using a pre-arranged encoding. (Future versions of the
protocol might have some kind of encoding negotiation.) The characters form
s-expressions, but with a highly restricted syntax. The s-exps denote
{\it messages\/},which are divided into {\it requests\/} and {\it responses\/}.
Requests are represented as lists of the form |(code tag . args)|, where
|code| is any keyword symbol other than |:ok| and~|:error|, |tag| is a
client-supplied integer identifier for this message, and |args| is an
arbitrary list of of Lisp objects. If |args| is null and the tag is not
important, the parentheses may be elided; thus, |:code| is interpreted
as a designator for |(:code 0)|.
Responses take the form |(response-code request-code tag . args)|, where
|response-code| is either |:ok| or~|:error|, and the |request-code| and
|tag| are taken from the request to which this message is a response.
A successful request generates a response whose status is |:ok|, and the
args that follow contain the results of the request. If processing the
request caused an error to be signaled, then an {\it error response\/}
is generated with the form |(:error code tag name message)|, where |name|
is the name of the error condition signaled and |message| is a string
containing any available information about the cause of the error.
@l
(deftype code () 'keyword)
(deftype tag () 'integer)
(deftype request-code () '(and code (not response-code)))
(deftype response-code () '(member :ok :error))
@t@l
(deftest code-types
(values (typep :foo 'request-code)
(typep :foo 'response-code)
(typep :ok 'request-code)
(typep :ok 'response-code)
(typep 'foo 'request-code)
(typep 'foo 'response-code))
t nil nil t nil nil)
@ A request message is any message in which the code isn't a response code.
@l
(deftype request-message () '(cons request-code (cons tag list)))
(defun make-request-message (code tag &rest args)
(declare (request-code code)
(tag tag))
`(,code ,tag ,@args))
(defun request-code (message) (car message))
(defun request-tag (message) (cadr message))
(defun request-args (message) (cddr message))
@t@l
(deftest make-request-message
(equal (make-request-message :foo 0 'bar 'baz) '(:foo 0 bar baz))
t)
(deftest request-message-type
(values (typep '(:foo 0 foo) 'request-message)
(typep '(:ok 0 foo) 'request-message)
(typep '(:foo) 'request-message))
t nil nil)
@ A response message begins with a response code, which is followed by what
looks like a request message.
@l
(deftype response-message () '(cons response-code request-message))
(defun make-response-message (response-code request-code tag &rest args)
(declare (response-code response-code)
(request-code request-code)
(tag tag))
`(,response-code ,request-code ,tag ,@args))
(defun response-code (message) (car message))
(defun response-request-code (message) (cadr message))
(defun response-tag (message) (caddr message))
(defun response-args (message) (cdddr message))
@t@l
(deftest make-response-message
(equal (make-response-message :ok :foo 0 'bar 'baz) '(:ok :foo 0 bar baz))
t)
(deftest response-message-type
(values (typep '(:ok :foo 0 t nil t) 'response-message)
(typep '(:ok :ok 0 t nil t) 'response-message)
(typep '(:foo :foo 0 t nil t) 'response-message))
t nil nil)
@1*Sending messages. Sending messages is simple: we just print the
character representation to standard output, followed by a newline (for
aesthetic purposes only).
@l
(defun send-message (message)
(let ((*print-case* :downcase)
(*print-pretty* nil))
(write-string (format nil "~S~%" message))
(finish-output)))
@ Here's a little convenience method for sending error messages.
@l
(defun send-error-message (code tag condition)
(send-message
(make-response-message :error code tag
(type-of condition)
(princ-to-string condition))))
@1*Reading messages. To pull a request off the wire, we'll use the Lisp
reader, but in a very careful way. We start by collecting the characters
that comprise the message using the Lisp reader with |*read-suppress*|
bound to true; that should catch the most basic syntax errors. Then we'll
peek at the first character so we can handle the abbreviated request syntax
described above. If it's a fully parenthesized message, we try to read the
code and tag of the message---if we can't get those, then we can't even
send a proper error response---followed by the arguments.
This routine will signal errors if it detects any abnormalities in the
syntax of the message; it is expected that higher-level routines will
establish handlers that can cope with such errors in a sensible way (e.g.,
by ignoring the whole message).
@l
(defun read-request (&optional (stream *standard-input*) &aux ;
(*readtable* *request-readtable*))
(with-input-from-string
(*standard-input*
(with-output-to-string (string-stream)
(let ((*standard-input* (make-echo-stream stream string-stream))
(*read-suppress* t))
(read))))
(macrolet ((read-typed-object (type)
(let ((object (gensym)))
`(let ((,object (read)))
(check-type ,object ,type)
,object))))
(let ((c (peek-char t)))
(case c
(#\: (list (read-typed-object request-code) 0))
(#\( (read-char)
(let* ((code (read-typed-object request-code))
(tag (read-typed-object tag))
(args @<Try to read arguments until closing paren@>))
(list* code tag args))))))))
@ Arguments are read one at at a time until we see the closing delimiter.
If an error is signaled while attempting to read an argument, we'll send
back an error response, but nevertheless decline to handle the condition.
@<Try to read arguments...@>=
(flet ((send-error-response (condition)
(send-error-message code tag condition)))
(loop until (char= (peek-char t) #\))
collect (handler-bind ((reader-error #'send-error-response))
(read))))
@t We'll frequently read requests from strings during tests, so we'll
define a little helper function for that.
@l
(defun read-request-from-string (string)
(with-input-from-string (stream string)
(read-request stream)))
@t@l
(deftest read-request
(values (equal (read-request-from-string ":foo") '(:foo 0))
(equal (read-request-from-string "(:foo 0)") '(:foo 0))
(null (read-request-from-string "<invalid>")))
t t t)
@2*Raw symbols. Two of the main reasons for this system's existence are
arglist display and symbol completion. We'll come to the actual handling
of such requests shortly, but for now we'll stick to what's required to
read the arguments. The issue is that in both cases, there's a chance that
the primary argument will not be the name of any interned symbol. The
probability is low to moderate in the first case; e.g., the user may have
paused in typing the name of a symbol when the arglist request is issued.
But there's a very high probability indeed in the second case---otherwise,
what's the point of completion? If we were to just use |read| on such an
incomplete symbol name, the effect would be to (1)~intern a symbol that we
probably don't want, and (2)~lose information about whether and what kind
of a package prefix was specified. Neither is desirable in this application.
Our solution is to introduce a new data structure which preserves the
information present in the original (source) representation, but also
makes it easy to find the designated symbol. We call these `raw symbols'.
A raw symbol instance can be thought of having two sets of slots, each
representing a different aspect of the raw symbol. The first is the
`parsed' version: normalized (i.e., without escapes), case-folded package
and symbol names with a boolean flag denoting internal access. The second
is a `split' version of the original token, consisting of the raw package
prefix, markers, and symbol name. (There's an additional slot, |print-case|,
which is used to help transform the case of symbol names back to the case
given on input; we can think of it as encoding information implicit in the
split slots, but which we pick up during parsing.) The parsed slots make it
easy to find the designated symbol; the split slots (plus |print-case|) make
it easy to reconstruct the original source or variants thereof. We'll have
occasion to use both sets, especially when we get to symbol completion.
@l
(defstruct raw-symbol
package internal name ; parsed
prefix markers suffix ; split
(print-case :downcase))
@ We'll define \.{\#"} as a reader macro for constructing raw symbols.
The raw symbol is terminated by an unescaped \.{"} character.
@l
@<Define token reading utility routines@>
@<Define raw symbol reading routine@>
(defun raw-symbol-reader (stream subchar arg)
(declare (ignore arg))
(read-raw-symbol subchar stream t))
(set-dispatch-macro-character #\# #\" #'raw-symbol-reader *request-readtable*)
@ @<Glob...@>=
(defvar *request-readtable* (copy-readtable nil))
@ The predicate |whitespacep| determines whether or not a given character
should be treated as whitespace. Note, however, that this routine does
not---and can not, at least not portably---examine the current readtable
to determine which characters currently have ${\it whitespace}_2$ syntax.
@<Define token reading...@>=
(defun whitespacep (char)
(find char *whitespace* :test #'char=))
@ Only the characters named `Newline' and `Space' are required to be
present in a conforming Common Lisp implementation, but most also
support the semi-standard names `Tab', `Linefeed', `Return', and~`Page'.
@<Glob...@>=
(defparameter *whitespace*
(coerce (remove-duplicates
(remove nil (mapcar #'name-char ;
'("Newline" "Space" ;
"Tab" "Linefeed" "Return" "Page"))))
'string))
@ Portably determining whether or not a character is a token delimiter is
possible, but tricky. We don't bother with such tricks here; we just assume
that the only delimiters are whitespace and terminating macro characters.
@<Define token reading...@>=
(defun token-delimiter-p (char)
(declare (type character char))
(or (whitespacep char)
(multiple-value-bind (function non-terminating-p) ;
(get-macro-character char)
(and function (not non-terminating-p)))))
@ We'll use adjustable arrays with fill pointers as token buffers.
The average length of the symbols exported by the \.{COMMON-LISP}
package is about~12, so we'll start with that as an initial capacity.
@<Define token reading...@>=
(defun make-token-buffer ()
(make-array 12 :element-type 'character :fill-pointer 0 :adjustable t))
@ Since Common Lisp doesn't provide a |read-extended-token| function,
we parse the symbol syntax as we read characters one at a time. There's
a bit of bookkeeping, but nothing overly complex. We'll keep two buffers,
|tok| and~|raw|, which will eventually be used to produce the `parsed'
and `split' components, respectively, of the |raw-symbol| instance
we'll return. We record the location of escapes and package markers
as we encounter them, and also track the case of unescaped alphabetic
characters. At the end, we'll case-fold and split up the accumulated token.
@<Define raw symbol reading...@>=
(defun read-raw-symbol (delimiter &optional stream recursive-p)
(loop with tok = (make-token-buffer) ; normalized, eventually case folded
and raw = (make-token-buffer) ; raw token with escapes
and single-escape ; flag: escape the next character
and multiple-escape ; flag: escape the following characters
and escaped = '() ; accumulated list of escaped indices
and markers = '() ; accumulated list of package marker indices
and offset = 0 ; difference between normalized and raw marker indices
and all-upper = t ; unescaped character case
and all-lower = t ; ditto
for char = (read-char stream nil delimiter recursive-p) and i upfrom 0
do @<Process the character |char| and maybe extend |tok|@>
do (vector-push-extend char raw)
finally (setq escaped (nreverse escaped) markers (nreverse markers))
@<Case-fold the unescaped characters in |tok|@>
@<Construct and return a |raw-symbol| instance@>))
@ We assume that \.{:} is the package marker, \.{\\} is the (unique)
single escape character, and \.{\char'174} is the (unique) multiple escape
character. We completely ignore the whole notion of potential numbers.
The order of the clauses here is important: single escapes, for instance,
are recognized even inside of a multiple-escape pair, and in the interest
of preventing run-away arguments, an unescaped closing delimiter
{\it always\/} terminates the token.
@<Process the char...@>=
(cond (single-escape
(push (vector-push-extend char tok) escaped)
(setq single-escape nil))
((char= char #\\)
(setq single-escape t))
((char= char delimiter)
(loop-finish))
((char= char #\|)
(setq multiple-escape (not multiple-escape)))
(multiple-escape
(push (vector-push-extend char tok) escaped))
((char= char #\:)
(let ((marker (vector-push-extend char tok)))
(when (or (> (length markers) 1)
(and (first markers) (/= marker (1+ (first markers)))))
(error "Too many colons in token."))
(push marker markers)
(setq offset (- marker i))))
((token-delimiter-p char)
(cerror "Ignore the delimiter."
"Unexpected delimiter ~S while reading token." char)
(unread-char char stream)
(loop-finish))
(t (vector-push-extend char tok)
(when (both-case-p char)
(if (upper-case-p char)
(setq all-lower nil)
(setq all-upper nil)))))
@ @<Construct and return...@>=
(return
(let ((print-case (if all-lower :downcase *print-case*)))
(if markers
(destructuring-bind (i &optional (j i)) markers
(make-raw-symbol :package (if (plusp i) (subseq tok 0 i) "KEYWORD")
:internal (string= (subseq tok i (1+ j)) "::")
:name (subseq tok (1+ j))
:prefix (subseq raw 0 (- i offset))
:markers (subseq raw (- i offset) (1+ (- j offset)))
:suffix (subseq raw (1+ (- j offset)))
:print-case print-case))
(make-raw-symbol :name tok :suffix raw :print-case print-case))))
@t Don't call |parse-symbol| on a string with embedded {\sc NUL} characters.
This limitation doesn't matter in practice because this function is only
used for testing.
@l
(defun parse-symbol (string)
(with-input-from-string (stream string)
(read-raw-symbol #\Nul stream)))
(deftest (parse-symbol bare)
(equalp (parse-symbol "foo")
(make-raw-symbol :name "FOO" :suffix "foo"))
t)
(deftest (parse-symbol keyword)
(equalp (parse-symbol ":foo")
(make-raw-symbol :package "KEYWORD" :name "FOO"
:prefix "" :markers ":" :suffix "foo"))
t)
(deftest (parse-symbol external)
(equalp (parse-symbol "foo:bar")
(make-raw-symbol :package "FOO" :name "BAR"
:prefix "foo" :markers ":" :suffix "bar"))
t)
(deftest (parse-symbol internal)
(equalp (parse-symbol "FOO::BAR")
(make-raw-symbol :package "FOO" :internal t :name "BAR"
:prefix "FOO" :markers "::" :suffix "BAR"
:print-case :upcase))
t)
(deftest (parse-symbol escaped)
(equalp (parse-symbol "f\\oo|::barbaz|::G\\:rack")
(make-raw-symbol :package "FoO::barbaz"
:internal t
:name "G:RACK"
:prefix "f\\oo|::barbaz|"
:markers "::"
:suffix "G\\:rack"
:print-case :upcase))
t)
(deftest (parse-symbol error)
(handler-case (parse-symbol ":foo:")
(error () t))
t)
@ The case folding rules for tokens are specified by \S23.1.2 of the
{\sc ansi} Common Lisp standard (`Effect of Readtable Case on the Lisp
Reader'). Because of the semantics of |:invert|, we must wait until
the entire token has been accumulated before we do any actual folding,
but we've been tracking the case of unescaped characters as we go along
in the variables |all-lower| and~|all-upper|.
@<Case-fold...@>=
(labels ((escaped (i) (member i escaped :test #'=))
(fold (transform)
(loop for char across tok and i upfrom 0
unless (escaped i)
do (setf (char tok i) (funcall transform char))))
(lower () (fold #'char-downcase))
(raise () (fold #'char-upcase)))
(ecase (readtable-case *readtable*)
(:upcase (raise))
(:downcase (lower))
(:preserve)
(:invert (cond (all-lower (raise))
(all-upper (lower))))))
@t@l
(defun parse-symbol-with-case (token case)
(let ((*readtable* (copy-readtable nil)))
(setf (readtable-case *readtable*) case)
(parse-symbol token)))
(deftest (parse-symbol :downcase)
(equalp (parse-symbol-with-case "FOO:\\XBAR" :downcase)
(make-raw-symbol :package "foo" :name "Xbar"
:prefix "FOO" :markers ":" :suffix "\\XBAR"
:print-case :upcase))
t)
(deftest (parse-symbol :preserve)
(equalp (parse-symbol-with-case "FOO:\\xbar" :preserve)
(make-raw-symbol :package "FOO" :name "xbar"
:prefix "FOO" :markers ":" :suffix "\\xbar"
:print-case :upcase))
t)
(deftest (parse-symbol :invert upper)
(equalp (parse-symbol-with-case "FOO:\\XBAR" :invert)
(make-raw-symbol :package "foo" :name "Xbar"
:prefix "FOO" :markers ":" :suffix "\\XBAR"
:print-case :upcase))
t)
(deftest (parse-symbol :invert lower)
(equalp (parse-symbol-with-case "foo:\\xbar" :invert)
(make-raw-symbol :package "FOO" :name "xBAR"
:prefix "foo" :markers ":" :suffix "\\xbar"))
t)
(deftest (parse-symbol :invert mixed)
(equalp (parse-symbol-with-case "Foo:\\xBar" :invert)
(make-raw-symbol :package "Foo" :name "xBar"
:prefix "Foo" :markers ":" :suffix "\\xBar"
:print-case :upcase))
t)
@ We look up raw symbols using |find-raw-symbol|, which signals an error if
either the designated symbol does not exist or the specified access doesn't
match the symbol's accessibility in the designated package.
@l
(defun find-package-or-lose (package-name)
(if package-name
(or (find-package package-name)
(error 'no-such-package-error :package package-name))
*package*))
(defun raw-symbol-accessible-p (raw-symbol accessibility)
(or (null (raw-symbol-package raw-symbol))
(raw-symbol-internal raw-symbol)
(eq accessibility :external)))
(defun find-raw-symbol (raw-symbol)
(let ((package (find-package-or-lose (raw-symbol-package raw-symbol)))
(name (raw-symbol-name raw-symbol)))
(multiple-value-bind (symbol status) (find-symbol name package)
(unless symbol
(error 'no-such-symbol-error :package package :name name))
(unless (raw-symbol-accessible-p raw-symbol status)
(cerror "Use the symbol anyway."
'symbol-accessibility-error :package package :name name))
(values symbol status))))
@t Don't name a package \.{"XXXXXX"}, ok?
@l
(deftest (find-raw-symbol ok)
(find-raw-symbol (parse-symbol "sludge::find-raw-symbol"))
find-raw-symbol
:internal)
(deftest (find-raw-symbol no-package-error)
(handler-case (find-raw-symbol (parse-symbol "xxxxxx:foo"))
(package-error () t))
t)
(deftest (find-raw-symbol accessibility-error)
(handler-case (find-raw-symbol (parse-symbol "sludge:find-raw-symbol"))
(package-error () t))
t)
@ @<Condition classes@>=
(define-condition no-such-package-error (package-error)
()
(:report (lambda (condition stream)
(format stream "No such package: ~A." ;
(package-error-package condition)))))
(define-condition no-such-symbol-error (package-error)
((symbol-name :accessor package-error-symbol-name :initarg :name))
(:report (lambda (condition stream)
(format stream "There is no symbol named ~S in package ~A."
(package-error-symbol-name condition)
(package-name (package-error-package condition))))))
(define-condition symbol-accessibility-error (package-error)
((symbol-name :accessor package-error-symbol-name :initarg :name))
(:report (lambda (condition stream)
(format stream "The symbol ~S is not external in package ~A."
(package-error-symbol-name condition)
(package-name (package-error-package condition))))))
@1*Handling requests. Request handlers are methods of the generic function
|handle-request|, |eql|-specialized on request codes.
@l
(defgeneric handle-request (code tag &rest args))
@ The default handler just signals an error.
@l
(defmethod handle-request (code tag &rest args)
(error 'invalid-request-message :message `(,code ,tag ,@args)))
@ @<Condition classes@>=
(define-condition invalid-request-message (error)
((message :reader invalid-request-message :initarg :message))
(:report (lambda (condition stream)
(format stream "Invalid request ~S."
(invalid-request-message condition)))))
@ We now have all of the pieces in place to define the core of our main
loop. If \eof\ is encountered during the read, we exit the loop. If an
error occurs during request handling, we send an error response and
continue processing with the next message. If the client signals their
desire to disconnect, we acknowledge the request and exit the loop. We also
establish a |continue| restart which simply ignores the current request and
picks up with the next one.
@<Read and handle a request@>=
(with-simple-restart (continue "Ignore this request.")
(let ((request (handler-case (read-request)
(reader-error () (continue))
(type-error () (continue))
(invalid-request-message () (continue))
(end-of-file () (return)))))
(destructuring-bind (code tag &rest args) request
(handler-case (apply #'handle-request code tag args)
(client-disconnect (condition)
(send-message ;
(apply #'make-response-message :ok code tag
(client-disconnect-args condition)))
(return))
(error (condition)
(send-error-message code tag condition))))))
@ Methods for |handle-request| tend to follow a similar pattern, so we'll
provide a defining macro that abstracts it a bit. The request arguments are
bound using |destructuring-bind| to the parameters specified by |lambda-list|.
The body should return a designator for a list of response arguments,
from which a response message will be constructed and sent. We'll leave it
up to the main loop to handle any errors by constructing and sending error
responses.
@l
(defmacro define-request-handler (request-code lambda-list &body body)
"Define a method on HANDLE-REQUEST, EQL-specialized on REQUEST-CODE."
(let ((code (make-symbol "CODE"))
(tag (make-symbol "TAG"))
(args (make-symbol "ARGS"))
(response-args (make-symbol "RESPONSE-ARGS")))
`(defmethod handle-request ((,code (eql ,request-code)) ,tag &rest ,args)
(declare (optimize safety))
(destructuring-bind ,lambda-list ,args
(let ((,response-args (progn ,@body)))
(send-message (apply #'make-response-message :ok ,code ,tag
(if (listp ,response-args)
,response-args
(list ,response-args)))))))))