[bug#78277] ui: Allow evaluating multi-expressions strings with read/eval.

Message ID 9a8eec38e544b343c74c287b6a59c0be83fa0409.1746517343.git.maxim.cournoyer@gmail.com
State New
Headers
Series [bug#78277] ui: Allow evaluating multi-expressions strings with read/eval. |

Commit Message

Maxim Cournoyer May 6, 2025, 7:42 a.m. UTC
This can be useful when evaluating a scheme->file store output for example,
which has multiple top level expressions.

* guix/ui.scm (read/eval): Also accept a port object as argument.  Read and
evaluate all expressions from input port or string.

Change-Id: I0213706fa4824c3a8ffe5d93f44f263048cb62c2
---
 guix/ui.scm | 35 ++++++++++++++++++++++++++---------
 1 file changed, 26 insertions(+), 9 deletions(-)


base-commit: a897159a7cdb521d64fd2a5034b6c895968a7f51
  

Comments

Ludovic Courtès May 7, 2025, 3:27 p.m. UTC | #1
Hi,

Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:

> This can be useful when evaluating a scheme->file store output for example,
> which has multiple top level expressions.
>
> * guix/ui.scm (read/eval): Also accept a port object as argument.  Read and
> evaluate all expressions from input port or string.
>
> Change-Id: I0213706fa4824c3a8ffe5d93f44f263048cb62c2

Very useful, thanks!

Perhaps you can add a test for example with ‘guix build -e’ in
‘tests/guix-build.sh’?

Otherwise, LGTM.

Ludo’.
  
Maxim Cournoyer May 9, 2025, 2:09 p.m. UTC | #2
Hi!

Ludovic Courtès <ludo@gnu.org> writes:

> Hi,
>
> Maxim Cournoyer <maxim.cournoyer@gmail.com> writes:
>
>> This can be useful when evaluating a scheme->file store output for example,
>> which has multiple top level expressions.
>>
>> * guix/ui.scm (read/eval): Also accept a port object as argument.  Read and
>> evaluate all expressions from input port or string.
>>
>> Change-Id: I0213706fa4824c3a8ffe5d93f44f263048cb62c2
>
> Very useful, thanks!
>
> Perhaps you can add a test for example with ‘guix build -e’ in
> ‘tests/guix-build.sh’?

Done!  See commit 18ed22536d0.  Thanks for the review!
  

Patch

diff --git a/guix/ui.scm b/guix/ui.scm
index d462f7133e0..cd9eb1013d0 100644
--- a/guix/ui.scm
+++ b/guix/ui.scm
@@ -15,7 +15,7 @@ 
 ;;; Copyright © 2019, 2020 Tobias Geerinckx-Rice <me@tobias.gr>
 ;;; Copyright © 2019, 2021 Simon Tournier <zimon.toutoune@gmail.com>
 ;;; Copyright © 2020 Arun Isaac <arunisaac@systemreboot.net>
-;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com>
+;;; Copyright © 2020, 2025 Maxim Cournoyer <maxim.cournoyer@gmail.com>
 ;;; Copyright © 2018 Steve Sprang <scs@stevesprang.com>
 ;;; Copyright © 2022 Taiju HIGASHI <higashi@taiju.info>
 ;;; Copyright © 2022 Liliana Marie Prikler <liliana.prikler@gmail.com>
@@ -926,13 +926,18 @@  (define %guix-user-module
       module)))
 
 (define (read/eval str)
-  "Read and evaluate STR, raising an error if something goes wrong."
-  (let ((exp (catch #t
-               (lambda ()
-                 (call-with-input-string str read))
-               (lambda args
-                 (leave (G_ "failed to read expression ~s: ~s~%")
-                        str args)))))
+  "Read and evaluate STR, which can also be a port, raising an error if
+something goes wrong.  STR may contain one or more expressions; the return
+value is that of the last evaluated expression."
+  (define (read/safe port)
+    (catch #t
+      (lambda ()
+        (read port))
+      (lambda args
+        (leave (G_ "failed to read expression ~s: ~s~%")
+               str args))))
+
+  (define (eval/safe exp)
     (catch #t
       (lambda ()
         (eval exp (force %guix-user-module)))
@@ -956,7 +961,19 @@  (define (read/eval str)
           ((error args ...)
            (apply display-error #f (current-error-port) args))
           (what? #f))
-        (exit 1)))))
+        (exit 1))))
+
+  (let ((call-with-port-or-string (if (port? str)
+                                      call-with-port
+                                      call-with-input-string)))
+    (call-with-port-or-string
+     str
+     (lambda (port)
+       (let loop ((exp (read/safe port))
+                  (result #f))
+         (if (eof-object? exp)
+             result
+             (loop (read/safe port) (eval/safe exp))))))))
 
 (define (read/eval-package-expression str)
   "Read and evaluate STR and return the package it refers to, or exit an