unofficial mirror of help-gnu-emacs@gnu.org
 help / color / mirror / Atom feed
* the (declare special) declaration with lexical scope.
@ 2023-04-23  5:07 Madhu
  2023-04-23  9:47 ` Platon Pronko
  2023-04-23 14:22 ` Emanuel Berg
  0 siblings, 2 replies; 4+ messages in thread
From: Madhu @ 2023-04-23  5:07 UTC (permalink / raw)
  To: help-gnu-emacs

[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?




^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: the (declare special) declaration with lexical scope.
  2023-04-23  5:07 the (declare special) declaration with lexical scope Madhu
@ 2023-04-23  9:47 ` Platon Pronko
  2023-04-24  2:16   ` Madhu
  2023-04-23 14:22 ` Emanuel Berg
  1 sibling, 1 reply; 4+ messages in thread
From: Platon Pronko @ 2023-04-23  9:47 UTC (permalink / raw)
  To: Madhu, help-gnu-emacs

I'm not qualified to answer the question about lexical scope.

However I'm interested why you didn't consider to make the entire mustache function
into a macro that expands to concat call with variables already interpolated?
This way it will work regardless of lexical or dynamic binding.

I suppose this might break if template is dynamically generated somewhere,
however templates are usually static and thus can be embedded at compile time
(it will be the fastest-performing option, aside from other benefits).

Here's an example of what I mean:

(defun mustache-build (template)
   (let ((pattern-start (string-match "{{\\(.*?\\)}}" template)))
     (if (not pattern-start)
         template
       `(,(substring template 0 pattern-start)
         (prin1-to-string ,(intern (match-string 1 template)))
         ,@(if (>= (match-end 0) (length template)) nil (mustache-build (substring template (match-end 0))))))))
(defmacro mustache (template)
   `(concat ,@(mustache-build template)))

(macroexpand '(mustache "a={{a}} b={{b}}"))
; (concat "a=" (prin1-to-string a) " b=" (prin1-to-string b))

(let ((a 42) (b '(+ 2 3)))
   (mustache "a={{a}} b={{b}}"))
; "a=42 b=(+ 2 3)"

--
Best regards,
Platon Pronko
PGP 2A62D77A7A2CB94E



^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: the (declare special) declaration with lexical scope.
  2023-04-23  5:07 the (declare special) declaration with lexical scope Madhu
  2023-04-23  9:47 ` Platon Pronko
@ 2023-04-23 14:22 ` Emanuel Berg
  1 sibling, 0 replies; 4+ messages in thread
From: Emanuel Berg @ 2023-04-23 14:22 UTC (permalink / raw)
  To: help-gnu-emacs

Madhu wrote:

> 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)))

You need this for the byte-compiler:

  (declare-function get-dynamic-x nil)

As for your question - that would be a dynamic let-closure and
not a lexical. Interesting. What would the implications be?

And see if you can use `dlet'.

-- 
underground experts united
https://dataswamp.org/~incal




^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: the (declare special) declaration with lexical scope.
  2023-04-23  9:47 ` Platon Pronko
@ 2023-04-24  2:16   ` Madhu
  0 siblings, 0 replies; 4+ messages in thread
From: Madhu @ 2023-04-24  2:16 UTC (permalink / raw)
  To: platon7pronko; +Cc: help-gnu-emacs

*  Platon Pronko <d4e72287-61c4-129c-9886-c0881237b9da@gmail.com>
Wrote on Sun, 23 Apr 2023 17:47:40 +0800
> However I'm interested why you didn't consider to make the entire
> mustache function into a macro that expands to concat call with
> variables already interpolated?  This way it will work regardless of
> lexical or dynamic binding.

You're right. If I had started that way it lexenv wouldn't have been
an issue and my goal here was mainly to get the "rendering context"
from the (dynamic or lexical) environment.

But I imagined the idea of "templating" involves compiling a template
pattern to a funcallable and calling it repeatedly with different
arguments.


> I suppose this might break if template is dynamically generated
> somewhere, however templates are usually static and thus can be
> embedded at compile time (it will be the fastest-performing option,
> aside from other benefits).
>
> Here's an example of what I mean:
>
> (defun mustache-build (template)
>   (let ((pattern-start (string-match "{{\\(.*?\\)}}" template)))
>     (if (not pattern-start)
>         template
>       `(,(substring template 0 pattern-start)
>         (prin1-to-string ,(intern (match-string 1 template)))
>         ,@(if (>= (match-end 0) (length template)) nil (mustache-build
>         (substring template (match-end 0))))))))
> (defmacro mustache (template)
>   `(concat ,@(mustache-build template)))
>
> (macroexpand '(mustache "a={{a}} b={{b}}"))
> ; (concat "a=" (prin1-to-string a) " b=" (prin1-to-string b))
>
> (let ((a 42) (b '(+ 2 3)))
>   (mustache "a={{a}} b={{b}}"))
> ; "a=42 b=(+ 2 3)"


This is enough for the problem I was working on.



^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2023-04-24  2:16 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-04-23  5:07 the (declare special) declaration with lexical scope Madhu
2023-04-23  9:47 ` Platon Pronko
2023-04-24  2:16   ` Madhu
2023-04-23 14:22 ` Emanuel Berg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).