prolog.el 14.4 KB
Newer Older
Eric S. Raymond's avatar
Eric S. Raymond committed
1 2
;;; prolog.el --- major mode for editing and running Prolog under Emacs

Glenn Morris's avatar
Glenn Morris committed
3
;; Copyright (C) 1986, 1987, 2001, 2002, 2003, 2004, 2005, 2006, 2007
4
;; Free Software Foundation, Inc.
Eric S. Raymond's avatar
Eric S. Raymond committed
5

Richard M. Stallman's avatar
Richard M. Stallman committed
6
;; Author: Masanobu UMEDA <umerin@mse.kyutech.ac.jp>
Eric S. Raymond's avatar
Eric S. Raymond committed
7
;; Keywords: languages
Eric S. Raymond's avatar
Eric S. Raymond committed
8

root's avatar
root committed
9 10 11 12
;; This file is part of GNU Emacs.

;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
13
;; the Free Software Foundation; either version 3, or (at your option)
root's avatar
root committed
14 15 16 17 18 19 20 21
;; any later version.

;; GNU Emacs 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
Erik Naggum's avatar
Erik Naggum committed
22
;; along with GNU Emacs; see the file COPYING.  If not, write to the
Lute Kamstra's avatar
Lute Kamstra committed
23 24
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
root's avatar
root committed
25

26 27 28 29
;;; Commentary:

;; This package provides a major mode for editing Prolog.  It knows
;; about Prolog syntax and comments, and can send regions to an inferior
30
;; Prolog interpreter process.  Font locking is tuned towards GNU Prolog.
31

Eric S. Raymond's avatar
Eric S. Raymond committed
32 33
;;; Code:

34 35
(defvar comint-prompt-regexp)
(defvar comint-process-echoes)
36

37
(defgroup prolog nil
38
  "Major mode for editing and running Prolog under Emacs."
39
  :link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces)
40 41
  :group 'languages)

42

