branch: externals/futur
commit ebc19418d4e87ad4436268504404e45e95fe86e6
Author: Stefan Monnier <[email protected]>
Commit: Stefan Monnier <[email protected]>
futur-server.el: New file
---
futur-server.el | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/futur-server.el b/futur-server.el
new file mode 100644
index 0000000000..0a91c8ee63
--- /dev/null
+++ b/futur-server.el
@@ -0,0 +1,89 @@
+;;; futur-server.el --- An ELisp server for the Futur library -*-
lexical-binding: t -*-
+
+;; Copyright (C) 2026 Free Software Foundation, Inc.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This aims to provide a batch server to run arbitrary ELisp code,
+;; similar to what you might do via
+;;
+;; emacs --batch --eval "(foo bar)"
+;;
+;; but where the server stays around to try and avoid startup time.
+;; The aim is to be usable even for simple a short queries that
+;; expect an "immediate" answer, for example because we want the
+;; execution to be sandboxed.
+
+;;; Code:
+
+;;;; Base protocol
+
+(defun futur--read-stdin ()
+ "Read a sexp from a single line on stdin."
+ (unless noninteractive (error "futur--read-stdin works only in batch mode"))
+ (read-from-minibuffer "" nil nil t))
+
+(defun futur--print-stdout (sexp sid)
+ "Print SEXP on stdout using ID as the leading marker."
+ (unless noninteractive (error "futur--print-stdout works only in batch
mode"))
+ (let ((print-length nil)
+ (print-level nil)
+ (coding-system-for-write 'emacs-internal)
+ (print-circle t)
+ (print-gensym t)
+ (print-escape-newlines nil)
+ ;; SWP aren't currently printed in a `read'able way, so we may
+ ;; as well print them bare.
+ (print-symbols-bare t))
+ (terpri t)
+ (princ sid t)
+ (prin1 sexp t)
+ (terpri t)))
+
+(defun futur-elisp-server ()
+ ;; We don't need a cryptographically secure ID, but just something that's
+ ;; *very* unlikely to occur by accident elsewhere.
+ (let ((sid (format "fes:%s "
+ (secure-hash 'sha1
+ (format "%S:%S:%S"
+ (random t) (current-time)
+ (emacs-pid))))))
+ (futur--print-stdout :ready sid)
+ (while t
+ (let ((input (condition-case err (cons :read-success (futur--read-stdin))
+ (t err))))
+ (if (not (eq :read-success (car-safe input)))
+ ;; FIXME: We can get an `end-of-file' error if the input line
+ ;; is not a complete sexp but also if stdin was closed.
+ ;; To distinguish the two it seems we have to look at
+ ;; the actual error string :-(.
+ (if (equal input '(end-of-file "Error reading from stdin"))
+ (kill-emacs)
+ (futur--print-stdout `(:read-error . ,input) sid))
+ ;; Confirm we read successfully so the client can
+ ;; distinguish where problems come from.
+ (let ((rid (cadr input)))
+ (futur--print-stdout `(:read-success ,rid) sid)
+ (let ((result
+ (condition-case err
+ `(:funcall-success ,rid
+ . ,(apply #'funcall (cddr input)))
+ (t `(:funcall-error ,rid . ,err)))))
+ (futur--print-stdout result sid))))))))
+
+
+(provide 'futur-server)
+;;; futur-server.el ends here