Search code examples
javascriptbrowsermacroscommon-lispbookmarklet

Why I am not being able to use this Common Lisp macro that parses javascript code?


I am using the Nyxt web browser which is an interesting Common Lisp application. Nyxt is designed to be an infinitely extensible browser. Thus, the user can change the code and/or create extensions while the program is running. This is live hackability by design.

One of the possible extensions is to create a new command for the web browser. There is more than one way to create a new command. One of them is by using a bookmarklet command. It must be highlighted the macro function responsible for defining this command:

(defmacro nyxt::define-bookmarklet-command (name documentation source)
  "Define a bookmarklet command, the source can either be a JavaScript string to
evaluate, or a file:// URL with a file path to a JavaScript source file."
  `(define-command-global ,name (&optional (buffer (current-buffer)))
     ,documentation
     (let* ((source ,source)
            (source (if (nyxt::file-url-p source)
                        (nyxt::read-file-string source)
                        source)))
       (ffi-buffer-evaluate-javascript-async buffer source))))
(sera:export-always 'nyxt::define-bookmarklet-command :nyxt)

This definition is placed here on the source code. I managed to create a bookmarklet command for Nyxt before. Basically, a translation from this Javascript snippet:

(function() { const rate = prompt('Set the
 new playback rate', 2.5); if (rate != null) { const video =
 document.getElementsByTagName('video')[0]; video.playbackRate =
 parseFloat(rate); } })();

Converted to the following common-lisp snippet using Nyxt definitions:

(define-bookmarklet-command live-hack-youtube-speed "Change youtube
 videos speed" "(function() { const rate = prompt('Set the
 new playback rate', 2.5); if (rate != null) { const video =
 document.getElementsByTagName('video')[0]; video.playbackRate =
 parseFloat(rate); } })();")

Now, I am trying a new bookmarklet customization for Nyxt. Basically, finding the 'next' page and click on it. This would work on pages like the GNU manuals. In Javascript, this is:

(document.querySelectorAll('[rel="next"]'))[0].click()

Thus, I tried the following using Nyxt's macro:

  (define-bookmarklet-command goNext "Follow the link labeled next" 
     "(function() {(document.querySelectorAll('[rel="next"]'))[0].click()})();")

Awkwardly enough, I receive this error message:

Error while parsing arguments to DEFMACRO DEFINE-BOOKMARKLET-COMMAND: too many elements in (GONEXT "Follow the link labeled next" "(function() {(document.querySelectorAll('[rel=" NEXT "]'))[0].click()})();") to satisfy lambda list (NAME DOCUMENTATION NYXT/WEB-MODE::SOURCE): exactly 3 expected, but got 5 [Condition of type SB-KERNEL::ARG-COUNT-ERROR]

I am not sure why this is happening. I am passing on the macro 3 arguments. However, the error message indicates 5.

I have a knowledge gap on CL macros. Why is this happening?


Solution

  • It turns out the problem was not my comprehesion of the Common Lisp macro. Actually, it was something simple. I needed to escape the quotes around "next" with \"next\":

    (define-bookmarklet-command go-next "no documentation yet" "(function() {(document.querySelectorAll('[rel=\"next\"]'))[0].click()})();")
    

    Now, the problem is solved and the customization works as expected on Nyxt :) The REPL returns T after receiving the definition and it works on Nyxt's GUI.

    NYXT>(define-bookmarklet-command go-next "no documentation yet" "(function() {(document.querySelectorAll('[rel=\"next\"]'))[0].click()})();")
    T
    

    enter image description here