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
From: Marco Antoniotti
Subject: Re: (Long) Just-in-time local variables, followup
Date: 
Message-ID: <lwww5u714t.fsf@galvani.parades.rm.cnr.it>
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