aboutsummaryrefslogtreecommitdiffstats
path: root/Macroexpand.scm
diff options
context:
space:
mode:
Diffstat (limited to 'Macroexpand.scm')
-rw-r--r--Macroexpand.scm370
1 files changed, 370 insertions, 0 deletions
diff --git a/Macroexpand.scm b/Macroexpand.scm
new file mode 100644
index 0000000..3b658f8
--- /dev/null
+++ b/Macroexpand.scm
@@ -0,0 +1,370 @@
+;; Copyright (C) 1999 Free Software Foundation, Inc.
+;;
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this software; see the file COPYING. If not, write to
+;; the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+;;
+;; As a special exception, the Free Software Foundation gives permission
+;; for additional uses of the text contained in its release of GUILE.
+;;
+;; The exception is that, if you link the GUILE library with other files
+;; to produce an executable, this does not by itself cause the
+;; resulting executable to be covered by the GNU General Public License.
+;; Your use of that executable is in no way restricted on account of
+;; linking the GUILE library code into it.
+;;
+;; This exception does not however invalidate any other reasons why
+;; the executable file might be covered by the GNU General Public License.
+;;
+;; This exception applies only to the code released by the
+;; Free Software Foundation under the name GUILE. If you copy
+;; code from other Free Software Foundation releases into a copy of
+;; GUILE, as the General Public License permits, the exception does
+;; not apply to the code that you add in this way. To avoid misleading
+;; anyone as to the status of such modified files, you must delete
+;; this exception notice from them.
+;;
+;; If you write modifications of your own for GUILE, it is your choice
+;; whether to permit this exception to apply to your modifications.
+;; If you do not wish that, delete this exception notice.
+
+;;;; "Macroexpand.scm", macro expansion, respecting hygiene.
+;;; Author: Radey Shouman
+
+;; It is possible to break MACRO:EXPAND by redefining primitive
+;; syntax, eg LAMBDA, LET, QUOTE to different primitive syntax,
+;; or by defining any of @LAMBDA, @LET, @LET*, @LETREC, @DO,
+;; or @EXPAND as primitive syntax.
+
+;; We still need LET-SYNTAX and LETREC-SYNTAX.
+
+(define macro:expand
+ (let (($lambda (renamed-identifier 'lambda '()))
+ ($let (renamed-identifier 'let '()))
+ ($let* (renamed-identifier 'let* '()))
+ ($letrec (renamed-identifier 'letrec '()))
+ ($do (renamed-identifier 'do '()))
+ ($define (renamed-identifier 'define '()))
+ ($quote (renamed-identifier 'quote '()))
+ ($quasiquote (renamed-identifier 'quasiquote '()))
+ ($unquote (renamed-identifier 'unquote '()))
+ ($unquote-splicing (renamed-identifier 'unquote-splicing '()))
+ ($case (renamed-identifier 'case '()))
+ ($cond (renamed-identifier 'cond '()))
+ ($begin (renamed-identifier 'begin '()))
+ ($if (renamed-identifier 'if '()))
+ ($and (renamed-identifier 'and '()))
+ ($or (renamed-identifier 'or '()))
+ ($set! (renamed-identifier 'set! '()))
+ ($delay (renamed-identifier 'delay '()))
+ ($syntax-quote (renamed-identifier 'syntax-quote '()))
+ ($@apply (renamed-identifier '@apply '()))
+ ($else (renamed-identifier 'else '()))
+ (@lambda (renamed-identifier '@lambda '()))
+ (@let (renamed-identifier '@let '()))
+ (@let* (renamed-identifier '@let* '()))
+ (@letrec (renamed-identifier '@letrec '()))
+ (@do (renamed-identifier '@do '()))
+ (@expand (renamed-identifier '@expand '())))
+
+ (define expander
+ (macro:compile-syntax-rules
+ '(syntax-rules (lambda let letrec let* do @let*)
+ ((_ (lambda ?formals ?body ...))
+ (@lambda ?formals ?body ...))
+
+ ((_ (let ((?name ?val) ...) ?body ...))
+ (@let ((?name ...) ?val ...) ?body ...))
+ ((_ (let ?proc ((?name ?val) ...) ?body ...))
+ (@expand
+ (letrec ((?proc (lambda (?name ...) ?body ...)))
+ (?proc ?val ...))))
+
+ ((_ (letrec ((?name ?val) ...) ?body ...))
+ (@letrec ((?name ...) ?val ...) ?body ...))
+
+ ((_ (let* () ?body ...))
+ (@let (()) ?body ...))
+ ((_ (let* ((?name1 ?val1) (?name ?val) ...) ?body ...))
+ (@expand
+ (@let* (?name1 ?val1) (let* ((?name ?val) ...) ?body ...))))
+ ((_ (@let* (?name ?val ...) (let* () ?body ...)))
+ (@let* (?name ?val ...) ?body ...))
+ ((_ (@let* (?name ?val ...)
+ (let* ((?name2 ?val2) (?name3 ?val3) ...) ?body ...)))
+ (@expand
+ (@let* (?name ?val ... ?name2 ?val2)
+ (let* ((?name3 ?val3) ...) ?body ...))))
+ ((_ (@let* (?name ?val ...) ?body ...))
+ (@let* (?name ?val ...) ?body ...))
+
+ ((_ (do ((?var ?init ?step) ...)
+ (?test ?clause ...)
+ ?body ...))
+ (@do (?var ...) (?init ...)
+ (?test ?clause ...)
+ (?body ...)
+ (?step ...)))
+
+ ((_ ?form)
+ ?form))
+ '()))
+
+ (define (simplify-identifiers expr env)
+ (let simplify ((expr expr))
+ (cond ((identifier? expr)
+ (let ((sym (identifier->symbol expr)))
+ (if (identifier-equal? sym expr env) sym expr)))
+ ((pair? expr)
+ (cons (simplify (car expr))
+ (simplify (cdr expr))))
+ (else expr))))
+
+ (define (unpaint expr)
+ (cond ((identifier? expr)
+ (identifier->symbol expr))
+ ((pair? expr)
+ (cons (unpaint (car expr)) (unpaint (cdr expr))))
+ ((vector? expr)
+ (list->vector (map unpaint (vector->list expr))))
+ (else expr)))
+
+ (define (defines->bindings defs)
+ (reverse ;purely cosmetic
+ (map (lambda (b)
+ (if (pair? (cadr b))
+ (list (caadr b)
+ (cons $lambda (cons (cdadr b) (cddr b))))
+ (cdr b)))
+ defs)))
+
+ (define (expand-define expr env)
+ (let ((binding (car (defines->bindings (list expr)))))
+ (cons (simplify-identifiers $define env)
+ (list (simplify-identifiers (car binding) env)
+ (macro:expand (cadr binding) env)))))
+
+ (define (expand-body expr-list env)
+ (let loop ((defines '())
+ (exprs expr-list))
+ (if (null? exprs) #f ; should check higher up.
+ (let ((exp1 (macro:expand (car exprs) env)))
+ (if (and (pair? exp1)
+ (identifier? (car exp1))
+ (identifier-equal? (car exp1) $define env))
+ (loop (cons exp1 defines) (cdr exprs))
+ (if (null? defines)
+ (cons exp1 (expand* (cdr exprs) env))
+ (let ((bindings (defines->bindings defines)))
+ (list
+ (macro:expand
+ (cons $letrec (cons bindings exprs))
+ env)))))))))
+
+ (define (expand* exprs env)
+ (map (lambda (x)
+ (macro:expand x env))
+ exprs))
+
+ ;;(@lambda formals body ...)
+ (define (expand-lambda expr env)
+ (let* ((formals (cadr expr))
+ (body (cddr expr))
+ (bound
+ (let recur ((f formals))
+ (cond ((null? f) '())
+ ((pair? f) (cons 'required (recur (cdr f))))
+ ((identifier? f) (list 'rest-list))
+ (else (error 'lambda 'bad-formals expr)))))
+ (env1 (extended-environment formals bound env)))
+ (cons (simplify-identifiers $lambda env)
+ (cons (simplify-identifiers formals env1)
+ (expand-body body env1)))))
+
+ ;;(@let ((formals) bindings) body ...)
+ (define (expand-let expr env)
+ (let* ((formals (caadr expr))
+ (bindings (expand* (cdadr expr) env))
+ (env1 (extended-environment formals
+ (map (lambda (x) 'let) formals)
+ env)))
+ (cons (simplify-identifiers $let env)
+ (cons (map list formals bindings)
+ (expand-body (cddr expr) env1)))))
+
+ (define (expand-let* expr env)
+ (let loop ((inp (cadr expr))
+ (formals '())
+ (bindings '())
+ (env1 env))
+ (if (null? inp)
+ (cons (simplify-identifiers $let* env)
+ (map list (reverse formals) (reverse bindings))
+ (expand-body (cddr expr) env1))
+ (loop (cddr inp)
+ (cons (car inp) formals)
+ (cons (macro:expand (cadr inp) env1) bindings)
+ (extended-environment (car inp) 'let* env1)))))
+
+ ;;(@letrec ((formals) bindings) body ...)
+ (define (expand-letrec expr env)
+ (let* ((formals (caadr expr))
+ (env1 (extended-environment
+ formals
+ (map (lambda (x) 'letrec) formals)
+ env))
+ (bindings (expand* (cdadr expr) env1)))
+ (cons (simplify-identifiers $letrec env)
+ (cons (map list formals bindings)
+ (expand-body (cddr expr) env1)))))
+
+ ;;(@do vars inits (test clause ...) (body ...) steps)
+ (define (expand-do expr env)
+ (let* ((vars (cadr expr))
+ (inits (expand* (caddr expr) env))
+ (env1 (extended-environment
+ vars (map (lambda (x) 'do) inits) env))
+ (steps (expand* (list-ref expr 5) env1)))
+ (cons (simplify-identifiers $do env)
+ (cons
+ (map list vars inits steps)
+ (cons (expand* (cadddr expr) env1)
+ (expand* (list-ref expr 4) env1))))))
+
+ (define (expand-quote expr env)
+ (let ((obj (cadr expr)))
+ (if (or (boolean? obj)
+ (number? obj)
+ (string? obj))
+ obj
+ (list (simplify-identifiers $quote env)
+ (unpaint obj)))))
+
+ (define (expand-quasiquote expr env)
+ (list (simplify-identifiers $quasiquote env)
+ (let qq ((expr (cadr expr))
+ (level 0))
+ (cond ((vector? expr)
+ (list->vector (qq (vector->list expr) level)))
+ ((not (pair? expr))
+ (unpaint expr))
+ ((not (identifier? (car expr)))
+ (cons (qq (car expr) level) (qq (cdr expr) level)))
+ ((identifier-equal? (car expr) $quasiquote env)
+ (list (simplify-identifiers $quasiquote env)
+ (qq (cadr expr) (+ level 1))))
+ ((or (identifier-equal? (car expr) $unquote env)
+ (identifier-equal? (car expr) $unquote-splicing env))
+ (list (simplify-identifiers (car expr) env)
+ (if (zero? level)
+ (macro:expand (cadr expr) env)
+ (qq (cadr expr) (- level 1)))))
+ (else
+ (cons (qq (car expr) level)
+ (qq (cdr expr) level)))))))
+
+ (define (expand-case expr env)
+ (cons (simplify-identifiers $case env)
+ (cons (macro:expand (cadr expr) env)
+ (map (lambda (clause)
+ (cond ((pair? (car clause))
+ (cons (unpaint (car clause))
+ (expand* (cdr clause) env)))
+ ((and (identifier? (car clause))
+ (identifier-equal? $else
+ (car clause) env))
+ (cons (simplify-identifiers
+ (car clause) env)
+ (expand* (cdr clause) env)))
+ (else (error 'macro:expand 'case
+ "bad clause" expr))))
+ (cddr expr)))))
+
+ (define (expand-cond expr env)
+ (cons (simplify-identifiers $cond env)
+ (map (lambda (clause) (expand* clause env))
+ (cdr expr))))
+
+ ;; for IF, BEGIN, SET!
+ (define (expand-simple expr env)
+ (cons (simplify-identifiers (car expr) env)
+ (expand* (cdr expr) env)))
+
+ (define (expand-primitives expr env)
+ (let loop ((expr (list '@expand expr)))
+ (let* ((expanded (expander expr env))
+ (head (car expanded)))
+ (cond ((identifier-equal? @LAMBDA head env)
+ (expand-lambda expanded env))
+ ((identifier-equal? @LET head env)
+ (expand-let expanded env))
+ ((identifier-equal? @LET* head env)
+ (expand-let* expanded env))
+ ((identifier-equal? @LETREC head env)
+ (expand-letrec expanded env))
+ ((identifier-equal? @DO head env)
+ (expand-do expanded env))
+ ((identifier-equal? $QUOTE head env)
+ (expand-quote expanded env))
+ ((identifier-equal? $QUASIQUOTE head env)
+ (expand-quasiquote expanded env))
+ ((identifier-equal? $BEGIN head env)
+ (expand-simple expanded env))
+ ((identifier-equal? $IF head env)
+ (expand-simple expanded env))
+ ((identifier-equal? $AND head env)
+ (expand-simple expanded env))
+ ((identifier-equal? $OR head env)
+ (expand-simple expanded env))
+ ((identifier-equal? $SET! head env)
+ (expand-simple expanded env))
+ ((identifier-equal? $DELAY head env)
+ (expand-simple expanded env))
+ ((identifier-equal? $@APPLY head env)
+ (expand-simple expanded env))
+ ((identifier-equal? $CASE head env)
+ (expand-case expanded env))
+ ((identifier-equal? $COND head env)
+ (expand-cond expanded env))
+ ((and (identifier-equal? $DEFINE head env)
+ (null? (environment->tree env)))
+ (expand-define expanded env))
+ ((identifier-equal? $SYNTAX-QUOTE head env)
+ (cons (simplify-identifiers head env)
+ (cdr expanded)))
+ ((identifier-equal? @EXPAND head env)
+ (loop expanded))
+ (else
+ (print 'macro:expand
+ "Warning: unknown primitive syntax" (car expanded))
+ expanded)))))
+
+ (lambda (expr env)
+ (let loop ((expr expr))
+ (let ((expanded (@macroexpand1 expr env)))
+ (cond ((not expanded)
+ (cond ((pair? expr)
+ (if (list? expr)
+ (expand* expr env)
+ (print 'macro:expand "expansion not a list" expr)))
+ ((identifier? expr)
+ (simplify-identifiers expr env))
+ (else expr)))
+ ((eq? expanded expr)
+ (expand-primitives expr env))
+ (else
+ (loop expanded))))))))
+
+;;; Local Variables:
+;;; eval: (put 'identifier-case 'scheme-indent-function 1)
+;;; End: