1. Christian Kellermann
  2. schemeweb

Commits

Christian Kellermann  committed 77257d5

Add documentation in form of README

  • Participants
  • Parent commits ba4e223
  • Branches master

Comments (0)

Files changed (1)

File README.html

View file
  • Ignore whitespace
+<HTML><HEAD>
+<TITLE>schemeweb</TITLE>
+</HEAD><BODY><FONT FACE="ARIAL">
+<P>
+<H1>SchemeWeb</H1>
+</P>
+<P>
+Literate programming is the art of writing computer programs in such a way that
+the source code is presented clearly and engagingly for a human reader while
+still making it available to the compiler.  Literate programming embeds source
+code within its technical documentation; it permits the code to be presented in
+an order that suits the human reader, then re-orders the code for compilation.
+</P>
+<P>
+This note describes SchemeWeb, a literate programming system specialized for
+the Scheme programming language.
+</P>
+<P>
+<H2> Literate Programming</H2>
+</P>
+<P>
+Donald Knuth invented literate programming during the development of his TeX
+typesetting program in 1981 in response to a challenge from Tony Hoare to
+publish the source code to TeX.  Knuth admits to being terrified at the
+prospect of publishing his code, but, drawing on the ideas of <I>holon</I>
+programming developed by Pierre Arnoul de Marneffe, Knuth developed the
+original WEB system that combined program code and technical documentation
+into a single document that provided a clear, engaging description to a human
+reader and simultaneously provided source code to the compiler.
+</P>
+<P>
+Knuth suggests that the structure of a software program may be thought of as a
+<I>web</I> made up of many interconnected pieces.  The documentation of the
+program describes each piece of the web individually, specifying how it relates
+to its neighbors.  That description is provided in prose form, for the human
+reader, and in code form, for the compiler; the combination of the two forms a
+whole that is greater than the sum of its parts.
+</P>
+<P>
+The prose description of the software program proceeds in an order that makes
+sense to the writer (and hopefully conveys that sense to the reader).  In most
+cases, compilers have rules about the order in which they must see the various
+parts of a program that conflict with the order that the author chooses for
+explication.  Thus, literate programming systems provide a means of
+automatically extracting the source code from the web and re-ordering it for
+the compiler; the program that does this is called <I>tangle</I>, because it
+takes the neatly-ordered document and jumbles it into a mess that only a
+compiler could understand.  At the same time, a <I>weave</I> program reformats
+the web using the available tools of a typesetter, intertwining prose and code
+to produce pleasing documentation for the human reader of the web; the name
+suggests taking the individual strands from a web and using them to produce
+silken cloth.
+</P>
+<P>
+<H2>SchemeWeb</H2>
+</P>
+<P>
+SchemeWeb is a minimal literate programming system that provides two features
+specifically intended to simplify programming in Scheme.  First, a chunk
+beginning with a left-parenthesis or semi-colon is automatically taken to be
+Scheme code; there is no need to mark the code in any way.  A convenient
+side-effect of this feature is that a file containing only Scheme code is a
+valid SchemeWeb file, allowing SchemeWeb source files and traditional Scheme
+code files to co-exist gracefully.  Second, each input file expands to a single
+load-object, so it may be loaded directly into a running top-level with a
+single command.  Thus, SchemeWeb is trivially easy to adopt; just write a
+SchemeWeb source file where you would otherwise use a traditional Scheme code
+file.
+</P>
+<P>
+<H3>Literate Scheme source files</H3>
+</P>
+<P>
+The input file for a SchemeWeb program has extension </FONT><FONT FACE="COURIER">.lss</FONT><FONT FACE="ARIAL"> and consists of a
+series of chunks, which are a maximal sequence of non-empty lines separated by
+a maximal sequence of empty lines.
+</P>
+<P>
+Text chunks are blocks of prose that describe the code or otherwise help the
+user understand the program.  Text chunks are important to the human reader of
+a literate program, but are ignored by the compiler that processes it.
+</P>
+<P>
+Unnamed code chunks are chunks distinguished by having a left-parenthesis or
+semi-colon as the first non-whitespace character on their first line.  Unnamed
+code chunks contain Scheme source code, and are extracted into the tangled
+output in the order they appear in the web input.  Unnamed code chunks can also
+contain references to named code chunks written as the name enclosed within
+chevrons: <FONT FACE="COURIER">&lt;&lt;chunk name&gt;&gt;<FONT FACE="ARIAL">.
+</P>
+<P>
+Named code chunks are chunks whose first line consists of a chevron-quoted
+chunk-name followed by an equals-sign, like this:
+</P>
+<P>
+<FONT FACE="COURIER">
+<PRE>
+    &lt;&lt;chunk name&gt;&gt;=
+</PRE>
+<FONT FACE="ARIAL">
+</P>
+<P>
+The line may have leading or trailing whitespace; any whitespace between the
+chevrons is significant.  The remaining lines of a named chunk are its body.
+</P>
+<P>
+A SchemeWeb program is converted to Scheme code by ignoring text chunks,
+concatenating unnamed code chunks in order, and replacing references to named
+code chunks with their body text.  The chunk-replacement process is recursive;
+if a named code chunk refers in its body text to another named code chunk, the
+referenced chunk will appear in place of the reference.  As a consequence, the
+code chunks form a directed acyclic graphy, rooted at the unnamed code chunk.
+A named code chunk may be introduced in pieces; all of the named code chunks
+with the same name are concatenated into a single body text in the order they
+appear in the input.
+</P>
+<P>
+As an aid to the formatter, text chunks may contain display code within
+double-brackets, such as
+<FONT FACE="COURIER">&#91;&#91;(cons obj rest)&#93;&#93;<FONT FACE="ARIAL">.
+The formatter treats such code by formatting it in a monospace font, as in this
+note.  Because a chunk is only code if its first line is a named reference or
+if it begins with a left-parenthesis or semi-colon, it is possible to include
+lengthy blocks of display code in a literate program (or temporarily
+comment-out a block of code) by writing a block of code with double-brackets on
+the first and last lines, like this:
+</P>
+<P>
+<PRE><FONT FACE="COURIER">&#91;&#91;
+(multiple lines
+          (of)
+          (display code))
+&#93;&#93;<FONT FACE="ARIAL"></PRE>
+</P>
+<P>
+which will be displayed as:
+</P>
+<P>
+<PRE><FONT FACE="COURIER">| (multiple lines
+|           (of)
+|           (display code))<FONT FACE="ARIAL"</PRE>
+</P>
+<P>
+SchemeWeb weaves its output into html.  Text chunks are displayed in Arial,
+code (whether in chunks or in display) is displayed in Courier, and display
+chunks are offset to the right of a vertical line to show they are not part of
+the tangled output.  Other formatting commands may be passed to the browser by
+embedding html tags into text chunks.
+</P>
+<P>
+This note is itself a SchemeWeb literate Scheme source file, and contains the
+complete source code of the SchemeWeb literate programming system.
+</P>
+<P>
+<H3></FONT><FONT FACE="COURIER">Tangle</FONT><FONT FACE="ARIAL">, </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">, and </FONT><FONT FACE="COURIER">lload</FONT><FONT FACE="ARIAL"></H3>
+</P>
+<P>
+SchemeWeb provides three user-callable procedures.
+</P>
+<P>
+The </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> procedure </FONT><FONT FACE="COURIER">(tangle input output)</FONT><FONT FACE="ARIAL"> takes two arguments and
+returns a string; it may write a file or port as a side-effect.   Both
+arguments are optional; if </FONT><FONT FACE="COURIER">output</FONT><FONT FACE="ARIAL"> is given, </FONT><FONT FACE="COURIER">input</FONT><FONT FACE="ARIAL"> must also be given.
+Both arguments may contain either ports or strings containing filenames.  If
+the arguments are given as strings, the corresponding ports are opened; if the
+filename extension is omitted from the input filename, it defaults to </FONT><FONT FACE="COURIER">.lss</FONT><FONT FACE="ARIAL">,
+and if the filename extension is omitted from the output filename, it defaults
+to </FONT><FONT FACE="COURIER">.ss</FONT><FONT FACE="ARIAL">.  If no input is specified, input is taken from the current input
+port.  Output is written to a file or port only if the output argument is
+specified; regardless, the tangled output is returned as the value of the
+function.
+</P>
+<P>
+The </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL"> procedure </FONT><FONT FACE="COURIER">(weave input)</FONT><FONT FACE="ARIAL"> takes a single argument which may be
+either a port or a string containing a filename.  If the argument is given as a
+string, the corresponding port is opened; if the filename extension is omitted
+from the input filename; it defaults to </FONT><FONT FACE="COURIER">.lss</FONT><FONT FACE="ARIAL">.  </FONT><FONT FACE="COURIER">Weave</FONT><FONT FACE="ARIAL"> writes output to a
+file whose name is constructed using the same basename as the input file and
+extension </FONT><FONT FACE="COURIER">.html</FONT><FONT FACE="ARIAL">, and returns no value.
+</P>
+<P>
+The </FONT><FONT FACE="COURIER">lload</FONT><FONT FACE="ARIAL"> procedure </FONT><FONT FACE="COURIER">(lload filename)</FONT><FONT FACE="ARIAL"> takes a single string argument and
+returns nothing; it loads the Scheme source code in a SchemeWeb literate Scheme
+source file into a running Scheme top-level as a side-effect.  If the filename
+extension is omitted from the input filename, it defaults to </FONT><FONT FACE="COURIER">.lss</FONT><FONT FACE="ARIAL">.
+</FONT><FONT FACE="COURIER">Lload</FONT><FONT FACE="ARIAL"> calls </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> to extract the Scheme source code from the </FONT><FONT FACE="COURIER">.lss</FONT><FONT FACE="ARIAL">
+file.
+</P>
+<P>
+The </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL"> procedure provided by SchemeWeb is considerably less featureful
+than the </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL"> function of other literate programming systems such as CWEB
+and </FONT><FONT FACE="COURIER">noweb</FONT><FONT FACE="ARIAL">.  As described in his Usenet posting cited in the references,
+the author believes that most working code is never published outside the text
+editor where it is written, so fancy weaving is seldom needed.  Other </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">s
+provide such features as code pretty-printing, indexing of variable and function
+names, and cross-referencing of chunk definitions and usages, but when the code
+is viewed from within the programmer's text editor, a simple search replaces all
+the indexing and cross-referencing features, and pretty-printing only causes
+confusion by making the on-screen and printed versions of the program look
+different.  In fact, the author believes that the point of diminishing returns
+is quickly reached when adding features to </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">, and it is unlikely any of
+those other features will ever be added to SchemeWeb.  In fact, the author
+disclaims any responsibility for </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL"> on the grounds he never uses it.
+</P>
+<P>
+<H2>The SchemeWeb implementation</H2>
+</P>
+<P>
+SchemeWeb provides the three procedures </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL">, </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">, and </FONT><FONT FACE="COURIER">lload</FONT><FONT FACE="ARIAL">.
+It also uses many local procedures, but these are defined within the three
+exported procedures to avoid namespace pollution.  The three procedures are
+described below.
+</P>
+<P>
+<H3></FONT><FONT FACE="COURIER">Tangle</FONT><FONT FACE="ARIAL"></H3>
+</P>
+<P>
+</FONT><FONT FACE="COURIER">Tangle</FONT><FONT FACE="ARIAL"> takes zero, one, or two arguments.
+</P>
+<P><FONT FACE="COURIER"><PRE>
+; TANGLE [INPUT [OUTPUT]] -- extract code, return as string, optionally write output
+(define (tangle . args)
+  </FONT><FONT FACE="ARIAL">&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;</FONT><FONT FACE="COURIER">
+  </FONT><FONT FACE="ARIAL">&laquo;functions used by </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL">&raquo;</FONT><FONT FACE="COURIER">
+  </FONT><FONT FACE="ARIAL">&laquo;main code for </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL">&raquo;</FONT><FONT FACE="COURIER">)
+</PRE></FONT><FONT FACE="ARIAL"></P>
+<P>
+The main code block of </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> sets up input and output ports as specified
+by the arguments, builds a dictionary of the chunks in the input, then calls
+</FONT><FONT FACE="COURIER">tangl</FONT><FONT FACE="ARIAL"> to write the tangled source code.
+</P>
+<P><PRE>&laquo;main code for </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+(let ((i (open-input (if (pair? args) (car args) (current-input-port)) ".lss"))
+      (o (open-output-string)))
+  (let ((dict (build i)))
+    (if (pair? dict)
+        (begin
+          (tangl "" dict "" o)
+          (close-input-port i)
+          (let ((s (get-output-string o)))
+            (close-output-port o)
+            (if (and (pair? args) (pair? (cdr args)))
+                (let ((o (open-output (cadr args) ".ss")))
+                  (display s o)
+                  (close-output-port o)))
+            s)))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+Tangling works in two phases.  First, the input is read, and code chunks, both
+named and unnamed, are stored in a dictionary; that work is done by the call to
+</FONT><FONT FACE="COURIER">build</FONT><FONT FACE="ARIAL">.  Then, a recursive process performs depth-first search through the
+code-chunk call-graph, writing code as it proceeds, starting at a chunk named
+</FONT><FONT FACE="COURIER">""</FONT><FONT FACE="ARIAL"> (the unnamed chunk); that work is done by the call to </FONT><FONT FACE="COURIER">tangl</FONT><FONT FACE="ARIAL">.  The
+dictionary is implemented as an association-list; a-lists have linear time
+complexity, but unless the input is very large, that will have little effect on
+the actual running time of the program.
+</P>
+<P>
+<H4>The recursive tangling phase</H4>
+</P>
+<P>
+</FONT><FONT FACE="COURIER">Tangl</FONT><FONT FACE="ARIAL"> is the recursive function that performs depth-first search through
+the code-chunk call-graph, writing output as it goes.  The four arguments to
+</FONT><FONT FACE="COURIER">tangl</FONT><FONT FACE="ARIAL"> are the name of the code-chunk to be tangled, the a-list that stores
+the code-chunk dictionary, the </FONT><FONT FACE="COURIER">indent</FONT><FONT FACE="ARIAL"> that preceeds each line, and the
+port on which output is written.  The </FONT><FONT FACE="COURIER">indent</FONT><FONT FACE="ARIAL"> is interesting; </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL">
+works hard to make its output look nice.  </FONT><FONT FACE="COURIER">Tangl</FONT><FONT FACE="ARIAL"> first looks up the code
+associated with the named chunk, then loops through each code line, parsing it
+to look for calls to additional code-chunks.  If it finds a chunk-call,
+</FONT><FONT FACE="COURIER">tangl</FONT><FONT FACE="ARIAL"> calls itself recursively to expand the code of the called chunk,
+being careful to set the </FONT><FONT FACE="COURIER">indent</FONT><FONT FACE="ARIAL">.
+</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; tangl name dict indent output -- write tangled output
+(define (tangl name dict indent output)
+  (let loop ((lines (cdr (assoc name dict))))
+    (call-with-values
+      (lambda () (parse-line (car lines)))
+      (lambda (prefix call-name suffix)
+        (display prefix output)
+        (if (and (not (string=? "" call-name))
+                 (assoc call-name dict))
+            (tangl call-name
+                   dict
+                   (make-string (+ (string-length indent)
+                                   (string-length prefix))
+                                #\space)
+                   output))
+        (cond ((not (string=? "" suffix)) (loop (cons suffix (cdr lines))))
+              ((pair? (cdr lines))
+                (newline output)
+                (display indent output)
+                (loop (cdr lines))))))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+</FONT><FONT FACE="COURIER">Tangl</FONT><FONT FACE="ARIAL"> calls </FONT><FONT FACE="COURIER">parse-line</FONT><FONT FACE="ARIAL"> to extract chunk-call references from code lines.
+</FONT><FONT FACE="COURIER">Parse-line</FONT><FONT FACE="ARIAL"> returns three values: the </FONT><FONT FACE="COURIER">prefix</FONT><FONT FACE="ARIAL"> that preceeds a chunk-call
+reference, the </FONT><FONT FACE="COURIER">call-name</FONT><FONT FACE="ARIAL"> of a chunk-call reference, and the </FONT><FONT FACE="COURIER">suffix</FONT><FONT FACE="ARIAL"> that
+follows a chunk-call reference.  If the code line has no chunk-call reference,
+the </FONT><FONT FACE="COURIER">call-name</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">suffix</FONT><FONT FACE="ARIAL"> will be empty strings.
+</P>
+<P><PRE>&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; parse-line line -- extract prefix, call-name, suffix from line
+(define (parse-line line)
+  (let* ((start (string-index line "&lt;&lt;" 0))
+         (end (if start (string-index line "&gt;&gt;" (+ start 2)) #f)))
+    (if (and start end)
+        (values (substring line 0 start)
+                (substring line (+ start 2) end)
+                (substring line (+ end 2) (string-length line)))
+        (values line "" ""))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+<H4>The dictionary building phase</H4>
+</P>
+<P>
+The </FONT><FONT FACE="COURIER">build</FONT><FONT FACE="ARIAL"> function takes an input port and returns a dictionary.  </FONT><FONT FACE="COURIER">Build</FONT><FONT FACE="ARIAL">
+loops over each chunk on the input port until it finds a </FONT><FONT FACE="COURIER">null?</FONT><FONT FACE="ARIAL"> chunk
+indicating the end of the input.  Code chunks, whether named or unnamed, are
+added to the dictionary; text chunks are ignored.  </FONT><FONT FACE="COURIER">Build</FONT><FONT FACE="ARIAL"> calls </FONT><FONT FACE="COURIER">get-name</FONT><FONT FACE="ARIAL">
+to extract the chunk name from the first line of named chunks.
+</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; build port -- build dictionary from port
+(define (build input)
+  (let loop ((par (read-par input)) (dict '()))
+    (cond ((null? par) dict)
+          ((unnamed? par) (loop (read-par input) (add-dict "" dict par)))
+          ((named? par) (loop (read-par input)
+                              (add-dict (get-name par) dict (cdr par))))
+          (else (loop (read-par input) dict)))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; get-name par -- extract name from first line of par
+(define (get-name par)
+  (let* ((line (car par))
+         (start (string-index line "&lt;&lt;" 0))
+         (end (string-index line "&gt;&gt;=" (+ start 2))))
+    (substring line (+ start 2) end)))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+The </FONT><FONT FACE="COURIER">add-dict</FONT><FONT FACE="ARIAL"> function adds a chunk to the dictionary.  It loops through the
+dictionary a-list, looking for a chunk with the appropriate name; if none
+exists, it creates a new entry in the a-list.  </FONT><FONT FACE="COURIER">Add-dict</FONT><FONT FACE="ARIAL"> calls </FONT><FONT FACE="COURIER">dedent</FONT><FONT FACE="ARIAL"> to
+remove leading space from the chunk, so that SchemeWeb authors can indent code
+chunks to make them more easily visible, if desired.
+</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; add-dict name dict lines -- append lines to name in dict, or create new name
+(define (add-dict name dict lines)
+  (if (null? dict)
+      (cons (cons name (dedent lines)) dict)
+      (let loop ((item (car dict)) (unscanned (cdr dict)) (scanned '()))
+        (cond ((string=? (car item) name)
+                (cons (append item (dedent lines))
+                      (append unscanned (reverse scanned))))
+              ((null? unscanned)
+                (cons (cons name (dedent lines)) (cons item (reverse scanned))))
+              (else (loop (car unscanned) (cdr unscanned) (cons item scanned)))))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+The </FONT><FONT FACE="COURIER">dedent</FONT><FONT FACE="ARIAL"> function takes a list of strings and removes leading whitespace
+they have in common.  </FONT><FONT FACE="COURIER">Dedent</FONT><FONT FACE="ARIAL"> looks at the first character of each string in
+the list, and if all strings have the same first character, each string will be
+shortened; </FONT><FONT FACE="COURIER">dedent</FONT><FONT FACE="ARIAL"> continues to call itself recursively until all leading
+whitespace has been removed.
+</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; dedent ls -- remove common indentation from a list of strings
+(define (dedent ls)
+  (define (all xs)
+    (or (null? xs)
+        (and (car xs)
+             (all (cdr xs)))))
+  (cond ((null? ls) ls)
+        ((null? (cdr ls)) (list (string-trim-left (car ls))))
+        ((and (&lt; 1 (length ls))
+         (all (map (lambda (s) (positive? (string-length s))) ls))
+         (char-whitespace? (string-ref (car ls) 0))
+         (apply char=? (map (lambda (s) (string-ref s 0)) ls)))
+           (dedent (map (lambda (s) (substring s 1 (string-length s))) ls)))
+        (else ls)))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+<H3></FONT><FONT FACE="COURIER">Weave</FONT><FONT FACE="ARIAL"></H3>
+</P>
+<P>
+Weave takes a single argument, the name of the </FONT><FONT FACE="COURIER">.lss</FONT><FONT FACE="ARIAL"> SchemeWeb literate
+Scheme source file.  The mainline code of </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL"> opens and closes needed
+files and calls </FONT><FONT FACE="COURIER">weeve</FONT><FONT FACE="ARIAL"> to do the actual work of creating output.
+</P>
+<P><FONT FACE="COURIER"><PRE>
+; WEAVE INPUT
+(define (weave input)
+  </FONT><FONT FACE="ARIAL">&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;</FONT><FONT FACE="COURIER">
+  </FONT><FONT FACE="ARIAL">&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;</FONT><FONT FACE="COURIER">
+  (let ((i (open-input input ".lss"))
+        (o (open-output (base-name input ".lss") ".html")))
+    (weeve (base-name input ".lss") i o)
+    (close-input-port i)
+    (close-output-port o)))
+</PRE></FONT><FONT FACE="ARIAL"></P>
+<P>
+</FONT><FONT FACE="COURIER">Weeve</FONT><FONT FACE="ARIAL"> takes the base-name of the file being woven (which it writes to the
+title of the </FONT><FONT FACE="COURIER">.html</FONT><FONT FACE="ARIAL"> output file, the input file, and the output file.  It
+writes a header and footer.  In between, it loops over each chunk, classifies
+it, and calls the appropriate function to write it to the output.
+</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; weeve input output
+(define (weeve name input output)
+  (display "&lt;HTML&gt;&lt;HEAD&gt;" output) (newline output)
+  (if (not (string=? "" name))
+      (begin
+        (display "&lt;TITLE&gt;" output) (display name output)
+        (display "&lt;/TITLE&gt;" output) (newline output)))
+  (display "&lt;/HEAD&gt;&lt;BODY&gt;&lt;FONT FACE=\"ARIAL\"&gt;" output) (newline output)
+  (let loop ((par (read-par input)))
+    (if (pair? par)
+        (begin (cond ((named? par) (write-named par output))
+                     ((unnamed? par) (write-unnamed par output))
+                     ((code? par) (write-code par output))
+                     ((pair? par) (write-text par output)))
+               (loop (read-par input)))))
+  (display "&lt;/FONT&gt;&lt;/BODY&gt;&lt;/HEAD&gt;&lt;/HTML&gt;" output) (newline output))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+<H4>Functions that write chunks</H4>
+</P>
+<P>
+There are four functions that write chunks, one for each type of chunk.  The
+three that write code change the font face to courier, but are careful to set
+the font face to arial before they leave, because that is the default font used
+to display text.  These procedures do all the setting of fonts and writing of
+newlines, but call other functions to actually write code and text.
+</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; write-named par output
+(define (write-named par output)
+  (display "&lt;P&gt;&lt;PRE&gt;" output)
+  (call-with-values
+    (lambda () (parse-line (car par)))
+    (lambda (prefix call-name suffix)
+      (display "&amp;laquo;" output)
+      (display-text call-name output)
+      (display "&amp;raquo;" output)
+      (display "&amp;equiv;" output)))
+  (display "&lt;/FONT&gt;&lt;FONT FACE=\"COURIER\"&gt;" output) (newline output)
+  (for-each (lambda (s) (display-code s output) (newline output)) (cdr par))
+  (display "&lt;/PRE&gt;&lt;/FONT&gt;&lt;FONT FACE=\"ARIAL\"&lt;/P&gt;" output) (newline output))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; write-unnamed par output
+(define (write-unnamed par output)
+  (display "&lt;P&gt;&lt;FONT FACE=\"COURIER\"&gt;&lt;PRE&gt;" output) (newline output)
+  (for-each (lambda (s) (display-code s output) (newline output)) par)
+  (display "&lt;/PRE&gt;&lt;/FONT&gt;&lt;FONT FACE=\"ARIAL\"&gt;&lt;/P&gt;" output) (newline output))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; write-code par output
+(define (write-code par output)
+  (display "&lt;P&gt;&lt;FONT FACE=\"COURIER\"&gt;&lt;PRE&gt;" output) (newline output)
+  (let loop ((par (cdr par)))
+    (display "| " output) (display-code (car par) output) (newline output)
+    (if (and (pair? (cdr par)) (pair? (cddr par))) (loop (cdr par))))
+  (display "&lt;/PRE&gt;&lt;/FONT&gt;&lt;FONT FACE=\"ARIAL\"&gt;&lt;/P&gt;" output) (newline output))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; write-text par output
+(define (write-text par output)
+  (display "&lt;P&gt;" output) (newline output)
+  (for-each (lambda (s) (display-text s output) (newline output)) par)
+  (display "&lt;/P&gt;" output) (newline output))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+<H4>Functions that write code and text</H4>
+</P>
+<P>
+These functions actually write code and text.  They are called in various
+contexts by the four functions that write chunks, and are careful to never
+write a newline, because various contexts may require that they are called
+within a line, not to write an entire line.
+</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; display-code line output
+(define (display-code line output)
+  (let loop ((line line))
+    (call-with-values
+      (lambda () (parse-line line))
+      (lambda (prefix call-name suffix)
+        (display (quote-html prefix) output)
+        (if (not (string=? "" call-name))
+            (begin (display "&lt;/FONT&gt;&lt;FONT FACE=\"ARIAL\"&gt;&amp;laquo;" output)
+                   (display-text call-name output)
+                   (display "&amp;raquo;&lt;/FONT&gt;&lt;FONT FACE=\"COURIER\"&gt;" output)))
+        (if (not (string=? "" suffix)) (loop suffix))))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; display-text line output
+(define (display-text line output)
+  (let loop ((line line))
+    (let* ((start (string-index line "[[" 0))
+           (end (if start (string-index line "]]" (+ start 2)) #f)))
+      (if (and start end)
+          (begin (display (substring line 0 start) output)
+                 (display "&lt;/FONT&gt;&lt;FONT FACE=\"COURIER\"&gt;" output)
+                 (display (quote-html (substring line (+ start 2) end)) output)
+                 (display "&lt;/FONT&gt;&lt;FONT FACE=\"ARIAL\"&gt;" output)
+                 (loop (substring line (+ end 2) (string-length line))))
+          (display line output)))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+These functions call a utility function </FONT><FONT FACE="COURIER">quote-html</FONT><FONT FACE="ARIAL"> that changes literal
+less-than, greater-than and ampersand symbols to their html equivalents.  Note
+that the ampersand replacement must come first because it is part of the
+replacement text of the other symbols.
+</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; quote-html -- quote special html characters &lt;, &gt;, and &amp;
+(define (quote-html str)
+  (let* ((s1 (string-replace str "&amp;" "&amp;amp;"))
+         (s2 (string-replace s1 "&lt;" "&amp;lt;"))
+         (s3 (string-replace s2 "&gt;" "&amp;gt;")))
+    s3))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+<H3>Utility functions, and functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL"></H3>
+</P>
+<P>
+There are several utility functions required by </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">.  They
+are described below.
+</P>
+<P>
+<H4>Chunk-classification functions</H4>
+</P>
+<P>
+Chunks may be of four types: named, unnamed, display-code, and text.  The three
+functions below identify the first three types; anything else is a text chunk.
+</P>
+<P><PRE>&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; named? par -- #t if named paragraph, #f otherwise
+(define (named? par)
+  (if (null? par)
+      #f
+      (let* ((line (string-trim (car par)))
+             (start (string-index line "&lt;&lt;" 0))
+             (end (if start (string-index line "&gt;&gt;=" (+ start 2)) #f)))
+        (and start end (zero? start) (= end (- (string-length line) 3))))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P><PRE>&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; unnamed? par -- #t if unnamed paragraph, #f otherwise
+(define (unnamed? par)
+  (if (null? par)
+      #f
+      (let ((line (string-trim (car par))))
+        (and (not (string=? "" line))
+             (or (char=? #\; (string-ref line 0))
+                 (char=? #\( (string-ref line 0)))))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; code? par -- #t if code paragraph (for display), #f otherwise
+(define (code? par)
+  (and (string=? "[[" (car par))
+       (string=? "]]" (car (reverse par)))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+<H4>Port-handling functions</H4>
+</P>
+<P>
+These functions open input ports and output ports.  The parameter may be either
+a port or a string.  If it is a string, the appropriate port is opened, perhaps
+using a default extension.  If it is a port, it is returned unchanged.  These
+functions use several non-R5RS extensions &mdash; </FONT><FONT FACE="COURIER">file-exists?</FONT><FONT FACE="ARIAL">,
+</FONT><FONT FACE="COURIER">delete-file</FONT><FONT FACE="ARIAL">, and </FONT><FONT FACE="COURIER">error</FONT><FONT FACE="ARIAL"> &mdash; which exist in most Scheme systems.
+</P>
+<P><PRE>&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; open-input port-or-file . ext -- open input file or port, optional extension
+(define (open-input port-or-file . ext)
+  (cond ((input-port? port-or-file) port-or-file)
+        ((not (string? port-or-file)) (error "error opening file"))
+        ((file-exists? port-or-file)
+          (open-input-file port-or-file))
+        ((and (pair? ext)
+              (file-exists? (string-append port-or-file (car ext))))
+          (open-input-file (string-append port-or-file (car ext))))
+        (else (error (string-append "can't open " port-or-file)))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P><PRE>&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; open-output port-or-file . ext -- open output file or port, optional extension
+(define (open-output port-or-file . ext)
+  (cond ((output-port? port-or-file) port-or-file)
+        ((not (string? port-or-file)) (error "error opening file"))
+        ((file-exists? port-or-file)
+          (delete-file port-or-file)
+          (open-output-file port-or-file))
+        ((null? ext) (open-output-file port-or-file))
+        ((file-exists? (string-append port-or-file (car ext)))
+          (delete-file (string-append port-or-file (car ext)))
+          (open-output-file (string-append port-or-file (car ext))))
+        (else (open-output-file (string-append port-or-file (car ext))))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+The </FONT><FONT FACE="COURIER">base-name</FONT><FONT FACE="ARIAL"> function strips an extension from a filename.
+</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; base-name file-name suffix -- delete suffix from file-name if it matches
+(define (base-name file-name suffix)
+  (let ((len-file (string-length file-name))
+        (len-suffix (string-length suffix)))
+    (if (string=? (substring file-name (- len-file len-suffix) len-file) suffix)
+        (substring file-name 0 (- len-file len-suffix))
+        file-name)))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+<H4>String functions</H4>
+</P>
+<P>
+The following functions remove leading and trailing whitespace from strings.
+</P>
+<P><PRE>&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; string-trim-left s -- remove whitespace from left end of string s
+(define (string-trim-left s)
+  (cond ((string=? "" s) s)
+        ((char-whitespace? (string-ref s 0))
+          (string-trim-left (substring s 1 (string-length s))))
+        (else s)))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P><PRE>&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; string-trim-right s -- remove whitespace from right end of string s
+(define (string-trim-right s)
+  (cond ((string=? "" s) s)
+        ((char-whitespace? (string-ref s (- (string-length s) 1)))
+          (string-trim-right (substring s 0 (- (string-length s) 1))))
+        (else s)))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P><PRE>&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; string-trim s -- remove whitespace from both ends of string s
+(define (string-trim s) (string-trim-left (string-trim-right s)))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+Function </FONT><FONT FACE="COURIER">string-index</FONT><FONT FACE="ARIAL"> finds the first occurrence of a search string in a
+target string, starting after a specified position within the string.  It
+returns the index within the search string where the target string begins, or
+</FONT><FONT FACE="COURIER">#f</FONT><FONT FACE="ARIAL"> if the search string does not appear in the designated portion of the
+target string.  The algorithm is simple brute-force search.
+</P>
+<P><PRE>&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; string-index search target start -- first appearance at or after start or #f
+(define (string-index search target start)
+  (let ((search-len (string-length search))
+        (target-len (string-length target)))
+    (let loop ((k start))
+      (cond ((&lt; search-len (+ k target-len)) #f)
+            ((string=? (substring search k (+ k target-len)) target) k)
+            (else (loop (+ k 1)))))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+Function </FONT><FONT FACE="COURIER">string-replace</FONT><FONT FACE="ARIAL"> examines a target string, replacing all occurrences
+of a search string with a replacement string; it returns a newly-allocated
+string containing all the replacements.  If the search string is not found
+within the target string, no replacements are made, and the original target
+string is returned.
+</P>
+<P><PRE>&laquo;functions used by </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; string-replace target search replace
+(define (string-replace target search replace)
+  (let ((search-len (string-length search))
+        (target-len (string-length target)))
+    (let loop ((k 0))
+      (cond ((&lt; target-len (+ k search-len)) target)
+            ((string=? (substring target k (+ k search-len)) search)
+              (string-append
+                 (substring target 0 k)
+                 replace
+                 (string-replace
+                   (substring target (+ k search-len) target-len)
+                   search
+                   replace)))
+            (else (loop (+ k 1)))))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+<H4>Functions that read ports</H4>
+</P>
+<P>
+SchemeWeb is concerned with chunks that consist of lines.  The following two
+functions read lines and chunks (called paragraphs) from an input port.  Note
+that </FONT><FONT FACE="COURIER">read-line</FONT><FONT FACE="ARIAL"> is careful to handle lines terminated by a carriage return,
+a line feed, or both, so it can be used on a variety of operating systems.
+</P>
+<P><PRE>&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; read-line [port] -- read next line from port, return line or eof-object
+(define (read-line . port)
+  (define (eat c p)
+    (if (and (not (eof-object? (peek-char p))) (char=? (peek-char p) c))
+        (read-char p)))
+  (let ((p (if (null? port) (current-input-port) (car port))))
+    (let loop ((c (read-char p)) (line '()))
+      (cond ((eof-object? c) (if (null? line) c (list-&gt;string (reverse line))))
+            ((char=? #\newline c) (eat #\return p) (list-&gt;string (reverse line)))
+            ((char=? #\return c) (eat #\newline p) (list-&gt;string (reverse line)))
+            (else (loop (read-char p) (cons c line)))))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+</FONT><FONT FACE="COURIER">Read-par</FONT><FONT FACE="ARIAL"> calls </FONT><FONT FACE="COURIER">read-line</FONT><FONT FACE="ARIAL"> to return a chunk.
+</P>
+<P><PRE>&laquo;functions common to </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> and </FONT><FONT FACE="COURIER">weave</FONT><FONT FACE="ARIAL">&raquo;&equiv;</FONT><FONT FACE="COURIER">
+; read-par p -- next maximal set of non-blank lines from p, or eof-object
+(define (read-par p)
+  (define (get-non-blank-line p)
+    (let blank ((s (read-line p)))
+      (if (and (not (eof-object? s)) (string=? "" s))
+          (blank (read-line p))
+          s)))
+  (let par ((s (get-non-blank-line p)) (ls '()))
+    (if (or (eof-object? s) (string=? "" s))
+        (reverse ls)
+        (par (read-line p) (cons s ls)))))
+</PRE></FONT><FONT FACE="ARIAL"</P>
+<P>
+<H3></FONT><FONT FACE="COURIER">Lload</FONT><FONT FACE="ARIAL"></H3>
+</P>
+<P>
+</FONT><FONT FACE="COURIER">Lload</FONT><FONT FACE="ARIAL"> calls </FONT><FONT FACE="COURIER">tangle</FONT><FONT FACE="ARIAL"> to extract the Scheme code from a SchemeWeb literate
+Scheme source file, then executes a <I>read-eval</I> loop to load the tangled
+code into the currently-running top-level environment:
+</P>
+<P><FONT FACE="COURIER"><PRE>
+; LLOAD FILE-NAME
+(define (lload file-name)
+  (let* ((ss (tangle file-name))
+         (i (open-input-string ss)))
+    (let loop ((obj (read i)))
+      (if (eof-object? obj)
+          (close-input-port i)
+          (begin (eval obj (interaction-environment))
+                 (loop (read i)))))))
+</PRE></FONT><FONT FACE="ARIAL"></P>
+<P>
+<H2>Obtaining and installing SchemeWeb</H2>
+</P>
+<P>
+SchemeWeb is available from
+<A HREF="//pbewig.googlepages.com"></FONT><FONT FACE="COURIER">pbewig.googlepages.com</FONT><FONT FACE="ARIAL"></A>.
+The SchemeWeb literate Scheme source file is available as
+<A HREF="//pbewig.googlepages.com/schemeweb.lss"></FONT><FONT FACE="COURIER">schemeweb.lss</FONT><FONT FACE="ARIAL"></A>.
+The woven version of SchemeWeb is available as
+<A HREF="//pbewig.googlepages.com/schemeweb.html"></FONT><FONT FACE="COURIER">schemeweb.html</FONT><FONT FACE="ARIAL"></A>.
+The tangled Scheme source code is available as
+<A HREF="//pbewig.googlepages.com/schemeweb.ss"></FONT><FONT FACE="COURIER">schemeweb.ss</FONT><FONT FACE="ARIAL"></A>.
+To install SchemeWeb, obtain </FONT><FONT FACE="COURIER">schemeweb.ss</FONT><FONT FACE="ARIAL">, copy it to a convenient
+directory, and say </FONT><FONT FACE="COURIER">(load "schemeweb.ss")</FONT><FONT FACE="ARIAL">; this is most usefully done
+in a standard prelude loaded each time the Scheme system is started.
+</P>
+<P>
+<H2>References</H2>
+</P>
+<P>
+Philip L. Bewig, <I>The Essence of Literate Programming</I>,
+</FONT><FONT FACE="COURIER">comp.programming.literate</FONT><FONT FACE="ARIAL">, May 27, 1996.  Describes the chunking mechanism
+as essential, and suggests that plain ascii text, not fancy formatting, is
+sufficient for literate programmers who view their work from inside a text
+editor rather than in print.
+</P>
+<P>
+Donald E. Knuth, <I>The WEB System of Structured Documentation</I>, Computer
+Science Department Report STAN-CS-83-980, Stanford University, 1983.  The
+original WEB program, in Pascal.
+</P>
+<P>
+Donald E. Knuth, <I>Computers and Typesetting, Volume B: TeX: The Program</I>,
+Addison-Wesley Professional, 1986 (ISBN 0201124273).  The program for which
+literate programming was invented.
+</P>
+<P>
+Donald E. Knuth, <I>Literate Programming</I>, Center for the Study of Language
+and Information, 1992 (ISBN 0937073814).  Reprints many of the early papers by
+Knuth and others that define and describe literate programming.
+</P>
+<P>
+Donald E. Knuth and Silvio Levy, <I>The CWEB System of Structured
+Documentation</I>, Addison-Wesley, 1993 (ISBN 0201575698).  Provides user
+documentation and complete source code for the CWEB literate programming system.
+</P>
+<P>
+Norman Ramsey, <I>Noweb &ndash; A Simple, Extensible Tool for Literate
+Programming</I>,
+<A HREF="//www.eecs.harvard.edu/~nr/noweb"></FONT><FONT FACE="COURIER">www.eecs.harvard.edu/~nr/noweb/</FONT><FONT FACE="ARIAL"></A>.
+Noweb is a programming-language- and formatter-independent alternative to
+Knuth's Web and CWEB systems.
+</P>
+</FONT></BODY></HEAD></HTML>