Subject: [bug#60753] [PATCH] gnu: home: Add home-emacs-service-type.

From: Andrew Tropin <[email-masked]>
Date: Wed, 01 Feb 2023 17:46:59 +0400

On 2023-02-01 12:59, Jelle Licht wrote:

> Andrew Tropin <[email-masked]> writes:
>
>> On 2023-01-23 11:18, Ludovic Courtès wrote:
>>
>>> Hi,
>>>
>>> Andrew Tropin <[email-masked]> skribis:
>>>
>>>> On 2023-01-17 10:02, Ludovic Courtès wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> Andrew Tropin <[email-masked]> skribis:
>>>>>
>>>>>>> What about accepting sexps (or gexps) instead of strings?  As in:
>>>>>>>
>>>>>>>   (init-file '((require 'whatever) (setq something t)))
>>>>>>
>>>>>> A quick minor note on this approach: it won't be possible to use
>>>>>> #'elisp-function inside such configuration because it will be
>>>>>> interpreted by guile reader, but actually rde lives without this
>>>>>> functionality completely ok.
>>>>>
>>>>> Specifically:
>>>>>
>>>>>   (write '#'x)
>>>>>   |= (syntax x)
>>>>>
>>>>> But we can use (guix read-print) and ensure that it prints #'.
>>>>>
>>>>
>>>> Do you have any links to docs/sample implementations on the topic of
>>>> extending guile reader, so we have an example to start with?
>>>
>>> It’s not the reader but rather the writer that we’d want to tweak.
>>
>> Right, it already can read #'x as (syntax x) and we can print it
>> properly later, but AFAIK comments are ignored by the default reader.
>> So I would expect to do something (very roughly) like this:
>>
>> --8<---------------cut here---------------start------------->8---
>> (parameterize (((@@ (guix gexp) read-procedure) read-with-comments))
>>   #~(list 'hello ; Comment I would like to preserve during serialization
>>           'guix))
>> --8<---------------cut here---------------end--------------->8---
>>
>> Of course it doesn't work, but I hope demonstrates the idea.
>>
>>>
>>> In (guix read-print), ‘pretty-print-with-comments’ already special
>>> cases quasiquote etc. so that it prints ‘`’ (backtick) and not
>>> ‘quasiquote'.  We’d add clauses for ‘syntax’ and ‘quasisyntax’.
>>>
>>
>> It seems ice-9 pretty-print also preserves backticks, but I see that
>> pretty-print-with-comments also preserves gexps, which is cool.  Adding
>> syntax will make it even cooler.
>>
>>>> I think it will be cool to hook up a custom reader, ideally comment
>>>> preserving, for emacs lisp inside scheme files.
>>>
>>> (guix read-print) is what you want.  :-)
>>>
>>
>> Can you give a hint on how to use it for preserving comments, please?
>>
>>>>>> Do we want something like this possible?
>>>>>>
>>>>>> (init-file `((require 'whatever)
>>>>>>              (setq something t)
>>>>>>              (load ,(local-file "old-init.el")))
>>>>>
>>>>> It’d be nice.  In that case, we’ll want it to be a gexp though:
>>>>>
>>>>>   #~((require 'whatever) (load #$(local-file …)))
>>>>>
>>>>
>>>> gexps are nice, but do we really need/want them here?  Do you have any
>>>> thoughts on what are the benifits over quasiquotes in this case?  Maybe
>>>> some examples?
>>>
>>> The benefit in the example above is that the gexp would actually work
>>> whereas the sexp wouldn’t :-), unless there’s code somewhere to manually
>>> traverse the sexp adn replace the <local-file> record with its store
>>> item (which is what gexps are about).
>>>
>>> I hope that makes sense!
>>
>> With this simple serializer we already achieved quite good results: 
>> https://git.sr.ht/~abcdw/rde/tree/388d3ad95e8607543df3dcdf26d058b610e77389/src/rde/serializers/lisp.scm#L35
>>
>> For this input
>> --8<---------------cut here---------------start------------->8---
>> `((load ,(local-file "./feature-lists.scm"))
>>   ,#~(format #f "hello") ; top level gexps are evaluated
>>   (list ,#~(format #f "hello")) ; nested gexps are not
>>   ,#~";; hacky comment"
>>   ;; comment, which is not preserved
>>   #'hi-fn ; incorrectly serialized, but fixable by alternative
>>           ; pretty-print
>>   )
>> --8<---------------cut here---------------end--------------->8---
>>
>> it provides quite satisfying results:
>> --8<---------------cut here---------------start------------->8---
>> (load "/gnu/store/xb6ma0mcgg1zzq645s63arvy3qskmbiz-feature-lists.scm")
>> hello
>> (list (format #f "hello"))
>> ;; hacky comment
>> (syntax hi-fn)
>> --8<---------------cut here---------------end--------------->8---
>>
>> It's a little incosistent (top level gexp are evaluated, but nested are
>> not), comments are not preserved and #' serialized incorrectly, but
>> other than that it works very good.
>>
>> WDYT about overall approach used here?  or we can do it radically
>> better?
>
> Not saying it's better in any particular way, but I have had this locally
> for all my elisp-read-by-guile-written-back-to-elisp needs:

I saw it in guix-home-manager and probably you've made the thread on
rde-devel too.  I tried some parts of this code, but didn't succeed to
get a complete working out of it, now I have a little more knowledge and
hope will get better results :)

>
> --8<---------------cut here---------------start------------->8---
> (define-module (jlicht build elisp-write)
>   #:use-module (ice-9 match)
>   #:use-module (srfi srfi-1)
>   #:export (elisp-write))
>
> (define (elisp-write in-list? exp port)
>   "Stack-blowing implementation that writes guile's internal elisp
> representation to something that can be parsed by Emacs."
>   ;; Definitions from (language elisp parser)'s quotation-symbols: 
>   (define symbol-strings
>     '((#{`}# . "`")
>       (#{,}# . ",")
>       (#{,@}# . ",@")))
>   (define (elisp-symbol? sym)
>     (assq sym symbol-strings))
>   (define (write-elisp-symbol sym port)
>     (format port "~A" (assq-ref symbol-strings sym)))
>   
>   (match exp
>     (((? elisp-symbol? sym) rest)
>      (write-elisp-symbol sym port)
>      (elisp-write in-list? rest port))
>     ;; Vector expression
>     (#(vs ...)
>      (format port "[")
>      (elisp-write #t vs port)
>      (format port "]"))
>     ;; Guile elisp implementation detail
>     ('(%set-lexical-binding-mode #f) 'skip)
>     ;; List walker
>     ((e ...)
>      (when (not in-list?) (format port "("))
>      (unless (null? e)
>        (elisp-write #f (car e) port)
>        (for-each (lambda (v)
>                    (format port " ")
>                    (elisp-write #f v port)) (cdr e)))
>      (when (not in-list?) (format port ")")))
>     ;; dotted pair
>     ((and (? pair?) (? dotted-list? l))
>      (format port "(")
>      (elisp-write #t (drop-right l 0) port)
>      (format port " . ")
>      (elisp-write #t (take-right l 0) port)
>      (format port ")"))
>     ;; Print simple primitives
>     (_ (write exp port))))
> --8<---------------cut here---------------end--------------->8---
>
> On the reader side I just use guile's elisp reader:
>
> --8<---------------cut here---------------start------------->8---
> (define-module (jlicht test elisp)
>   #:use-module (language elisp parser)
>   #:use-module (jlicht build elisp-write)
>   #:use-module (srfi srfi-26)
>   #:use-module (srfi srfi-64))
>
> (eval-when (expand load eval)
>   (read-hash-extend #\e (lambda (chr port) (read-elisp port))))

That's what I was looking for.  If you have any links related to the
topic of the reader extension, please let me know.

>
> (set! test-log-to-file #f)
>
> (define (roundtrip expr)
>   (let ((written (call-with-output-string (cut elisp-write #f expr <>))))
>     (call-with-input-string written read-elisp)))
>
> (define-syntax test-roundtrip-equals
>   (syntax-rules ()
>     ((_ expr)
>      (let ((e1 (roundtrip expr)))
>        (test-equal e1 (roundtrip e1))))))
>
> (define runner (test-runner-simple))
>
> (test-with-runner runner
>   (test-begin "roundtrip-elisp-fixed-point")
>   (test-roundtrip-equals 12)
>   (test-roundtrip-equals "hello")
>   (test-roundtrip-equals '#e#'my-fn)
>   (test-roundtrip-equals '#e[a b c])
>   (test-roundtrip-equals '#e`(+ 1 2 ,@(a b) ,c))

It would be cool to make elisp-unquote for #e, but I think I can take a
look at ungexp to understand how to implement it.

>   (test-end "roundtrip-elisp-fixed-point"))
>
> (exit (test-runner-fail-count runner))
> --8<---------------cut here---------------end--------------->8---
>
> I've also hooked it up in combination with a sequence of calls to
> `scheme-file' -> `computed-file' called `elisp-file', but that's a bit
> more hacky and less relevant to the current discussion.
>
> - Jelle

Thank you very much!

-- 
Best regards,
Andrew Tropin