1167 lines
45 KiB
Scheme
Executable file
1167 lines
45 KiB
Scheme
Executable file
#! /usr/bin/scheme --program
|
|
|
|
;;; scheme-prep.ss
|
|
;;;
|
|
;;; Copyright (c) 1998-2016 R. Kent Dybvig and Oscar Waddell
|
|
;;;
|
|
;;; Permission is hereby granted, free of charge, to any person obtaining a
|
|
;;; copy of this software and associated documentation files (the "Software"),
|
|
;;; to deal in the Software without restriction, including without limitation
|
|
;;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
;;; and/or sell copies of the Software, and to permit persons to whom the
|
|
;;; Software is furnished to do so, subject to the following conditions:
|
|
;;;
|
|
;;; The above copyright notice and this permission notice shall be included in
|
|
;;; all copies or substantial portions of the Software.
|
|
;;;
|
|
;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
;;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
;;; DEALINGS IN THE SOFTWARE.
|
|
|
|
;;; primitive commands
|
|
|
|
;;; \genlab
|
|
;;; \generated ... \endgenerated ; inject and process string returned by enclosed Scheme code
|
|
;;; \hindex
|
|
;;; \index
|
|
;;; \raw
|
|
;;; \scheme
|
|
;;; \schemeinit ... \endschemeinit
|
|
;;; \schemedisplay ... \endschemedisplay
|
|
;;; \schemedisplay[number-lines] ... \endschemedisplay ; insert line numbers
|
|
;;; \schemeverbatim ... \endschemeverbatim
|
|
;;; \schlbrace
|
|
;;; \schrbrace
|
|
;;; \transcript ... \endtranscript
|
|
;;; \transcript[endtranscript] ... \endtranscript
|
|
;;; \var
|
|
;;; \xdef
|
|
;;; \xedef
|
|
|
|
;;; \schemeoutput[mode]{filename} ; divert schemedisplay code to file
|
|
;;; \schemeoutput{} ; closes the open scheme output file
|
|
|
|
;;; commands inserted into the output
|
|
|
|
;;; \$, \&, \%, \#, \\
|
|
;;; \dots
|
|
;;; \endschemedisplay
|
|
;;; \hindex
|
|
;;; \is
|
|
;;; \label
|
|
;;; \null
|
|
;;; \schemeindent
|
|
;;; \scheme
|
|
;;; \schemeblankline
|
|
;;; \schemedisplay
|
|
;;; \schemelinestart ; relies on defn of \schemeindent=
|
|
;;; \schatsign
|
|
;;; \schbackslash
|
|
;;; \schcarat
|
|
;;; \schdot
|
|
;;; \schlbrace
|
|
;;; \schrbrace
|
|
;;; \schtilde
|
|
;;; \schunderscore
|
|
;;; \si
|
|
;;; The transerr, etc. commands require an empty argument so that
|
|
;;; we can put them in a \raw{} in the schemedisplay we generate for
|
|
;;; the transcript and still keep both htmlprep and latex happy with
|
|
;;; the result. html-prep would also accept {\transin}, but latex
|
|
;;; was not happy with that.
|
|
;;; \transerr{} ... \endtranserr{} ; transcript error msg typesetting
|
|
;;; \transin{} ... \endtransin{} ; transcript user input typesetting
|
|
;;; \transout{} ... \endtransout{} ; transcript program output typesetting
|
|
;;; \var
|
|
;;; \vdots
|
|
;;; any undefined input command
|
|
;;; anything else inserted by \raw
|
|
|
|
;;; Valid within \scheme{...}
|
|
;;; \dots
|
|
;;; \raw{<text>}
|
|
;;; \var{<text>}
|
|
;;; \var{<text>_<subscript>}
|
|
;;; \
|
|
;;; all other characters besides } and newline
|
|
|
|
;;; Valid within \schemedisplay ... \endschemedisplay
|
|
;;; \dots
|
|
;;; \raw{<text>}
|
|
;;; \var{<text>}
|
|
;;; \var{<text>_<subscript>}
|
|
;;; \vdots
|
|
;;; \
|
|
;;; all other characters besides }
|
|
;;; \label{text} may appear immediately after \schemedisplay
|
|
|
|
;;; index and hindex entries follow makeindex 2.13 syntax except that
|
|
;;; the special characters !, @, |, ", and \ are freely allowed within
|
|
;;; embeded {, } pairs in the "actual entry" portion of a level (the
|
|
;;; portion following @, if any). a modified version of makeindex 2.13
|
|
;;; that supports this extension (with the -d [allow delimited special
|
|
;;; chars] flag) is available here as well.
|
|
;;;
|
|
;;; in \index input, we also allow \scheme{...} to appear, with embedded
|
|
;;; special characters.
|
|
|
|
;;; \index syntax
|
|
;;; \index{levels}
|
|
;;; \index{levels|pageformat}
|
|
;;; levels --> level
|
|
;;; --> level!levels
|
|
;;; level --> keyentry (key and entry same)
|
|
;;; level --> key@entry (separate key and entry)
|
|
;;;
|
|
;;; Valid within keyentry
|
|
;;; \scheme{...}
|
|
|
|
;;; If multiple filenames are supplied on the command line, each file is
|
|
;;; processed in the same runtime image. Thus an \xdef in an earlier file
|
|
;;; persists through the processing of subsequent files.
|
|
|
|
#!chezscheme
|
|
(import (except (chezscheme) open-input-file) (dsm) (preplib) (script))
|
|
|
|
(define use-interaction-window (make-parameter #f))
|
|
(define interaction-window-width (make-parameter 28))
|
|
|
|
(define copy-through-newline
|
|
(lambda (ip op)
|
|
(state-case (c (read-char ip))
|
|
[(#\newline) (write-char c op)]
|
|
[(eof) (void)]
|
|
[else (write-char c op) (copy-through-newline ip op)])))
|
|
|
|
(define dovar
|
|
(lambda (ip op)
|
|
(read-open-brace ip)
|
|
(display "\\var{" op)
|
|
(let f ()
|
|
(state-case (c (read-char ip))
|
|
[(#\}) (write-char c op)]
|
|
[(#\') (display "$'$" op) (f)]
|
|
[(#\_)
|
|
(display "$_" op)
|
|
(state-case (c (read-char ip))
|
|
[(eof) (input-error "unexpected eof in \\var{}")]
|
|
[(#\{) (fprintf op "{~a}" (read-bracketed-text ip 1))]
|
|
[else (write-char c op)])
|
|
(write-char #\$ op)
|
|
(f)]
|
|
[(eof) (input-error "unexpected eof in \\var{}")]
|
|
[else (write-char c op) (f)]))))
|
|
|
|
(define (sscheme ip op) ; within \scheme
|
|
; unlike schemedisplay, does not allow { or }. { and } must be expressed
|
|
; as \schlbrace and \schrbrace
|
|
(state-case (c (read-char ip))
|
|
[(#\{) (input-error "unexpected { within \\scheme{}")]
|
|
[(#\}) (void)]
|
|
[(#\\)
|
|
; use read-alpha-command instead of read-command to avoid
|
|
; improper handling of special characters that follow a slash,
|
|
; including }
|
|
(let ([cmd (read-alpha-command ip)])
|
|
(case cmd
|
|
[(dots)
|
|
(display "{\\dots}" op)
|
|
(sscheme ip op)]
|
|
[(raw)
|
|
(display (read-bracketed-text ip) op)
|
|
(sscheme ip op)]
|
|
[(var)
|
|
(dovar ip op)
|
|
(sscheme ip op)]
|
|
[(schlbrace)
|
|
(display "\\schlbrace" op)
|
|
(sscheme ip op)]
|
|
[(schrbrace)
|
|
(display "\\schrbrace" op)
|
|
(sscheme ip op)]
|
|
[else ; assume random \ possibly followed by alphabetic chars
|
|
(fprintf op "{\\schbackslash}~a" cmd)
|
|
(sscheme ip op)]))]
|
|
[(#\.) (display "{\\schdot}" op) (sscheme ip op)]
|
|
[(#\~) (display "{\\schtilde}" op) (sscheme ip op)]
|
|
[(#\^) (display "{\\schcarat}" op) (sscheme ip op)]
|
|
[(#\@) (display "{\\schatsign}" op) (sscheme ip op)]
|
|
[(#\_) (display "{\\schunderscore}" op) (sscheme ip op)]
|
|
[(#\space) (display "~" op) (sscheme ip op)]
|
|
[(#\$ #\& #\% #\#) (fprintf op "\\~c" c) (sscheme ip op)]
|
|
[(#\newline) (input-error "line ended within \\scheme{}")]
|
|
[(eof) (input-error "file ended within \\scheme{}")]
|
|
[else (write-char c op) (sscheme ip op)]))
|
|
|
|
(define sschemedisplay
|
|
(P lambda (number-lines? labels) ; within a schemedisplay
|
|
; number-lines?
|
|
; Flag is true if we want the lines numbered.
|
|
;
|
|
; labels
|
|
; A list of labels to be attached to this schemedisplay.
|
|
; We insert them after the first newline in the schemedisplay
|
|
; (or before the \endschemedisplay if no newline) so that the
|
|
; label is attached to something other than whitespace (this
|
|
; prevents labels from referring to the preceeding page when the
|
|
; start of a schemedisplay falls right on a page break).
|
|
(define print-line-start
|
|
(lambda (line op)
|
|
(if number-lines?
|
|
(fprintf op "\\schemelinestartnumbered{~s}~%" line)
|
|
(fprintf op "\\schemelinestart~%"))))
|
|
(fprintf op "\\schemelinestart~%")
|
|
(when sout (newline sout))
|
|
(let loop ([n? #f] [line 0] [labels labels])
|
|
; n? is true if we've just passed a new line within the display; we use
|
|
; it to determine whether to insert \\\schemelinestart. (We insert
|
|
; \schemelinestart after \\ to prevent \\ from sucking up ensuing
|
|
; whitespace---including spaces used for indentation on the following
|
|
; line.)
|
|
(state-case (c (read-char ip))
|
|
[(#\\)
|
|
; use read-alpha-command instead of read-alpha-command to avoid
|
|
; improper handling of special characters that follow a slash
|
|
(let ([cmd (read-alpha-command ip)])
|
|
(case cmd
|
|
[(endschemedisplay)
|
|
(unless (null? labels)
|
|
(for-each (lambda (l) (fprintf op "\\label{~a}" l)) labels))
|
|
(display "\\endschemedisplay" op)]
|
|
[else
|
|
(when n? (display "\\\\\n" op) (print-line-start line op))
|
|
(case cmd
|
|
[(dots)
|
|
(display "{\\dots}" op)
|
|
(loop #f line labels)]
|
|
[(var)
|
|
(dovar ip op)
|
|
(loop #f line labels)]
|
|
[(raw)
|
|
(fprintf op "~a" (read-bracketed-text ip))
|
|
(loop #f line labels)]
|
|
[(vdots)
|
|
(display "{\\vdots}" op)
|
|
(loop #f line labels)]
|
|
[else ; assume random \ possibly followed by alphabetic chars
|
|
(fprintf op "{\\schbackslash}~a" cmd)
|
|
(when sout (fprintf sout "\\~a" cmd))
|
|
(loop #f line labels)])]))]
|
|
[else
|
|
(when n? (display "\\\\\n" op) (print-line-start line op))
|
|
(when sout (write-char c sout))
|
|
(state-case (c c)
|
|
[(#\;)
|
|
; convert ;=> into \is and ;== into \si
|
|
(state-case (c (peek-char ip))
|
|
[(#\=)
|
|
(read-char ip)
|
|
(when sout (write-char c sout))
|
|
(state-case (c (peek-char ip))
|
|
[(#\>)
|
|
(when sout (write-char c sout))
|
|
(read-char ip)
|
|
(display "\\is" op)
|
|
(loop #f line labels)]
|
|
[(#\=)
|
|
(when sout (write-char c sout))
|
|
(read-char ip)
|
|
(display "\\si" op)
|
|
(loop #f line labels)]
|
|
[else
|
|
(when sout (write-char c sout))
|
|
(display ";=" op)
|
|
(loop #f line labels)])]
|
|
[(#\-) ; should abstract this
|
|
(read-char ip)
|
|
(when sout (write-char c sout))
|
|
(state-case (c (peek-char ip))
|
|
[(#\>)
|
|
(when sout (write-char c sout))
|
|
(read-char ip)
|
|
(display "\\becomes" op) ; would prefer something else here
|
|
(loop #f line labels)]
|
|
[else
|
|
(when sout (write-char c sout))
|
|
(display ";-" op)
|
|
(loop #f line labels)])]
|
|
[else
|
|
(write-char #\; op)
|
|
(loop #f line labels)])]
|
|
;; Convert space to ~ since \obeyspaces doesn't seem to work for us
|
|
;; when \schemedisplay appears within a macro (see f2002/quiz02.stex).
|
|
[(#\space) (display "~" op) (loop #f line labels)]
|
|
[(#\.)
|
|
(display "{\\schdot}" op)
|
|
(loop #f line labels)]
|
|
[(#\~)
|
|
(display "{\\schtilde}" op)
|
|
(loop #f line labels)]
|
|
[(#\^)
|
|
(display "{\\schcarat}" op)
|
|
(loop #f line labels)]
|
|
[(#\@)
|
|
(display "{\\schatsign}" op)
|
|
(loop #f line labels)]
|
|
[(#\_)
|
|
(display "{\\schunderscore}" op)
|
|
(loop #f line labels)]
|
|
[(#\{)
|
|
(display "{\\schlbrace}" op)
|
|
(loop #f line labels)]
|
|
[(#\})
|
|
(display "{\\schrbrace}" op)
|
|
(loop #f line labels)]
|
|
[(#\$ #\& #\% #\#)
|
|
(fprintf op "\\~c" c)
|
|
(loop #f line labels)]
|
|
[(#\newline)
|
|
(unless (null? labels)
|
|
(for-each (lambda (l) (fprintf op "\\label{~a}" l)) labels))
|
|
(state-case (c (peek-char ip))
|
|
[(#\newline)
|
|
(read-char ip)
|
|
(fprintf op "~%\\schemeblankline")
|
|
(print-line-start line op)
|
|
(when sout (newline sout))
|
|
(loop #f (fx+ line 2) '())]
|
|
[else
|
|
(loop #t (fx+ line 1) '())])]
|
|
[(eof) (errorf #f "file ended within schemedisplay")]
|
|
[else (write-char c op) (loop #f line labels)])]))))
|
|
|
|
(define (sindex ip op)
|
|
; 1. read entire contents of \index{} form, w/o intepreting \scheme{...}
|
|
; separate at !s into 1 or more levels plus page format
|
|
; after |, if present; separate levels at @ into sort key
|
|
; and text, if present
|
|
; 2. for each level,
|
|
; a. compute output sort key
|
|
; - if input sort key is given, use it
|
|
; - otherwise use stripped version of input text
|
|
; - insert quotes where needed
|
|
; b. preprocess input text to produce output text
|
|
; - expand \scheme{...}
|
|
; - insert quotes where needed
|
|
; 3. produce output
|
|
; a. print \index{
|
|
; b. for each level, if output text is same as output sort key,
|
|
; print <output text>.
|
|
; otherwise print <output text>@<output sort key>
|
|
; c. separate levels with !
|
|
; d. print |<page format> if present in input
|
|
; e. print }
|
|
(define strip-sort-key
|
|
; presently strips only \scheme{ and matching }
|
|
(let ([buf (open-output-string)])
|
|
(lambda (ip)
|
|
(state-case (c (read-char ip))
|
|
[(#\\)
|
|
(let ([cmd (read-command ip)])
|
|
(case cmd
|
|
[(scheme)
|
|
(read-open-brace ip)
|
|
(display (read-bracketed-text ip 1) buf)
|
|
(strip-sort-key ip)]
|
|
[else (unexpected-command cmd)]))]
|
|
[(eof) (get-output-string buf)]
|
|
[else (write-char c buf) (strip-sort-key ip)]))))
|
|
(call-with-values
|
|
(lambda () (parse-index ip #t))
|
|
(lambda (levels page-format)
|
|
(let ([keys (map (lambda (s) (insert-quotes (open-input-string s) #f))
|
|
(map (lambda (level)
|
|
(if (car level)
|
|
(car level)
|
|
; strip text to create sort key
|
|
(strip-sort-key
|
|
(open-input-string (cdr level)))))
|
|
levels))]
|
|
[texts (map (lambda (level)
|
|
(insert-quotes
|
|
(open-input-string
|
|
(expand-entry (open-input-string (cdr level))))
|
|
#f))
|
|
levels)])
|
|
(let f ([keys keys] [texts texts] [delim #\{])
|
|
(unless (null? keys)
|
|
(write-char delim op)
|
|
(let ([key (car keys)] [text (car texts)])
|
|
(if (string=? key text)
|
|
(display text op)
|
|
(fprintf op "~a@~a" key text)))
|
|
(f (cdr keys) (cdr texts) #\!)))
|
|
(unless (string=? page-format "") (fprintf op "|~a" page-format))
|
|
(write-char #\} op)))))
|
|
|
|
(define expand-entry
|
|
; expands \scheme{} forms
|
|
(let ([buf (open-output-string)])
|
|
(lambda (ip)
|
|
(state-case (c (read-char ip))
|
|
[(#\\)
|
|
(let ([cmd (read-command ip)])
|
|
(case cmd
|
|
[(scheme)
|
|
(read-open-brace ip)
|
|
(display "\\scheme{" buf)
|
|
(sscheme ip buf)
|
|
(write-char #\} buf)
|
|
(expand-entry ip)]
|
|
[else
|
|
(fprintf buf "\\~a" cmd)
|
|
(expand-entry ip)]))]
|
|
[(eof) (get-output-string buf)]
|
|
[else (write-char c buf) (expand-entry ip)]))))
|
|
|
|
(define insert-quotes
|
|
(let ([buf (open-output-string)])
|
|
; if proper-nesting? is true, the characters ", @, !, and | lose their
|
|
; special meaning within nested groups.
|
|
(lambda (ip proper-nesting?)
|
|
(let loop ()
|
|
(state-case (c (read-char ip))
|
|
[(#\" #\@ #\! #\|)
|
|
(write-char #\" buf)
|
|
(write-char c buf)
|
|
(loop)]
|
|
[(#\\)
|
|
(state-case (c (peek-char ip))
|
|
[(#\@ #\! #\|) (write-char #\" buf)]
|
|
[else (void)])
|
|
(write-char c buf)
|
|
(loop)]
|
|
[(#\{)
|
|
(if proper-nesting?
|
|
(fprintf buf "{~a}" (read-bracketed-text ip 1))
|
|
(write-char c buf))
|
|
(loop)]
|
|
[(eof) (get-output-string buf)]
|
|
[else (write-char c buf) (loop)])))))
|
|
|
|
(define-syntactic-monad P
|
|
ip ; current input port
|
|
op ; current output port
|
|
ips ; input port stack; does not include ip
|
|
ops ; output port stack; does not include op
|
|
ifiles ; stack of input files [(cons ip ips) w/o string ports]
|
|
eofconts ; stack of continuations to call on eof
|
|
sout ; output port for code within schemedisplay or #f
|
|
)
|
|
|
|
(define process-string
|
|
(P lambda (s k)
|
|
(P s0
|
|
([ip (open-input-string s)]
|
|
[ips (cons ip ips)]
|
|
[eofconts (cons (P lambda ()
|
|
(P k
|
|
([op (car ops)] [ops (cdr ops)])
|
|
(get-output-string op)))
|
|
eofconts)]
|
|
[op (open-output-string)]
|
|
[ops (cons op ops)]))))
|
|
|
|
(define s0
|
|
(P lambda ()
|
|
(state-case (c (read-char ip))
|
|
[(#\\)
|
|
(let ([cmd (read-command ip)])
|
|
(cond
|
|
[(get-def cmd '()) =>
|
|
(lambda (proc)
|
|
(unless (or (command-symbol? cmd)
|
|
(eq? cmd 'schemedisplay)
|
|
(eq? cmd 'schemeinit)
|
|
(eq? cmd 'transcript))
|
|
(suppress-white-space ip))
|
|
(P proc))]
|
|
[else (fprintf op "\\~a" cmd) (P s0)]))]
|
|
[(#\%)
|
|
(write-char #\% op)
|
|
(copy-through-newline ip op)
|
|
(P s0)]
|
|
[(eof)
|
|
(close-port ip)
|
|
(if (null? ips)
|
|
(when sout (close-port sout))
|
|
(P (car eofconts)
|
|
([ip (car ips)]
|
|
[ips (cdr ips)]
|
|
[ifiles (if (eq? ip (car ifiles)) (pop-ifile ifiles) ifiles)]
|
|
[eofconts (cdr eofconts)])))]
|
|
[else (write-char c op) (P s0)])))
|
|
|
|
;--------------------------------------------------------------------------
|
|
|
|
(define go
|
|
(lambda (fn)
|
|
(let ([ip (open-input-file (format "~a.stex" fn))])
|
|
(let ([op (open-output-file (format "~a.tex" fn) 'replace)])
|
|
(fprintf op "%%% DO NOT EDIT THIS FILE~%")
|
|
(fprintf op "%%% Edit the .stex version instead~%~%")
|
|
; preplib parameters
|
|
(parameterize ([current-ifile #f]
|
|
[genlab-prefix "s"]
|
|
[genlab-counters '()])
|
|
(P s0
|
|
([ip ip]
|
|
[op op]
|
|
[ips '()]
|
|
[ops '()]
|
|
[ifiles (push-ifile ip '())]
|
|
[eofconts (list s0)]
|
|
[sout #f]))
|
|
(close-port op))))))
|
|
|
|
(global-def genlab
|
|
(P lambda ()
|
|
(display (genlab) op)
|
|
(P s0)))
|
|
|
|
(global-def hindex
|
|
(P lambda ()
|
|
(P process-string () (read-bracketed-text ip)
|
|
(P lambda (lab)
|
|
(fprintf op "\\hindex{~a}" lab)
|
|
(read-open-brace ip)
|
|
(sindex ip op)
|
|
(P s0)))))
|
|
|
|
(global-def index
|
|
(P lambda ()
|
|
(let ([lab (genlab)])
|
|
(fprintf op "\\label{~a}\\hindex{~a}" lab lab))
|
|
(read-open-brace ip)
|
|
(sindex ip op)
|
|
(P s0)))
|
|
|
|
(global-def raw
|
|
(P lambda ()
|
|
(display (read-bracketed-text ip) op)
|
|
(P s0)))
|
|
|
|
(global-def scheme
|
|
(P lambda ()
|
|
(read-open-brace ip)
|
|
(display "\\scheme{" op)
|
|
(sscheme ip op)
|
|
(write-char #\} op)
|
|
(P s0)))
|
|
|
|
(global-def schemedisplay
|
|
(P lambda ()
|
|
(display "\\schemedisplay\n" op)
|
|
(let loop ([options '()] [labels '()])
|
|
; currently a bit sloppy on the grammar
|
|
(state-case (c (read-char ip))
|
|
[(#\\)
|
|
; use read-alpha-command instead of read-command to avoid
|
|
; improper handling of special characters that follow a slash
|
|
(let ([cmd (read-alpha-command ip)])
|
|
(case cmd
|
|
[(label)
|
|
(loop options (cons (read-bracketed-text ip) labels))]
|
|
[(raw)
|
|
(fprintf op "\\raw{~a}~%" (read-bracketed-text ip))
|
|
(loop options labels)]
|
|
[else
|
|
(input-error "invalid command \\~s following \\schemedisplay" cmd)]))]
|
|
[(#\newline)
|
|
(P sschemedisplay () (memq 'number-lines options) labels)]
|
|
[(#\[)
|
|
(unread-char c ip)
|
|
(let ([opt (read-optional-arg ip)])
|
|
(if (equal? opt "number-lines")
|
|
(loop (cons 'number-lines options) labels)
|
|
(input-error "unexpected optional argument ~a to \\schemedisplay" opt)))]
|
|
[else (input-error "expected newline after \\schemedisplay")]))
|
|
(P s0)))
|
|
|
|
(global-def schemeoutput
|
|
(P lambda ()
|
|
(let* ([mode (if (equal? (read-optional-arg ip) "append") 'append 'replace)]
|
|
[filename (read-bracketed-text ip)])
|
|
(when sout (close-port sout))
|
|
(if (eq? filename "")
|
|
(P s0 ([sout #f]))
|
|
(P s0 ([sout (open-output-file filename mode)]))))))
|
|
|
|
(global-def var
|
|
(P lambda ()
|
|
(display "\\scheme{" op)
|
|
(dovar ip op)
|
|
(write-char #\} op)
|
|
(P s0)))
|
|
|
|
(global-def def
|
|
(P lambda ()
|
|
(let* ([cmd (state-case (c (read-char ip))
|
|
[(#\\) (read-command ip)]
|
|
[else (input-error "invalid \\def syntax")])]
|
|
[pattern (read-def-pattern ip)]
|
|
[template (read-bracketed-text ip)])
|
|
(fprintf op "\\def\\~a" cmd)
|
|
(for-each
|
|
(lambda (x)
|
|
(cond
|
|
[(char? x) (write-char x op)]
|
|
[(number? x) (fprintf op "#~a" x)]
|
|
[else (errorf #f "unexpected parsed pattern element ~s" x)]))
|
|
pattern)
|
|
(fprintf op "{~a}" template)
|
|
(P s0))))
|
|
|
|
(global-def xdef
|
|
(P lambda ()
|
|
(let* ([cmd (state-case (c (read-char ip))
|
|
[(#\\) (read-command ip)]
|
|
[else (input-error "invalid \\xdef syntax")])]
|
|
[pattern (read-def-pattern ip)]
|
|
[template (read-bracketed-text ip)])
|
|
(set-def! cmd '() #f
|
|
(P lambda ()
|
|
(P s0
|
|
([ip (open-input-string
|
|
(expand-template template
|
|
(read-args ip pattern cmd) cmd))]
|
|
[ips (cons ip ips)]
|
|
[eofconts (cons s0 eofconts)]))))
|
|
(P s0))))
|
|
|
|
(global-def xedef
|
|
(P lambda ()
|
|
(let* ([cmd (state-case (c (read-char ip))
|
|
[(#\\) (read-command ip)]
|
|
[else (input-error "invalid \\xedef syntax")])]
|
|
[pattern (read-def-pattern ip)]
|
|
[template (read-bracketed-text ip)])
|
|
(P process-string () template
|
|
(P lambda (template)
|
|
(set-def! cmd '() #f
|
|
(P lambda ()
|
|
(P s0
|
|
([ip (open-input-string
|
|
(expand-template template
|
|
(read-args ip pattern cmd) cmd))]
|
|
[ips (cons ip ips)]
|
|
[eofconts (cons s0 eofconts)]))))
|
|
(P s0))))))
|
|
|
|
(global-def schemeverbatim
|
|
(P lambda ()
|
|
(display "\\begin{verbatim}\n" op)
|
|
(let f ()
|
|
(state-case (c (read-char ip))
|
|
[(#\\)
|
|
(let ([cmd (read-command ip)])
|
|
(case cmd
|
|
[(endschemeverbatim) (void)]
|
|
[else (write-char c op) (display cmd op) (f)]))]
|
|
[(eof) (unexpected-eof "after \\schemeverbatim")]
|
|
[else (write-char c op) (f)]))
|
|
(display "\\end{verbatim}\n" op)
|
|
(P s0)))
|
|
|
|
(global-def timestamp
|
|
(P lambda ()
|
|
(display (date-and-time) op)
|
|
(P s0)))
|
|
|
|
;--------------------------------------------------------------------------
|
|
(let ()
|
|
|
|
(define-syntax define-enumeration
|
|
(lambda (x)
|
|
(define iota
|
|
(case-lambda
|
|
[(n) (iota 0 n)]
|
|
[(i n) (if (= i n) '() (cons i (iota (+ i 1) n)))]))
|
|
(syntax-case x ()
|
|
[(_ name (enum ...))
|
|
(and (identifier? #'name) (andmap identifier? #'(enum ...)))
|
|
(with-syntax ([len (length #'(enum ...))])
|
|
(with-syntax ([(i ...) (iota #'len)])
|
|
#'(begin (define enum i) ... (define name len))))])))
|
|
|
|
(define-record tag (markup entity))
|
|
|
|
(define make-tag-port
|
|
(lambda (ip)
|
|
; To preserve the indentation of Scheme expressions in the .stex source,
|
|
; we have to add the leading whitespace the user would have entered to
|
|
; compensate for the indentation---due to the prompt---of the first line.
|
|
; We compute the current indentation level when writing non-echo output.
|
|
; We indent subsequent input lines after we read the first line that
|
|
; follows some output.
|
|
(define-enumeration num-modes (writing reading indenting))
|
|
(define mode writing)
|
|
(define indent 0)
|
|
(define adjust-indent ; called by output routine
|
|
(lambda (x)
|
|
(define do-char
|
|
(lambda (c)
|
|
(set! indent (if (char=? c #\newline) 0 (fx+ indent 1)))))
|
|
; shouldn't do this if we're printing on behalf of the repl
|
|
(begin ; unless (use-interaction-window)
|
|
(unless (fx= mode writing) (set! indent 0))
|
|
(set! mode writing)
|
|
(if (char? x)
|
|
(do-char x)
|
|
(let ([len (string-length x)])
|
|
(do ([i 0 (fx+ i 1)]) ((fx= i len))
|
|
(do-char (string-ref x i))))))))
|
|
(define whitespace?
|
|
(lambda (x)
|
|
(if (char? x)
|
|
(char-whitespace? x)
|
|
(let loop ([i (fx- (string-length x) 1)])
|
|
(or (fx< i 0)
|
|
(and (char-whitespace? (string-ref x i))
|
|
(loop (fx- i 1))))))))
|
|
(let ([line '()] [bline '()] [buffer '()] [ip ip])
|
|
(define (handler msg . args)
|
|
(record-case (cons msg args)
|
|
[char-ready? (p)
|
|
(critical-section
|
|
(or (not (null? line)) (char-ready? ip)))]
|
|
[peek-char (p)
|
|
(define read-line
|
|
(lambda ()
|
|
(when (fx= mode writing) (set! mode reading))
|
|
(when (fx= mode indenting)
|
|
(unless (fx= indent 0)
|
|
(set! buffer
|
|
(cons (make-tag #f (make-string indent #\space))
|
|
buffer))))
|
|
(let loop ()
|
|
(let ([c (read-char ip)])
|
|
(if (eof-object? c)
|
|
'()
|
|
(let ([x (make-tag #f c)])
|
|
(set! buffer (cons x buffer))
|
|
(if (eqv? c #\newline)
|
|
(begin (set! mode indenting) (list x))
|
|
(cons x (loop)))))))))
|
|
(critical-section
|
|
(if (null? line)
|
|
(let ([old bline])
|
|
(set! line (read-line))
|
|
(set! bline '())
|
|
(if (null? line)
|
|
(begin
|
|
; When the last expression in a transcript calls read
|
|
; and then prints something in the transcript window,
|
|
; we have to be sure that the newline character that
|
|
; sent the line of buffered text is properly tagged as
|
|
; having been read via the transcript input port since
|
|
; read simply peeks at the whitespace character and the
|
|
; subsequent repl read (returning #!eof) will usurp the
|
|
; newline unless we patch it up here.
|
|
(when (and (> (length old) 1)
|
|
(eqv? (tag-entity (car old)) #\newline)
|
|
(eq? (tag-markup (cadr old)) 'transin))
|
|
(set-tag-markup! (car old) 'transin))
|
|
#!eof)
|
|
(tag-entity (car line))))
|
|
(tag-entity (car line))))]
|
|
[unread-char (c p)
|
|
(critical-section
|
|
(when (null? bline)
|
|
(errorf 'unread-char "too many consecutive unreads from ~s" p))
|
|
(let ([x (car bline)])
|
|
(set! bline (cdr bline))
|
|
(set-tag-markup! x #f)
|
|
(set-tag-entity! x c)
|
|
(set! line (cons x line))))]
|
|
[clear-input-port (p)
|
|
(critical-section
|
|
(set! line '())
|
|
(set! bline '()))]
|
|
[clear-output-port (p) (void)]
|
|
[close-port (p) (mark-port-closed! p)]
|
|
[flush-output-port (p) (void)]
|
|
[file-position (p . pos)
|
|
(if (null? pos)
|
|
(most-negative-fixnum)
|
|
(errorf 'transcript-port "cannot reposition"))]
|
|
[port-name (p) "tag-port"]
|
|
; intentionally missing block-read and read-char
|
|
[tag-read-char (markup p)
|
|
(critical-section
|
|
(let ([c (peek-char p)])
|
|
(unless (null? line)
|
|
(let ([x (car line)])
|
|
(set! line (cdr line))
|
|
(set-tag-markup! x markup)
|
|
(set! bline (cons x bline))))
|
|
c))]
|
|
[tag-write-entity (markup x p)
|
|
(critical-section
|
|
(adjust-indent x)
|
|
(set! buffer (cons (make-tag markup x) buffer)))]
|
|
[tag-get-repl-interaction-strings ()
|
|
(let ([repl (open-output-string)] [intr (open-output-string)])
|
|
(define maxcols (interaction-window-width))
|
|
(define col 0)
|
|
(define col-display
|
|
(lambda (entity p)
|
|
(define col-write-char
|
|
(lambda (c p)
|
|
(cond
|
|
[(char=? c #\newline) (set! col 0)]
|
|
[(= col maxcols) (newline p) (set! col 0)])
|
|
(write-char c p)
|
|
(set! col (+ col 1))))
|
|
(if (char? entity)
|
|
(col-write-char entity p)
|
|
(for-each (lambda (c) (col-write-char c p)) (string->list entity)))))
|
|
(define choose-port
|
|
(lambda (mkup)
|
|
(case mkup
|
|
[(#f transerr traceout) repl]
|
|
[(transin transout) intr]
|
|
[else (errorf 'scheme-prep.ss "unexpected markup ~s" mkup)])))
|
|
(define reclaim-first-newline
|
|
; the newline that we entered after the expression in the repl window should
|
|
; be charged to the repl window, not the interaction window.
|
|
; (misattributed as being read by transin since the repl's read just peeks at
|
|
; the newline, and subsequent read during eval phase claims the newline)
|
|
(lambda (ls)
|
|
(let f ([ls ls])
|
|
(if (null? ls)
|
|
'()
|
|
(let ([x (car ls)])
|
|
(case (tag-markup x)
|
|
[(transin) (when (eqv? (tag-entity x) #\newline) (set-tag-markup! x #f))]
|
|
[(transout) (void)]
|
|
[else (f (cdr ls))]))))
|
|
ls))
|
|
(let loop ([ls (reclaim-first-newline (reverse buffer))] [markup #f])
|
|
(if (null? ls)
|
|
(begin
|
|
(set! buffer '())
|
|
(when markup (fprintf (choose-port markup) "\\raw{\\end~s{}}" markup))
|
|
(values
|
|
(get-output-string repl)
|
|
(let ([x (get-output-string intr)])
|
|
(if (eq? x "") #f x))))
|
|
(let ([x (car ls)])
|
|
(let ([new (tag-markup x)] [entity (tag-entity x)])
|
|
(unless (eq? new markup)
|
|
(when markup (fprintf (choose-port markup) "\\raw{\\end~s{}}" markup))
|
|
(when new (fprintf (choose-port new) "\\raw{\\~s{}}" new)))
|
|
(let ([p (choose-port new)])
|
|
(if (eq? p repl)
|
|
(display entity p)
|
|
(col-display entity p)))
|
|
(loop (cdr ls) new))))))]
|
|
[tag-get-output-string ()
|
|
(let ([op (open-output-string)])
|
|
(let loop ([ls (reverse buffer)] [markup #f])
|
|
(if (null? ls)
|
|
(begin
|
|
(set! buffer '())
|
|
(when markup (fprintf op "\\raw{\\end~s{}}" markup))
|
|
(get-output-string op))
|
|
(let ([x (car ls)])
|
|
(let ([new (tag-markup x)] [entity (tag-entity x)])
|
|
(unless (eq? new markup)
|
|
(when markup (fprintf op "\\raw{\\end~s{}}" markup))
|
|
(when new (fprintf op "\\raw{\\~s{}}" new)))
|
|
(display entity op)
|
|
(loop (cdr ls) new))))))]
|
|
[delete-last-line-if-unmarked ()
|
|
; clean up the "\n> \n" at the end of the transcript
|
|
; by deleting everything from the last newline upto, but
|
|
; not including the second newline.
|
|
(let trim ([ls buffer] [n? #f])
|
|
(if (or (null? ls) (tag-markup (car ls)))
|
|
(set! buffer ls)
|
|
(let foo ([first (tag-entity (car ls))])
|
|
(cond
|
|
[(string? first)
|
|
(let scan ([i (- (string-length first) 1)] [n? n?])
|
|
(cond
|
|
[(fx< i 0) (trim (cdr ls) n?)]
|
|
[(char=? (string-ref first i) #\newline)
|
|
(if (not n?)
|
|
(scan (fx- i 1) #t)
|
|
(begin
|
|
(set-tag-entity!
|
|
(car ls)
|
|
(substring first 0 i))
|
|
(set! buffer ls)))]
|
|
[else (scan (fx- i 1) n?)]))]
|
|
[(char=? first #\newline)
|
|
(if (not n?)
|
|
(trim (cdr ls) #t)
|
|
(set! buffer (cdr ls)))]
|
|
[else (trim (cdr ls) n?)]))))
|
|
; trim the last newline at the end of the transcript
|
|
; since transcript env will insert some vspace of its own
|
|
(unless (null? buffer)
|
|
(let ([first (car buffer)])
|
|
(when (eqv? (tag-entity first) #\newline)
|
|
(set! buffer (cdr buffer)))))]
|
|
[else (errorf 'tag-port "operation ~s not handled" msg)]))
|
|
(make-input/output-port handler "" ""))))
|
|
|
|
(define make-markup-port
|
|
(lambda (tag-p imarkup omarkup)
|
|
(define (handler msg . args)
|
|
(record-case (cons msg args)
|
|
[block-read (p str cnt)
|
|
(critical-section
|
|
(let ([c (peek-char p)])
|
|
(if (eof-object? c)
|
|
c
|
|
(if (= cnt 0)
|
|
0
|
|
(begin
|
|
(read-char p)
|
|
(string-set! str 0 c)
|
|
1)))))]
|
|
[char-ready? (p) (char-ready? tag-p)]
|
|
[clear-input-port (p) (clear-input-port tag-p)]
|
|
[clear-output-port (p) (clear-output-port tag-p)]
|
|
[close-port (p) (mark-port-closed! p) (close-port tag-p)]
|
|
[flush-output-port (p) (flush-output-port tag-p)]
|
|
[file-position (p . pos)
|
|
(if (null? pos)
|
|
(most-negative-fixnum)
|
|
(errorf 'transcript-port "cannot reposition"))]
|
|
[port-name (p) "markup"]
|
|
[peek-char (p) (peek-char tag-p)]
|
|
[read-char (p) ((port-handler tag-p) 'tag-read-char imarkup tag-p)]
|
|
[unread-char (c p) (unread-char c tag-p)]
|
|
[write-char (c p) ((port-handler tag-p) 'tag-write-entity omarkup c tag-p)]
|
|
[block-write (p str cnt)
|
|
((port-handler tag-p)
|
|
'tag-write-entity
|
|
omarkup
|
|
(substring str 0 cnt)
|
|
tag-p)
|
|
cnt]
|
|
[else (errorf 'markup-port "operation ~s not handled" msg)]))
|
|
(make-input/output-port handler "" "")))
|
|
|
|
(define scheme-transcript
|
|
(P lambda (console-input labels)
|
|
(let ([tp (make-tag-port (open-input-string console-input))])
|
|
(let ([normal (make-markup-port tp #f #f)]
|
|
[trace (make-markup-port tp #f 'traceout)]
|
|
[effect (make-markup-port tp 'transin 'transout)]
|
|
[errors (make-markup-port tp #f 'transerr)]
|
|
[handler (port-handler tp)])
|
|
(handler 'tag-write-entity #f "\\schemedisplay" tp)
|
|
(for-each
|
|
(lambda (l)
|
|
(handler 'tag-write-entity #f (format "\\label{~a}" l) tp))
|
|
labels)
|
|
(handler 'tag-write-entity #f "\n" tp)
|
|
(parameterize ([console-input-port normal]
|
|
[console-output-port normal]
|
|
[console-error-port normal]
|
|
[current-input-port effect]
|
|
[current-output-port effect]
|
|
[current-error-port effect]
|
|
[trace-output-port trace])
|
|
(new-cafe
|
|
(lambda (x)
|
|
(with-exception-handler default-exception-handler
|
|
(lambda ()
|
|
(eval x))))))
|
|
; We could do this with string bashing. I modified the port.
|
|
(handler 'delete-last-line-if-unmarked tp)
|
|
(handler 'tag-write-entity #f "\\endschemedisplay\n" tp)
|
|
(if (use-interaction-window)
|
|
(let-values ([(repl interaction) (handler 'tag-get-repl-interaction-strings tp)])
|
|
(P s0
|
|
([ip
|
|
(if (not interaction)
|
|
(open-input-string repl)
|
|
(open-input-string
|
|
(string-append
|
|
"\\startrepl{}" repl
|
|
"\\endrepl{}\\startinteraction{}\\schemedisplay\n"
|
|
interaction
|
|
"\\endschemedisplay\\endinteraction{}")))]
|
|
[ips (cons ip ips)]
|
|
[eofconts (cons s0 eofconts)])))
|
|
(let ([transcript (handler 'tag-get-output-string tp)])
|
|
(P s0
|
|
([ip (open-input-string transcript)]
|
|
[ips (cons ip ips)]
|
|
[eofconts (cons s0 eofconts)]))))))))
|
|
|
|
(define make-string-evaluator
|
|
(lambda (start-mark evaluator)
|
|
(P lambda ()
|
|
(define text-upto
|
|
(let ([op (open-output-string)])
|
|
(lambda (endmark)
|
|
(state-case (c (read-char ip))
|
|
[(#\\)
|
|
(let ([mk (read-alpha-command ip)])
|
|
(if (eq? mk endmark)
|
|
(get-output-string op)
|
|
(begin
|
|
(write-char #\\ op)
|
|
(display mk op)
|
|
(text-upto endmark))))]
|
|
[(eof) (unexpected-eof (format "after \\~a" start-mark))]
|
|
[else
|
|
(write-char c op)
|
|
(text-upto endmark)]))))
|
|
(let loop ([endmark #f] [labels '()])
|
|
(state-case (c (read-char ip))
|
|
[(#\[) ;]
|
|
(when endmark
|
|
(input-error "expected newline after \\~a[~a]" start-mark endmark))
|
|
(unread-char c ip)
|
|
(let ([ls (string->list (read-optional-arg ip))])
|
|
(when (null? ls)
|
|
(input-error "empty optional argument to \\~a" start-mark))
|
|
(when (not (char=? (car ls) #\\))
|
|
(input-error "\\~a terminator must begin with \\" start-mark))
|
|
(let ([rest (cdr ls)])
|
|
(when (or (null? rest) (not (andmap char-alphabetic? (cdr ls))))
|
|
(input-error
|
|
"\\~a terminator must be \\ followed by alphabetic character(s)" start-mark))
|
|
(loop (string->symbol (list->string rest)) labels)))]
|
|
[(#\\)
|
|
; currently a bit sloppy on the grammar
|
|
; could require all labels one after the other
|
|
; use read-alpha-command instead of read-command to avoid
|
|
; improper handling of special characters that follow a slash
|
|
(let ([cmd (read-alpha-command ip)])
|
|
(case cmd
|
|
[(label)
|
|
(loop endmark (cons (read-bracketed-text ip) labels))]
|
|
[else
|
|
(input-error "invalid command \\~s following \\~a" cmd start-mark)]))]
|
|
[(#\newline)
|
|
(let ([endmark
|
|
(or endmark
|
|
(string->symbol (string-append "end" start-mark)))])
|
|
(P evaluator () (text-upto endmark) endmark (reverse labels)))]
|
|
[(#\space #\tab) (loop endmark labels)]
|
|
[else
|
|
(let ([txt (format "expected newline after \\~a" start-mark)])
|
|
(input-error
|
|
(if endmark
|
|
(string-append txt (format "[~a]" endmark))
|
|
txt)))])))))
|
|
|
|
(global-def schemeinit
|
|
; whitespace has not suppressed when we are invoked
|
|
(make-string-evaluator "schemeinit"
|
|
(P lambda (s endmark labels)
|
|
(unless (null? labels) (input-error "what on earth do you mean putting \\label after \\schemeinit ???"))
|
|
; We could perhaps make this easier by not trying to share the
|
|
; make-string-evaluator code with \transcript and instead let
|
|
; the code simply read from the true ip instead, so that input-error
|
|
; does its job without our assistance. Unfortunately, I just thought
|
|
; of that and don't have the heart to do any more than document the
|
|
; possibility.
|
|
(let ([real-abort-handler (abort-handler)])
|
|
(let ([sip (open-input-string s)])
|
|
; nesting is not an issue.
|
|
(let f ()
|
|
; scan ahead to first non-whitespace character, if any
|
|
(let skip-whitespace ()
|
|
(let ([c (peek-char sip)])
|
|
(when (and (char? c) (char-whitespace? c))
|
|
(read-char sip)
|
|
(skip-whitespace))))
|
|
; record the current file position so we adjust the true
|
|
; file-position based on the string-input-port's file position
|
|
; if we run into an error
|
|
(let ([last-sip-pos (file-position sip)])
|
|
(define scout ; be prepared
|
|
(lambda (thunk)
|
|
(parameterize
|
|
([abort-handler
|
|
(lambda args
|
|
(let ([end (file-position ip)]
|
|
[endmark-len
|
|
(+ 1
|
|
(string-length (symbol->string endmark)))])
|
|
(file-position
|
|
ip
|
|
(- end
|
|
endmark-len
|
|
(- (string-length s) last-sip-pos))))
|
|
(parameterize ([abort-handler real-abort-handler])
|
|
(input-error
|
|
"previous error occurred within \\schemeinit")))])
|
|
(thunk))))
|
|
(let ([x (scout (lambda () (read sip)))])
|
|
(unless (eof-object? x)
|
|
(scout (lambda () (eval x)))
|
|
(f)))))))
|
|
(P s0 ()))))
|
|
|
|
(global-def generated
|
|
(P lambda ()
|
|
(let ([sop (open-output-string)])
|
|
(let loop ()
|
|
(state-case (c (peek-char ip))
|
|
[(#\space #\tab #\newline) (read-char ip) (loop)]
|
|
[(#\\)
|
|
(read-char ip)
|
|
(let ([cmd (read-alpha-command ip)])
|
|
(case cmd
|
|
[(endgenerated)
|
|
(P s0 ([ip (open-input-string (get-output-string sop))]
|
|
[ips (cons ip ips)]
|
|
[eofconts (cons s0 eofconts)]))]
|
|
[else (input-error "unexpected command \\~s following \\generated" cmd)]))]
|
|
[else
|
|
(parameterize ([current-output-port sop])
|
|
(eval (read ip))) ; should protect against errors
|
|
(loop)])))))
|
|
|
|
(global-def transcript
|
|
; whitespace has not suppressed when we are invoked
|
|
(make-string-evaluator "transcript"
|
|
(P lambda (s endmark labels)
|
|
(P scheme-transcript () s labels))))
|
|
|
|
(global-def useinteractionwindow
|
|
(P lambda ()
|
|
(use-interaction-window #t)
|
|
(P s0 ())))
|
|
|
|
(global-def enduseinteractionwindow
|
|
(P lambda ()
|
|
(use-interaction-window #f)
|
|
(P s0 ())))
|
|
|
|
(global-def interactionwindowwidth
|
|
(P lambda ()
|
|
(let ([s (read-bracketed-text ip)])
|
|
(let ([n (string->number s)])
|
|
(unless n (errorf #f "expected numeric argument to \\interactionwindowwidth"))
|
|
; should be more clever here to make it work in bgroup ... egroup fashion
|
|
(interaction-window-width n)
|
|
(P s0 ())))))
|
|
|
|
)
|
|
|
|
(populate-source-directories)
|
|
|
|
(command-line-case (command-line)
|
|
[((keyword --help)) (usage)]
|
|
[(filename* ...)
|
|
(for-each go
|
|
(let ([found (find-filename "scheme-prep.tex")])
|
|
(if found
|
|
(cons found filename*)
|
|
filename*)))])
|