From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: "Pascal J. Bourguignon" Newsgroups: gmane.emacs.devel Subject: Re: macros and the lexical environment Date: Wed, 05 Jun 2013 23:08:05 +0200 Organization: Informatimago Message-ID: <87y5aomfka.fsf@kuiper.lan.informatimago.com> References: <87ip1tim3t.fsf@ferrier.me.uk> <87hahdo4pu.fsf@kuiper.lan.informatimago.com> <87d2s1hsp2.fsf@ferrier.me.uk> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Trace: ger.gmane.org 1370466842 24783 80.91.229.3 (5 Jun 2013 21:14:02 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Wed, 5 Jun 2013 21:14:02 +0000 (UTC) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Wed Jun 05 23:14:03 2013 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1UkL1u-000841-Mv for ged-emacs-devel@m.gmane.org; Wed, 05 Jun 2013 23:14:02 +0200 Original-Received: from localhost ([::1]:46084 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UkL1u-0004GY-8P for ged-emacs-devel@m.gmane.org; Wed, 05 Jun 2013 17:14:02 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:55177) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UkL1p-0004GN-H8 for emacs-devel@gnu.org; Wed, 05 Jun 2013 17:13:59 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1UkL1n-0006BW-Et for emacs-devel@gnu.org; Wed, 05 Jun 2013 17:13:57 -0400 Original-Received: from plane.gmane.org ([80.91.229.3]:57621) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UkL1n-0006BQ-4j for emacs-devel@gnu.org; Wed, 05 Jun 2013 17:13:55 -0400 Original-Received: from list by plane.gmane.org with local (Exim 4.69) (envelope-from ) id 1UkL1k-0007td-4a for emacs-devel@gnu.org; Wed, 05 Jun 2013 23:13:52 +0200 Original-Received: from amontsouris-651-1-111-59.w83-202.abo.wanadoo.fr ([83.202.226.59]) by main.gmane.org with esmtp (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Wed, 05 Jun 2013 23:13:52 +0200 Original-Received: from pjb by amontsouris-651-1-111-59.w83-202.abo.wanadoo.fr with local (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Wed, 05 Jun 2013 23:13:52 +0200 X-Injected-Via-Gmane: http://gmane.org/ Original-Lines: 179 Original-X-Complaints-To: usenet@ger.gmane.org X-Gmane-NNTP-Posting-Host: amontsouris-651-1-111-59.w83-202.abo.wanadoo.fr Face: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAQMAAABtzGvEAAAABlBMVEUAAAD///+l2Z/dAAAA oElEQVR4nK3OsRHCMAwF0O8YQufUNIQRGIAja9CxSA55AxZgFO4coMgYrEDDQZWPIlNAjwq9 033pbOBPtbXuB6PKNBn5gZkhGa86Z4x2wE67O+06WxGD/HCOGR0deY3f9Ijwwt7rNGNf6Oac l/GuZTF1wFGKiYYHKSFAkjIo1b6sCYS1sVmFhhhahKQssRjRT90ITWUk6vvK3RsPGs+M1RuR mV+hO/VvFAAAAABJRU5ErkJggg== X-Accept-Language: fr, es, en User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3 (gnu/linux) Cancel-Lock: sha1:YjA1MDU2MmU2NGY3MmM4YmUwYTg4YTNiOWQ5YmY1NGMyZGU2N2I2YQ== X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 80.91.229.3 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:160147 Archived-At: Nic Ferrier writes: > Nic Ferrier said: > >>> However. It's implementation is a pain. > > "Pascal J. Bourguignon" 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