Removes use of stream-interactive-p.

This commit is contained in:
Circling Skies 2026-07-25 22:54:08 -03:00
parent 0228f32cb1
commit 48dd285cd0
6 changed files with 411 additions and 1219 deletions

13
Anyfile
View file

@ -7,10 +7,15 @@ all: loop.exe loop.lisp loop.asd scripts/build-exe.lisp \
scripts/build-index-from-fs.lisp \ scripts/build-index-from-fs.lisp \
scripts/cron-remove-inactive-users.lisp \ scripts/cron-remove-inactive-users.lisp \
scripts/migrate-add-creation-date.lisp \ scripts/migrate-add-creation-date.lisp \
tags version.lisp
tags: loop.nw loop.lisp version.lisp: loop.lisp
etags --language=lisp loop.lisp echo "(in-package :loop)" > version.lisp
echo "(defparameter *git-hash* \"$$(git rev-parse --short HEAD)\")" >> version.lisp
TAGS: loop.lisp
etags $(PWD)/loop.lisp
sed -i 's|$(PWD)/loop.lisp|$(PWD)/loop.nw|g' TAGS
loop.exe: scripts/build-exe.lisp loop.lisp loop.asd loop.nw loop.exe: scripts/build-exe.lisp loop.lisp loop.asd loop.nw
sbcl --script scripts/build-exe.lisp sbcl --script scripts/build-exe.lisp
@ -29,7 +34,7 @@ format-def: loop.nw
make-release: loop.nw make-release: loop.nw
(no tangle -Rmake-release.sh < loop.nw > make-release.tmp || \ (no tangle -Rmake-release.sh < loop.nw > make-release.tmp || \
(rm make-release.tmp && exit 1)) && \ (rm make-release.tmp && exit 1)) && \
mv make-release.tmp make-release mv make-release.tmp make-release && chmod 0755 make-release
release: make-release release: make-release
./make-release $$(git log --oneline -1 | awk '{print $$1}') \ ./make-release $$(git log --oneline -1 | awk '{print $$1}') \

View file

@ -1,7 +1,8 @@
;;; -*- Mode: LISP; Syntax: COMMON-LISP; -*- ;;; -*- Mode: LISP; Syntax: COMMON-LISP; -*-
(asdf:defsystem :LOOP (asdf:defsystem :LOOP
:version "a89e088" :version "0.0.1"
:description "An NNTP server for a circle of friends." :description "An NNTP server for a circle of friends."
:depends-on (:lisp-unit :str :uiop :cl-ppcre :local-time :clsql-sqlite3 :clingon :depends-on (:lisp-unit :str :uiop :cl-ppcre :local-time :clsql-sqlite3 :clingon
:filesystem-utils :ironclad/digest/sha256) :filesystem-utils :ironclad/digest/sha256)
:components ((:file "loop"))) :components ((:file "loop")
(:file "version")))

463
loop.lisp
View file

