(herald st)                             ; @(#)st.t      1.3 88/06/30
;;; SchemeTeX --- Simple support for literate programming in Scheme.
;;; February 1988, John D. Ramsdell.
;;;
;;; Copyright 1988 by The MITRE Corporation.
;;; Permission to use, copy, modify, and distribute this
;;; software and its documentation for any purpose and without
;;; fee is hereby granted, provided that the above copyright
;;; notice appear in all copies.  The MITRE Corporation
;;; makes no representations about the suitability of this
;;; software for any purpose.  It is provided "as is" without
;;; express or implied warranty.
;;;
;;; SchemeTeX
;;; defines a new source file format in which source lines are divided
;;; into text and code.  Lines of code start with a line beginning with
;;; '(', and continue until the line that contains the matching ')'.  The
;;; text lines remain, and they are treated as comments.  When producing
;;; a document, both the text lines and the code lines are copied into
;;; the document source file, but the code lines are surrounded by a pair
;;; of formatting commands.  The formatting commands are in begin-code
;;; and end-code.  SchemeTeX is currently set up for use with LaTeX.
;;;
;;; Exports: load-st, compile-st, and TeX-st.
;;; (load-st filespec optional-load-env)      Loads Scheme TeX source.
;;; (compile-st filespec)                     Compiles Scheme TeX source.
;;; (tex-st filespec)                         Makes LaTeX input.

(define st-extension 'st)
(define src-extension 't)
(define tex-extension 'tex)

(define (load-st filespec . options)
 (let ((t-filename (tangle filespec)))
   (and t-filename
        (apply load t-filename options))))

(define (compile-st filespec)
 (let ((t-filename (tangle filespec)))
   (and t-filename
        (compile-file t-filename))))

(define (tex-st st-filespec)
 (let* ((st-filename (st-filespec->st-filename st-filespec))
        (tex-filename (st-filename->filename st-filename tex-extension)))
   (with-open-streams ((st-port (open st-filename '(in)))
                       (tex-port (open tex-filename '(out))))
                      (if (weave-port st-port tex-port)
                          'done
                          'failed))))

(define (tangle st-filespec)            ; => t-filename or false.
 (let* ((st-filename (st-filespec->st-filename st-filespec))
        (t-filename (st-filename->filename st-filename src-extension)))
   (if (and (file-exists? t-filename)
            (file-newer? t-filename st-filename))
       t-filename                      ; No need to tangle.
       (with-open-streams ((st-port (open st-filename '(in)))
                           (t-port (open t-filename '(out))))
                          (and (tangle-port st-port t-port)
                               t-filename)))))

(define (st-filespec->st-filename st-filespec)
 (->filename
  (cond ((symbol? st-filespec)
         (list '() st-filespec st-extension))
        ((and (pair? st-filespec)
              (= (length st-filespec) 2))
         (append st-filespec (list st-extension)))
        (else st-filespec))))

(define (st-filename->filename st-filename default-type)
 (make-filename
  (filename-fs st-filename)
  (filename-dir st-filename)
  (filename-name st-filename)
  (if (eq? default-type (filename-type st-filename))
      '()
      default-type)
  ;;broken?  (filename-generation st-filename)
  ))

(define (tangle-port st-port t-port)    ; => false on failure.
 (labels
     (((tex-mode-and-saw-newline)
       (let ((ch (read-char st-port)))
         (cond ((eof? ch) '#t)
               ((char= ch #\left-paren)
                (unread-char st-port)
                (t-mode))
               ((char= ch #\newline)
                (tex-mode-and-saw-newline))
               (else (tex-mode-within-a-line)))))
      ((tex-mode-within-a-line)
       (if (eof? (read-line st-port))
           '#t
           (tex-mode-and-saw-newline)))
      ((t-mode)                        ; This routine should return
       (print (read-refusing-eof st-port) t-port)
       (newline t-port)                ; #f when read-refusing-eof
       (tex-mode-within-a-line)))      ; obtains an error.
   (tex-mode-and-saw-newline)))

(define begin-code "\\begin{astyped}")
(define end-code "\\end{astyped}")
(define begin-comment "\\notastyped{")

(define (weave-port st-port tex-port)
 (let ((spaces 0)                      ; Expansion of tabs into spaces.
       (hpos 0))                       ; Used in get-char and get-line.
   (catch leave                        ; Exit with leave when EOF is found.
     (labels                           ; All input is read with
         (((get-char eof-value)        ; get-char and get-line.
           (if (fx> spaces 0)
               (block (set spaces (fx- spaces 1)) #\space)
               (let ((ch (read-char st-port)))
                 (cond ((eof? ch) (leave eof-value))
                       ((char= ch #\tab)
                        (set spaces (fx- 8 (logand 7 hpos)))
                        (set hpos (fx+ hpos spaces))
                        (get-char eof-value))
                       ((char= ch #\newline)
                        (set hpos 0) ch)
                       (else (set hpos (fx+ hpos 1)) ch)))))
          ((get-line eof-value)
           (set hpos 0)
           (let ((ch (read-line st-port)))
             (if (eof? ch)
                 (leave eof-value)
                 ch)))
          ((tex-write-char ch)         ; Write to TeX file
           (if (or (char= ch #\\)      ; escaping TeX's special
                   (char= ch #\{)      ; characters.
                   (char= ch #\})
                   (char= ch #\$)
                   (char= ch #\&)
                   (char= ch #\#)
                   (char= ch #\^)
                   (char= ch #\_)
                   (char= ch #\%)
                   (char= ch #\~))
               (format tex-port "\\verb-~a-" ch)
               (write-char tex-port ch)))
          ((tex-mode-and-saw-newline)  ; State at which decision must
           (let ((ch (get-char '#t)))  ; be made if to go into T code
             (if (char= ch #\left-paren) ; mode or stay in TeX mode.
                 (t-mode)
                 (block
                   (if (not (char= ch #\semicolon)) ; For those who want
                       (write-char tex-port ch)) ; to use regular load.
                   (if (char= ch #\newline)
                       (tex-mode-and-saw-newline)
                       (tex-mode-within-a-line))))))
          ((tex-mode-within-a-line)    ; Copy out TeX line.
           (let ((line (get-line '#t)))
             (write-line tex-port line)
             (tex-mode-and-saw-newline)))
          ((t-mode)                    ; Change from TeX mode
           (write-line tex-port begin-code) ; to T code mode.
           (write-char tex-port #\()
           (sexpr 1))
          ((sexpr parens)              ; parens is used to watch
           (let ((ch (get-char '#f)))  ; for the closing paren
             (cond ((char= ch #\semicolon) ; used to detect the
                    (copy-comment '#f) ; end of T code mode.
                    (sexpr parens))
                   (else
                    (sexpr-write-char parens ch)))))
          ((copy-comment eof-value)    ; Handle comment.
           (let ((line (get-line eof-value)))
             (write-string tex-port begin-comment)
             (write-char tex-port #\semicolon)
             (write-string tex-port line)
             (write-char tex-port #\})
             (newline tex-port)))
          ((sexpr-write-char parens ch)
           (tex-write-char ch)
           (cond ((char= ch #\left-paren)
                  (sexpr (fx+ parens 1)))
                 ((char= ch #\right-paren)
                  (if (fx= 1 parens)   ; Done reading sexpr.
                      (t-mode-after-sexpr)
                      (sexpr (fx- parens 1))))
                 ((char= ch #\")
                  (copy-out-string parens))
                 ((char= ch #\#)       ; Worrying about #\( and #\).
                  (maybe-char-syntax parens))
                 (else (sexpr parens))))
          ((copy-out-string parens)
           (let ((ch (get-char '#f)))
             (tex-write-char ch)
             (cond ((char= ch #\\)
                    (let ((ch (get-char '#f)))
                      (tex-write-char ch)
                      (copy-out-string parens)))
                   ((char= ch #\")
                    (sexpr parens))
                   (else (copy-out-string parens)))))
          ((maybe-char-syntax parens)
           (let ((ch (get-char '#f)))
             (cond ((char= ch #\backslash)
                    (tex-write-char ch)
                    (let ((ch (get-char '#f)))
                      (tex-write-char ch)
                      (sexpr parens)))
                   (else
                    (unread-char st-port)
                    (sexpr parens)))))
          ((t-mode-after-sexpr)
           (let ((ch (get-char '#t)))
             (cond ((char= ch #\semicolon)
                    (copy-comment '#t)
                    (t-mode-merge))
                   ((char= ch #\newline)
                    (newline tex-port)
                    (t-mode-merge))
                   ((char= ch #\space)
                    (tex-write-char ch)
                    (t-mode-after-sexpr))
                   (else
                    (read-error st-port "Bad text following code")))))
          ((t-mode-merge)
           (let ((ch (get-char '#t)))
             (cond ((char= ch #\left-paren)
                    (write-char tex-port ch)
                    (sexpr 1))
                   (else
                    (write-line tex-port end-code)
                    (write-char tex-port ch)
                    (if (char= ch #\newline)
                        (tex-mode-and-saw-newline)
                        (tex-mode-within-a-line)))))))
       (tex-mode-and-saw-newline)))))