43
(defcustom prolog-program-name
Stefan Monnier's avatar
Stefan Monnier committed
44
  (let ((names '("prolog" "gprolog" "swipl")))
45 46 47 48
    (while (and names
		(not (executable-find (car names))))
      (setq names (cdr names)))
    (or (car names) "prolog"))
Stefan Monnier's avatar
Stefan Monnier committed
49
  "Program name for invoking an inferior Prolog with `run-prolog'."
50 51
  :type 'string
  :group 'prolog)
root's avatar
root committed
52

53
(defcustom prolog-consult-string "reconsult(user).\n"
Stefan Monnier's avatar
Stefan Monnier committed
54
  "(Re)Consult mode (for C-Prolog and Quintus Prolog). "
55 56
  :type 'string
  :group 'prolog)
root's avatar
root committed
57

58
(defcustom prolog-compile-string "compile(user).\n"
Stefan Monnier's avatar
Stefan Monnier committed
59
  "Compile mode (for Quintus Prolog)."
60 61
  :type 'string
  :group 'prolog)
root's avatar
root committed
62

63
(defcustom prolog-eof-string "end_of_file.\n"
Stefan Monnier's avatar
Stefan Monnier committed
64
  "String that represents end of file for Prolog.
65
When nil, send actual operating system end of file."
66 67
  :type 'string
  :group 'prolog)
root's avatar
root committed
68

69 70 71 72
(defcustom prolog-indent-width 4
  "Level of indentation in Prolog buffers."
  :type 'integer
  :group 'prolog)
root's avatar
root committed
73

74 75 76 77 78 79 80 81 82 83
(defvar prolog-font-lock-keywords
  '(("\\(#[<=]=>\\|:-\\)\\|\\(#=\\)\\|\\(#[#<>\\/][=\\/]*\\|!\\)"
     0 font-lock-keyword-face)
    ("\\<\\(is\\|write\\|nl\\|read_\\sw+\\)\\>"
     1 font-lock-keyword-face)
    ("^\\(\\sw+\\)\\s-*\\((\\(.+\\))\\)*"
     (1 font-lock-function-name-face)
     (3 font-lock-variable-name-face)))
  "Font-lock keywords for Prolog mode.")

84
(defvar prolog-mode-syntax-table
root's avatar
root committed
85 86 87
  (let ((table (make-syntax-table)))
    (modify-syntax-entry ?_ "w" table)
    (modify-syntax-entry ?\\ "\\" table)
88 89
    (modify-syntax-entry ?/ ". 14" table)
    (modify-syntax-entry ?* ". 23" table)
root's avatar
root committed
90 91 92 93
    (modify-syntax-entry ?+ "." table)
    (modify-syntax-entry ?- "." table)
    (modify-syntax-entry ?= "." table)
    (modify-syntax-entry ?% "<" table)
94
    (modify-syntax-entry ?\n ">" table)
root's avatar
root committed
95 96 97
    (modify-syntax-entry ?< "." table)
    (modify-syntax-entry ?> "." table)
    (modify-syntax-entry ?\' "\"" table)
98
    table))
root's avatar
root committed
99

100
(defvar prolog-mode-abbrev-table nil)
root's avatar
root committed
101 102 103 104
(define-abbrev-table 'prolog-mode-abbrev-table ())

(defun prolog-mode-variables ()
  (make-local-variable 'paragraph-separate)
105
  (setq paragraph-separate (concat "%%\\|$\\|" page-delimiter)) ;'%%..'
root's avatar
root committed
106 107
  (make-local-variable 'paragraph-ignore-fill-prefix)
  (setq paragraph-ignore-fill-prefix t)
Karl Heuer's avatar
Karl Heuer committed
108
  (make-local-variable 'imenu-generic-expression)
109
  (setq imenu-generic-expression '((nil "^\\sw+" 0)))
root's avatar
root committed
110 111 112 113 114
  (make-local-variable 'indent-line-function)
  (setq indent-line-function 'prolog-indent-line)
  (make-local-variable 'comment-start)
  (setq comment-start "%")
  (make-local-variable 'comment-start-skip)
115 116 117
  (setq comment-start-skip "\\(?:%+\\|/\\*+\\)[ \t]*")
  (make-local-variable 'comment-end-skip)
  (setq comment-end-skip "[ \t]*\\(\n\\|\\*+/\\)")
root's avatar
root committed
118
  (make-local-variable 'comment-column)
119
  (setq comment-column 48))
root's avatar
root committed
120

121 122 123
(defvar prolog-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map "\e\C-x" 'prolog-consult-region)
Stefan Monnier's avatar
Stefan Monnier committed
124 125
    (define-key map "\C-c\C-l" 'inferior-prolog-load-file)
    (define-key map "\C-c\C-z" 'switch-to-prolog)
126
    map))
Stefan Monnier's avatar
Stefan Monnier committed
127 128 129 130 131 132 133 134 135 136 137 138
 
(easy-menu-define prolog-mode-menu prolog-mode-map "Menu for Prolog mode."
  ;; Mostly copied from scheme-mode's menu.
  ;; Not tremendously useful, but it's a start.
  '("Prolog"
    ["Indent line" indent-according-to-mode t]
    ["Indent region" indent-region t]
    ["Comment region" comment-region t]
    ["Uncomment region" uncomment-region t]
    "--"
    ["Run interactive Prolog session" run-prolog t]
    ))
root's avatar
root committed
139

Jim Blandy's avatar
Jim Blandy committed
140
;;;###autoload
root's avatar
root committed
141 142 143 144 145
(defun prolog-mode ()
  "Major mode for editing Prolog code for Prologs.
Blank lines and `%%...' separate paragraphs.  `%'s start comments.
Commands:
\\{prolog-mode-map}
146
Entry to this mode calls the value of `prolog-mode-hook'
root's avatar
root committed
147 148 149 150
if that value is non-nil."
  (interactive)
  (kill-all-local-variables)
  (use-local-map prolog-mode-map)
151
  (set-syntax-table prolog-mode-syntax-table)
root's avatar
root committed
152 153 154
  (setq major-mode 'prolog-mode)
  (setq mode-name "Prolog")
  (prolog-mode-variables)
Stefan Monnier's avatar
Stefan Monnier committed
155
  (set (make-local-variable 'comment-add) 1)
156 157 158 159
  ;; font lock
  (setq font-lock-defaults '(prolog-font-lock-keywords
                             nil nil nil
                             beginning-of-line))
160
  (run-mode-hooks 'prolog-mode-hook))
root's avatar
root committed
161

Stefan Monnier's avatar
Stefan Monnier committed
162
(defun prolog-indent-line ()
root's avatar
root committed
163 164 165 166 167
  "Indent current line as Prolog code.
With argument, indent any additional lines of the same clause
rigidly along with this one (not yet)."
  (interactive "p")
  (let ((indent (prolog-indent-level))
Stefan Monnier's avatar
Stefan Monnier committed
168
	(pos (- (point-max) (point))))
root's avatar
root committed
169
    (beginning-of-line)
Stefan Monnier's avatar
Stefan Monnier committed
170
    (indent-line-to indent)
root's avatar
root committed
171
    (if (> (- (point-max) pos) (point))
Stefan Monnier's avatar
Stefan Monnier committed
172
	(goto-char (- (point-max) pos)))))
root's avatar
root committed
173 174

(defun prolog-indent-level ()
175
  "Compute Prolog indentation level."
root's avatar
root committed
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
  (save-excursion
    (beginning-of-line)
    (skip-chars-forward " \t")
    (cond
     ((looking-at "%%%") 0)		;Large comment starts
     ((looking-at "%[^%]") comment-column) ;Small comment starts
     ((bobp) 0)				;Beginning of buffer
     (t
      (let ((empty t) ind more less)
	(if (looking-at ")")
	    (setq less t)		;Find close
	  (setq less nil))
	;; See previous indentation
	(while empty
	  (forward-line -1)
	  (beginning-of-line)
 	  (if (bobp)
 	      (setq empty nil)
 	    (skip-chars-forward " \t")
 	    (if (not (or (looking-at "%[^%]") (looking-at "\n")))
 		(setq empty nil))))
 	(if (bobp)
 	    (setq ind 0)		;Beginning of buffer
	  (setq ind (current-column)))	;Beginning of clause
	;; See its beginning
	(if (looking-at "%%[^%]")
	    ind
	  ;; Real prolog code
	  (if (looking-at "(")
	      (setq more t)		;Find open
	    (setq more nil))
	  ;; See its tail
	  (end-of-prolog-clause)
	  (or (bobp) (forward-char -1))
	  (cond ((looking-at "[,(;>]")
		 (if (and more (looking-at "[^,]"))
		     (+ ind prolog-indent-width) ;More indentation
		   (max tab-width ind))) ;Same indentation
		((looking-at "-") tab-width) ;TAB
		((or less (looking-at "[^.]"))
		 (max (- ind prolog-indent-width) 0)) ;Less indentation
		(t 0))			;No indentation
	  )))
     )))

(defun end-of-prolog-clause ()
  "Go to end of clause in this line."
  (beginning-of-line 1)
  (let* ((eolpos (save-excursion (end-of-line) (point))))
    (if (re-search-forward comment-start-skip eolpos 'move)
	(goto-char (match-beginning 0)))
    (skip-chars-backward " \t")))

;;;
;;; Inferior prolog mode
;;;
232 233 234 235
(defvar inferior-prolog-mode-map
  (let ((map (make-sparse-keymap)))
    ;; This map will inherit from `comint-mode-map' when entering
    ;; inferior-prolog-mode.
236 237
    (define-key map [remap self-insert-command]
      'inferior-prolog-self-insert-command)
238 239 240 241
    map))

(defvar inferior-prolog-mode-syntax-table prolog-mode-syntax-table)
(defvar inferior-prolog-mode-abbrev-table prolog-mode-abbrev-table)
root's avatar
root committed
242

243 244 245 246
(declare-function comint-mode "comint")
(declare-function comint-send-string "comint" (process string))
(declare-function comint-send-region "comint" (process start end))
(declare-function comint-send-eof "comint" ())
247

248
(define-derived-mode inferior-prolog-mode comint-mode "Inferior Prolog"
root's avatar
root committed
249 250 251 252 253
  "Major mode for interacting with an inferior Prolog process.

The following commands are available:
\\{inferior-prolog-mode-map}

254 255 256
Entry to this mode calls the value of `prolog-mode-hook' with no arguments,
if that value is non-nil.  Likewise with the value of `comint-mode-hook'.
`prolog-mode-hook' is called after `comint-mode-hook'.
root's avatar
root committed
257

258 259
You can send text to the inferior Prolog from other buffers using the commands
`process-send-region', `process-send-string' and \\[prolog-consult-region].
root's avatar
root committed
260 261 262 263

Commands:
Tab indents for Prolog; with argument, shifts rest
 of expression rigidly with the current line.
264 265
Paragraphs are separated only by blank lines and '%%'.
'%'s start comments.
root's avatar
root committed
266 267 268 269 270 271

Return at end of buffer sends line as input.
Return not at end copies rest of line to end and sends it.
\\[comint-kill-input] and \\[backward-kill-word] are kill commands, imitating normal Unix input editing.
\\[comint-interrupt-subjob] interrupts the shell or its current subjob if any.
\\[comint-stop-subjob] stops. \\[comint-quit-subjob] sends quit signal."
272 273
  (setq comint-prompt-regexp "^| [ ?][- ] *")
  (prolog-mode-variables))
root's avatar
root committed
274

Stefan Monnier's avatar
Stefan Monnier committed
275 276
(defvar inferior-prolog-buffer nil)

277 278 279 280 281 282
(defvar inferior-prolog-flavor 'unknown
  "Either a symbol or a buffer position offset by one.
If a buffer position, the flavor has not been determined yet and
it is expected that the process's output has been or will
be inserted at that position plus one.")

Stefan Monnier's avatar
Stefan Monnier committed
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
(defun inferior-prolog-run (&optional name)
  (with-current-buffer (make-comint "prolog" (or name prolog-program-name))
    (inferior-prolog-mode)
    (setq-default inferior-prolog-buffer (current-buffer))
    (make-local-variable 'inferior-prolog-buffer)
    (when (and name (not (equal name prolog-program-name)))
      (set (make-local-variable 'prolog-program-name) name))
    (set (make-local-variable 'inferior-prolog-flavor)
         ;; Force re-detection.
         (let* ((proc (get-buffer-process (current-buffer)))
                (pmark (and proc (marker-position (process-mark proc)))))
           (cond
            ((null pmark) (1- (point-min)))
            ;; The use of insert-before-markers in comint.el together with
            ;; the potential use of comint-truncate-buffer in the output
            ;; filter, means that it's difficult to reliably keep track of
            ;; the buffer position where the process's output started.
            ;; If possible we use a marker at "start - 1", so that
            ;; insert-before-marker at `start' won't shift it.  And if not,
            ;; we fall back on using a plain integer.
            ((> pmark (point-min)) (copy-marker (1- pmark)))
            (t (1- pmark)))))
    (add-hook 'comint-output-filter-functions
              'inferior-prolog-guess-flavor nil t)))

(defun inferior-prolog-process (&optional dontstart)
  (or (and (buffer-live-p inferior-prolog-buffer)
           (get-buffer-process inferior-prolog-buffer))
      (unless dontstart
        (inferior-prolog-run)
        ;; Try again.
        (inferior-prolog-process))))

316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
(defun inferior-prolog-guess-flavor (&optional ignored)
  (save-excursion
    (goto-char (1+ inferior-prolog-flavor))
    (setq inferior-prolog-flavor
          (cond
           ((looking-at "GNU Prolog") 'gnu)
           ((looking-at "Welcome to SWI-Prolog") 'swi)
           ((looking-at ".*\n") 'unknown) ;There's at least one line.
           (t inferior-prolog-flavor))))
  (when (symbolp inferior-prolog-flavor)
    (remove-hook 'comint-output-filter-functions
                 'inferior-prolog-guess-flavor t)
    (if (eq inferior-prolog-flavor 'gnu)
        (set (make-local-variable 'comint-process-echoes) t))))

Jim Blandy's avatar
Jim Blandy committed
331
;;;###autoload
Stefan Monnier's avatar
Stefan Monnier committed
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
(defalias 'run-prolog 'switch-to-prolog)
;;;###autoload
(defun switch-to-prolog (&optional name)
  "Run an inferior Prolog process, input and output via buffer *prolog*.
With prefix argument \\[universal-prefix], prompt for the program to use."
  (interactive
   (list (when current-prefix-arg
           (let ((proc (inferior-prolog-process 'dontstart)))
             (if proc
                 (if (yes-or-no-p "Kill current process before starting new one? ")
                     (kill-process proc)
                   (error "Abort")))
             (read-string "Run Prolog: " prolog-program-name)))))
  (unless (inferior-prolog-process 'dontstart)
    (inferior-prolog-run name))
  (pop-to-buffer inferior-prolog-buffer))
root's avatar
root committed
348

349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
(defun inferior-prolog-self-insert-command ()
  "Insert the char in the buffer or pass it directly to the process."
  (interactive)
  (let* ((proc (get-buffer-process (current-buffer)))
         (pmark (and proc (marker-position (process-mark proc)))))
    (if (and (eq inferior-prolog-flavor 'gnu)
             pmark
             (null current-prefix-arg)
             (eobp)
             (eq (point) pmark)
             (save-excursion
               (goto-char (- pmark 3))
               (looking-at " \\? ")))
        (comint-send-string proc (string last-command-char))
      (call-interactively 'self-insert-command))))

root's avatar
root committed
365
(defun prolog-consult-region (compile beg end)
366 367
  "Send the region to the Prolog process made by \"M-x run-prolog\".
If COMPILE (prefix arg) is not nil, use compile mode rather than consult mode."
root's avatar
root committed
368
  (interactive "P\nr")
Stefan Monnier's avatar
Stefan Monnier committed
369 370 371 372 373 374
  (let ((proc (inferior-prolog-process)))
    (comint-send-string proc
                        (if compile prolog-compile-string
                          prolog-consult-string))
    (comint-send-region proc beg end)
    (comint-send-string proc "\n")		;May be unnecessary
root's avatar
root committed
375
    (if prolog-eof-string
Stefan Monnier's avatar
Stefan Monnier committed
376 377 378
        (comint-send-string proc prolog-eof-string)
      (with-current-buffer (process-buffer proc)
        (comint-send-eof))))) ;Send eof to prolog process.
root's avatar
root committed
379 380 381

(defun prolog-consult-region-and-go (compile beg end)
  "Send the region to the inferior Prolog, and switch to *prolog* buffer.
382
If COMPILE (prefix arg) is not nil, use compile mode rather than consult mode."
root's avatar
root committed
383 384
  (interactive "P\nr")
  (prolog-consult-region compile beg end)
385
  (pop-to-buffer inferior-prolog-buffer))
Eric S. Raymond's avatar
Eric S. Raymond committed
386

Stefan Monnier's avatar
Stefan Monnier committed
387 388 389 390 391 392 393 394 395 396
(defun inferior-prolog-load-file ()
  "Pass the current buffer's file to the inferior prolog process."
  (interactive)
  (save-buffer)
  (let ((file buffer-file-name)
        (proc (inferior-prolog-process)))
    (with-current-buffer (process-buffer proc)
      (comint-send-string proc (concat "['" (file-relative-name file) "'].\n"))
      (pop-to-buffer (current-buffer)))))

Richard M. Stallman's avatar
Richard M. Stallman committed
397 398
(provide 'prolog)

Stefan Monnier's avatar
Stefan Monnier committed
399
;; arch-tag: f3ec6748-1272-4ab6-8826-c50cb1607636
Eric S. Raymond's avatar
Eric S. Raymond committed
400
;;; prolog.el ends here