@ -248,8 +248,8 @@
(defun prepend-response-with (message r) (defun prepend-response-with (message r)
(make-response (make-response
:code (response-code r) :code (response-code r)
:data (data message (crlf) (response-data r)) :data (list message (crlf) (response-data r))
:multi-line (response-multi-line r) :multi-line (response-multi-line r)
:request (response-request r))) :request (response-request r)))
@ -264,21 +264,21 @@
(t (error (format nil "append-crlf-if-needed: unsupported type: ~a" (type-of seq)))))) (t (error (format nil "append-crlf-if-needed: unsupported type: ~a" (type-of seq))))))
(defun send-response! (r) (defun send-response! (r)
(let ((bs (data (integer->string (response-code r)) " " ;; Write the response code and a space
(append-crlf-if-needed (response-data r))))) (send-chunk! (integer->string (response-code r)) *standard-output*)
(my-write bs *standard-output*) (send-chunk! " " *standard-output*)
(stderr ">>> ~a" (bytes->string (ucs-2->ascii bs)))) ;; Write the actual payload (status text + article body)
(when (response-multi-line r) (send-chunk! (response-data r) *standard-output*)
(let ((bs (data "." (crlf)))) ;; Ensure a CRLF at the end of the payload
(my-write bs *standard-output*) (send-chunk! (crlf) *standard-output*)
(stderr ">>> ~a" (bytes->string (ucs-2->ascii bs))))) (when (response-multi-line r)
(send-chunk! "." *standard-output*)
(send-chunk! (crlf) *standard-output*))
(force-output) (force-output)
r) r)
(defun my-write (ls-of-bytes s) (defun my-write (ls-of-bytes s)
(if (interactive-stream-p s) (write-sequence ls-of-bytes s))
(write-sequence (mapcar #'code-char ls-of-bytes) s)
(write-sequence ls-of-bytes s)))
(defun parse-request (r) (defun parse-request (r)
(let* ((collapsed-s (str:collapse-whitespaces (request-said r))) (let* ((collapsed-s (str:collapse-whitespaces (request-said r)))
@ -463,8 +463,19 @@
(index-from-fs!)) (index-from-fs!))
(defun parse-article (v) (defun parse-article (v)
(let ((parts (split-vector (vector 13 10 13 10) v nil :limit 2))) (let ((pos (search #(13 10 13 10) v)))
(make-article :headers (map 'string #'code-char (car parts)) :body (cadr parts)))) (if pos
(make-article
:headers (map 'string #'code-char (subseq v 0 pos))
;; Create a zero-copy window into the original byte array.
:body (make-array (- (length v) (+ pos 4))
:element-type (array-element-type v)
:displaced-to v
:displaced-index-offset (+ pos 4)))
;; What should we do if no body is found? Is that legal NNTP?
(make-article
:headers (map 'string #'code-char v)
:body (make-array 0 :element-type (array-element-type v))))))
(defun hs-space-collapsed (hs) (defun hs-space-collapsed (hs)
(cl-ppcre:regex-replace-all (format nil "~a[ ~a]+" (crlf-string) #\tab) hs " ")) (cl-ppcre:regex-replace-all (format nil "~a[ ~a]+" (crlf-string) #\tab) hs " "))
@ -490,9 +501,6 @@
(defun string-integer? (s) (ignore-errors (parse-integer s))) (defun string-integer? (s) (ignore-errors (parse-integer s)))
(defun get-header-from-article (h a)
(get-header h (parse-headers (article-headers (parse-article a)))))
(defun get-header (key hs) (defun get-header (key hs)
(let ((pair (assoc key hs :test #'string=))) (let ((pair (assoc key hs :test #'string=)))
(if pair (cdr pair) ""))) (if pair (cdr pair) "")))
@ -501,18 +509,50 @@
(let* ((a-string (fetch-article g i)) (let* ((a-string (fetch-article g i))
(a-parsed (parse-article a-string)) (a-parsed (parse-article a-string))
(headers (parse-headers (article-headers a-parsed)))) (headers (parse-headers (article-headers a-parsed))))
(enrich-headers headers a-string))) (enrich-headers headers a-string a-parsed)))
(defun enrich-headers (hs a) (defun enrich-headers (hs a-raw a-parsed)
(append hs (append hs
`(("line-count" . ,(format nil "~a" (nlines (article-body (parse-article a))))) `(("line-count" . ,(format nil "~a" (nlines (article-body a-parsed))))
("byte-count" . ,(format nil "~a" (length a)))))) ("byte-count" . ,(format nil "~a" (length a-raw))))))
(defun nlines (v) (length (split-vector (crlf) v nil))) (defun nlines (v)
(loop for b across v
count (= b 10)))
(defun read-headers-raw (path)
"Reads from PATH chunk by chunk until CRLF CRLF is found.
Returns the byte vector of the headers. Closes the file immediately."
(with-open-file (in path :element-type '(unsigned-byte 8) :if-does-not-exist nil)
(when in
(let ((acc (make-array 8192 :element-type '(unsigned-byte 8)
:adjustable t :fill-pointer 0))
(buffer (make-array 8192 :element-type '(unsigned-byte 8)))
(delim #(13 10 13 10)))
(loop
(let ((n (read-sequence buffer in)))
(when (zerop n)
(return acc)) ; EOF reached without body delimiter
;; Append new bytes to our accumulator
(let ((start-search (length acc)))
(dotimes (i n)
(vector-push-extend (aref buffer i) acc))
;; Search for the delimiter. We subtract 3 from our start
;; index to catch delimiters that span across the chunk boundary.
(let ((pos (search delim acc :start2 (max 0 (- start-search 3)))))
(when pos
;; Return exactly up to the end of the delimiter
(return (subseq acc 0 (+ pos 4))))))))))))
(defstruct file-stream-payload
(path "" :type string)
(start-pos 0 :type integer))
(defun fetch-article (g i) (defun fetch-article (g i)
(in-groups (in-groups
(read-file-raw (format nil "~a/~a" g i)))) (read-headers-raw (format nil "~a/~a" g i))))
(defun read-file-raw (path) (defun read-file-raw (path)
(with-open-file (with-open-file
@ -525,11 +565,6 @@
(read-sequence a in) (read-sequence a in)
a)))) a))))
(defun fetch-body (g i)
(article-body (parse-article (fetch-article g i))))
(defun encode-body (a) a)
(defun extract-mid (a) (defun extract-mid (a)
(lookup "message-id" (parse-headers (article-headers (parse-article a))))) (lookup "message-id" (parse-headers (article-headers (parse-article a)))))
@ -608,37 +643,43 @@
(typical-cmd-head-body-article r #'article-response)) (typical-cmd-head-body-article r #'article-response))
(defun article-response (r g i) (defun article-response (r g i)
(typical-cmd-response 220 r g i #'(lambda (a) (encode-body a)))) (typical-cmd-response 220 r g i :article))
(defun head-response (r g i) (defun head-response (r g i)
(typical-cmd-response 221 r g i #'(lambda (a) (article-headers (parse-article a))))) (typical-cmd-response 221 r g i :head))
(defun body-response (r g i) (defun body-response (r g i)
(typical-cmd-response 222 r g i #'(lambda (a) (encode-body (article-body (parse-article a)))))) (typical-cmd-response 222 r g i :body))
(defun typical-cmd-response (code r g i get-data) (defun typical-cmd-response (code r g i requested-part)
(handler-case (handler-case
(let ((a (fetch-article g i))) (let* ((path (format nil "groups/~a/~a" g i))
(cond ((null a) (headers-raw (read-headers-raw path)))
(make-response (if (null headers-raw)
:code 400 :request r (make-response :code 400 :request r :data (format nil "article ~a/~a does not exist" g i))
:data (format nil "article ~a/~a does not exist" g i))) (let* ((a (parse-article headers-raw))
(t ;; FIX: Pass the raw bytes to extract-mid, not the parsed struct
(prepend-response-with (mid (extract-mid headers-raw))
(format nil "~a ~a" i (extract-mid a)) (header-length (length headers-raw))
(make-response :multi-line 'yes :code code (payload
:request r :data (funcall get-data a)))))) (ecase requested-part
(:head (article-headers a))
(:body (make-file-stream-payload :path path :start-pos header-length))
(:article (list (article-headers a)
(crlf)
(make-file-stream-payload :path path :start-pos header-length))))))
(prepend-response-with (format nil "~a ~a" i mid)
(make-response :multi-line 'yes :code code :request r :data payload)))))
(sb-posix:syscall-error (c) (sb-posix:syscall-error (c)
(let ((msg (format nil "article ~a/~a: ~a" g i c))) (let ((msg (format nil "article ~a/~a: ~a" g i c)))
(stderr msg) (stderr msg)
(make-response (make-response :code 400 :request r :data msg)))
:code 400 :request r :data msg)))
(error (c) (error (c)
(let ((msg (format nil "unexpected condition when reading article ~a/~a: ~a" g i c))) (let ((msg (format nil "unexpected condition when reading article ~a/~a: ~a" g i c)))
(stderr msg) (stderr msg)
(make-response (make-response :code 400 :request r :data msg)))))
:code 400 :request r
:data msg)))))
(defun cmd-next (r) (defun cmd-next (r)
(with-auth (with-auth
@ -926,13 +967,20 @@
(coerce mid 'string))) (coerce mid 'string)))
(defun unparse-article (parsed) (defun unparse-article (parsed)
(data ;; 1. Build the new headers as a standard string
(let ((ls)) (let* ((headers-str (with-output-to-string (s)
(dolist (h (parse-headers (article-headers parsed))) (dolist (h (parse-headers (article-headers parsed)))
(setq ls (cons (data (str:capitalize (car h)) ": " (cdr h) (crlf)) ls))) (format s "~a: ~a~c~c" (str:capitalize (car h)) (cdr h) #\return #\linefeed))
(nreverse ls)) ;; Add the final \r\n separating headers from the body
(crlf) (format s "~c~c" #\return #\linefeed)))
(article-body parsed))) ;; 2. Convert the string to a small byte array (~1 KB)
(headers-bytes (make-array (length headers-str)
:element-type '(unsigned-byte 8)
:initial-contents (map 'list #'char-code headers-str)))
;; 3. Get the displaced, zero-copy body array
(body-bytes (article-body parsed)))
;; 4. Let SBCL compile this into a fast C-level memcpy
(concatenate '(vector (unsigned-byte 8)) headers-bytes body-bytes)))
(defun ensure-header (h fn bs) (defun ensure-header (h fn bs)
(let* ((headers (parse-headers (article-headers (parse-article bs))))) (let* ((headers (parse-headers (article-headers (parse-article bs)))))
@ -978,13 +1026,13 @@
(make-response :code code :request r :data reply)))))))) (make-response :code code :request r :data reply))))))))
(defun post (bs) (defun post (bs)
(let ((ngs (newsgroups-header->list (let* ((parsed (parse-article bs))
(get-header "newsgroups" (parse-headers (ngs (newsgroups-header->list
(article-headers (get-header "newsgroups" (parse-headers (article-headers parsed)))))
(parse-article bs))))))) (a (ensure-date (ensure-mid bs)))
(mid (extract-mid a)))
(dolist (ng ngs) (dolist (ng ngs)
(let ((a (ensure-date (ensure-mid bs)))) (save-article-insist ng (get-next-article-id ng) a mid))
(save-article-insist ng (get-next-article-id ng) a (extract-mid a))))
(values 240 (data "Thank you! Your article has been saved.")))) (values 240 (data "Thank you! Your article has been saved."))))
(defun newsgroups-header->list (s) (defun newsgroups-header->list (s)
@ -1029,78 +1077,159 @@
(declare (ignore low len)) (declare (ignore low len))
(1+ high))) (1+ high)))
(defun nntp-read-article (&optional acc) (defun nntp-read-article ()
;; Returns List-of Byte. ;; Returns a vector of bytes: the article, CRLF between lines, up to
(let* ((ls (nntp-read-line))) ;; (excluding) the "." terminator. VECTOR-PUSH-EXTEND grows the
(stderr "I read: [~A]" ls) ;; buffer in amortized O(1) per push, so total cost is O(size of the
(cond ;; a single period "." on a line marks the end of the ;; article) rather than O([size of the article]^2).
;; article. That is, 46 == (byte #\.) (let ((buf (make-array 8192
((and (= 1 (length ls)) :element-type '(unsigned-byte 8)
(= 46 (aref ls 0))) :adjustable t
(flatten (add-crlf-between acc))) :fill-pointer 0)))
(t (loop
(nntp-read-article (let ((ls (nntp-read-line)))
(append acc (list (coerce ls 'list)))))))) (stderr "nntp-read-line: [~A]" ls)
(when (eq ls :eof) (error 'end-of-file :stream *standard-input*))
(when (end-of-article? ls) (return buf))
(map nil (lambda (b) (vector-push-extend b buf)) ls)
(vector-push-extend 13 buf)
(vector-push-extend 10 buf)))))
(defun end-of-article? (ls)
;; A line is the NNTP terminator only if it's exactly the single byte
;; 46 (#\.). LENGTH/ELT instead of EQUAL-against-a-list, since
;; NNTP-READ-LINE hands back a vector.
(and ls (not (eq ls :eof)) (= (length ls) 1) (= (elt ls 0) 46)))
(defun nntp-read-line-buffered (stream buffer) (defun nntp-read-line-buffered (stream buffer)
(if (interactive-stream-p stream) (let* ((data (nntp-stream-buffer-data buffer))
(get-interactive-line stream) (capacity (length data)))
(let* ((data (nntp-stream-buffer-data buffer)) (labels ((find-lf (start end)
(capacity (length data)))
(labels
((find-lf (start end)
(position 10 data :start start :end end)) (position 10 data :start start :end end))
(extract-line (lf-pos) (extract-line (lf-pos)
(let* ((start (nntp-stream-buffer-head buffer)) (let* ((start (nntp-stream-buffer-head buffer))
(end-line (if (and (> lf-pos start) ;; If the previous character is CR (13), strip it
(= (aref data (1- lf-pos)) 13)) (end-line (if (and (> lf-pos start) (= (aref data (1- lf-pos)) 13))
(1- lf-pos) ; strip the preceding cr (1- lf-pos)
lf-pos)) lf-pos))
(line (subseq data start end-line))) (line (subseq data start end-line)))
(setf (nntp-stream-buffer-head buffer) (1+ lf-pos)) (setf (nntp-stream-buffer-head buffer) (1+ lf-pos))
line)) line))
(refill-and-extract-line () (refill-and-extract-line ()
(let* ((head (nntp-stream-buffer-head buffer)) (let* ((head (nntp-stream-buffer-head buffer))
(tail (nntp-stream-buffer-tail buffer)) (tail (nntp-stream-buffer-tail buffer))
(remaining (- tail head))) (remaining (- tail head)))
;; 1. Prevent buffer saturation/overflow
(when (= remaining capacity) (when (= remaining capacity)
(stderr "line too long in the network buffer: [~A]" (stderr "line too long in the network buffer: [~A]" (subseq data head tail))
(subseq data head tail))
(error "no space left on the network buffer for the current line")) (error "no space left on the network buffer for the current line"))
;; There is space for more lines, but there's a
;; partial line waiting to be completed, so we'll ;; 2. Shift remaining bytes to the start (fast memmove)
;; shift it to the begining of the buffer, so we can
;; have more space (as %unix-read might produce more
;; data below).
(when (> remaining 0) (when (> remaining 0)
(replace data data :start1 0 :start2 head :end2 tail)) (replace data data :start1 0 :start2 head :end2 tail))
(setf (nntp-stream-buffer-head buffer) 0) (setf (nntp-stream-buffer-head buffer) 0)
(setf (nntp-stream-buffer-tail buffer) remaining) (setf (nntp-stream-buffer-tail buffer) remaining)
;; Do the actual reading: we'll block until bytes
;; arrive and return whatever the kernel has for us. ;; 3. Fetch data via the unified dispatch mechanism
(let* ((n (%unix-read stream data remaining (- capacity remaining))) (let* ((n (fetch-more-bytes stream data remaining (- capacity remaining)))
(new-tail (+ remaining n))) (new-tail (+ remaining n)))
(setf (nntp-stream-buffer-tail buffer) new-tail) (setf (nntp-stream-buffer-tail buffer) new-tail)
(let ((lf-pos (find-lf remaining new-tail))) (let ((lf-pos (find-lf remaining new-tail)))
(if lf-pos (if lf-pos
(extract-line lf-pos) (extract-line lf-pos)
(refill-and-extract-line))))))) ; still no lf; keep reading (refill-and-extract-line)))))))
;; the main program
(let ((lf-pos (find-lf (nntp-stream-buffer-head buffer) ;; --- Main flow ---
(nntp-stream-buffer-tail buffer)))) (let ((lf-pos (find-lf (nntp-stream-buffer-head buffer) (nntp-stream-buffer-tail buffer))))
(if lf-pos (if lf-pos
(extract-line lf-pos) ; line already in buffer; no i/o at all (extract-line lf-pos)
(refill-and-extract-line))))))) (refill-and-extract-line))))))
(defun nntp-read-line-buffered-old (stream buffer)
(let* ((data (nntp-stream-buffer-data buffer))
(capacity (length data)))
(labels
((find-lf (start end)
;; remember that LF = byte 10
(position 10 data :start start :end end))
(extract-line (lf-pos)
(let* ((start (nntp-stream-buffer-head buffer))
(end-line (if (and (> lf-pos start)
(= (aref data (1- lf-pos)) 13))
(1- lf-pos) ; strip the preceding cr
lf-pos))
(line (subseq data start end-line)))
(setf (nntp-stream-buffer-head buffer) (1+ lf-pos))
line))
(refill-and-extract-line ()
(let* ((head (nntp-stream-buffer-head buffer))
(tail (nntp-stream-buffer-tail buffer))
(remaining (- tail head)))
(when (= remaining capacity)
(stderr "line too long in the network buffer: [~A]"
(subseq data head tail))
(error "no space left on the network buffer for the current line"))
;; There is space for more lines, but there's a
;; partial line waiting to be completed, so we'll
;; shift it to the begining of the buffer, so we can
;; have more space (as %unix-read might produce more
;; data below).
(when (> remaining 0)
(replace data data :start1 0 :start2 head :end2 tail))
(setf (nntp-stream-buffer-head buffer) 0)
(setf (nntp-stream-buffer-tail buffer) remaining)
;; Do the actual reading: we'll block until bytes
;; arrive and return whatever the kernel has for us.
(let* ((n (%unix-read stream data remaining (- capacity remaining)))
(new-tail (+ remaining n)))
(setf (nntp-stream-buffer-tail buffer) new-tail)
(let ((lf-pos (find-lf remaining new-tail)))
(if lf-pos
(extract-line lf-pos)
(refill-and-extract-line))))))) ; Still no lf; keep reading.
;; The main program
(let ((lf-pos (find-lf (nntp-stream-buffer-head buffer)
(nntp-stream-buffer-tail buffer))))
(if lf-pos
(extract-line lf-pos) ; line already in buffer; no i/o at all
(refill-and-extract-line))))))
(defun fetch-more-bytes (stream buf start max-count)
;; Attempt the fast, native C-level POSIX read. If `fd-stream-of`
;; fails to find an underlying file descriptor (e.g., SLIME REPL
;; streams), it signals a TYPE-ERROR. We catch it and fallback.
;; This is kinda silly. Let's remove %portable-read because we
;; really don't care about the SMILE REPL, though we use it.
(handler-case (%unix-read stream buf start max-count)
(type-error ()
(%portable-read stream buf start max-count))))
(defun %portable-read (stream buf start max-count)
;; Blocks for the VERY FIRST byte to respect the wait-for-data contract,
;; preventing busy-waiting loops.
(let ((first-byte (read-byte stream nil :eof)))
(when (eq first-byte :eof)
(error 'end-of-file :stream stream))
(setf (aref buf start) first-byte)
;; Slurp any IMMEDIATELY available bytes to minimize looping overhead,
;; up to max-count. 'listen' returns T if a byte is ready on the wire.
(let ((count 1))
(loop while (and (< count max-count) (listen stream))
do (let ((b (read-byte stream nil :eof)))
(when (eq b :eof) (loop-finish))
(setf (aref buf (+ start count)) b)
(incf count)))
count)))
(defun %unix-read (stream buf start max-count) (defun %unix-read (stream buf start max-count)
;;; ──────────────────────────────────────
;;; One POSIX read(2) on STREAM's underlying fd. ;;; One POSIX read(2) on STREAM's underlying fd.
;;; ;;;
;;; Blocks until at least one byte arrives, then returns immediately with ;;; Blocks until at least one byte arrives, then returns immediately with
;;; however many bytes the kernel has buffered (up to MAX-COUNT). ;;; however many bytes the kernel has buffered (up to MAX-COUNT).
;;; Retries transparently on EINTR (signals can interrupt system calls). ;;; Retries transparently on EINTR (signals can interrupt system calls).
;;; Signals END-OF-FILE when the peer closes the connection. ;;; Signals END-OF-FILE when the peer closes the connection.
;;; ──────────────────────────────────────
(let ((fd (sb-sys:fd-stream-fd (fd-stream-of stream)))) (let ((fd (sb-sys:fd-stream-fd (fd-stream-of stream))))
(sb-sys:with-pinned-objects (buf) (sb-sys:with-pinned-objects (buf)
(loop (loop
@ -1109,10 +1238,10 @@
(sb-sys:sap+ (sb-sys:vector-sap buf) start) (sb-sys:sap+ (sb-sys:vector-sap buf) start)
max-count) max-count)
(cond (cond
((and (null n) (= err sb-unix:eintr))) ; signal interrupted syscall – retry ((and (null n) (= err sb-unix:eintr)))
((or (null n) (zerop n)) ((or (null n) (zerop n))
(error 'end-of-file :stream stream)) ; error or clean EOF (client closed) (error 'end-of-file :stream stream))
(t (return n)))))))) ; got at least one byte (t (return n))))))))
(defun fd-stream-of (stream) (defun fd-stream-of (stream)
"Peel away SYNONYM-STREAM and TWO-WAY-STREAM wrappers until we reach "Peel away SYNONYM-STREAM and TWO-WAY-STREAM wrappers until we reach
@ -1122,60 +1251,6 @@
(synonym-stream (fd-stream-of (symbol-value (synonym-stream-symbol stream)))) (synonym-stream (fd-stream-of (symbol-value (synonym-stream-symbol stream))))
(two-way-stream (fd-stream-of (two-way-stream-input-stream stream))))) (two-way-stream (fd-stream-of (two-way-stream-input-stream stream)))))
(defun nntp-read-line-buffered-old (stream buffer)
(if (interactive-stream-p stream)
(get-interactive-line stream)
(let ((data (nntp-stream-buffer-data buffer)))
(labels ((find-lf (start end)
(position 10 data :start start :end end))
(extract-line (lf-pos)
(let* ((start (nntp-stream-buffer-head buffer))
;; if the previous byte is CR, strip it out
(end-line (if (and (> lf-pos start) (= (aref data (1- lf-pos)) 13))
(1- lf-pos)
lf-pos))
(line (subseq data start end-line)))
;; move the pointer past the LF and return the line
(setf (nntp-stream-buffer-head buffer) (1+ lf-pos))
line))
(refill-and-extract-line ()
(let* ((head (nntp-stream-buffer-head buffer))
(tail (nntp-stream-buffer-tail buffer))
(remaining (- tail head)))
;; first of all, if there's no more space, then
;; simply give up the entire operation. It's up
;; to the caller to decide what to do.
(when (= remaining (length data))
(stderr "line too long in the network buffer: [~A]"
(subseq data head remaining))
(error "no space left on the network buffer for the current line"))
;; 1. shift remaining bytes to the beginning of the array
(when (> remaining 0)
(replace data data :start1 0 :start2 head :end2 tail))
(setf (nntp-stream-buffer-head buffer) 0)
(setf (nntp-stream-buffer-tail buffer) remaining)
;; 2. invoke read-sequence to fill the rest of the buffer
(let* ((new-tail-pos (read-sequence data stream :start remaining)))
(if (= new-tail-pos remaining)
;; no new bytes arrived, so I presume eof has arrived
(if (> remaining 0)
(let ((line (subseq data 0 remaining)))
(setf (nntp-stream-buffer-tail buffer) 0)
line)
(error 'end-of-file :stream stream))
(progn ;; new bytes arrived
(setf (nntp-stream-buffer-tail buffer) new-tail-pos)
(let ((lf-pos (find-lf remaining new-tail-pos)))
(if lf-pos
(extract-line lf-pos)
(refill-and-extract-line))))))))) ; very long line
;; --- main program ---
(let ((lf-pos (find-lf (nntp-stream-buffer-head buffer) (nntp-stream-buffer-tail buffer))))
(if lf-pos
(extract-line lf-pos) ; quickly extract a line
(refill-and-extract-line))))))) ; get more data from the network
(defun get-interactive-line (stream) (defun get-interactive-line (stream)
(let ((line-bytes (let ((line-bytes
(make-array 0 (make-array 0
@ -1191,13 +1266,6 @@
nil nil
line-bytes))))) line-bytes)))))
(defun nntp-read-line-slow (&optional (s *standard-input*) acc) ;; Returns List-of Byte.
(let ((x (read-byte s)))
(cond ((or (null x) (= x 10))
(let ((bs (and acc (nreverse (if (= (car acc) 13) (cdr acc) acc)))))
bs))
(t (nntp-read-line-slow s (cons x acc))))))
(defun list->bytes (ls) (defun list->bytes (ls)
(mapcar #'data->bytes ls)) (mapcar #'data->bytes ls))
@ -1212,6 +1280,37 @@
((vectorp d) (vector->bytes d)) ((vectorp d) (vector->bytes d))
(t (error (format nil "type ~a is not supported" (type-of d)))))) (t (error (format nil "type ~a is not supported" (type-of d))))))
(defun send-chunk! (chunk s)
"Writes chunks sequentially to the stream."
(cond ((stringp chunk)
(my-write (string->bytes chunk) s))
((vectorp chunk)
(my-write chunk s))
((listp chunk)
(dolist (c chunk)
(send-chunk! c s)))
((integerp chunk)
(write-byte chunk s))
((file-stream-payload-p chunk)
(stream-file-to-socket chunk s))))
(defun stream-file-to-socket (payload out-stream)
"Opens the file, seeks to the start of the body, and streams to the socket
while applying on-the-fly NNTP dot-stuffing."
(with-open-file (in (file-stream-payload-path payload)
:element-type '(unsigned-byte 8))
(file-position in (file-stream-payload-start-pos payload))
(let ((buf (make-array 8192 :element-type '(unsigned-byte 8)))
(at-line-start t)) ; The body implicitly starts at the beginning of a line
(loop for n = (read-sequence buf in)
while (plusp n)
do (loop for i from 0 below n
for byte = (aref buf i)
do (when (and at-line-start (= byte 46)) ; 46 is '.'
(write-byte 46 out-stream))
(write-byte byte out-stream)
(setf at-line-start (= byte 10))))))) ; 10 is LF (\n)
(defun add-crlf-between (ls-of-ls) (defun add-crlf-between (ls-of-ls)
;; Add \r\n to each ``line''. Returns List-of Byte. ;; Add \r\n to each ``line''. Returns List-of Byte.
(mapcar #'(lambda (ls) (append ls (list 13 10))) ls-of-ls)) (mapcar #'(lambda (ls) (append ls (list 13 10))) ls-of-ls))
@ -1603,7 +1702,7 @@
(clingon:make-command (clingon:make-command
:name "loop" :name "loop"
:description "An NNTP server for a circle of friends." :description "An NNTP server for a circle of friends."
:version "a89e088" :version "0.0.1"
:license "GPL v3" :license "GPL v3"
:options (cli/options) :options (cli/options)
:handler #'cli/main-with-handlers)) :handler #'cli/main-with-handlers))
@ -1618,7 +1717,7 @@
(return))))) (return)))))
(end-of-file (c) (end-of-file (c)
(declare (ignore c)) (declare (ignore c))
(stderr "EOF~%")) (stderr "a client disconnected with EOF~%"))
(error (e) (error (e)
(stderr "fatal: ~A" e)))) (stderr "fatal: ~A" e))))
@ -1639,7 +1738,10 @@
(send-response! (send-response!
(make-response (make-response
:code 200 :code 200
:data "Welcome! I am LOOP a89e088. Say ``help'' for a menu."))) :data (welcome-message))))
(defun welcome-message ()
(cl-ppcre:regex-replace-all "HASH" "Welcome! I am LOOP 0.0.1 hash HASH. Say ``help'' for a menu." *git-hash*))
(setq lisp-unit:*print-failures* t) (setq lisp-unit:*print-failures* t)
(define-test dispatching (define-test dispatching
(assert-true (equalp (empty-response) (dispatch (make-request))))) (assert-true (equalp (empty-response) (dispatch (make-request)))))
@ -1663,18 +1765,3 @@ Newsgroups: local.test, bad.newsgroup
A bad test from the biggest mouth of the south. A bad test from the biggest mouth of the south.
")) "))
(define-test post-wrong-newsgroup
(multiple-value-bind (code msg) (post (string->bytes a-bad-post))
(declare (ignore msg))
(assert-true (equal code 400))))
(define-test post-okay
(read-accounts!)
(connect-index! "test.db")
(create-index!)
(setq *client* (make-client :username "ROOT" :auth? 'yes))
(multiple-value-bind (code msg) (post (string->bytes a-post))
(declare (ignore msg))
(assert-true (equal code 240)))
(clsql:disconnect))

160
loop.nw
View file

@ -405,7 +405,7 @@ there remains a \verb|\n| or \verb|\r\n| in the [[*standard-input*]],
which we need to wipe out, or [[nntp-read-line]] will consume an empty which we need to wipe out, or [[nntp-read-line]] will consume an empty
line, which is what causes that 400 response. line, which is what causes that 400 response.
<<Loop's REPL.lisp>>= <<Loop REPL.lisp>>=
(defun repl (r) (defun repl (r)
(in-package :loop) (in-package :loop)
(loop (loop
@ -594,9 +594,7 @@ else should we handle this?
<<Procedures for requests and responses.lisp>>= <<Procedures for requests and responses.lisp>>=
(defun my-write (ls-of-bytes s) (defun my-write (ls-of-bytes s)
(if (interactive-stream-p s) (write-sequence ls-of-bytes s))
(write-sequence (mapcar #'code-char ls-of-bytes) s)
(write-sequence ls-of-bytes s)))
@ %def my-write @ %def my-write
\section{Main loop} \section{Main loop}
@ -646,7 +644,10 @@ itself---so we can cascade actions based on a user's request.
(send-response! (send-response!
(make-response (make-response
:code 200 :code 200
:data "<<Welcome message>>"))) :data (welcome-message))))
(defun welcome-message ()
(cl-ppcre:regex-replace-all "HASH" "<<Welcome message>>" *git-hash*))
@ %def main @ %def main
\noindent It's always useful to know which version exactly we're \noindent It's always useful to know which version exactly we're
@ -660,7 +661,7 @@ dealing with:
So we put a release tag on \lp's banner. So we put a release tag on \lp's banner.
<<Welcome message>>= <<Welcome message>>=
Welcome! I am <<Name>> <<Version>>. Say ``help'' for a menu. Welcome! I am <<Name>> <<Version>> hash HASH. Say ``help'' for a menu.
@ @
\noindent We take the opportunity and describe \lp's package, \noindent We take the opportunity and describe \lp's package,
@ -675,7 +676,7 @@ An NNTP server for a circle of friends.
@ @
<<Version>>= <<Version>>=
a89e088 0.0.1
@ @
\section{Parsing of requests} \section{Parsing of requests}
@ -1902,6 +1903,15 @@ is cross-posting. First, notice that we're---so far---duplicating
articles on the file system. (We will undo that once we reimplement articles on the file system. (We will undo that once we reimplement
our index. More to follow.) our index. More to follow.)
{\bf Note to self}. As professor G.~has written, we'll need to
redesign [[post]] to take two arguments, the article in [[tmp-path]] (its
first argument) and then [[headers-raw]] (its second argument). We
then parse the headers with [[parse-headers]] so that we can grab the
newsgroups to which the article is to be posted so that we handle the
job to [[save-article-insist]]. Of course, we'll need to get a
message-id as well, but let's worry about that later. (Yeah, we need
to optimize [[get-next-article-id]].)
<<Command post.lisp>>= <<Command post.lisp>>=
(defun post (bs) (defun post (bs)
(let* ((parsed (parse-article bs)) (let* ((parsed (parse-article bs))
@ -2149,18 +2159,15 @@ productivity and education.
<<Command post.lisp>>= <<Command post.lisp>>=
(defun nntp-read-line-buffered (stream buffer) (defun nntp-read-line-buffered (stream buffer)
(if (interactive-stream-p stream) (let* ((data (nntp-stream-buffer-data buffer))
(get-interactive-line stream) (capacity (length data)))
(let* ((data (nntp-stream-buffer-data buffer)) (labels ((find-lf (start end)
(capacity (length data)))
(labels
((find-lf (start end)
(position 10 data :start start :end end)) (position 10 data :start start :end end))
(extract-line (lf-pos) (extract-line (lf-pos)
(let* ((start (nntp-stream-buffer-head buffer)) (let* ((start (nntp-stream-buffer-head buffer))
(end-line (if (and (> lf-pos start) ;; If the previous character is CR (13), strip it
(= (aref data (1- lf-pos)) 13)) (end-line (if (and (> lf-pos start) (= (aref data (1- lf-pos)) 13))
(1- lf-pos) ; strip the preceding cr (1- lf-pos)
lf-pos)) lf-pos))
(line (subseq data start end-line))) (line (subseq data start end-line)))
(setf (nntp-stream-buffer-head buffer) (1+ lf-pos)) (setf (nntp-stream-buffer-head buffer) (1+ lf-pos))
@ -2169,34 +2176,109 @@ productivity and education.
(let* ((head (nntp-stream-buffer-head buffer)) (let* ((head (nntp-stream-buffer-head buffer))
(tail (nntp-stream-buffer-tail buffer)) (tail (nntp-stream-buffer-tail buffer))
(remaining (- tail head))) (remaining (- tail head)))
;; 1. Prevent buffer saturation/overflow
(when (= remaining capacity) (when (= remaining capacity)
(stderr "line too long in the network buffer: [~A]" (stderr "line too long in the network buffer: [~A]" (subseq data head tail))
(subseq data head tail))
(error "no space left on the network buffer for the current line")) (error "no space left on the network buffer for the current line"))
;; There is space for more lines, but there's a
;; partial line waiting to be completed, so we'll ;; 2. Shift remaining bytes to the start (fast memmove)
;; shift it to the begining of the buffer, so we can
;; have more space (as %unix-read might produce more
;; data below).
(when (> remaining 0) (when (> remaining 0)
(replace data data :start1 0 :start2 head :end2 tail)) (replace data data :start1 0 :start2 head :end2 tail))
(setf (nntp-stream-buffer-head buffer) 0) (setf (nntp-stream-buffer-head buffer) 0)
(setf (nntp-stream-buffer-tail buffer) remaining) (setf (nntp-stream-buffer-tail buffer) remaining)
;; Do the actual reading: we'll block until bytes
;; arrive and return whatever the kernel has for us. ;; 3. Fetch data via the unified dispatch mechanism
(let* ((n (%unix-read stream data remaining (- capacity remaining))) (let* ((n (fetch-more-bytes stream data remaining (- capacity remaining)))
(new-tail (+ remaining n))) (new-tail (+ remaining n)))
(setf (nntp-stream-buffer-tail buffer) new-tail) (setf (nntp-stream-buffer-tail buffer) new-tail)
(let ((lf-pos (find-lf remaining new-tail))) (let ((lf-pos (find-lf remaining new-tail)))
(if lf-pos (if lf-pos
(extract-line lf-pos) (extract-line lf-pos)
(refill-and-extract-line))))))) ; Still no lf; keep reading. (refill-and-extract-line)))))))
;; The main program
(let ((lf-pos (find-lf (nntp-stream-buffer-head buffer) ;; --- Main flow ---
(nntp-stream-buffer-tail buffer)))) (let ((lf-pos (find-lf (nntp-stream-buffer-head buffer) (nntp-stream-buffer-tail buffer))))
(if lf-pos (if lf-pos
(extract-line lf-pos) ; line already in buffer; no i/o at all (extract-line lf-pos)
(refill-and-extract-line))))))) (refill-and-extract-line))))))
(defun nntp-read-line-buffered-old (stream buffer)
(let* ((data (nntp-stream-buffer-data buffer))
(capacity (length data)))
(labels
((find-lf (start end)
;; remember that LF = byte 10
(position 10 data :start start :end end))
(extract-line (lf-pos)
(let* ((start (nntp-stream-buffer-head buffer))
(end-line (if (and (> lf-pos start)
(= (aref data (1- lf-pos)) 13))
(1- lf-pos) ; strip the preceding cr
lf-pos))
(line (subseq data start end-line)))
(setf (nntp-stream-buffer-head buffer) (1+ lf-pos))
line))
(refill-and-extract-line ()
(let* ((head (nntp-stream-buffer-head buffer))
(tail (nntp-stream-buffer-tail buffer))
(remaining (- tail head)))
(when (= remaining capacity)
(stderr "line too long in the network buffer: [~A]"
(subseq data head tail))
(error "no space left on the network buffer for the current line"))
;; There is space for more lines, but there's a
;; partial line waiting to be completed, so we'll
;; shift it to the begining of the buffer, so we can
;; have more space (as %unix-read might produce more
;; data below).
(when (> remaining 0)
(replace data data :start1 0 :start2 head :end2 tail))
(setf (nntp-stream-buffer-head buffer) 0)
(setf (nntp-stream-buffer-tail buffer) remaining)
;; Do the actual reading: we'll block until bytes
;; arrive and return whatever the kernel has for us.
(let* ((n (%unix-read stream data remaining (- capacity remaining)))
(new-tail (+ remaining n)))
(setf (nntp-stream-buffer-tail buffer) new-tail)
(let ((lf-pos (find-lf remaining new-tail)))
(if lf-pos
(extract-line lf-pos)
(refill-and-extract-line))))))) ; Still no lf; keep reading.
;; The main program
(let ((lf-pos (find-lf (nntp-stream-buffer-head buffer)
(nntp-stream-buffer-tail buffer))))
(if lf-pos
(extract-line lf-pos) ; line already in buffer; no i/o at all
(refill-and-extract-line))))))
(defun fetch-more-bytes (stream buf start max-count)
;; Attempt the fast, native C-level POSIX read. If `fd-stream-of`
;; fails to find an underlying file descriptor (e.g., SLIME REPL
;; streams), it signals a TYPE-ERROR. We catch it and fallback.
;; This is kinda silly. Let's remove %portable-read because we
;; really don't care about the SMILE REPL, though we use it.
(handler-case (%unix-read stream buf start max-count)
(type-error ()
(%portable-read stream buf start max-count))))
(defun %portable-read (stream buf start max-count)
;; Blocks for the VERY FIRST byte to respect the wait-for-data contract,
;; preventing busy-waiting loops.
(let ((first-byte (read-byte stream nil :eof)))
(when (eq first-byte :eof)
(error 'end-of-file :stream stream))
(setf (aref buf start) first-byte)
;; Slurp any IMMEDIATELY available bytes to minimize looping overhead,
;; up to max-count. 'listen' returns T if a byte is ready on the wire.
(let ((count 1))
(loop while (and (< count max-count) (listen stream))
do (let ((b (read-byte stream nil :eof)))
(when (eq b :eof) (loop-finish))
(setf (aref buf (+ start count)) b)
(incf count)))
count)))
(defun %unix-read (stream buf start max-count) (defun %unix-read (stream buf start max-count)
;;; One POSIX read(2) on STREAM's underlying fd. ;;; One POSIX read(2) on STREAM's underlying fd.
@ -2230,6 +2312,11 @@ productivity and education.
Phew! That was a lot! Take heart, though: you've just gone through the Phew! That was a lot! Take heart, though: you've just gone through the
most complicated task of \Lp. All the rest is a total breeze. most complicated task of \Lp. All the rest is a total breeze.
{\bf Note to self}. Let's not allow a large line. If a line is too
large, we should just truncate it. Also, let's try to avoid this
branching out. We can probably find a solution that's general enough
for both terminal and socket.
<<Command post.lisp>>= <<Command post.lisp>>=
(defun get-interactive-line (stream) (defun get-interactive-line (stream)
(let ((line-bytes (let ((line-bytes
@ -3183,7 +3270,7 @@ something to think about.
<<Table of commands.lisp>> <<Table of commands.lisp>>
<<Macros.lisp>> <<Macros.lisp>>
<<Little procedures.lisp>> <<Little procedures.lisp>>
<<Loop's REPL.lisp>> <<Loop REPL.lisp>>
<<Procedures for requests and responses.lisp>> <<Procedures for requests and responses.lisp>>
<<Essential operations relative to the index.lisp>> <<Essential operations relative to the index.lisp>>
<<How to create and connect to the index.lisp>> <<How to create and connect to the index.lisp>>
@ -3245,7 +3332,8 @@ The \lp\ system definition:
:version "<<Version>>" :version "<<Version>>"
:description "<<Description>>" :description "<<Description>>"
:depends-on (<<List of packages to be loaded.lisp>>) :depends-on (<<List of packages to be loaded.lisp>>)
:components ((:file "loop"))) :components ((:file "loop")
(:file "version")))
@ @
\section{Other source code} \section{Other source code}
@ -3267,7 +3355,7 @@ finds our definitions of interest and inserts a new blank line before
the definition. Such action makes function definitions separated by the definition. Such action makes function definitions separated by
two blank lines in some cases. We then remove the excess with the two blank lines in some cases. We then remove the excess with the
second program. Notice we need the {\tt -E} option because we're second program. Notice we need the {\tt -E} option because we're
using the {\tt |} metacharacter. using the {\tt \textbar} metacharacter.
The second program find a blank line as its first step. Then we say The second program find a blank line as its first step. Then we say
{\tt N} to expand the pattern space to include the next line. Then we {\tt N} to expand the pattern space to include the next line. Then we

0
make-release Normal file → Executable file
View file

989
noweb.sty
View file

@ -1,989 +0,0 @@
% noweb.sty -- LaTeX support for noweb
% DON'T read or edit this file! Use ...noweb-source/tex/support.nw instead.
{\obeyspaces\AtBeginDocument{\global\let =\ }} % from texbook, p 381
\def\nwopt@nomargintag{\let\nwmargintag=\@gobble}
\def\nwopt@margintag{%
\def\nwmargintag##1{\leavevmode\llap{##1\kern\nwmarginglue\kern\codemargin}}}
\def\nwopt@margintag{%
\def\nwmargintag##1{\leavevmode\kern-\codemargin\nwthemargintag{##1}\kern\codemargin}}
\def\nwthemargintag#1{\llap{#1\kern\nwmarginglue}}
\nwopt@margintag
\newdimen\nwmarginglue
\nwmarginglue=0.3in
\def\nwtagstyle{\footnotesize\Rm}
% make \hsize in code sufficient for 88 columns
\setbox0=\hbox{\tt m}
\newdimen\codehsize
\codehsize=91\wd0 % 88 columns wasn't enough; I don't know why
\newdimen\codemargin
\codemargin=0pt
\newdimen\nwdefspace
\nwdefspace=\codehsize
% need to use \textwidth in {\LaTeX} to handle styles with
% non-standard margins (David Bruce). Don't know why we sometimes
% wanted \hsize. 27 August 1997.
%% \advance\nwdefspace by -\hsize\relax
\ifx\textwidth\undefined
\advance\nwdefspace by -\hsize\relax
\else
\advance\nwdefspace by -\textwidth\relax
\fi
\chardef\other=12
\def\setupcode{%
\chardef\\=`\\
\chardef\{=`\{
\chardef\}=`\}
\catcode`\$=\other
\catcode`\&=\other
\catcode`\#=\other
\catcode`\%=\other
\catcode`\~=\other
\catcode`\_=\other
\catcode`\^=\other
\catcode`\"=\other % fixes problem with german.sty
\obeyspaces\Tt
}
%\let\nwlbrace=\{
%\let\nwrbrace=\}
\def\nwendquote{\relax\ifhmode\spacefactor=1000 \fi}
{\catcode`\^^M=\active % make CR an active character
\gdef\newlines{\catcode`\^^M=\active % make CR an active character
\def^^M{\par\startline}}%
\gdef\eatline#1^^M{\relax}%
}
%%% DON'T \gdef^^M{\par\startline}}% in case ^^M appears in a \write
\def\startline{\noindent\hskip\parindent\ignorespaces}
\def\nwnewline{\ifvmode\else\hfil\break\leavevmode\hbox{}\fi}
\def\setupmodname{%
\catcode`\$=3
\catcode`\&=4
\catcode`\#=6
\catcode`\%=14
\catcode`\~=13
\catcode`\_=8
\catcode`\^=7
\catcode`\ =10
\catcode`\^^M=5
\let\nwlbrace\lbrace
\let\nwrbrace\rbrace
\let\{\nwlbrace
\let\}\nwrbrace
% bad news --- don't know what catcode to give "
\Rm}
\def\LA{\begingroup\maybehbox\bgroup\setupmodname\It$\langle$}
\def\RA{\/$\rangle$\egroup\endgroup}
\def\code{\leavevmode\begingroup\setupcode\newlines}
\def\edoc{\endgroup}
\let\maybehbox\relax
\newbox\equivbox
\setbox\equivbox=\hbox{$\equiv$}
\newbox\plusequivbox
\setbox\plusequivbox=\hbox{$\mathord{+}\mathord{\equiv}$}
% \moddef can't have an argument because there might be \code...\edoc
\def\moddef{\leavevmode\kern-\codemargin\LA}
\def\endmoddef{\RA\ifmmode\equiv\else\unhcopy\equivbox\fi
\nobreak\hfill\nobreak}
\def\plusendmoddef{\RA\ifmmode\mathord{+}\mathord{\equiv}\else\unhcopy\plusequivbox\fi
\nobreak\hfill\nobreak}
\def\chunklist{%
\errhelp{I changed \chunklist to \nowebchunks.
I'll try to avoid such incompatible changes in the future.}%
\errmessage{Use \string\nowebchunks\space instead of \string\chunklist}}
\def\nowebchunks{\message{<Warning: You need noweave -x to use \string\nowebchunks>}}
\def\nowebindex{\message{<Warning: You need noweave -index to use \string\nowebindex>}}
% here is support for the new-style (capitalized) font-changing commands
% thanks to Dave Love
\ifx\documentstyle\undefined
\let\Rm=\rm \let\It=\it \let\Tt=\tt % plain
\else\ifx\selectfont\undefined
\let\Rm=\rm \let\It=\it \let\Tt=\tt % LaTeX OFSS
\else % LaTeX NFSS
\def\Rm{\reset@font\rm}
\def\It{\reset@font\it}
\def\Tt{\reset@font\tt}
\def\Bf{\reset@font\bf}
\fi\fi
\ifx\reset@font\undefined \let\reset@font=\relax \fi
\def\noweboptions#1{%
\def\@nwoptionlist{#1}%
\@for\@nwoption:=\@nwoptionlist\do{%
\@ifundefined{nwopt@\@nwoption}{%
\@latexerr{There is no such noweb option as '\@nwoption'}\@eha}{%
\csname nwopt@\@nwoption\endcsname}}}
\codemargin=10pt
\advance\codehsize by \codemargin % make room for indentation of code
\advance\nwdefspace by \codemargin % and fix adjustment for def/use
\def\setcodemargin#1{%
\advance\codehsize by -\codemargin % make room for indentation of code
\advance\nwdefspace by -\codemargin % and fix adjustment for def/use
\codemargin=#1
\advance\codehsize by \codemargin % make room for indentation of code
\advance\nwdefspace by \codemargin % and fix adjustment for
% def/use
}
\def\nwopt@shift{%
\dimen@=-0.8in
\if@twoside % Values for two-sided printing:
\advance\evensidemargin by \dimen@
\else % Values for one-sided printing:
\advance\evensidemargin by \dimen@
\advance\oddsidemargin by \dimen@
\fi
% \advance \marginparwidth -\dimen@
}
\let\nwopt@noshift\@empty
\def\nwbegincode#1{%
\begingroup
\topsep \nwcodetopsep
\@beginparpenalty \@highpenalty
\@endparpenalty -\@highpenalty
\@begincode }
\def\nwendcode{\endtrivlist \endgroup \filbreak} % keeps code on 1 page
\newenvironment{webcode}{%
\@begincode
}{%
\endtrivlist}
\def\@begincode{%
\trivlist \item[]%
\leftskip\@totalleftmargin \advance\leftskip\codemargin
\rightskip\hsize \advance\rightskip -\codehsize
\parskip\z@ \parindent\z@ \parfillskip\@flushglue
\linewidth\codehsize
\@@par
\def\par{\leavevmode\null \@@par \penalty\nwcodepenalty}%
\obeylines
\@noligs \ifx\verbatim@nolig@list\undefined\else
\let\do=\nw@makeother \verbatim@nolig@list \do@noligs\`
\fi
\setupcode \frenchspacing \@vobeyspaces
\nowebsize \setupcode
\let\maybehbox\mbox }
\newskip\nwcodetopsep \nwcodetopsep = 3pt plus 1.2pt minus 1pt
\let\nowebsize=\normalsize
\def\nwopt@tinycode{\let\nowebsize=\tiny}
\def\nwopt@footnotesizecode{\let\nowebsize=\footnotesize}
\def\nwopt@scriptsizecode{\let\nowebsize=\scriptsize}
\def\nwopt@smallcode{\let\nowebsize=\small}
\def\nwopt@normalsizecode{\let\nowebsize=\normalsize}
\def\nwopt@largecode{\let\nowebsize=\large}
\def\nwopt@Largecode{\let\nowebsize=\Large}
\def\nwopt@LARGEcode{\let\nowebsize=\LARGE}
\def\nwopt@hugecode{\let\nowebsize=\huge}
\def\nwopt@Hugecode{\let\nowebsize=\Huge}
\newcount\nwcodepenalty \nwcodepenalty=\@highpenalty
\def\nw@makeother#1{\catcode`#1=12 }
\def\nwbegindocs#1{\ifvmode\noindent\fi}
\let\nwenddocs=\relax
\let\nwdocspar=\filbreak
\def\@nwsemifilbreak#1{\vskip0pt plus#1\penalty-200\vskip0pt plus -#1}
\newdimen\nwbreakcodespace
\nwbreakcodespace=0.2in % by default, leave no more than this on a page
\def\nwopt@breakcode{%
\def\nwdocspar{\@nwsemifilbreak{0.2in}}%
\def\nwendcode{\endtrivlist\endgroup} % ditches filbreak
}
\raggedbottom
\def\code{\leavevmode\begingroup\setupcode\@vobeyspaces\obeylines}
\let\edoc=\endgroup
\newdimen\@original@textwidth
\def\ps@noweb{%
\@original@textwidth=\textwidth
\let\@mkboth\@gobbletwo
\def\@oddfoot{}\def\@evenfoot{}% No feet.
\if@twoside % If two-sided printing.
\def\@evenhead{\hbox to \@original@textwidth{%
\Rm \thepage\qquad{\Tt\leftmark}\hfil\today}}% Left heading.
\def\@oddhead{\hbox to \@original@textwidth{%
\Rm \today\hfil{\Tt\leftmark}\qquad\thepage}}% Right heading.
\else % If one-sided printing.
\def\@oddhead{\hbox to \@original@textwidth{%
\Rm \today\hfil{\Tt\leftmark}\qquad\thepage}}% Right heading.
\let\@evenhead\@oddhead
\fi
\let\chaptermark\@gobble
\let\sectionmark\@gobble
\let\subsectionmark\@gobble
\let\subsubsectionmark\@gobble
\let\paragraphmark\@gobble
\let\subparagraphmark\@gobble
\def\nwfilename{\begingroup\let\do\@makeother\dospecials
\catcode`\{=1 \catcode`\}=2 \nw@filename}
\def\nw@filename##1{\endgroup\markboth{##1}{##1}\let\nw@filename=\nw@laterfilename}%
}
\def\nw@laterfilename#1{\endgroup\clearpage \markboth{#1}{#1}}
\let\nwfilename=\@gobble
\def\nwcodecomment#1{\@@par\penalty\nwcodepenalty
\if@firstnwcodecomment
\vskip\nwcodecommentsep\penalty\nwcodepenalty\@firstnwcodecommentfalse
\fi%
\hspace{-\codemargin}{%
\rightskip=0pt plus1in
\interlinepenalty\nwcodepenalty
\let\\\relax\footnotesize\Rm #1\@@par\penalty\nwcodepenalty}}
\def\@nwalsodefined#1{\nwcodecomment{\@nwlangdepdef\ \nwpageprep\ \@pagesl{#1}.}}
\def\@nwused#1{\nwcodecomment{\@nwlangdepcud\ \nwpageprep\ \@pagesl{#1}.}}
\def\@nwnotused#1{\nwcodecomment{\@nwlangdeprtc.}}
\def\nwoutput#1{\nwcodecomment{\@nwlangdepcwf\ {\Tt \@stripstar#1*\stripped}.}}
\def\@stripstar#1*#2\stripped{#1}
\newcommand{\nwprevdefptr}[1]{%
\mbox{$\mathord{\triangleleft}\,\mathord{\mbox{\subpageref{#1}}}$}}
\newcommand{\nwnextdefptr}[1]{%
\mbox{$\mathord{\mbox{\subpageref{#1}}}\,\mathord{\triangleright}$}}
\newcommand{\@nwprevnextdefs}[2]{%
{\nwtagstyle
\ifx\relax#1\else ~~\nwprevdefptr{#1}\fi
\ifx\relax#2\else ~~\nwnextdefptr{#2}\fi}}
\newcommand{\@nwusesondefline}[1]{{\nwtagstyle~~(\@pagenumsl{#1})}}
\newcommand{\@nwstartdeflinemarkup}{\nobreak\hskip 1.5em plus 1fill\nobreak}
\newcommand{\@nwenddeflinemarkup}{\nobreak\hskip \nwdefspace minus\nwdefspace\nobreak}
\def\nwopt@longxref{%
\let\nwalsodefined\@nwalsodefined
\let\nwused\@nwused
\let\nwnotused\@nwnotused
\let\nwprevnextdefs\@gobbletwo
\let\nwusesondefline\@gobble
\let\nwstartdeflinemarkup\relax
\let\nwenddeflinemarkup\relax
}
\def\nwopt@shortxref{%
\let\nwalsodefined\@gobble
\let\nwused\@gobble
\let\nwnotused\@gobble
\let\nwprevnextdefs\@nwprevnextdefs
\let\nwusesondefline\@nwusesondefline
\let\nwstartdeflinemarkup\@nwstartdeflinemarkup
\let\nwenddeflinemarkup\@nwenddeflinemarkup
}
\def\nwopt@noxref{%
\let\nwalsodefined\@gobble
\let\nwused\@gobble
\let\nwnotused\@gobble
\let\nwprevnextdefs\@gobbletwo
\let\nwusesondefline\@gobble
\let\nwstartdeflinemarkup\relax
\let\nwenddeflinemarkup\relax
}
\nwopt@shortxref % to hell with backward compatibility!
\newskip\nwcodecommentsep \nwcodecommentsep=3pt plus 1pt minus 1pt
\newif\if@firstnwcodecomment\@firstnwcodecommenttrue
\newcount\@nwlopage\newcount\@nwhipage % range lo..hi-1
\newcount\@nwlosub % subpage of lo
\newcount\@nwhisub % subpage of hi
\def\@nwfirstpage#1#2#3{% subpage page xref-tag
\@nwlopage=#2 \@nwlosub=#1
\def\@nwloxreftag{#3}%
\advance\@nwpagecount by \@ne
\@nwhipage=\@nwlopage\advance\@nwhipage by \@ne }
\def\@nwnextpage#1#2#3{% subpage page xref-tag
\ifnum\@nwhipage=#2
\advance\@nwhipage by \@ne
\advance\@nwpagecount by \@ne
\@nwhisub=#1
\def\@nwhixreftag{#3}\else
\ifnum#2<\@nwlopage \advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\@nwfirstpage{#1}{#2}{#3}\else
\ifnum#2>\@nwhipage \advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\@nwfirstpage{#1}{#2}{#3}\else
\@nwlosub=0 \@nwhisub=0
\fi\fi\fi
}
\newcount\@nwpagetemp
\newcount\@nwpagecount
\def\@nwfirstpagel#1{% label
\@ifundefined{r@#1}{\@warning{Reference `#1' on page \thepage \space undefined}%
\nwix@cons\nw@pages{\\{\bf ??}}}{%
\edef\@tempa{\noexpand\@nwfirstpage\subpagepair{#1}{#1}}\@tempa}}
\def\@nwnextpagel#1{% label
\@ifundefined{r@#1}{\@warning{Reference `#1' on page \thepage \space undefined}%
\nwix@cons\nw@pages{\\{\bf ??}}}{%
\edef\@tempa{\noexpand\@nwnextpage\subpagepair{#1}{#1}}\@tempa}}
\def\@pagesl#1{% list of labels
\gdef\nw@pages{}\@nwpagecount=0
\def\\##1{\@nwfirstpagel{##1}\let\\=\@nwnextpagel}#1%
\advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\def\\##1{\@nwhyperpagenum##1}%
\ifnum\@nwpagecount=1 \nwpageword \else \nwpagesword\fi~\commafy{\nw@pages}}
\def\@nwhyperpagenum#1#2{\nwhyperreference{#2}{#1}}
\def\@pagenumsl#1{% list of labels -- doesn't include word `pages', commas, or `and'
\gdef\nw@pages{}\@nwpagecount=0
\def\\##1{\@nwfirstpagel{##1}\let\\=\@nwnextpagel}#1%
\advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa%
\def\\##1{\@nwhyperpagenum##1\let\\=\@nwpagenumslrest}\nw@pages}
\def\@nwpagenumslrest#1{~\@nwhyperpagenum#1}
\def\subpages#1{% list of {{subpage}{page}}
\gdef\nw@pages{}\@nwpagecount=0
\def\\##1{\edef\@tempa{\noexpand\@nwfirstpage##1{}}\@tempa
\def\\####1{\edef\@tempa{\noexpand\@nwnextpage####1}\@tempa}}#1%
\advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\def\\##1{\@firstoftwo##1}%
\ifnum\@nwpagecount=1 \nwpageword \else \nwpagesword\fi~\commafy{\nw@pages}}
\def\@nwaddrange{\advance\@nwhipage by \m@ne
\ifnum\@nwhipage=\@nwlopage
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}}%
\else
\count@=\@nwhipage \advance\count@ by \m@ne
\ifnum\count@=\@nwlopage % consecutive pages
\edef\@tempa{\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}%
{\@nwloxreftag}}%
\noexpand\noexpand\noexpand\\%
{{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}}
{\@nwhixreftag}}}%
\else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100
\ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else
\count@=\@nwlopage \divide\count@ by 100
\@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100
\ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi
\multiply\@nwpagetemp by 100
\advance \@nwhipage by -\@nwpagetemp
\edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}%
\fi
\fi
\fi%
\fi
\fi%
\edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa}
\def\nwpageword{\@nwlangdepchk} % chunk, was page
\def\nwpagesword{\@nwlangdepchks} % chunk, was page
\def\nwpageprep{\@nwlangdepin} % in, was on
\newcommand\nw@genericref[2]{% what to do, name of ref
\expandafter\nw@g@nericref\csname r@#2\endcsname#1{#2}}
\newcommand\nw@g@nericref[3]{% control sequence, what to do, name
\ifx#1\relax
\ref{#3}% trigger the standard `undefined ref' mechanisms
\else
\expandafter#2#1.\\%
\fi}
\def\nw@selectone#1#2#3\\{#1}
\def\nw@selecttwo#1#2#3\\{#2}
\def\nw@selectonetwo#1#2#3\\{{#1}{#2}}
\newcommand{\subpageref}[1]{%
\nwhyperreference{#1}{\nw@genericref\@subpageref{#1}}}
\def\@subpageref#1#2#3\\{%
\@ifundefined{2on#2}{#2}{\nwthepagenum{#1}{#2}}}
\newcommand{\subpagepair}[1]{% % produces {subpage}{page}
\@ifundefined{r@#1}%
{{0}{0}}%
{\nw@genericref\@subpagepair{#1}}}
\def\@subpagepair#1#2#3\\{%
\@ifundefined{2on#2}{{0}{#2}}{{#1}{#2}}}
\newcommand{\sublabel}[1]{%
\leavevmode % needed to make \@bsphack work
\@bsphack
\nwblindhyperanchor{#1}%
\if@filesw {\let\thepage\relax
\def\protect{\noexpand\noexpand\noexpand}%
\edef\@tempa{\write\@auxout{\string
\newsublabel{#1}{{}{\thepage}}}}%
\expandafter}\@tempa
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
\newcommand{\nosublabel}[1]{%
\@bsphack\if@filesw {\let\thepage\relax
\def\protect{\noexpand\noexpand\noexpand}%
\edef\@tempa{\write\@auxout{\string
\newlabel{#1}{{0}{\thepage}}}}%
\expandafter}\@tempa
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
\newcommand\newsublabel{%
\nw@settrailers
\global\let\newsublabel\@newsublabel
\@newsublabel}
\newcommand{\@newsublabel}[2]{%
\edef\this@page{\@cdr#2\@nil}%
\ifx\this@page\last@page\else
\sub@page=\z@
\fi
\edef\last@page{\this@page}
\advance\sub@page by \@ne
\ifnum\sub@page=\tw@
\global\@namedef{2on\this@page}{}%
\fi
\pendingsublabel{#1}%
\edef\@tempa##1{\noexpand\newlabel{##1}%
{{\number\sub@page}{\this@page}\nw@labeltrailers}}%
\pending@sublabels
\def\pending@sublabels{}}
\newcommand\nw@settrailers{% -- won't work on first run
\@ifpackageloaded{nameref}%
{\gdef\nw@labeltrailers{{}{}{}}}%
{\gdef\nw@labeltrailers{}}}
\renewcommand\nw@settrailers{%
\@ifundefined{@secondoffive}%
{\gdef\nw@labeltrailers{}}%
{\gdef\nw@labeltrailers{{}{}{}}}}
\newcommand{\nextchunklabel}[1]{%
\nwblindhyperanchor{#1}% % looks slightly bogus --- nr
\@bsphack\if@filesw {\let\thepage\relax
\edef\@tempa{\write\@auxout{\string\pendingsublabel{#1}}}%
\expandafter}\@tempa
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
\newcommand\pendingsublabel[1]{%
\def\@tempa{\noexpand\@tempa}%
\edef\pending@sublabels{\noexpand\@tempa{#1}\pending@sublabels}}
\def\pending@sublabels{}
\def\last@page{\relax}
\newcount\sub@page
\def\@alphasubpagenum#1#2{#2\ifnum#1=0 \else\@alph{#1}\fi}
\def\@nosubpagenum#1#2{#2}
\def\@numsubpagenum#1#2{#2\ifnum#1=0 \else.\@arabic{#1}\fi}
\def\nwopt@nosubpage{\let\nwthepagenum=\@nosubpagenum\nwopt@nomargintag}
\def\nwopt@numsubpage{\let\nwthepagenum=\@numsubpagenum}
\def\nwopt@alphasubpage{\let\nwthepagenum=\@alphasubpagenum}
\nwopt@alphasubpage
\newcount\@nwalph@n
\let\@nwalph@d\@tempcnta
\let\@nwalph@bound\@tempcntb
\def\@nwlongalph#1{{%
\@nwalph@n=#1\advance\@nwalph@n by-1
\@nwalph@bound=26
\loop\ifnum\@nwalph@n<\@nwalph@bound\else
\advance\@nwalph@n by -\@nwalph@bound
\multiply\@nwalph@bound by 26
\repeat
\loop\ifnum\@nwalph@bound>1
\divide\@nwalph@bound by 26
\@nwalph@d=\@nwalph@n\divide\@nwalph@d by \@nwalph@bound
% d := d * bound ; n -:= d; d := d / bound --- saves a temporary
\multiply\@nwalph@d by \@nwalph@bound
\advance\@nwalph@n by -\@nwalph@d
\divide\@nwalph@d by \@nwalph@bound
\advance\@nwalph@d by 1 \@alph{\@nwalph@d}%
\repeat
}}
\newcount\nw@chunkcount
\nw@chunkcount=\@ne
\newcommand{\weblabel}[1]{%
\@bsphack
\nwblindhyperanchor{#1}%
\if@filesw {\let\thepage\relax
\def\protect{\noexpand\noexpand\noexpand}%
\edef\@tempa{\write\@auxout{\string
\newsublabel{#1}{{}{\number\nw@chunkcount}}}}%
\expandafter}\@tempa
\global\advance\nw@chunkcount by \@ne
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
\def\nwopt@webnumbering{%
\let\sublabel=\weblabel
\def\nwpageword{chunk}\def\nwpagesword{chunks}%
\def\nwpageprep{in}}
% \nwindexdefn{printable name}{identifying label}{label of chunk}
% \nwindexuse{printable name}{identifying label}{label of chunk}
\def\nwindexdefn#1#2#3{\@auxix{\protect\nwixd}{#2}{#3}}
\def\nwindexuse#1#2#3{\@auxix{\protect\nwixu}{#2}{#3}}
\def\@auxix#1#2#3{% {marker}{id label}{subpage label}
\@bsphack\if@filesw {\let\nwixd\relax\let\nwixu\relax
\def\protect{\noexpand\noexpand\noexpand}%
\edef\@tempa{\write\@auxout{\string\nwixadd{#1}{#2}{#3}}}%
\expandafter}\@tempa
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
% \nwixadd{marker}{idlabel}{subpage label}
\def\nwixadd#1#2#3{%
\@ifundefined{nwixl@#2}%
{\global\@namedef{nwixl@#2}{#1{#3}}}%
{\expandafter\nwix@cons\csname nwixl@#2\endcsname{#1{#3}}}}
\def\@nwsubscriptident#1#2{\mbox{$\mbox{#1}_{\mathrm{\subpageref{#2}}}$}}
\def\@nwnosubscriptident#1#2{#1}
\def\@nwhyperident#1#2{\leavevmode\nwhyperreference{#2}{#1}}
\def\nwopt@subscriptidents{%
\let\nwlinkedidentq\@nwsubscriptident
\let\nwlinkedidentc\@nwsubscriptident
}
\def\nwopt@nosubscriptidents{%
\let\nwlinkedidentq\@nwnosubscriptident
\let\nwlinkedidentc\@nwnosubscriptident
}
\def\nwopt@hyperidents{%
\let\nwlinkedidentq\@nwhyperident
\let\nwlinkedidentc\@nwhyperident
}
\def\nwopt@nohyperidents{%
\let\nwlinkedidentq\@nwnosubscriptident
\let\nwlinkedidentc\@nwnosubscriptident
}
\def\nwopt@subscriptquotedidents{%
\let\nwlinkedidentq\@nwsubscriptident
}
\def\nwopt@nosubscriptquotedidents{%
\let\nwlinkedidentq\@nwnosubscriptident
}
\def\nwopt@hyperquotedidents{%
\let\nwlinkedidentq\@nwhyperident
}
\def\nwopt@nohyperquotedidents{%
\let\nwlinkedidentq\@nwnosubscriptident
}
\nwopt@hyperidents
\newcount\@commacount
\def\commafy#1{%
{\nwix@listcount{#1}\@commacount=\nwix@counter
\let\@comma@each=\\%
\ifcase\@commacount\let\\=\@comma@each\or\let\\=\@comma@each\or
\def\\{\def\\{ \@nwlangdepand\ \@comma@each}\@comma@each}\else
\def\\{\def\\{, %
\advance\@commacount by \m@ne
\ifnum\@commacount=1 \@nwlangdepand~\fi\@comma@each}\@comma@each}\fi
#1}}
\def\nwix@cons#1#2{% {list}{\marker{element}}
{\toks0=\expandafter{#1}\def\@tempa{#2}\toks2=\expandafter{\@tempa}%
\xdef#1{\the\toks0 \the\toks2 }}}
\def\nwix@uses#1{% {label}
\def\nwixu{\\}\let\nwixd\@gobble\@nameuse{nwixl@#1}}
\def\nwix@defs#1{% {label}
\def\nwixd{\\}\let\nwixu\@gobble\@nameuse{nwixl@#1}}
\newcount\nwix@counter
\def\nwix@listcount#1{% {list with \\}
{\count@=0
\def\\##1{\advance\count@ by \@ne }%
#1\global\nwix@counter=\count@ }}
\def\nwix@usecount#1{\nwix@listcount{\nwix@uses{#1}}}
\def\nwix@defcount#1{\nwix@listcount{\nwix@defs{#1}}}
\def\nwix@id@defs#1{% index pair
{{\Tt \@car#1\@nil}%
\def\\##1{\nwix@defs@space\subpageref{##1}}\nwix@defs{\@cdr#1\@nil}}}
% useful above to change ~ into something that can break
% this option is undocumented because I think breakdefs is always right
\def\nwopt@breakdefs{\def\nwix@defs@space{\penalty200\ }}
\def\nwopt@nobreakdefs{\def\nwix@defs@space{~}} % old code
\nwopt@breakdefs
\def\nwidentuses#1{% list of index pairs
\nwcodecomment{\@nwlangdepuss\ \let\\=\nwix@id@defs\commafy{#1}.}}
\def\nwix@totaluses#1{% list of index pairs
{\count@=0
\def\\##1{\nwix@usecount{\@cdr##1\@nil}\advance\count@ by\nwix@counter}%
#1\global\nwix@counter\count@ }}
\def\nwix@id@uses#1#2{% {ident}{label}
\nwix@usecount{#2}\ifnum\nwix@counter>0
{\advance\leftskip by \codemargin
\nwcodecomment{{\Tt #1}, \@nwlangdepusd\ \nwpageprep\ \@pagesl{\nwix@uses{#2}}.}}%
\else
\ifnw@hideunuseddefs\else
{\advance\leftskip by \codemargin \nwcodecomment{{\Tt #1}, \@nwlangdepnvu.}}%
\fi
\fi}
\def\nwidentdefs#1{% list of index pairs
\ifnw@hideunuseddefs\nwix@totaluses{#1}\else\nwix@listcount{#1}\fi
\ifnum\nwix@counter>0
\nwcodecomment{\@nwlangdepdfs:}%
{\def\\##1{\nwix@id@uses ##1}#1}%
\fi}
\newif\ifnw@hideunuseddefs\nw@hideunuseddefsfalse
\def\nwopt@hideunuseddefs{\nw@hideunuseddefstrue}
\def\nwopt@noidentxref{%
\let\nwidentdefs\@gobble
\let\nwidentuses\@gobble}
\def\nw@underlinedefs{% {list with \nwixd, \nwixu}
\let\\=\relax\def\nw@comma{, }
\def\nwixd##1{\\\underline{\subpageref{##1}}\let\\\nw@comma}%
\def\nwixu##1{\\\subpageref{##1}\let\\\nw@comma}}
\def\nw@indexline#1#2{%
{\indent {\Tt #1}: \nw@underlinedefs\@nameuse{nwixl@#2}\par}}
\newenvironment{thenowebindex}{\parindent=-10pt \parskip=\z@
\advance\leftskip by 10pt
\advance\rightskip by 0pt plus1in\par\@afterindenttrue
\def\\##1{\nw@indexline##1}}{}
\def\nowebindex{%
\@ifundefined{nwixs@i}%
{\@warning{The \string\nowebindex\space is empty}}%
{\begin{thenowebindex}\@nameuse{nwixs@i}\end{thenowebindex}}}
\def\nowebindex@external{%
{\let\nwixadds@c=\@gobble
\def\nwixadds@i##1{\nw@indexline##1}%
\def\nwixaddsx##1##2{\@nameuse{nwixadds@##1}{##2}}%
\begin{thenowebindex}\@input{\jobname.nwi}\end{thenowebindex}}}
\def\nwixlogsorted#1#2{% list data
\@bsphack\if@filesw
\toks0={#2}\immediate\write\@auxout{\string\nwixadds{#1}{\the\toks0}}
\if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack}
\def\nwixadds#1#2{%
\@ifundefined{nwixs@#1}%
{\global\@namedef{nwixs@#1}{\\{#2}}}%
{\expandafter\nwix@cons\csname nwixs@#1\endcsname{\\{#2}}}}
\let\nwixaddsx=\@gobbletwo
\def\nwopt@externalindex{%
\ifx\nwixadds\@gobbletwo % already called
\else
\let\nwixaddsx=\nwixadds \let\nwixadds=\@gobbletwo
\let\nowebindex=\nowebindex@external
\let\nowebchunks=\nowebchunks@external
\fi}
\def\nowebchunks{%
\@ifundefined{nwixs@c}%
{\@warning{The are no \string\nowebchunks}}%
{\begin{thenowebchunks}\@nameuse{nwixs@c}\end{thenowebchunks}}}
\def\nowebchunks@external{%
{\let\nwixadds@i=\@gobble
\def\nwixadds@c##1{\nw@onechunk##1}%
\def\nwixaddsx##1##2{\@nameuse{nwixadds@##1}{##2}}%
\begin{thenowebchunks}\@input{\jobname.nwi}\end{thenowebchunks}}}
\@namedef{r@nw@notdef}{{0}{(\@nwlangdepnvd)}}
\def\nw@chunkunderlinedefs{% {list of labels with \nwixd, \nwixu}
\let\\=\relax\def\nw@comma{, }
\def\nwixd##1{\\\underline{\subpageref{##1}}\let\\\nw@comma}%
\def\nwixu##1{\\\subpageref{##1}\let\\\nw@comma}}
\def\nw@onechunk#1#2#3{% {name}{label of first definition}{list with \nwixd, \nwixu}
\@ifundefined{r@#2}{}{%
\indent\LA #1~{\nwtagstyle\subpageref{#2}}\RA
\if@nwlongchunks{~\nw@chunkunderlinedefs#3}\fi\par}}
\newenvironment{thenowebchunks}{\vskip3pt
\parskip=\z@\parindent=-10pt \advance\leftskip by 10pt
\advance\rightskip by 0pt plus10pt \@afterindenttrue
\def\\##1{\nw@onechunk##1}}{}
\newif\if@nwlongchunks
\@nwlongchunksfalse
\let\nwopt@longchunks\@nwlongchunkstrue
\newcommand\@nw@hyper@ref{\hyperreference} % naras
\newcommand\@nw@hyper@anc{\blindhyperanchor} % naras
\newcommand\@nw@hyperref@ref[2]{\hyperlink{noweb.#1}{#2}} % nr
\newcommand\@nw@hyperref@anc[1]{\hypertarget{noweb.#1}{\relax}} % nr
%%\renewcommand\@nw@hyperref@ref[2]{{#2}} % nr
%%\renewcommand\@nw@hyperref@anc[1]{} % nr
\newcommand\nwhyperreference{%
\@ifundefined{hyperlink}
{\@ifundefined{hyperreference}
{\global\let\nwhyperreference\@gobble}
{\global\let\nwhyperreference\@nw@hyper@ref}}
{\global\let\nwhyperreference\@nw@hyperref@ref}%
\nwhyperreference
}
\newcommand\nwblindhyperanchor{%
\@ifundefined{hyperlink}
{\@ifundefined{hyperreference}
{\global\let\nwblindhyperanchor\@gobble}
{\global\let\nwblindhyperanchor\@nw@hyper@anc}}
{\global\let\nwblindhyperanchor\@nw@hyperref@anc}%
\nwblindhyperanchor
}
\newcommand\nwanchorto{%
\begingroup\let\do\@makeother\dospecials
\catcode`\{=1 \catcode`\}=2 \nw@anchorto}
\newcommand\nw@anchorto[1]{\endgroup\def\nw@next{#1}\nw@anchortofin}
\newcommand\nw@anchortofin[1]{#1\footnote{See URL \texttt{\nw@next}.}}
\let\nwanchorname\@gobble
\newif\ifhtml
\htmlfalse
\let\nwixident=\relax
\def\nwbackslash{\char92}
\def\nwlbrace{\char123}
\def\nwrbrace{\char125}
\def\nwopt@english{%
\def\@nwlangdepdef{This definition is continued}%
\def\@nwlangdepcud{This code is used}%
\def\@nwlangdeprtc{Root chunk (not used in this document)}%
\def\@nwlangdepcwf{This code is written to file}%
\def\@nwlangdepchk{chunk}%
\def\@nwlangdepchks{chunks}%
\def\@nwlangdepin{in}%
\def\@nwlangdepand{and}%
\def\@nwlangdepuss{Uses}%
\def\@nwlangdepusd{used}%
\def\@nwlangdepnvu{never used}%
\def\@nwlangdepdfs{Defines}%
\def\@nwlangdepnvd{never defined}%
}
\let\nwopt@american\nwopt@english
\def\nwopt@portuges{%
\def\@nwlangdepdef{Defini\c{c}\~ao continuada em}%
% This definition is continued
\def\@nwlangdepcud{C\'odigo usado em}%
% This code is used
\def\@nwlangdeprtc{Fragmento de topo (sem uso no documento)}%
% Root chunk (not used in this document)
\def\@nwlangdepcwf{Este c\'odigo foi escrito no ficheiro}%
% This code is written to file
\def\@nwlangdepchk{fragmento}%
% chunk
\def\@nwlangdepchks{fragmentos}%
% chunks
\def\@nwlangdepin{no(s)}%
% in
\def\@nwlangdepand{e}%
% and
\def\@nwlangdepuss{Usa}%
% Uses
\def\@nwlangdepusd{usado}%
% used
\def\@nwlangdepnvu{nunca usado}%
% never used
\def\@nwlangdepdfs{Define}%
% Defines
\def\@nwlangdepnvd{nunca definido}%
% never defined
}
\def\nwopt@brazil{%
\def\@nwlangdepdef{Defini\c{c}\~{a}o continua}%
% This definition is continued
\def\@nwlangdepcud{C\'odigo usado}%
% This code is used
\def\@nwlangdeprtc{Trecho raiz, isto é, este trecho come\c{c}a aqui}%
% Root chunk (not used in this document)
\def\@nwlangdepcwf{C\'odigo escrito no arquivo}%
% This code is written to file
\def\@nwlangdepchk{trecho}%
% chunk
\def\@nwlangdepchks{trechos}%
% chunks
\def\@nwlangdepin{@}%
% in
\def\@nwlangdepand{e}%
% and
\def\@nwlangdepuss{Usa}%
% Uses
\def\@nwlangdepusd{usado}%
% used
\def\@nwlangdepnvu{nunca usado}%
% never used
\def\@nwlangdepdfs{Define}%
% Defines
\def\@nwlangdepnvd{nunca definido}%
% never defined
}
\def\nwopt@frenchb{%
\def\@nwlangdepdef{Suite de la d\'efinition}%
% This definition is continued
\def\@nwlangdepcud{Ce code est employ\'e}%
% This code is used
\def\@nwlangdeprtc{Morceau racine (pas employ\'e dans ce document)}%
% Root chunk (not used in this document)
\def\@nwlangdepcwf{Ce code est \'ecrit dans le fichier}%
% This code is written to file
\def\@nwlangdepchk{le morceau}%
% chunk
\def\@nwlangdepchks{les morceaux}%
% chunks
\def\@nwlangdepin{dans}%
% in
\def\@nwlangdepand{et}%
% and
\def\@nwlangdepuss{Utilise}%
% Uses
\def\@nwlangdepusd{utilis\'{e}}%
% used
\def\@nwlangdepnvu{jamais employ\'{e}}%
% never used
\def\@nwlangdepdfs{D\'{e}finit}%
% Defines
% Cannot use the accent here: \def\@nwlangdepnvd{jamais d\'{e}fini}%
\def\@nwlangdepnvd{jamais defini}%
% never defined
}
\let\nwopt@french\nwopt@frenchb
\def\nwopt@german{%
\def\@nwlangdepdef{Diese Definition wird fortgesetzt}%
% This definition is continued
\def\@nwlangdepcud{Dieser Code wird benutzt}%
% This code is used
\def\@nwlangdeprtc{Hauptteil (nicht in diesem Dokument benutzt)}%
% Root chunk (not used in this document)
%\def\@nwlangdepcwf{Dieser Code schreibt man zum File}%
\def\@nwlangdepcwf{Dieser Code geht in Datei}%
% This code is written to file
\def\@nwlangdepchk{dem Teil}%
% chunk
%\def\@nwlangdepchks{Teils}%
\def\@nwlangdepchks{den Teilen}%
% chunks
\def\@nwlangdepin{in}%
% in
\def\@nwlangdepand{und}%
% and
\def\@nwlangdepuss{Benutzt}%
% Uses
\def\@nwlangdepusd{benutzt}%
% used
\def\@nwlangdepnvu{nicht benutzt}%
% never used
\def\@nwlangdepdfs{Definiert}%
% Defines
\def\@nwlangdepnvd{nicht definiert}%
% never defined
}
\def\nwopt@german{%
\def\@nwlangdepdef{Diese Definition wird fortgesetzt}%
% This definition is continued
\def\@nwlangdepcud{Dieser Code wird benutzt}%
% This code is used
\def\@nwlangdeprtc{Hauptteil (nicht in diesem Dokument benutzt)}%
% Root chunk (not used in this document)
\def\@nwlangdepcwf{Dieser Code schreibt man zum File}%
% This code is written to file
\def\@nwlangdepchk{Teil}%
% chunk
\def\@nwlangdepchks{Teils}%
% chunks
\def\@nwlangdepin{im}%
% in
\def\@nwlangdepand{und}%
% and
\def\@nwlangdepuss{Benutzt}%
% Uses
\def\@nwlangdepusd{benutzt}%
% used
\def\@nwlangdepnvu{nicht benutzt}%
% never used
\def\@nwlangdepdfs{Definiert}%
% Defines
\def\@nwlangdepnvd{nicht definiert}%
% never defined
}
\let\nwopt@ngerman\nwopt@german
\ifx\languagename\undefined % default is English
\noweboptions{english}
\else
\@ifundefined{nwopt@\languagename}
{\noweboptions{english}}
{\expandafter\noweboptions\expandafter{\languagename}}
\fi
\let\obeyedspace\@xobeysp