emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: macros and the lexical environment


From: Pascal J. Bourguignon
Subject: Re: macros and the lexical environment
Date: Wed, 05 Jun 2013 23:08:05 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3 (gnu/linux)

Nic Ferrier <address@hidden> writes:

> Nic Ferrier said:
>
>>> However. It's implementation is a pain. 
>
> "Pascal J. Bourguignon" <address@hidden> replied:
>
>> ???
>>> I decided that it would be better to expand the format string into a
>>> list of variable references. But that doesn't work well unless the
>>> string is static, using a reference for the format string doesn't
>>> work because it's not available at compile time:
>>>
>>>  (let ((v 42)
>>>        (a "hello world")
>>>        (template "${a} - the answer is ${v}"))
>>>     (s-lex-format template))
>>>
>>> will fail to expand.
>
> and yours suffers from the same problem of course.

Indeed, I didn't read closely the end of your message, that's what a
java work day does to your brain :-(

> In summary, you haven't resolved the problem, it's fundamental in
> elisp. You can't get at the names of the variables in scope at compile
> time and have:

Now, as I said in my previous answer, you can do the book-keeping
yourself.  This means, define macros to use instead of defun, lambda,
let, let*, etc.   In Common Lisp it's a simple matter, shadowing
CL:DEFUN etc, and defining macros named DEFUN, etc, in whatever other
package.

Since we don't have packages in emacs, we'll have to use different
names, for example, edefun, elambda, elet, elet*, etc. (Or one could
spend some time to hack obarrays into real CL-like packages, but I doubt
it would be accepted by emacs maintainers, given the ostracism 'cl is
victim of.

Anyways:


(eval-when (compile load eval)
  (defun split-body (body)
    (let ((docstring    (when (and (stringp (first body))
                                   (rest body))
                          (list (pop body))))
          (declarations (loop
                           while (and body
                                      (listp (first body))
                                      (assoc (first (first body)) 
defun-declarations-alist))
                           collect (pop body))))
      (list docstring declarations body)))

  (defun arglist-parameter-names (arglist)
    (set-difference arglist '(&optional &rest))))


(defvar *environment* '())

(defmacro elambda (arglist &rest docstring-decl-body)
  (let ((variables (arglist-parameter-names arglist)))
    (destructuring-bind (docstring declarations body) (split-body 
docstring-decl-body)
      `(lambda ,arglist
         ,@docstring
         ,@declarations
         (let ((*environment* (append ',variables *environment*)))
           ,@body)))))

(defmacro edefun (name arglist &rest docstring-decl-body)
  (let ((variables (arglist-parameter-names arglist)))
   (destructuring-bind (docstring declarations body) (split-body 
docstring-decl-body)
     `(defun ,name ,arglist
        ,@docstring
        ,@declarations
        (let ((*environment* (append ',variables *environment*)))
          ,@body)))))

(defmacro elet (varlist  &rest body)
  (let ((variables (mapcar (lambda (binding) (if (atom binding) binding (first 
binding)))
                           varlist)))
    `(let ((*environment* (append ',variables *environment*)))
       (let ,varlist 
         ,@body))))

(defmacro elet* (varlist  &rest body)
  (let ((variables (mapcar (lambda (binding) (if (atom binding) binding (first 
binding)))
                           varlist)))
    `(let ((*environment* (append ',variables *environment*)))
       (let* ,varlist 
         ,@body))))


(defun s-lex-format* (string environment)
  (loop
     with last = 0
     with arguments = '()
     with result = ""
     for start = (search "${" string) then (search "${" string :start2 last)
     while start
     do (let ((end (search "}" string :start2 start)))
          (unless end (error "Missing } after ${"))
          (push (intern (subseq string (+ 2 start) end)) arguments)
          (setf result (format "%s%s%%s" result (subseq string last start))
                last (1+ end)))
     finally (let* ((end (length string))
                    (format-control (concat result (subseq string last end)))
                    (undefined (list 'undefined))
                    (values (mapcar (lambda (variable)
                                      (let ((value (getf environment variable 
undefined)))
                                        (when (eq value undefined)
                                          (error "Undefined variable %s" 
variable))
                                        value))
                                    (nreverse arguments))))
               (return (apply (function format) format-control values))))))

(defmacro s-lex-format (string-expression)
  `(s-lex-format* ,string-expression
                  (list ,@(mapcan (lambda (variable)
                                    (list `',variable variable))
                                  *environment*))))

(elet ((v 42)
       (a "hello world"))
   (s-lex-format "${a} - the answer is ${v}"))
--> "hello world - the answer is 42"

(let ((control-string "${a} - the answer is ${v}"))
  (elet ((v 42)
         (a "hello world"))
        (s-lex-format control-string)))
--> "hello world - the answer is 42"



In general, there's an advantage to proceed this way, in that you can
disclose explicitely the lexical environment you want to publish:

(let ((control-string "${a} / ${private}- the answer is ${v}"))
  (elet ((v 42)
         (a "hello world"))
    (let ((private 'secret)) 
        (s-lex-format control-string))))
==> Lisp error: (error "Undefined variable private")



Or, you could just use explicitely the with-environment macro:

(let ((control-string "${a} - the answer is ${v}"))
   (let ((v 42)
         (a "hello world")
         (private 'secret))
     (with-environment (env v a)
        (s-lex-format* control-string env))))
--> "hello world - the answer is 42"


Also, notice that if the parameters are not known at compilation time,
there's little reason to use a macro…  You could as well have written:

(let ((control-string "${a} - the answer is ${v}"))
   (let ((v 42)
         (a "hello world")
         (private 'secret))
     (s-lex-format* control-string (list 'a a 'v v))))
--> "hello world - the answer is 42"




-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
A bad day in () is better than a good day in {}.
You can take the lisper out of the lisp job, but you can't take the lisp out
of the lisper (; -- antifuchs




reply via email to

[Prev in Thread] Current Thread [Next in Thread]