1 ;;; ediprolog.el --- Emacs Does Interactive Prolog
3 ;; Copyright (C) 2006, 2007, 2008, 2009, 2012, 2013 Free Software Foundation, Inc.
5 ;; Author: Markus Triska <markus.triska@gmx.at>
6 ;; Keywords: languages, processes
9 ;; This file is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 3, or (at your option)
14 ;; This file is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
24 ;; These definitions let you interact with SWI-Prolog in all buffers.
25 ;; You can consult Prolog programs and evaluate embedded queries.
30 ;; Copy ediprolog.el to your load-path and add to your .emacs:
32 ;; (require 'ediprolog)
33 ;; (global-set-key [f10] 'ediprolog-dwim)
35 ;; Restart Emacs and customize ediprolog with
37 ;; M-x customize-group RET ediprolog RET
43 ;; The central function is `ediprolog-dwim' (Do What I Mean), which is
44 ;; bound to F10 by the snippet above. Depending on the content at
45 ;; point, `ediprolog-dwim' does the "appropriate" thing: If point is
46 ;; on a query, F10 sends the query to a Prolog process, and you
47 ;; interact with the process in the current buffer as on a terminal.
48 ;; Queries start with "?-" or ":-", possibly preceded by "%" and
49 ;; whitespace. An example of a query is (without leading ";;"):
51 ;; %?- member(X, [a,b,c]).
53 ;; If you press F10 when point is on that query, you get:
55 ;; %?- member(X, [a,b,c]).
61 ;; When waiting for output of the Prolog process, you can press C-g to
62 ;; unblock Emacs and continue with other work. To resume interaction
63 ;; with the Prolog process, use M-x ediprolog-toplevel RET.
65 ;; If you press F10 when point is NOT on a query, the buffer content
66 ;; is consulted in the Prolog process, and point is moved to the first
69 ;; For convenience, the most recent interactions with the Prolog
70 ;; process are logged in the buffer "*ediprolog-history*".
72 ;; Use M-x ediprolog-localize RET to make any Prolog process started
73 ;; in the current buffer buffer-local. This way, you can run distinct
74 ;; processes simultaneously. Revert with M-x ediprolog-unlocalize RET.
76 ;; `ediprolog-dwim' with prefix arguments has special meanings:
78 ;; C-0 F10 kill Prolog process
79 ;; C-1 F10 always consult buffer (even when point is on a query)
80 ;; C-2 F10 always consult buffer, using a new process
81 ;; C-7 F10 equivalent to `ediprolog-toplevel'
82 ;; C-u F10 first consult buffer, then evaluate query (if any)
83 ;; C-u C-u F10 like C-u F10, with a new process
85 ;; Tested with SWI-Prolog 5.6.55 + Emacs 21.2, 22.3, 23.1 and 24.3
89 (defconst ediprolog-version "0.9z")
91 (defgroup ediprolog nil
92 "Transparent interaction with SWI-Prolog."
97 (defcustom ediprolog-program
98 (or (executable-find "swipl") (executable-find "pl") "swipl")
99 "Program name of the Prolog executable."
104 (defcustom ediprolog-program-switches nil
105 "List of switches passed to the Prolog process. Example:
106 '(\"-G128M\" \"-O\")"
108 :type '(repeat string))
111 (defcustom ediprolog-prefix "%@ "
112 "String to prepend when inserting output from the Prolog
113 process into the buffer."
117 (defcustom ediprolog-max-history 80000
118 "Maximal size of history buffers storing recent interactions, or
119 nil to never truncate the history."
123 (defvar ediprolog-process nil "A Prolog process.")
125 (defvar ediprolog-temp-buffer nil
126 "Buffer that temporarily saves process output ")
128 (defvar ediprolog-seen-prompt nil
129 "Whether a prompt was (recently) emitted by the Prolog process.")
131 (defvar ediprolog-read-term nil
132 "Whether the Prolog process waits for the user to enter a term.")
134 (defvar ediprolog-indent-prefix ""
135 "Any whitespace occurring before the most recently executed query.")
137 (defvar ediprolog-temp-file nil
138 "File name of a temporary file used for consulting the buffer.")
140 (defvar ediprolog-prompt "?ediprolog- "
141 "Prompt used in the Prolog session. It must differ from the
142 default Prolog prompt.")
144 (defvar ediprolog-consult-buffer "*ediprolog-consult*"
145 "Buffer used to display consult output.")
147 (defvar ediprolog-consult-window nil
148 "Window used to show consult output.")
150 (defvar ediprolog-history-buffer nil
151 "Buffer that stores recent interactions.")
153 (defvar ediprolog-interrupted nil
154 "True iff waiting for the previous query was interrupted with C-g.")
156 (defmacro ediprolog-wait-for-prompt-after (&rest forms)
157 "Evaluate FORMS and wait for prompt."
159 (setq ediprolog-seen-prompt nil)
160 (ediprolog-ensure-buffer "temp")
161 (with-current-buffer ediprolog-temp-buffer
162 (let (buffer-read-only)
164 ;; execute forms with default-directory etc. from invocation buffer
166 (while (not ediprolog-seen-prompt)
167 ;; Wait for output/sentinel and update consult window, if any.
168 ;; As `accept-process-output' does not run the sentinel in
169 ;; Emacs <= 23.1, we use `sit-for' to do both. However,
170 ;; `sit-for' returns immediately if keyboard input is
171 ;; available, so we must discard input.
175 (defmacro ediprolog-remember-interruption (form)
176 "Set `ediprolog-interrupted' if evaluation of FORM was interrupted."
179 (quit (setq ediprolog-interrupted t))))
181 ;; Only the sentinel can reliably detect if no more output follows -
182 ;; even if process-status is 'exit, further output can still follow.
183 (defun ediprolog-sentinel (proc str)
184 (when (buffer-live-p (process-buffer proc))
185 (with-current-buffer (process-buffer proc)
186 (let ((status (with-temp-buffer
188 (while (search-backward "\n" nil t)
193 (substring (current-time-string) 4 -5) status) "green" t))
194 (when (string-match "^\\(?:finished\n\\|exited abnormally\\|killed\n\\)"
196 (setq ediprolog-seen-prompt t)))))
198 (defun ediprolog-ensure-buffer (name)
199 (let ((str (format "*ediprolog-%s*" name))
200 (var (intern (format "ediprolog-%s-buffer" name))))
201 (unless (buffer-live-p (symbol-value var))
202 (set var (generate-new-buffer str))
203 (with-current-buffer (symbol-value var)
204 (buffer-disable-undo)
205 (setq buffer-read-only t)))))
207 (defun ediprolog-log (str &optional col nl)
208 (ediprolog-ensure-buffer "history")
209 (with-current-buffer ediprolog-history-buffer
210 (let (buffer-read-only)
211 (goto-char (point-max))
212 (let ((s (format "%s%s" (if (and nl (not (bolp))) "\n" "") str)))
213 (insert (if col (propertize s 'face `(:background ,col)) s)))
214 (let ((size (- (point-max) (point-min))))
215 (when (and ediprolog-max-history
216 (> size ediprolog-max-history))
217 ;; delete older half of the (possibly narrowed) history
218 (delete-region (point-min) (+ (point-min) (/ size 2))))))))
220 (defun ediprolog-run-prolog ()
221 "Start a Prolog process."
222 (let ((args (cons ediprolog-program ediprolog-program-switches)))
223 (ediprolog-log (format "%s: starting: %S\n"
224 (substring (current-time-string) 4 -5) args)
227 (ediprolog-wait-for-prompt-after
228 (setq ediprolog-process
229 (apply #'start-process "ediprolog" (current-buffer) args))
230 (set-process-sentinel ediprolog-process 'ediprolog-sentinel)
231 (set-process-filter ediprolog-process
232 'ediprolog-wait-for-prompt-filter)
233 (ediprolog-send-string
234 (format "set_prolog_flag(color_term, false),\
235 '$set_prompt'('%s').\n" ediprolog-prompt)))
237 (ediprolog-log "No prompt found." "red" t)
238 (error "No prompt from: %s" ediprolog-program)))))
240 (defun ediprolog-kill-prolog ()
241 "Kill the Prolog process and run the process sentinel."
242 (when (ediprolog-running)
243 (delete-process ediprolog-process)))
245 (defun ediprolog-show-consult-output (str)
246 (with-current-buffer (get-buffer-create ediprolog-consult-buffer)
247 (setq buffer-read-only t)
248 (let (buffer-read-only)
251 (goto-char (point-min))
252 ;; remove normal consult status lines, which start with "%"
253 (while (re-search-forward "^[\t ]*%.*\n" nil t)
254 (delete-region (match-beginning 0) (match-end 0))))
255 (setq str (buffer-string)))
256 ;; show consult output in a separate window unless it is a prefix of
257 ;; success (i.e., consulted without errors), or still an incomplete
258 ;; line that starts with a comment character
259 (unless (or (string-match "^[\t ]*\\(?:%.*\\)?\\'" str)
260 (let ((success "true."))
261 (and (<= (length str) (length success))
262 (string= str (substring success 0 (length str))))))
263 (setq ediprolog-consult-window (display-buffer ediprolog-consult-buffer))
264 (set-window-dedicated-p ediprolog-consult-window t)
265 (fit-window-to-buffer ediprolog-consult-window (/ (frame-height) 2))))
267 (defun ediprolog-consult-filter (proc str)
268 "Filter used when consulting a file, showing consult output."
269 (with-current-buffer (ediprolog-temp-buffer proc)
270 (goto-char (point-max))
271 (let (buffer-read-only)
273 (with-current-buffer (process-buffer proc)
275 (when (re-search-backward
276 (format "^%s" (regexp-quote ediprolog-prompt)) nil t)
277 (with-current-buffer (process-buffer proc)
278 (setq ediprolog-seen-prompt t)))
279 (skip-chars-backward "\n")
280 (ediprolog-show-consult-output (buffer-substring (point-min) (point)))))
282 (defun ediprolog-wait-for-prompt-filter (proc str)
283 "Filter that only waits until prompt appears."
284 (with-current-buffer (ediprolog-temp-buffer proc)
285 (goto-char (point-max))
286 (let (buffer-read-only)
288 (with-current-buffer (process-buffer proc)
290 (when (re-search-backward
291 (format "^%s" (regexp-quote ediprolog-prompt)) nil t)
292 (with-current-buffer (process-buffer proc)
293 (setq ediprolog-seen-prompt t)))))
297 (defun ediprolog-dwim (&optional arg)
298 "Load current buffer into Prolog or post query (Do What I Mean).
299 If invoked on a line starting with `:-' or `?-', possibly
300 preceded by `%' and whitespace, call `ediprolog-interact' with
301 the query as argument. Otherwise, call `ediprolog-consult'.
303 With prefix argument 0, kill the Prolog process. With prefix 1,
304 equivalent to `ediprolog-consult'. With prefix 2, equivalent to
305 `ediprolog-consult' with a new Prolog process. With prefix 7,
306 equivalent to `ediprolog-toplevel'. With just C-u, first call
307 `ediprolog-consult' and then, if point is on a query, call
308 `ediprolog-interact' with it as argument. Analogously, C-u C-u
309 for `ediprolog-consult' with a new process. With other prefix
310 arguments, equivalent to `ediprolog-remove-interactions'."
313 (unless (ediprolog-running)
314 (error "No Prolog process running"))
315 (ediprolog-kill-prolog)
316 (message "Prolog process killed."))
317 ((eq arg 1) (ediprolog-consult))
318 ((eq arg 2) (ediprolog-consult t))
320 (unless (ediprolog-more-solutions)
321 (error "No query in progress"))
322 (ediprolog-toplevel))
323 ((equal arg '(4)) (ediprolog-consult) (ediprolog-query))
324 ((equal arg '(16)) (ediprolog-consult t) (ediprolog-query))
325 ((null arg) (unless (ediprolog-query) (ediprolog-consult)))
326 (t (ediprolog-remove-interactions))))
328 (defun ediprolog-process-ready ()
329 "Error if the previous query is still in progress."
330 (when (and ediprolog-interrupted
332 (ediprolog-more-solutions))
333 (error "Previous query still in progress, see `ediprolog-toplevel'"))
334 (setq ediprolog-interrupted nil))
336 (defun ediprolog-query ()
337 "If point is on a query, send it to the process and start interaction."
338 (ediprolog-process-ready)
339 (when (and (not (and transient-mark-mode mark-active))
342 (looking-at "\\([\t ]*\\)%*[\t ]*[:?]-")))
343 ;; whitespace preceding the query is the indentation level
344 (setq ediprolog-indent-prefix (match-string 1))
345 (let* ((from (goto-char (match-end 0)))
346 (to (if (re-search-forward "\\.[\t ]*\\(?:%.*\\)?$" nil t)
347 ;; omit trailing whitespace
348 (+ (point) (skip-chars-backward "\t "))
349 (error "Missing `.' at the end of this query")))
350 (query (buffer-substring-no-properties from to)))
352 (insert "\n" ediprolog-indent-prefix ediprolog-prefix)
354 (format "%s\n" (mapconcat #'identity
355 ;; `%' can precede each query line
356 (split-string query "\n[ \t%]*") " "))))
360 (defun ediprolog-interact (query)
361 "Send QUERY to Prolog process and interact as on a terminal.
363 You can use \\[keyboard-quit] to unblock Emacs in the case of
364 longer-running queries. When the query completes and the toplevel
365 asks for input, use \\[ediprolog-toplevel] to resume interaction
366 with the Prolog process."
367 (unless (ediprolog-running)
368 (ediprolog-run-prolog))
369 (set-marker (process-mark ediprolog-process) (point))
370 (set-process-buffer ediprolog-process (current-buffer))
371 (set-process-filter ediprolog-process 'ediprolog-interact-filter)
372 (ediprolog-ensure-buffer "temp")
373 (with-current-buffer ediprolog-temp-buffer
374 (let (buffer-read-only)
376 (setq ediprolog-seen-prompt nil
377 ediprolog-read-term nil)
378 (ediprolog-send-string query)
379 (ediprolog-toplevel))
381 (defun ediprolog-send-string (str)
382 "Send string to Prolog process and log it."
383 (ediprolog-log str "cyan")
384 (process-send-string ediprolog-process str))
386 (defun ediprolog-toplevel ()
387 "Start or resume Prolog toplevel interaction in the buffer.
389 You can use this function if you have previously quit (with
390 \\[keyboard-quit]) waiting for a longer-running query and now
391 want to resume interaction with the toplevel."
393 (when ediprolog-process
394 (select-window (display-buffer (process-buffer ediprolog-process))))
395 (ediprolog-remember-interruption
396 (while (ediprolog-more-solutions)
399 ;; poll for user input; meanwhile, process output can arrive
400 (while (and (ediprolog-more-solutions) (null str))
401 (goto-char (process-mark ediprolog-process))
402 (if ediprolog-read-term
404 (setq str (concat (read-string "Input: ") "\n"))
405 (ediprolog-insert-at-marker
406 str ediprolog-indent-prefix ediprolog-prefix)
407 (setq ediprolog-read-term nil))
409 (when (setq char (if (>= emacs-major-version 22)
410 (read-char nil nil 0.1)
411 (with-timeout (0.1 nil)
413 ;; char-to-string might still yield an error (C-0 etc.)
414 (setq str (char-to-string char)))
416 (message "Non-character key")
417 ;; non-character keys must not remain in the input
418 ;; buffer, lest `read-char' return immediately
420 (when (ediprolog-more-solutions)
421 (if (eq char ?\C-c) ; char can be nil too
422 ;; sending C-c directly yields strange SWI buffering
423 (interrupt-process ediprolog-process)
424 (ediprolog-send-string str)))))))
427 (defun ediprolog-remove-interactions ()
428 "Remove all lines starting with `ediprolog-prefix' from buffer.
430 In transient mark mode, the function operates on the region if it
435 (when (and transient-mark-mode mark-active)
436 (narrow-to-region (region-beginning) (region-end)))
437 (goto-char (point-min))
438 (flush-lines (concat "^[\t ]*" (regexp-quote ediprolog-prefix)))))
439 (message "Interactions removed."))
443 (defun ediprolog-consult (&optional new-process)
444 "Buffer is loaded into a Prolog process. If NEW-PROCESS is
445 non-nil, start a new process. Otherwise use the existing process,
446 if any. In case of errors, point is moved to the position of the
447 first error, and the mark is left at the previous position.
449 In transient mark mode, the function operates on the region if it
452 (when (string= (buffer-name) ediprolog-consult-buffer)
453 (error "Cannot consult the consult buffer"))
454 (when (window-live-p ediprolog-consult-window)
456 ;; deleting the window can still raise an error, if the window
457 ;; was the only window in the frame and the consult buffer was
458 ;; killed (and it thus displays a different buffer now)
459 (delete-window ediprolog-consult-window)
461 (when (buffer-live-p ediprolog-consult-buffer)
462 (bury-buffer ediprolog-consult-buffer))
464 (ediprolog-kill-prolog))
465 (unless (ediprolog-running)
466 (ediprolog-run-prolog))
467 (ediprolog-process-ready)
468 (set-process-buffer ediprolog-process (current-buffer))
469 (unless ediprolog-temp-file
470 (setq ediprolog-temp-file (make-temp-file "ediprolog")))
471 (let ((start (if (and transient-mark-mode mark-active)
472 (region-beginning) (point-min)))
473 (end (if (and transient-mark-mode mark-active)
474 (region-end) (point-max))))
475 (write-region start end ediprolog-temp-file nil 'silent))
476 (set-process-filter ediprolog-process 'ediprolog-consult-filter)
477 (ediprolog-remember-interruption
478 (ediprolog-wait-for-prompt-after
479 (ediprolog-send-string (format "['%s'].\n" ediprolog-temp-file))))
480 (message "%s consulted." (if (and transient-mark-mode mark-active)
482 ;; go to line of the first error, if any
483 (let ((line (with-current-buffer ediprolog-temp-buffer
484 (when (save-excursion
485 (goto-char (point-min))
486 (re-search-forward "^ERROR.*?:\\([0-9]+\\)" nil t))
487 (string-to-number (match-string 1))))))
489 (if (and transient-mark-mode mark-active)
490 (when (fboundp 'line-number-at-pos)
491 (goto-line (+ (line-number-at-pos (region-beginning)) line -1)))
494 (defun ediprolog-running ()
495 "True iff `ediprolog-process' is a running process."
496 (and (processp ediprolog-process)
497 (eq (process-status ediprolog-process) 'run)))
499 (defun ediprolog-more-solutions ()
500 "True iff there could be more solutions from the process."
501 (not ediprolog-seen-prompt))
503 (defun ediprolog-interact-filter (proc string)
504 "Insert output from the process and update the state."
505 (when (and (buffer-live-p (ediprolog-temp-buffer proc))
506 (buffer-live-p (process-buffer proc)))
508 (with-current-buffer (ediprolog-temp-buffer proc)
509 (goto-char (point-max))
510 (let (buffer-read-only)
512 (with-current-buffer (process-buffer proc)
513 (ediprolog-log string))
514 ;; read a term from the user?
515 (when (re-search-backward "^|: $" nil t)
516 (with-current-buffer (process-buffer proc)
517 (setq ediprolog-read-term t))
518 (setq str (buffer-string))
519 (let (buffer-read-only)
522 (goto-char (point-max))
523 (when (re-search-backward
524 (format "^%s" (regexp-quote ediprolog-prompt)) nil t)
525 (with-current-buffer (process-buffer proc)
526 (setq ediprolog-seen-prompt t))
527 ;; ignore further output due to accidental user input (C-j,
528 ;; C-m, etc.) while the query was running
529 (set-process-filter proc 'ediprolog-ignore-filter)
530 (skip-chars-backward "\n")
531 (setq str (buffer-substring (point-min) (point))))
533 (goto-char (point-max))
534 ;; delay final line if it can still be completed to prompt
535 (let ((l (buffer-substring (line-beginning-position) (point))))
536 (when (and (<= (length l) (length ediprolog-prompt))
537 (string= l (substring ediprolog-prompt 0 (length l))))
538 (goto-char (line-beginning-position))))
539 ;; delay emitting newlines until we are sure no prompt
540 ;; follows; one or two newlines can precede a prompt
541 (let ((d (abs (skip-chars-backward "\n"))))
543 (forward-char (- d 2))))
544 (setq str (buffer-substring (point-min) (point)))
545 (let (buffer-read-only)
546 (delete-region (point-min) (point))))
549 ;; precede each line with ediprolog prefices
551 (goto-char (point-min))
552 (while (search-forward "\n" nil t)
554 (format "\n%s%s" (with-current-buffer (process-buffer proc)
555 ediprolog-indent-prefix) ediprolog-prefix)))
556 (setq str (buffer-string)))
557 (with-current-buffer (process-buffer proc)
558 (let ((near (<= (abs (- (point) (process-mark proc))) 1)))
559 (ediprolog-insert-at-marker str)
561 ;; catch up with output if point was reasonably close
562 (goto-char (process-mark proc))))))))))
565 (defun ediprolog-insert-at-marker (&rest args)
566 "Insert strings ARGS at marker and update the marker."
568 (goto-char (process-mark ediprolog-process))
570 (apply #'insert args)
571 (set-marker (process-mark ediprolog-process) (point))))
573 (defun ediprolog-ignore-filter (proc str)
574 "Log and then ignore all process output."
575 (with-current-buffer (process-buffer proc)
576 (ediprolog-log str "gray")))
578 (defun ediprolog-temp-buffer (proc)
579 (with-current-buffer (process-buffer proc)
580 ;; temp buffer can be buffer local
581 ediprolog-temp-buffer))
583 (defun ediprolog-map-variables (func)
584 "Call FUNC with all ediprolog variables that can become buffer-local."
585 (mapc func '(ediprolog-process
587 ediprolog-program-switches
588 ediprolog-temp-buffer
589 ediprolog-history-buffer
590 ediprolog-seen-prompt
591 ediprolog-interrupted
593 ediprolog-indent-prefix
594 ediprolog-temp-file)))
597 (defun ediprolog-localize ()
598 "After `ediprolog-localize', any Prolog process started from
599 this buffer becomes buffer-local."
601 (unless (local-variable-p 'ediprolog-process)
602 (ediprolog-map-variables #'make-local-variable)
603 (setq ediprolog-temp-file nil
604 ediprolog-process nil
605 ediprolog-history-buffer nil
606 ediprolog-temp-buffer nil)))
608 (defun ediprolog-unlocalize ()
609 "Revert the effect of `ediprolog-localize'."
611 (when (local-variable-p 'ediprolog-process)
612 (ediprolog-kill-prolog)
613 (ediprolog-map-variables #'kill-local-variable)))
617 ;;; ediprolog.el ends here