summaryrefslogtreecommitdiffstats
path: root/schmooz.scm
diff options
context:
space:
mode:
Diffstat (limited to 'schmooz.scm')
-rw-r--r--schmooz.scm605
1 files changed, 605 insertions, 0 deletions
diff --git a/schmooz.scm b/schmooz.scm
new file mode 100644
index 0000000..9664ac3
--- /dev/null
+++ b/schmooz.scm
@@ -0,0 +1,605 @@
+;;; schmooz.scm: Program for extracting texinfo comments from Scheme.
+;;; Copyright (C) 1998 Radey Shouman and Aubrey Jaffer.
+;
+;Permission to copy this software, to redistribute it, and to use it
+;for any purpose is granted, subject to the following restrictions and
+;understandings.
+;
+;1. Any copy made of this software must include this copyright notice
+;in full.
+;
+;2. I have made no warrantee or representation that the operation of
+;this software will be error-free, and I am under no obligation to
+;provide any services, by way of maintenance, update, or otherwise.
+;
+;3. In conjunction with products arising from the use of this
+;material, there shall be no use of my name in any advertising,
+;promotional, or sales literature without prior written consent in
+;each case.
+
+;;$Header: /usr/local/cvsroot/slib/schmooz.scm,v 1.7 1998/09/10 20:34:26 radey Exp $
+;;$Name: $
+
+;;; REPORT an error or warning
+(define report
+ (lambda args
+ (display *scheme-source-name*)
+ (display ": In function `")
+ (display *procedure*)
+ (display "': ")
+ (newline)
+
+ (display *derived-txi-name*)
+ (display ": ")
+ (display *output-line*)
+ (display ": warning: ")
+ (apply qreport args)))
+
+(define qreport
+ (lambda args
+ (for-each (lambda (x) (write x) (display #\ )) args)
+ (newline)))
+
+(require 'common-list-functions) ;some
+(require 'string-search)
+(require 'fluid-let)
+(require 'line-i/o) ;read-line
+(require 'filename)
+(require 'scanf)
+;;(require 'debug) (set! *qp-width* 100) (define qreport qpn)
+
+;;; This allows us to test without generating files
+(define *scheme-source* (current-input-port))
+(define *scheme-source-name* "stdin")
+(define *derived-txi* (current-output-port))
+(define *derived-txi-name* "?")
+
+(define *procedure* #f)
+(define *output-line* 0)
+
+(define CONTLINE -80)
+
+;;; OUT indents and displays the arguments
+(define (out indent . args)
+ (cond ((>= indent 0)
+ (newline *derived-txi*)
+ (set! *output-line* (+ 1 *output-line*))
+ (do ((j indent (- j 8)))
+ ((> 8 j)
+ (do ((i j (- i 1)))
+ ((>= 0 i))
+ (display #\ *derived-txi*)))
+ (display #\ *derived-txi*))))
+ (for-each (lambda (a)
+ (cond ((symbol? a)
+ (display a *derived-txi*))
+ ((string? a)
+ (display a *derived-txi*)
+ #+f
+ (cond ((string-index a #\nl)
+ (set! *output-line* (+ 1 *output-line*))
+ (report "newline in string" a))))
+ (else
+ (display a *derived-txi*))))
+ args))
+
+;; LINE is a string, ISTRT the index in LINE at which to start.
+;; Returns a list (next-char-number . list-of-tokens).
+;; arguments look like:
+;; "(arg1 arg2)" or "{arg1,arg2}" or the whole line is split
+;; into whitespace separated tokens.
+(define (parse-args line istrt)
+ (define (tok1 istrt close sep? splice)
+ (let loop-args ((istrt istrt)
+ (args '()))
+ (let loop ((iend istrt))
+ (cond ((>= iend (string-length line))
+ (if close
+ (slib:error close "not found in" line)
+ (cons iend
+ (reverse
+ (if (> iend istrt)
+ (cons (substring line istrt iend) args)
+ args)))))
+ ((eqv? close (string-ref line iend))
+ (cons (+ iend 1)
+ (reverse (if (> iend istrt)
+ (cons (substring line istrt iend) args)
+ args))))
+ ((sep? (string-ref line iend))
+ (let ((arg (and (> iend istrt)
+ (substring line istrt iend))))
+ (if (equal? arg splice)
+ (let ((rest (tok1 (+ iend 1) close sep? splice)))
+ (cons (car rest)
+ (append args (cadr rest))))
+ (loop-args (+ iend 1)
+ (if arg
+ (cons arg args)
+ args)))))
+ (else
+ (loop (+ iend 1)))))))
+ (let skip ((istrt istrt))
+ (cond ((>= istrt (string-length line)) (cons istrt '()))
+ ((char-whitespace? (string-ref line istrt))
+ (skip (+ istrt 1)))
+ ((eqv? #\{ (string-ref line istrt))
+ (tok1 (+ 1 istrt) #\} (lambda (c) (eqv? c #\,)) #f))
+ ((eqv? #\( (string-ref line istrt))
+ (tok1 (+ 1 istrt) #\) char-whitespace? "."))
+ (else
+ (tok1 istrt #f char-whitespace? #f)))))
+
+
+;; Substitute @ macros in string LINE.
+;; Returns a list, the first element is the substituted version
+;; of LINE, the rest are lists beginning with '@dfn or '@args
+;; and followed by the arguments that were passed to those macros.
+;; MACS is an alist of (macro-name . macro-value) pairs.
+(define (substitute-macs line macs)
+ (define (get-word i)
+ (let loop ((j (+ i 1)))
+ (cond ((>= j (string-length line))
+ (substring line i j))
+ ((or (char-alphabetic? (string-ref line j))
+ (char-numeric? (string-ref line j)))
+ (loop (+ j 1)))
+ (else (substring line i j)))))
+ (let loop ((istrt 0)
+ (i 0)
+ (res '()))
+ (cond ((>= i (string-length line))
+ (list
+ (apply string-append
+ (reverse
+ (cons (substring line istrt (string-length line))
+ res)))))
+ ((char=? #\@ (string-ref line i))
+ (let* ((w (get-word i))
+ (symw (string->symbol w)))
+ (cond ((eq? '@cname symw)
+ (let ((args (parse-args
+ line (+ i (string-length w)))))
+ (cond ((and args (= 2 (length args)))
+ (loop (car args) (car args)
+ (cons
+ (string-append
+ "@code{" (cadr args) "}")
+ (cons (substring line istrt i) res))))
+ (else
+ (report "@cname wrong number of args" line)
+ (loop istrt (+ i (string-length w)) res)))))
+ ((eq? '@dfn symw)
+ (let* ((args (parse-args
+ line (+ i (string-length w))))
+ (inxt (car args))
+ (rest (loop inxt inxt
+ (cons (substring line istrt inxt)
+ res))))
+ (cons (car rest)
+ (cons (cons '@dfn (cdr args))
+ (cdr rest)))))
+ ((eq? '@args symw)
+ (let* ((args (parse-args
+ line (+ i (string-length w))))
+ (inxt (car args))
+ (rest (loop inxt inxt res)))
+ (cons (car rest)
+ (cons (cons '@args (cdr args))
+ (cdr rest)))))
+ ((assq symw macs) =>
+ (lambda (s)
+ (loop (+ i (string-length w))
+ (+ i (string-length w))
+ (cons (cdr s)
+ (cons (substring line istrt i) res)))))
+ (else (loop istrt (+ i (string-length w)) res)))))
+ (else (loop istrt (+ i 1) res)))))
+
+
+(define (sexp-def sexp)
+ (and (pair? sexp)
+ (memq (car sexp) '(DEFINE DEFVAR DEFCONST DEFINE-SYNTAX DEFMACRO))
+ (car sexp)))
+
+(define def->var-name cadr)
+
+(define (def->args sexp)
+ (define name (cadr sexp))
+ (define (body forms)
+ (if (pair? forms)
+ (if (null? (cdr forms))
+ (form (car forms))
+ (body (cdr forms)))
+ #f))
+ (define (form sexp)
+ (if (pair? sexp)
+ (case (car sexp)
+ ((LAMBDA) (cons name (cadr sexp)))
+ ((BEGIN) (body (cdr sexp)))
+ ((LET LET* LETREC)
+ (if (or (null? (cadr sexp))
+ (pair? (cadr sexp)))
+ (body (cddr sexp))
+ (body (cdddr sexp)))) ;named LET
+ (else #f))
+ #f))
+ (case (car sexp)
+ ((DEFINE) (if (pair? name)
+ name
+ (form (caddr sexp))))
+ ((DEFINE-SYNTAX) '())
+ ((DEFMACRO) (cons (cadr sexp) (caddr sexp)))
+ ((DEFVAR DEFCONST) #f)
+ (else (slib:error 'schmooz "doesn't look like definition" sexp))))
+
+;; Generate alist of argument macro definitions.
+;; If ARGS is a symbol or string, then the definitions will be used in a
+;; `defvar', if ARGS is a (possibly improper) list, they will be used in
+;; a `defun'.
+(define (scheme-args->macros args)
+ (define (arg->string a)
+ (if (string? a) a (symbol->string a)))
+ (define (arg->macros arg i)
+ (let ((s (number->string i))
+ (m (string-append "@var{" (arg->string arg) "}")))
+ (list (cons (string->symbol (string-append "@" s)) m)
+ (cons (string->symbol (string-append "@arg" s)) m))))
+ (let* ((fun? (pair? args))
+ (arg0 (if fun? (car args) args))
+ (args (if fun? (cdr args) '())))
+ (let ((m0 (string-append
+ (if fun? "@code{" "@var{") (arg->string arg0) "}")))
+ (append
+ (list (cons '@arg0 m0) (cons '@0 m0))
+ (let recur ((i 1)
+ (args args))
+ (cond ((null? args) '())
+ ((or (symbol? args) ;Rest list
+ (string? args))
+ (arg->macros args i))
+ (else
+ (append (arg->macros (car args) i)
+ (recur (+ i 1) (cdr args))))))))))
+
+;; Extra processing to be done for @dfn
+(define (out-cindex arg)
+ (out 0 "@cindex " arg))
+
+;; ARGS looks like the cadr of a function definition:
+;; (fun-name arg1 arg2 ...)
+(define (schmooz-fun defop args body xdefs)
+ (define (out-header args op)
+ (let ((fun (car args))
+ (args (cdr args)))
+ (out 0 #\@ op #\space fun)
+ (let loop ((args args))
+ (cond ((null? args))
+ ((symbol? args)
+ (loop (symbol->string args)))
+ ((string? args)
+ (out CONTLINE " "
+ (let ((n (- (string-length args) 1)))
+ (if (eqv? #\s (string-ref args n))
+ (substring args 0 n)
+ args))
+ " @dots{}"))
+ ((pair? args)
+ (out CONTLINE " "
+ (if (or (eq? '... (car args))
+ (equal? "..." (car args)))
+ "@dots{}"
+ (car args)))
+ (loop (cdr args)))
+ (else (slib:error 'schmooz-fun args))))))
+ (let* ((mac-list (scheme-args->macros args))
+ (ops (case defop
+ ((DEFINE-SYNTAX) '("defspec" . "defspecx"))
+ ((DEFMACRO) '("defmac" . "defmacx"))
+ (else '("defun" . "defunx")))))
+ (out-header args (car ops))
+ (let loop ((xdefs xdefs))
+ (cond ((pair? xdefs)
+ (out-header (car xdefs) (cdr ops))
+ (loop (cdr xdefs)))))
+ (for-each (lambda (subl)
+ (out 0 (car subl))
+ (for-each (lambda (l)
+ (case (car l)
+ ((@dfn)
+ (out-cindex (cadr l)))
+ ((@args)
+ (out-header
+ (cons (car args) (cdr l))
+ (cdr ops)))))
+ (cdr subl)))
+ (map (lambda (bl)
+ (substitute-macs bl mac-list))
+ body))
+ (out 0 "@end " (car ops))
+ (out 0)))
+
+(define (schmooz-var defop name body xdefs)
+ (let* ((mac-list (scheme-args->macros name)))
+ (out 0 "@defvar " name)
+ (let loop ((xdefs xdefs))
+ (cond ((pair? xdefs)
+ (out 0 "@defvarx " (car xdefs))
+ (loop (cdr xdefs)))))
+ (for-each (lambda (subl)
+ (out 0 (car subl))
+ (for-each (lambda (l)
+ (case (car l)
+ ((@dfn) (out-cindex (cadr l)))
+ (else
+ (report "bad macro" l))))
+ (cdr subl)))
+ (map (lambda (bl)
+ (substitute-macs bl mac-list))
+ body))
+ (out 0 "@end defvar")
+ (out 0)))
+
+;;; SCHMOOZ files.
+(define schmooz
+ (let* ((scheme-file? (filename:match-ci?? "*??scm"))
+ (txi-file? (filename:match-ci?? "*??txi"))
+ (texi-file? (let ((tex? (filename:match-ci?? "*??tex"))
+ (texi? (filename:match-ci?? "*??texi")))
+ (lambda (filename) (or (txi-file? filename)
+ (tex? filename)
+ (texi? filename))))))
+ (define (schmooz-texi-file file)
+ (call-with-input-file file
+ (lambda (port)
+ (do ((pos (find-string-from-port? "@include" port)
+ (find-string-from-port? "@include" port)))
+ ((not pos))
+ (let ((fname #f))
+ (cond ((not (eqv? 1 (fscanf port " %s" fname))))
+ ((not (txi-file? fname)))
+ ((not (file-exists?
+ (replace-suffix fname "txi" "scm"))))
+ (else (schmooz
+ (replace-suffix fname "txi" "scm")))))))))
+ (define (schmooz-scm-file file txi-name)
+ (display "Schmoozing ") (write file)
+ (display " -> ") (write txi-name) (newline)
+ (fluid-let ((*scheme-source* (open-file file "r"))
+ (*scheme-source-name* file)
+ (*derived-txi* (open-file txi-name "w"))
+ (*derived-txi-name* txi-name))
+ (set! *output-line* 1)
+ (schmooz-tops schmooz-top)
+ (close-input-port *scheme-source*)
+ (close-output-port *derived-txi*)))
+ (lambda files
+ (for-each (lambda (file)
+ (define sl (string-length file))
+ (cond ((scheme-file? file)
+ (schmooz-scm-file
+ file (replace-suffix file "scm" "txi")))
+ ((texi-file? file) (schmooz-texi-file file))))
+ files))))
+
+;;; SCHMOOZ-TOPS - schmooz top level forms.
+(define (schmooz-tops schmooz-top)
+ (let ((doc-lines '())
+ (doc-args #f))
+ (define (skip-ws line istrt)
+ (do ((i istrt (+ i 1)))
+ ((or (>= i (string-length line))
+ (not (memv (string-ref line i)
+ '(#\space #\tab #\;))))
+ (substring line i (string-length line)))))
+
+ (define (tok1 line)
+ (let loop ((i 0))
+ (cond ((>= i (string-length line)) line)
+ ((or (char-whitespace? (string-ref line i))
+ (memv (string-ref line i) '(#\; #\( #\{)))
+ (substring line 0 i))
+ (else (loop (+ i 1))))))
+
+ (define (read-cmt-line)
+ (cond ((eqv? #\; (peek-char *scheme-source*))
+ (read-char *scheme-source*)
+ (read-cmt-line))
+ (else (read-line *scheme-source*))))
+
+ (define (read-newline)
+ (if (char=? #\cr (read-char *scheme-source*))
+ (if (char=? #\nl (peek-char *scheme-source*))
+ (read-char *scheme-source*)
+ (report "stranded #\\cr"))))
+
+ (define (lp c)
+ (cond ((eof-object? c)
+ (cond ((pair? doc-lines)
+ (report "No definition found for @body doc lines"
+ (reverse doc-lines)))))
+ ((memv c '(#\cr #\nl))
+ (read-newline)
+ (set! *output-line* (+ 1 *output-line*))
+ (newline *derived-txi*)
+ (lp (peek-char *scheme-source*)))
+ ((char-whitespace? c)
+ (write-char (read-char *scheme-source*) *derived-txi*)
+ (lp (peek-char *scheme-source*)))
+ ((char=? c #\;)
+ (c-cmt c))
+ (else
+ (sx))))
+
+ (define (sx)
+ (let* ((s1 (read *scheme-source*))
+ (ss ;Read all forms separated only by single newlines.
+ (let recur ()
+ (case (peek-char *scheme-source*)
+ ((#\cr) (read-char *scheme-source*) (recur))
+ ((#\nl) (read-char *scheme-source*)
+ (if (eqv? #\( (peek-char *scheme-source*))
+ (cons (read *scheme-source*) (recur))
+ '()))
+ (else '())))))
+ (cond ((eof-object? s1))
+ (else
+ (schmooz-top s1 ss (reverse doc-lines) doc-args)
+ (set! doc-lines '())
+ (set! doc-args #f)
+ (lp (peek-char *scheme-source*))))))
+
+ (define (out-cmt line)
+ (let ((subl (substitute-macs line '())))
+ (newline *derived-txi*)
+ (display (car subl) *derived-txi*)
+ (for-each
+ (lambda (l)
+ (case (car l)
+ ((@dfn)
+ (out-cindex (cadr l)))
+ (else
+ (report "bad macro" line))))
+ (cdr subl))))
+
+ ;;Comments not transcribed to generated Texinfo files.
+ (define (c-cmt c)
+ (cond ((eof-object? c) (lp c))
+ ((eqv? #\; c)
+ (read-char *scheme-source*)
+ (c-cmt (peek-char *scheme-source*)))
+ ;; Escape to start Texinfo comments
+ ((eqv? #\@ c)
+ (let* ((line (read-line *scheme-source*))
+ (tok (tok1 line)))
+ (cond ((or (string=? tok "@body")
+ (string=? tok "@text"))
+ (set! doc-lines
+ (cons (skip-ws line (string-length tok))
+ doc-lines))
+ (body-cmt (peek-char *scheme-source*)))
+ ((string=? tok "@args")
+ (let ((args
+ (parse-args line (string-length tok))))
+ (set! doc-args (cdr args))
+ (set! doc-lines
+ (cons (skip-ws line (car args))
+ doc-lines)))
+ (body-cmt (peek-char *scheme-source*)))
+ (else
+ (out-cmt (if (string=? tok "@")
+ (skip-ws line 1)
+ line))
+ (doc-cmt (peek-char *scheme-source*))))))
+ ;; Transcribe the comment line to C source file.
+ (else
+ (read-line *scheme-source*) ;(out-c-cmt )
+ (lp (peek-char *scheme-source*)))))
+
+ ;;Comments incorporated in generated Texinfo files.
+ ;;Continue adding lines to DOC-LINES until a non-comment
+ ;;line is reached (may be a blank line).
+ (define (body-cmt c)
+ (cond ((eof-object? c) (lp c))
+ ((eqv? #\; c)
+ (set! doc-lines (cons (read-cmt-line) doc-lines))
+ (body-cmt (peek-char *scheme-source*)))
+ ((memv c '(#\nl #\cr))
+ (read-newline)
+ (lp (peek-char *scheme-source*)))
+ ;; Allow whitespace before ; in doc comments.
+ ((char-whitespace? c)
+ (read-char *scheme-source*)
+ (body-cmt (peek-char *scheme-source*)))
+ (else
+ (lp (peek-char *scheme-source*)))))
+
+ ;;Comments incorporated in generated Texinfo files.
+ ;;Transcribe comments to current position in Texinfo file
+ ;;until a non-comment line is reached (may be a blank line).
+ (define (doc-cmt c)
+ (cond ((eof-object? c) (lp c))
+ ((eqv? #\; c)
+ (out-cmt (read-cmt-line))
+ ;;(out-c-cmt (car ls))
+ (doc-cmt (peek-char *scheme-source*)))
+ ((memv c '(#\nl #\cr))
+ (read-newline)
+ (newline *derived-txi*)
+ (lp (peek-char *scheme-source*)))
+ ;; Allow whitespace before ; in doc comments.
+ ((char-whitespace? c)
+ (read-char *scheme-source*)
+ (doc-cmt (peek-char *scheme-source*)))
+ (else
+ (newline *derived-txi*)
+ (lp (peek-char *scheme-source*)))))
+ (lp (peek-char *scheme-source*))))
+
+(define (schmooz-top-doc-begin def1 defs doc proc-args)
+ (let ((op1 (sexp-def def1)))
+ (cond
+ ((not op1)
+ (or (null? doc)
+ (report "SCHMOOZ: no definition found for Texinfo documentation"
+ doc (car defs))))
+ (else
+ (let* ((args (def->args def1))
+ (args (if proc-args
+ (cons (if args (car args) (def->var-name def1))
+ proc-args)
+ args)))
+ (let loop ((ss defs)
+ (smatch (list (or args (def->var-name def1)))))
+ (if (null? ss)
+ (let ((smatch (reverse smatch)))
+ ((if args schmooz-fun schmooz-var)
+ op1 (car smatch) doc (cdr smatch)))
+ (if (eq? op1 (sexp-def (car ss)))
+ (let ((a (def->args (car ss))))
+ (loop (cdr ss)
+ (if args
+ (if a
+ (cons a smatch)
+ smatch)
+ (if a
+ smatch
+ (cons (def->var-name (car ss))
+ smatch)))))))))))))
+
+;;; SCHMOOZ-TOP - schmooz top level form sexp.
+(define (schmooz-top sexp1 sexps doc proc-args)
+ (cond ((not (pair? sexp1)))
+ ((pair? sexps)
+ (schmooz-top-doc-begin sexp1 sexps doc proc-args)
+ (set! doc '()))
+ (else
+ (case (car sexp1)
+ ((LOAD REQUIRE) ;If you redefine load, you lose
+ #f)
+ ((BEGIN)
+ (schmooz-top (cadr sexp1) '() doc proc-args)
+ (set! doc '())
+ (for-each (lambda (s)
+ (schmooz-top s '() doc #f))
+ (cddr sexp1)))
+ ((DEFVAR DEFINE DEFCONST DEFINE-SYNTAX DEFMACRO)
+ (let* ((args (def->args sexp1))
+ (args (if proc-args
+ (cons (if args (car args) (cadr sexp1))
+ proc-args)
+ args)))
+ (cond (args
+ (set! *procedure* (car args))
+ (cond ((pair? doc)
+ (schmooz-fun (car sexp1) args doc '())
+ (set! doc '()))))
+ (else
+ (cond ((pair? doc)
+ (schmooz-var (car sexp1) (cadr sexp1) doc '())
+ (set! doc '()))))))))))
+ (or (null? doc)
+ (report
+ "SCHMOOZ: no definition found for Texinfo documentation"
+ doc sexp))
+ (set! *procedure* #f))