From: Tom Breton
Subject: (Long) Just-in-time local variables, followup
Date:
Message-ID: <m3r9w2u0ma.fsf@world.std.com>
Thanks to everyone who pitched in to the just-in-time thread.
For different reasons various posted code wasn't quite what I was
looking for and sometimes didn't quite work (No offense intended).
Here's the code I ended up with, tested on emacs. One major change
was that I didn't want variables to be created multiple times. I
always want to say "variables ... declared", because that's how I was
raised (so to speak). I will post the full package, which is little
more than this plus trimmings and commentary, on gnu.emacs.sources.
Without further ado,
;;; local-vars.el --- macro to allow just-in-time local variables.
;;
;; Copyright (C) 1998 Tom Breton
;;
(defsubst local-var-form-p (form)
"Return whether a form is a local-var form."
(and (consp form)
(eq 'local-var (car form))))
(defmacro using-local-vars (&rest body)
"Allow local variables to be declared just-in-time.
This statement is similar to a let statement, except that the local
variables are not written in a block at the beginning of the form,
they are scattered thruout the form.
Each local variable is introduced by a local-var statement, which only
has meaning inside a using-local-vars form. For the time being, each
local-var statement can only bind one variable.
Usage: \( using-local-vars ... (local-var a value) ... \)"
( let
(reversed-var-list reversed-bodies)
( dolist ( form body)
(if
(local-var-form-p form)
;;Process (local-var ...) forms specially.
(progn
;;Since we don't support multiple variables per local-var
;;statement, we check the number of args. If we looped,
;;we'd merely check >= 3.
( if
( /= (length form ) 3)
(error "local-var must have exactly 2 arguments"))
( let
( (my-var (cadr form) )
(my-value (caddr form)))
;;Don't allow the same local variable to be created twice.
(if
(memq my-var reversed-var-list)
(error "You mustn't create the same local variable twice" )
;;Accumulate the local variables to both places, processed
;;appropriately for each.
(setq reversed-var-list
(cons my-var reversed-var-list))
(setq reversed-bodies
(cons (list 'setq my-var my-value) reversed-bodies)))))
;;Accumulate normal body forms normally.
(setq reversed-bodies
(cons form reversed-bodies))))
(setq var-list (reverse reversed-var-list))
(setq bodies (reverse reversed-bodies))
;;Make the list to be returned, spreading bodies.
(apply 'list 'let var-list bodies)))
;;Similar code that removes duplications and proceeds. This works,
;;but it is not the behavior I want.
'(
;;Process (local-var ...) forms specially.
(let
( (my-var (cadr form) )
(my-value (caddr form)))
;;Accumulate the local variables to both places, processed
;;appropriately for each. add-to-list removes duplicates.
(add-to-list 'reversed-var-list my-var)
(setq reversed-bodies
(cons (list 'setq my-var my-value) reversed-bodies)))
)
;;; End of this post, but see gnu.emacs.sources
--
Tom Breton
My parenthesis are longer than yours.... :)
Tom Breton <···@world.std.com> writes:
> Thanks to everyone who pitched in to the just-in-time thread.
>
> For different reasons various posted code wasn't quite what I was
> looking for and sometimes didn't quite work (No offense intended).
>
> Here's the code I ended up with, tested on emacs. One major change
> was that I didn't want variables to be created multiple times. I
> always want to say "variables ... declared", because that's how I was
> raised (so to speak). I will post the full package, which is little
> more than this plus trimmings and commentary, on gnu.emacs.sources.
>
Changes to make the beast run under Elisp are trivial.
llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
;;; -*- Package: COMMON-LISP -*-
;;; vardef-forms.lisp -- WITH-VARDEF and VARDEF forms for Common Lisp.
;;; Provide a way to introduce variable in a block sequence with the
;;; semantics (roughly) provided by C++, Java, Dylan etc.
;;;
;;; This code is put in the public domain and it is provided AS IS,
;;; with no warranty implied about its use or functionalities. Use at
;;; your own risk.
;;;
;;; Author: Marco Antoniotti <·······@cs.nyu.edu>
(defpackage "VARDEF-FORMS" (:use "COMMON-LISP" #+:CMU "CONDITIONS")
(:nicknames "VARDEF")
(:export "VARDEF" "WITH-VARDEF" "VARDEF-ERROR"))
(in-package "VARDEF-FORMS")
(define-condition vardef-error (program-error)
((name :reader vardef-error-name :initarg :name)
(form :reader vardef-error-form :initarg :form)
(msg :reader vardef-error-msg :initarg :msg)
)
(:default-initargs :msg "VARDEF form [~S] not within a WITH-VARDEF block.")
(:report (lambda (c s)
(format s (vardef-error-msg c)
(vardef-error-name c)
(vardef-error-form c))))
;; The report function might not be kosher because of the
;; variable message.
(:documentation
"Error signalled by VARDEF macro when used in wrong context.")
)
(defmacro with-vardef (&body forms)
"WITH-VARDEF provides a context within which the VARDEF forms can be used."
`(progn ,@(expand-vardefs forms ())))
(defmacro vardef (name form &key type)
"VARDEF forms can be used only within a WITH-VARDEF block.
Otherwise a VARDEF-ERROR (i.e. a PROGRAM-ERROR) is signalled.
Their syntax is (VARDEF <name> <init-form> :type <declaration>)."
(declare (ignore type))
`(error 'vardef-error :name ',name :form ',form))
(defun expand-vardefs (forms already-declared-vars)
"EXPAND-VARDEF is the real workhorse in charge of the expansion."
(cond ((null forms) ())
((eq (first (first forms)) 'vardef)
(destructuring-bind (vardef-kwd name init-form &key type)
(first forms)
(declare (ignore vardef-kwd))
(when (not (symbolp name))
(error 'vardef-error
:name name
:form init-form
:msg "Variable name in VARDEF is not a symbol [~S]."))
(when (member name already-declared-vars :test #'eq)
(error 'vardef-error
:name name
:form init-form
:msg "Variable ~S in VARDEF has been already declared."))
;; Remember that we splice the result.
;; An optimization would be to recognize several VARDEFs in
;; sequence and to block them into a single LET*.
`((let ((,name ,init-form))
,@(when type `((declare (type ,type ,name))))
,@(expand-vardefs (rest forms)
(cons name already-declared-vars))))))
(t
(cons (first forms)
(expand-vardefs (rest forms) already-declared-vars)))
))
;;; Note on :TYPE expansion.
;;; Since we are introducing variable names, it is appropriate to
;;; expand the :TYPE keyword of FUNCTION type specifiers with TYPE
;;; instead of FTYPE. At least this is what I understood from the
;;; Hyperspec.
;;; Examples:
#|
* (use-package "VARDEF")
T
* (macroexpand '(with-vardef
(print (fun a))
(vardef a 1 :type fixnum)
(print (fun a))))
(PROGN
(PRINT (FUN A))
(LET ((A 1))
(DECLARE (TYPE FIXNUM A))
(PRINT (FUN A))))
T
* (macroexpand '(with-vardef
(print (fun a))
(vardef a 1 :type (or null fixnum))
(vardef b '(qwe rty uio))
(print (fun a))))
(PROGN
(PRINT (FUN A))
(LET ((A 1))
(DECLARE (TYPE (OR NULL FIXNUM) A))
(LET ((B '(QWE RTY UIO)))
(PRINT (FUN A)))))
T
* (let ((a 22))
(with-vardef
(print (1+ a))
(vardef a 1 :type fixnum)
(print (1+ a))))
23
2
2
* (vardef www 'qwe)
Invoking debugger...
Leaving debugger.
* (with-vardef
(vardef x 2)
(print x)
(vardef w 3 :type fixnum)
(print (+ x w))
(vardef x 'www)
)
Invoking debugger...
Leaving debugger.
* (with-vardef
(vardef x 2)
(print x)
(vardef w 3 :type fixnum)
(print (+ x w))
(vardef "x" 'www)
)
Invoking debugger...
Leaving debugger.
*
|#
;;; end of file -- vardef-forms.lisp --
llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
--
Marco Antoniotti ===========================================
PARADES, Via San Pantaleo 66, I-00186 Rome, ITALY
tel. +39 - (0)6 - 68 10 03 16, fax. +39 - (0)6 - 68 80 79 26
http://www.parades.rm.cnr.it