help-gnu-emacs
[Top][All Lists]
Advanced

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

the (declare special) declaration with lexical scope.


From: Madhu
Subject: the (declare special) declaration with lexical scope.
Date: Sun, 23 Apr 2023 10:37:36 +0530

[Pardon me blog] So I wanted a quick templating system -- having heard
the term "mustache" thrown around in this context, I decided to name
this function after that.

```
(defun mustache-lookup-binding (string-key bindings)
  (cond ((null bindings)
         (eval (intern-soft string-key)))
        ((consp (car bindings))
         (cdr (cl-assoc (if (stringp (car (car bindings)))
                            string-key
                          (intern-soft string-key))
                        bindings :test #'equal)))
        (t (cl-assert (symbolp (car bindings)))
           (plist-get bindings (intern-soft string-key)))))

(cl-defun mustache (string &key readable bindings)
  (with-temp-buffer
    (insert string)
    (goto-char (point-min))
    (while (re-search-forward "{{\\([^}{]+\\)}}" nil t)
      (let* ((string-key (match-string 1))
             (val (mustache-lookup-binding string-key bindings))
             (replacement (if readable (prin1-to-string val) "")))
        (replace-match replacement nil nil nil 0)
        (unless readable
          (princ val (current-buffer)))))
    (buffer-substring (point-min) (point-max))))
```

This would let me write stuff like

```
(mustache "foo = {{foo}} bar = {{barf}}" :readable nil
          :bindings '((foo . 10) (barf . (10 + 10))))
;; "foo = 10 bar = (10 + 10)"
;; or
(let ((foo 10) (barf '(10 + 10)))
  (mustache  "foo = {{foo}} bar = {{barf}}"))

```

The important thing was to interpolate the values regular emacs
variables, say bound during a function call, into the template string.
This is all very good when using dynamic binding, but you can't reliably
use dynamic binding with emacs lisp anymore.

I'm sure stefan could come up with some rube goldberg constructs to
avoid the call to eval in the mustache-lookup-binding function above.
But in the absence of cl-symbol-value in elisp this is what I needed and
I was resigned to resorting to -*- lexical-binding: nil -*- for using
all this

However it turns out elisp has a little-documented way to achieve
(locally (declare (special)). It isn't documented in the doctsrings or
in the code, only in an info node. The semantics are not discernable
from reading the info node and look arbitrary instead being absed on
reliable CL precedent...

* (info "(elisp) Using Lexical Binding")
```
   Using ‘defvar’ without a value, it is possible to bind a variable
dynamically just in one file, or in just one part of a file while still
binding it lexically elsewhere.  For example:

     (let (_)
       (defvar x)      ; Let-bindings of ‘x’ will be dynamic within this let.
       (let ((x -99))  ; This is a dynamic binding of ‘x’.
         (defun get-dynamic-x ()
           x)))
```

With this I could write

```
(defmacro mustache-lexical (string)
  (let* ((keys-string (mustache-extract-keys string))
         (keys-symbol (mapcar #'intern-soft keys-string)))
    `(let (_)
       ,@(cl-loop for key in keys-symbol collect `(defvar ,key))
       (let (,@(cl-loop for key in keys-symbol collect (list key key)))
         (mustache ,string)))))
```

and this would I think works with lexical-binding:t

I've lost the flexibililty of a function (with :binding and :readable
keyword args to control the input and output) but I think I can call

```
(defun foobar (foo barf)
  (mustache-lexical  "foo = {{foo}} bar = {{barf}}"))

(foobar 10 20)
```

reliably in lexical elisp. Is the code above really reliable?
How stable and well rounded is  the
``` (let () (defvar x)) '''
feature?




reply via email to

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