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