Added babel-extension and test.

Pascal J. Bourguignon [2021-05-15 02:23]
Added babel-extension and test.
Filename
clext/telnet/babel-extension-test.lisp
clext/telnet/babel-extension.lisp
clext/telnet/packages.lisp
diff --git a/clext/telnet/babel-extension-test.lisp b/clext/telnet/babel-extension-test.lisp
new file mode 100644
index 0000000..eb1cc0a
--- /dev/null
+++ b/clext/telnet/babel-extension-test.lisp
@@ -0,0 +1,259 @@
+;;;; -*- mode:lisp;coding:utf-8 -*-
+;;;;**************************************************************************
+;;;;FILE:               babel-extension-test.lisp
+;;;;LANGUAGE:           Common-Lisp
+;;;;SYSTEM:             Common-Lisp
+;;;;USER-INTERFACE:     NONE
+;;;;DESCRIPTION
+;;;;
+;;;;    Tests decode-character.
+;;;;
+;;;;AUTHORS
+;;;;    <PJB> Pascal J. Bourguignon <pjb@informatimago.com>
+;;;;MODIFICATIONS
+;;;;    2021-05-15 <PJB> Created.
+;;;;BUGS
+;;;;LEGAL
+;;;;    AGPL3
+;;;;
+;;;;    Copyright Pascal J. Bourguignon 2021 - 2021
+;;;;
+;;;;    This program is free software: you can redistribute it and/or modify
+;;;;    it under the terms of the GNU Affero General Public License as published by
+;;;;    the Free Software Foundation, either version 3 of the License, or
+;;;;    (at your option) any later version.
+;;;;
+;;;;    This program is distributed in the hope that it will be useful,
+;;;;    but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;;    GNU Affero General Public License for more details.
+;;;;
+;;;;    You should have received a copy of the GNU Affero General Public License
+;;;;    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;;;;**************************************************************************
+
+(in-package "COM.INFORMATIMAGO.CLEXT.BABEL-EXTENSION.TEST")
+
+
+(define-test test/decode-character/us-ascii ()
+  (let ((encoding :us-ascii)
+        (octets (make-array 10 :element-type '(unsigned-byte 8) :initial-element 32)))
+
+    (check equal
+           (multiple-value-list (decode-character octets :start 0 :end 0 :encoding encoding))
+           '(nil t 1))
+
+    (loop :for code :from 0 :to 127
+          :do (setf (aref octets 0) code)
+              (check equal
+                     (multiple-value-list (decode-character octets :start 0 :end 1 :encoding encoding))
+                     (list (code-char code) t 1)
+                     (encoding code octets)))
+
+    (loop :for code :from 128 :to 255
+          :do (setf (aref octets 0) code)
+              (check equal
+                     (multiple-value-list (decode-character octets :start 0 :end 1 :encoding encoding))
+                     '(nil nil 1)
+                     (encoding code octets)))
+
+    (loop :for code :from 0 :to 127
+          :do (setf (aref octets 0) code)
+              (setf (aref octets 1) 65)
+              (check equal
+                     (multiple-value-list (decode-character octets :start 0 :end 2 :encoding encoding))
+                     (list (code-char code) t 1)
+                     (encoding code octets)))
+
+    (loop :for code :from 128 :to 255
+          :do (setf (aref octets 0) code)
+              (setf (aref octets 1) 65)
+              (check equal
+                     (multiple-value-list (decode-character octets :start 0 :end 2 :encoding encoding))
+                     '(nil nil 1)
+                     (encoding code octets)))))
+
+
+(define-test test/decode-character/iso-8859-1 ()
+  (let ((encoding :iso-8859-1)
+        (octets (make-array 10 :element-type '(unsigned-byte 8) :initial-element 32)))
+
+    (check equal
+           (multiple-value-list (decode-character octets :start 0 :end 0 :encoding encoding))
+           '(nil t 1))
+
+    (loop :for code :from 0 :to 255
+          :do (setf (aref octets 0) code)
+              (check equal
+                     (multiple-value-list (decode-character octets :start 0 :end 1 :encoding encoding))
+                     (list (code-char code) t 1)
+                     (encoding code octets)))
+
+    (loop :for code :from 0 :to 255
+          :do (setf (aref octets 0) code)
+              (setf (aref octets 1) 65)
+              (check equal
+                     (multiple-value-list (decode-character octets :start 0 :end 2 :encoding encoding))
+                     (list (code-char code) t 1)
+                     (encoding code octets)))))
+
+
+(defun utf-8-to-octets (code octets &key (start 0) end)
+  (assert (<= code char-code-limit) (code)
+          "Code ~D should be a unicode code point between 0 and ~A"
+          code char-code-limit)
+  (cond
+    ((<= code #x7f)
+     (assert (<= (+ start 1) (or end (length octets))))
+     (setf (aref octets start) code)
+     (incf start))
+    ((<= code #x7ff)
+     (assert (<= (+ start 2) (or end (length octets))))
+     (setf (aref octets start) (dpb (ldb (byte 5  6) code) (byte 5 0) #b11000000))
+     (incf start)
+     (setf (aref octets start) (dpb (ldb (byte 6  0) code) (byte 6 0) #b10000000))
+     (incf start))
+    ((<= code #xffff)
+     (assert (<= (+ start 3) (or end (length octets))))
+     (setf (aref octets start) (dpb (ldb (byte 4 12) code) (byte 4 0) #b11100000))
+     (incf start)
+     (setf (aref octets start) (dpb (ldb (byte 6  6) code) (byte 6 0) #b10000000))
+     (incf start)
+     (setf (aref octets start) (dpb (ldb (byte 6  0) code) (byte 6 0) #b10000000))
+     (incf start))
+    ((<= code #x10ffff)
+     (assert (<= (+ start 4) (or end (length octets))))
+     (setf (aref octets start) (dpb (ldb (byte 3 18) code) (byte 3 0) #b11110000))
+     (incf start)
+     (setf (aref octets start) (dpb (ldb (byte 6 12) code) (byte 6 0) #b10000000))
+     (incf start)
+     (setf (aref octets start) (dpb (ldb (byte 6  6) code) (byte 6 0) #b10000000))
+     (incf start)
+     (setf (aref octets start) (dpb (ldb (byte 6  0) code) (byte 6 0) #b10000000))
+     (incf start))
+    (t
+     (error "Invalid unicode code-point for utf-8 encoding ~D (#x~:*~X)" code)))
+  (values  start octets))
+
+(define-test test/utf-8-to-octets ()
+  (let ((octets (make-array 10 :element-type '(unsigned-byte 8) :initial-element 32)))
+    (assert-true (= 1 (utf-8-to-octets     #x45 octets)))
+    (assert-true (= 2 (utf-8-to-octets    #x745 octets)))
+    (assert-true (= 3 (utf-8-to-octets   #x7045 octets)))
+    (assert-true (= 4 (utf-8-to-octets #x100045 octets)))))
+
+(define-test test/decode-character/utf-8 ()
+  (let ((encoding :utf-8)
+        (octets (make-array 10 :element-type '(unsigned-byte 8) :initial-element 32)))
+
+    (check equal
+           (multiple-value-list (decode-character octets :start 0 :end 0 :encoding encoding))
+           '(nil t 1))
+
+    ;; Note: this includes the cases where code-char returns NIL:
+    (loop :for code :from 0 :below char-code-limit
+          :for next := (utf-8-to-octets code octets :start 0    :end (length octets))
+          :do (utf-8-to-octets 65   octets :start next :end (length octets))
+              (if (<= code #x7f)
+                  (check equal
+                         (multiple-value-list (decode-character octets :start 0 :end 1 :encoding encoding))
+                         (list (code-char code) t next)
+                         (encoding code octets))
+                  (check equal
+                         (multiple-value-list (decode-character octets :start 0 :end 1 :encoding encoding))
+                         (list nil t next)
+                         (encoding code octets)))
+              (if (<= code #x7ff)
+                  (check equal
+                         (multiple-value-list (decode-character octets :start 0 :end 2 :encoding encoding))
+                         (list (code-char code) t next)
+                         (encoding code octets))
+                  (check equal
+                         (multiple-value-list (decode-character octets :start 0 :end 2 :encoding encoding))
+                         (list nil t next)
+                         (encoding code octets)))
+              (if (<= code #xffff)
+                  (check equal
+                         (multiple-value-list (decode-character octets :start 0 :end 3 :encoding encoding))
+                         (list (code-char code) t next)
+                         (encoding code octets))
+                  (check equal
+                         (multiple-value-list (decode-character octets :start 0 :end 3 :encoding encoding))
+                         (list nil t next)
+                         (encoding code octets)))
+              (if (<= code #x10ffff)
+                  (check equal
+                         (multiple-value-list (decode-character octets :start 0 :end 4 :encoding encoding))
+                         (list (code-char code) t next)
+                         (encoding code octets))
+                  (check equal
+                         (multiple-value-list (decode-character octets :start 0 :end 4 :encoding encoding))
+                         (list nil t next)
+                         (encoding code octets))))
+
+    ;; Testing invalid utf-8 code sequences:
+
+    (check equal (multiple-value-list (decode-character (replace octets #(130)) :encoding encoding))
+           '(nil nil 1)
+           (encoding octets))
+
+    (check equal (multiple-value-list (decode-character (replace octets #(#b11000000 #b00100001)) :encoding encoding))
+           '(nil nil 2)
+           (encoding octets))
+    (check equal (multiple-value-list (decode-character (replace octets #(#b11000000 #b11100001)) :encoding encoding))
+           '(nil nil 2)
+           (encoding octets))
+
+    (check equal (multiple-value-list (decode-character (replace octets #(#b11100000 #b10110011 #b00100001)) :encoding encoding))
+           '(nil nil 3)
+           (encoding octets))
+    (check equal (multiple-value-list (decode-character (replace octets #(#b11100000 #b10110011 #b11100001)) :encoding encoding))
+           '(nil nil 3)
+           (encoding octets))
+
+    (check equal (multiple-value-list (decode-character (replace octets #(#b11110000 #b00100001 #b10110011 #b10110011)) :encoding encoding))
+           '(nil nil 4)
+           (encoding octets))
+    (check equal (multiple-value-list (decode-character (replace octets #(#b11110000 #b10110011 #b00100001 #b10110011)) :encoding encoding))
+           '(nil nil 4)
+           (encoding octets))
+    (check equal (multiple-value-list (decode-character (replace octets #(#b11110000 #b10110011 #b10110011 #b00100001)) :encoding encoding))
+           '(nil nil 4)
+           (encoding octets))
+    (check equal (multiple-value-list (decode-character (replace octets #(#b11110000 #b11100001 #b10110011 #b10110011)) :encoding encoding))
+           '(nil nil 4)
+           (encoding octets))
+    (check equal (multiple-value-list (decode-character (replace octets #(#b11110000 #b10110011 #b11100001 #b10110011)) :encoding encoding))
+           '(nil nil 4)
+           (encoding octets))
+    (check equal (multiple-value-list (decode-character (replace octets #(#b11110000 #b10110011 #b10110011 #b11100001)) :encoding encoding))
+           '(nil nil 4)
+           (encoding octets))))
+
+(define-test test/decode-character/eucjp ()
+  (let* ((encoding :eucjp)
+         (string "こんにちは / コンニチハ")
+         (octets (babel:string-to-octets string :encoding :eucjp)))
+    (loop
+      :for expected :across string
+      :for start := 0 :then (+ start size)
+      :for (character validp size) := (multiple-value-list (decode-character octets :start start :encoding encoding))
+      :do (assert-true character (character) "decode-character should have decoded a ~S character from ~A" encoding start)
+          (assert-true validp (validp) "decode-character should have decoded a valid ~S code sequence from ~A" encoding start)
+          (check char= character expected (encoding start octets character expected))
+      :finally (incf start size)
+               (check = start (length octets) (encoding start octets)))))
+
+
+(define-test test/all ()
+  (test/decode-character/us-ascii)
+  (test/decode-character/iso-8859-1)
+  (test/utf-8-to-octets)
+  (test/decode-character/utf-8)
+  (test/decode-character/eucjp))
+
+;; (test/all)
+;;;; THE END ;;;;
+
+
+
diff --git a/clext/telnet/babel-extension.lisp b/clext/telnet/babel-extension.lisp
new file mode 100644
index 0000000..88396f0
--- /dev/null
+++ b/clext/telnet/babel-extension.lisp
@@ -0,0 +1,266 @@
+;;;; -*- mode:lisp;coding:utf-8 -*-
+;;;;**************************************************************************
+;;;;FILE:               babel-extension.lisp
+;;;;LANGUAGE:           Common-Lisp
+;;;;SYSTEM:             Common-Lisp
+;;;;USER-INTERFACE:     NONE
+;;;;DESCRIPTION
+;;;;
+;;;;    A function to test for code sequences.
+;;;;
+;;;;AUTHORS
+;;;;    <PJB> Pascal J. Bourguignon <pjb@informatimago.com>
+;;;;MODIFICATIONS
+;;;;    2021-05-14 <PJB> Created
+;;;;BUGS
+;;;;LEGAL
+;;;;    AGPL3
+;;;;
+;;;;    Copyright Pascal J. Bourguignon 2021 - 2021
+;;;;
+;;;;    This program is free software: you can redistribute it and/or modify
+;;;;    it under the terms of the GNU Affero General Public License as published by
+;;;;    the Free Software Foundation, either version 3 of the License, or
+;;;;    (at your option) any later version.
+;;;;
+;;;;    This program is distributed in the hope that it will be useful,
+;;;;    but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;;    GNU Affero General Public License for more details.
+;;;;
+;;;;    You should have received a copy of the GNU Affero General Public License
+;;;;    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;;;;**************************************************************************
+
+(in-package "COM.INFORMATIMAGO.CLEXT.BABEL-EXTENSION")
+
+#|
+We would want to know when we have accumulated in a buffer enough bytes to decode a character, depending on the current encodng…
+babel doesn't provide a convenient (efficient) API to test that, but I hoped to be able to use OCTETS-TO-STRING for that.
+Unfortunately, handling of incomplete code sequences by the different encoding is not consistent.
+
+
+```
+cl-user> (babel:OCTETS-TO-STRING (coerce #(194 182) '(vector (unsigned-byte 8))) :start 0 :end 2 :errorp nil :encoding :utf-8)
+"¶"
+cl-user> (babel:OCTETS-TO-STRING (coerce #(194 182) '(vector (unsigned-byte 8))) :start 0 :end 1 :errorp nil :encoding :utf-8)
+"�"
+cl-user> (babel:OCTETS-TO-STRING (coerce #(194 182) '(vector (unsigned-byte 8))) :start 0 :end 2 :errorp nil :encoding :utf-16)
+"슶"
+cl-user> (babel:OCTETS-TO-STRING (coerce #(194 182) '(vector (unsigned-byte 8))) :start 0 :end 1 :errorp nil :encoding :utf-16)
+> Debug: Failed assertion: (= babel-encodings::i babel-encodings::end)
+> While executing: (:internal swank::invoke-default-debugger), in process new-repl-thread(1481).
+> Type cmd-/ to continue, cmd-. to abort, cmd-\ for a list of available restarts.
+> If continued: test the assertion again.
+> Type :? for other options.
+1 > :q
+; Evaluation aborted on #<simple-error #x302006CBABDD>.
+cl-user> (babel:octets-to-string (babel:string-to-octets "こんにちは 世界" :encoding :eucjp) :start 0 :end 2 :encoding :eucjp)
+"こ"
+cl-user> (babel:octets-to-string (babel:string-to-octets "こんにちは 世界" :encoding :eucjp) :start 0 :end 1 :encoding :eucjp)
+> Debug: Illegal :eucjp character starting at position 0.
+> While executing: (:internal swank::invoke-default-debugger), in process repl-thread(3921).
+> Type cmd-. to abort, cmd-\ for a list of available restarts.
+> Type :? for other options.
+1 > :q
+; Evaluation aborted on #<babel-encodings:end-of-input-in-character #x302006CA4EAD>.
+cl-user>
+```
+
+
+I would suggest to add a keyword parameter to specify what to do in such a case:
+
+| :on-invalid-code substitution-character | would insert the given substitution-character in place of the code. |
+| :on-invalid-code :ignore                | would ignore the code and go on.                                    |
+| :on-invalid-code :error                 | would signal a babel-encodings:character-decoding-error condition.  |
+
+
+
+I would propose also, to provide an efficient function to query the length of a code sequence for the next character:
+```
+(babel:decode-character bytes &key start end encoding)
+--> character ;
+    sequence-valid-p ;
+    length
+```
+
+- If a character can be decoded, then it is returned as primary value, otherwise NIL.
+
+- If the code sequence is definitely invalid then NIL, else T. Notably if it is just too short, but could be a valid code sequence if completed, T should be returned.
+
+- If the character is decoded and returned, then the length of the decoded code sequence is returned; if sequence-valid-p then a minimal code sequence length with the given prefix is returned; otherwise a minimum code sequence length.
+
+
+| character | sequence-valid-p | length                                                         |
+|-----------+------------------+----------------------------------------------------------------|
+| ch        | T                | length of the decoded sequence                                 |
+| ch        | NIL              | --impossible--                                                 |
+| NIL       | T                | minimal length of a valid code sequence with the given prefix. |
+| NIL       | NIL              | minimal length of a valid code sequence.                       |
+
+For example, in the case NIL T len, if len <= (- end start), then it means the given code sequence is valid, but the decoded code is not the code of a character.  eg. ```#(#xED #xA0 #x80)``` is UTF-8 for 55296, but ```(code-char 55296) --> nil```.
+
+
+```
+(babel:decode-character (coerce #(65 32 66) '(vector (unsigned-byte 8)))
+                         :start 0 :end 3 :encoding :utf-8)
+--> #\A
+    T
+    1
+
+(babel:decode-character (coerce #(195 128 32 80 97 114 105 115) '(vector (unsigned-byte 8)))
+                        :start 0 :end 3 :encoding :utf-8)
+--> #\À
+    T
+    2
+
+(babel:decode-character (coerce #(195 128 32 80 97 114 105 115) '(vector (unsigned-byte 8)))
+                        :start 0 :end 1 :encoding :utf-8)
+--> NIL
+    T
+    2
+
+(babel:decode-character (coerce #(195 195 32 80 97 114 105 115) '(vector (unsigned-byte 8)))
+                        :start 0 :end 1 :encoding :utf-8)
+--> NIL
+    T
+    2
+
+(babel:decode-character (coerce #(195 195 32 80 97 114 105 115) '(vector (unsigned-byte 8)))
+                        :start 0 :end 2 :encoding :utf-8)
+--> NIL
+    NIL
+    1
+
+(babel:decode-character (coerce #(#xED #xA0 #x80) '(vector (unsigned-byte 8)))
+                        :start 0 :end 3 :encoding :utf-8)
+--> NIL
+    T
+    3
+```
+
+|#
+
+
+;; (defparameter *replacement-character*
+;;   (if (<= 65535 char-code-limit)        ; does it really mean that the
+;;                                         ; implementation uses unicode?
+;;
+;;       (code-char 65533)                 ; #\Replacement_Character
+;;
+;;       ;; Let's assume ASCII:
+;;       (code-char 26)                    ; #\Sub
+;;       ;; SUB is #x3f  in EBCDIC
+;;       )
+;;
+;;   "A replacement character.")
+
+
+(defparameter *replacement-character* (code-char 65533)
+  ;; TODO: Is it always the same for all encodings?
+  "The replacement character used by babel.")
+
+
+(defun decode-character (octets &key (start 0) end (encoding :utf-8))
+  ;; we'll optimize :us-ascii, :iso-8895-1 and :utf-8 cases.
+  (let ((end (or end (length octets))))
+    (case encoding
+      ((:us-ascii :csascii :cp637 :ibm637 :us :iso646-us :ascii :iso-ir-6)
+       (if (<= end start)
+           (values nil t 1)
+           (let ((code (aref octets start)))
+             (if (<= 0 code 127)
+                 (values (code-char code) t 1)
+                 (values nil nil 1)))))
+      ((:iso-8859-1 :iso_8859-1 :latin1 :l1 :ibm819 :cp819 :csisolatin1)
+       (if (<= end start)
+           (values nil t 1)
+           (let ((code (aref octets start)))
+             (if (<= 0 code 255)
+                 (values (code-char code) t 1)
+                 (values nil nil 1)))))
+      ((:utf-8)
+       (if (<= end start)
+           (values nil t 1)
+           (let ((byte (aref octets start))
+                 (code 0))
+             (cond
+               ((<= 0 byte 127)         ; 1 byte
+                (values (code-char byte) t 1))
+               ((<= #b11000000 byte #b11011111) ; 2 bytes
+                (setf code (ash (ldb (byte 5 0) byte) 6))
+                (incf start)
+                (if (< start end)
+                    (let ((byte (aref octets start)))
+                      (if (<= #b10000000 byte #b10111111)
+                          (progn
+                            (setf code (dpb (ldb (byte 6 0) byte) (byte 6 0) code))
+                            (values (code-char code) t 2))
+                          (values nil nil 2)))
+                    (values nil t 2)))
+               ((<= #b11100000 byte #b11101111) ; 3 bytes
+                (setf code (ash (ldb (byte 4 0) byte) 12))
+                (incf start)
+                (if (< start end)
+                    (let ((byte (aref octets start)))
+                      (if (<= #b10000000 byte #b10111111)
+                          (progn
+                            (setf code (dpb (ldb (byte 6 0) byte) (byte 6 6) code))
+                            (incf start)
+                            (if (< start end)
+                                (let ((byte (aref octets start)))
+                                  (if (<= #b10000000 byte #b10111111)
+                                      (progn
+                                        (setf code (dpb (ldb (byte 6 0) byte) (byte 6 0) code))
+                                        (values (code-char code) t 3))
+                                      (values nil nil 3)))
+                                (values nil t 3)))
+                          (values nil nil 3)))
+                    (values nil t 3)))
+               ((<= #b11110000 byte #b11110111) ; 4 bytes
+                (setf code (ash (ldb (byte 3 0) byte) 18))
+                (incf start)
+                (if (< start end)
+                    (let ((byte (aref octets start)))
+                      (if (<= #b10000000 byte #b10111111)
+                          (progn
+                            (setf code (dpb (ldb (byte 6 0) byte) (byte 6 12) code))
+                            (incf start)
+                            (if (< start end)
+                                (let ((byte (aref octets start)))
+                                  (if (<= #b10000000 byte #b10111111)
+                                      (progn
+                                        (setf code (dpb (ldb (byte 6 0) byte) (byte 6 6) code))
+                                        (incf start)
+                                        (if (< start end)
+                                            (let ((byte (aref octets start)))
+                                              (if (<= #b10000000 byte #b10111111)
+                                                  (progn
+                                                    (setf code (dpb (ldb (byte 6 0) byte) (byte 6 0) code))
+                                                    (values (code-char code) t 4))
+                                                  (values nil nil 4)))
+                                            (values nil t 4)))
+                                      (values nil nil 4)))
+                                (values nil t 4)))
+                          (values nil nil 4)))
+                    (values nil t 4)))
+               (t
+                (values nil nil 1))))))
+      (otherwise
+       (handler-case
+           (octets-to-string octets :start start :end end :errorp nil :encoding encoding)
+         (:no-error (string)
+           (if (= 1 (length string))
+               (if (char= (aref string 0) *replacement-character*)
+                   (values nil t 1)     ; ???
+                   (values (aref string 0) t (length (string-to-octets string :encoding encoding))))
+               (values (aref string 0) t (length (string-to-octets string :end 1 :encoding encoding)))))
+         (end-of-input-in-character ()
+           (values nil t 1))            ; ???
+         (character-out-of-range ()
+           (values nil t 1))            ; ???
+         (character-decoding-error ()
+           (values nil nil 1) #|???|#))))))
+
+
+;;;; THE END ;;;;
diff --git a/clext/telnet/packages.lisp b/clext/telnet/packages.lisp
new file mode 100644
index 0000000..eba66bd
--- /dev/null
+++ b/clext/telnet/packages.lisp
@@ -0,0 +1,72 @@
+;;;; -*- mode:lisp;coding:utf-8 -*-
+;;;;**************************************************************************
+;;;;FILE:               packages.lisp
+;;;;LANGUAGE:           Common-Lisp
+;;;;SYSTEM:             Common-Lisp
+;;;;USER-INTERFACE:     NONE
+;;;;DESCRIPTION
+;;;;
+;;;;    The package definitions of the telnet REPL server.
+;;;;
+;;;;AUTHORS
+;;;;    <PJB> Pascal J. Bourguignon <pjb@informatimago.com>
+;;;;MODIFICATIONS
+;;;;    2021-05-13 <PJB> Created.
+;;;;BUGS
+;;;;LEGAL
+;;;;    AGPL3
+;;;;
+;;;;    Copyright Pascal J. Bourguignon 2021 - 2021
+;;;;
+;;;;    This program is free software: you can redistribute it and/or modify
+;;;;    it under the terms of the GNU Affero General Public License as published by
+;;;;    the Free Software Foundation, either version 3 of the License, or
+;;;;    (at your option) any later version.
+;;;;
+;;;;    This program is distributed in the hope that it will be useful,
+;;;;    but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;;    GNU Affero General Public License for more details.
+;;;;
+;;;;    You should have received a copy of the GNU Affero General Public License
+;;;;    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+;;;;**************************************************************************
+
+(defpackage "COM.INFORMATIMAGO.CLEXT.BABEL-EXTENSION"
+  (:use "COMMON-LISP"
+        "BABEL")
+  (:export "DECODE-CHARACTER"))
+
+(defpackage "COM.INFORMATIMAGO.CLEXT.BABEL-EXTENSION.TEST"
+  (:use "COMMON-LISP"
+        "COM.INFORMATIMAGO.CLEXT.BABEL-EXTENSION"
+        "COM.INFORMATIMAGO.COMMON-LISP.CESARUM.SIMPLE-TEST")
+  (:export "TEST/ALL"))
+
+(defpackage "COM.INFORMATIMAGO.CLEXT.TELNET.STREAM"
+  (:use "COMMON-LISP"
+        "BORDEAUX-THREADS"
+        "TRIVIAL-GRAY-STREAMS"
+        "COM.INFORMATIMAGO.COMMON-LISP.TELNET"
+        "COM.INFORMATIMAGO.COMMON-LISP.CESARUM.ASCII"
+        "COM.INFORMATIMAGO.CLEXT.BABEL-EXTENSION")
+  (:export "WITH-TELNET-ON-STREAM"
+           "TELNET-STREAM"))
+
+(defpackage "COM.INFORMATIMAGO.CLEXT.TELNET.REPL"
+  (:use "COMMON-LISP"
+        "BABEL"
+        "USOCKET"
+        "BORDEAUX-THREADS"
+        "COM.INFORMATIMAGO.COMMON-LISP.TELNET"
+        ;; "com.informatimago.common-lisp.cesarum"
+        "COM.INFORMATIMAGO.COMMON-LISP.INTERACTIVE.INTERACTIVE")
+  (:export "REPL-SERVER"
+           "REPL-SERVER-THREAD"
+           "REPL-SERVER-PORT"
+           "REPL-SERVER-INTERFACE"
+           "REPL-SERVER-MAX-CLIENTS"
+           "REPL-SERVER-CLIENT-THREADS"
+           "START-REPL-SERVER" "STOP-REPL-SERVER"))
+
+;;;; THE END ;;;;
ViewGit