0

I need to retrieve a local, lexical, runtime variable from a function, but I'm unable to modify the function to return it because it is from an external library. The variable I'm unable to reach is link-end.

(defun org-element-link-parser ()
  (let ((link-end) ...)
  ...
  (setq link-end (something))
  ...))

Language features I've looked into don't seem to be able to help, advising functions is basically a wrapper, and as to hooks, org-element-link-parser doesn't have hooks. I tried defining link-end in global scope, but it didn't change after the function quit.

phils
  • 48,657
  • 3
  • 76
  • 115
sextrism
  • 1
  • 1

2 Answers2

1

You can't do that and you shouldn't even try: let blocks are meant to define variables that are local in scope and limited in lifetime - they spring into existence when the block is entered and they go POOF when the block is exited.

Instead, you should call the function and deduce what you need from what the function returns. In this case:

(setq my-link-end (org-element-property :end (org-element-link-parser)))

The :end property of the element is actually after any white space that is trailing the link, so if you don't want that, you will need to back up:

(setq my-link-end
        (save-excursion
          (goto-char (org-element-property :end (org-element-link-parser)))
          (skip-chars-backward " \t")
          (point))) 

And there should probably be some error checking to make sure that you are at a link, otherwise the result of the org-element-property call will be nil and goto-char will barf.

NickD
  • 27,023
  • 3
  • 23
  • 42
  • I should've read the org-element-link-parser definition more carefully, I knew about :end, thought I didn't want it, but didn't realize that applying (skip-chars-backward " \t") on it is equivalent to `link-end`. In this case I won't need to expose a variable inside a let inside a lexical function, but I'll leave the question up just in wonder if it's possible. – sextrism Mar 30 '23 at 20:22
  • In a lot of programming contexts I might feel the need to tweak one library function, and I find an extreme poverty of tools for that (short of redefining the function and falling behind on updates) in all programming languages I know about. – sextrism Mar 30 '23 at 20:27
  • 1
    Emacs Lisp, like some other lisps, implements a general-purpose `advice` system (two of them, in fact) which is as good a system of "tweaking" other functions as I've encountered. https://www.gnu.org/software/emacs/manual/html_node/elisp/Advising-Functions.html documents the newer of the two systems. Advising functions is generally a last resort, but the ability is a blessing when you need it. – phils Mar 31 '23 at 00:00
  • 1
    I see you've indicated in another comment that advice is inadequate for your purpose, and I'm not trying to argue otherwise; I'm just saying that "an extreme poverty of tools" isn't an accurate description when you have (a) the ability to redefine functions; (b) the ability to advise them in many different ways; and (c) if you really, really, really want to (and you shouldn't), the ability to manipulate code as data. Compared to many languages, this is a veritable wealth of tools. – phils Mar 31 '23 at 00:09
1

The answer can be split into a general answer to the question as stated in the caption

Any way to access a lexical let variable outside of the let?

and an answer to your specific problem in the description, how to access link-end in org-element-link-parser. The general answer does not work for your specific problem since you do not want to modify org-element-link-parser.

The short answer to both parts is: Yes, you can.

First, I want to address your specific problem:

An excerpt from org-element-parser:

(defun org-element-link-parser ()
  (let (... link-end ...)
    (cond
     ...
     ;; all cases look like the following:
     ((looking-at ...)
      ...
      (setq link-end (match-end 0))
      ...
      )
     )
    ;; After the `cond':
    (save-excursion
      (setq post-blank
        (progn (goto-char link-end) (skip-chars-forward " \t")))
      (setq end (point)))
    (list 'link
      (list ;; The element properties:
       ...
       :end end
       ...))))

So, end is essentially link-end, only that there is the additional operation (skip-chars-forward " \t").

If you want to retrieve the value of link-end you have to reverse the effect of (skip-chars-forward " \t") to the property :end. Luckily the number of chars skipped by (skip-chars-forward " \t") is saved as property :post-blank.

I.e., with point at a link, do:

(when-let ((link (org-element-link-parser))
       (post-blank (org-element-property :post-blank link))
       (end (org-element-property :end link))
       (link-end (- end post-blank)))
  link-end)

Now, the answer to the general question

Any way to access a lexical let variable outside of the let?

Yes, define setter/getter functions within the let for accessing the variable.
The variable will be in the lexical environment of these functions.

(declare-function my-set nil)
(declare-function my-get nil)

(defun my-function ()
  "Function with the lexically bound variable `my-var'.

We define `my-set' and `my-get' here."
  (let ((my-var 1))
    (fset 'my-set
      (lambda (value)
        (setq my-var value)))
    (fset 'my-get
      (lambda ()
        my-var)))
  "The function could also return the lambdas.")

(format
 "The return value of `my-function': %s
The value of `my-var': %d
Setting `my-var' to 2: %d
The value after `my-set': %d"
 (my-function)
 (my-get)
 (my-set 2)
 (my-get))

The resulting output is:

The return value of `my-function': The function could also return the lambdas.
The value of `my-var': 1
Setting `my-var' to 2: 2
The value after `my-set': 2

Tobias
  • 32,569
  • 1
  • 34
  • 75