(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)))))