prolog.el 14.7 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

3
;; Copyright (C) 1986, 1987, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
Glenn Morris's avatar
Glenn Morris committed
4
;;   2008, 2009, 2010  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
;; This file is part of GNU Emacs.

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

;; 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
22
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
root's avatar
root committed
23

24 25 26 27
;;; Commentary:

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

Eric S. Raymond's avatar
Eric S. Raymond committed
30 31
;;; Code:

32 33
(defvar comint-prompt-regexp)
(defvar comint-process-echoes)
34
(require 'smie)
35

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

41

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

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

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

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

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

73 74 75 76 77 78 79 80 81 82
(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.")

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

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

102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
(defconst prolog-smie-op-levels
  ;; Rather than construct the operator levels table from the BNF,
  ;; we directly provide the operator precedences from GNU Prolog's
  ;; manual.  The only problem is that GNU Prolog's manual uses
  ;; precedence levels in the opposite sense (higher numbers bind less
  ;; tightly) than SMIE, so we use negative numbers.
  '(("." -10000 -10000)
    (":-" -1200 -1200)
    ("-->" -1200 -1200)
    (";" -1100 -1100)
    ("->" -1050 -1050)
    ("," -1000 -1000)
    ("\\+" -900 -900)
    ("=" -700 -700)
    ("\\=" -700 -700)
    ("=.." -700 -700)
    ("==" -700 -700)
    ("\\==" -700 -700)
    ("@<" -700 -700)
    ("@=<" -700 -700)
    ("@>" -700 -700)
    ("@>=" -700 -700)
    ("is" -700 -700)
    ("=:=" -700 -700)
    ("=\\=" -700 -700)
    ("<" -700 -700)
    ("=<" -700 -700)
    (">" -700 -700)
    (">=" -700 -700)
    (":" -600 -600)
    ("+" -500 -500)
    ("-" -500 -500)
    ("/\\" -500 -500)
    ("\\/" -500 -500)
    ("*" -400 -400)
    ("/" -400 -400)
    ("//" -400 -400)
    ("rem" -400 -400)
    ("mod" -400 -400)
    ("<<" -400 -400)
    (">>" -400 -400)
    ("**" -200 -200)
    ("^" -200 -200)
    ;; Prefix
    ;; ("+" 200 200)
    ;; ("-" 200 200)
    ;; ("\\" 200 200)
    )
  "Precedence levels of infix operators.")

(defconst prolog-smie-indent-rules
  '((":-")
    ("->"))
  "Prolog indentation rules.")

root's avatar
root committed
157 158
(defun prolog-mode-variables ()
  (make-local-variable 'paragraph-separate)
159
  (setq paragraph-separate (concat "%%\\|$\\|" page-delimiter)) ;'%%..'
root's avatar
root committed
160 161
  (make-local-variable 'paragraph-ignore-fill-prefix)
  (setq paragraph-ignore-fill-prefix t)
Karl Heuer's avatar
Karl Heuer committed
162
  (make-local-variable 'imenu-generic-expression)
163
  (setq imenu-generic-expression '((nil "^\\sw+" 0)))
164 165 166 167
  (smie-setup prolog-smie-op-levels prolog-smie-indent-rules)
  (set (make-local-variable 'forward-sexp-function)
       'smie-forward-sexp-command)
  (set (make-local-variable 'smie-indent-basic) prolog-indent-width)
root's avatar
root committed
168 169 170
  (make-local-variable 'comment-start)
  (setq comment-start "%")
  (make-local-variable 'comment-start-skip)
171 172 173
  (setq comment-start-skip "\\(?:%+\\|/\\*+\\)[ \t]*")
  (make-local-variable 'comment-end-skip)
  (setq comment-end-skip "[ \t]*\\(\n\\|\\*+/\\)")
root's avatar
root committed
174
  (make-local-variable 'comment-column)
175
  (setq comment-column 48))
root's avatar
root committed
176

177 178 179
(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
180 181
    (define-key map "\C-c\C-l" 'inferior-prolog-load-file)
    (define-key map "\C-c\C-z" 'switch-to-prolog)
182
    map))
183

Stefan Monnier's avatar
Stefan Monnier committed
184 185 186 187 188 189 190 191 192 193 194
(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
195

Jim Blandy's avatar
Jim Blandy committed
196
;;;###autoload
197
(define-derived-mode prolog-mode prog-mode "Prolog"
root's avatar
root committed
198 199 200 201
  "Major mode for editing Prolog code for Prologs.
Blank lines and `%%...' separate paragraphs.  `%'s start comments.
Commands:
\\{prolog-mode-map}
202
Entry to this mode calls the value of `prolog-mode-hook'
root's avatar
root committed
203 204
if that value is non-nil."
  (prolog-mode-variables)
Stefan Monnier's avatar
Stefan Monnier committed
205
  (set (make-local-variable 'comment-add) 1)
206 207
  (setq font-lock-defaults '(prolog-font-lock-keywords
                             nil nil nil
208
                             beginning-of-line)))
root's avatar
root committed
209 210 211 212 213 214 215 216 217 218 219 220

(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
;;;
221 222 223 224
(defvar inferior-prolog-mode-map
  (let ((map (make-sparse-keymap)))
    ;; This map will inherit from `comint-mode-map' when entering
    ;; inferior-prolog-mode.
225 226
    (define-key map [remap self-insert-command]
      'inferior-prolog-self-insert-command)
227 228 229 230
    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
231

232 233 234 235 236
(defvar inferior-prolog-error-regexp-alist
  ;; GNU Prolog used to not follow the GNU standard format.
  '(("^\\(.*?\\):\\([0-9]+\\) error: .*(char:\\([0-9]+\\)" 1 2 3)
    gnu))

237 238 239 240
(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" ())
241
(defvar compilation-error-regexp-alist)
242

243
(define-derived-mode inferior-prolog-mode comint-mode "Inferior Prolog"
root's avatar
root committed
244 245 246 247 248
  "Major mode for interacting with an inferior Prolog process.

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

249 250 251
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
252

253 254
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
255 256 257 258

Commands:
Tab indents for Prolog; with argument, shifts rest
 of expression rigidly with the current line.
259 260
Paragraphs are separated only by blank lines and '%%'.
'%'s start comments.
root's avatar
root committed
261 262 263 264 265 266

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."
267
  (setq comint-prompt-regexp "^| [ ?][- ] *")
268 269 270
  (set (make-local-variable 'compilation-error-regexp-alist)
       inferior-prolog-error-regexp-alist)
  (compilation-shell-minor-mode)
271
  (prolog-mode-variables))
root's avatar
root committed
272

Stefan Monnier's avatar
Stefan Monnier committed
273 274
(defvar inferior-prolog-buffer nil)

275 276 277 278 279 280
(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
281 282 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
(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))))

314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
(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
329
;;;###autoload
Stefan Monnier's avatar
Stefan Monnier committed
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
(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
346

347 348 349 350 351 352 353 354 355 356 357 358 359
(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 " \\? ")))
360 361 362 363
        ;; This is GNU prolog waiting to know whether you want more answers
        ;; or not (or abort, etc...).  The answer is a single char, not
        ;; a line, so pass this char directly rather than wait for RET to
        ;; send a whole line.
364
        (comint-send-string proc (string last-command-event))
365 366
      (call-interactively 'self-insert-command))))

root's avatar
root committed
367
(defun prolog-consult-region (compile beg end)
368 369
  "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
370
  (interactive "P\nr")
Stefan Monnier's avatar
Stefan Monnier committed
371 372 373 374 375 376
  (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
377
    (if prolog-eof-string
Stefan Monnier's avatar
Stefan Monnier committed
378 379 380
        (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
381 382 383

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

389 390 391
;; inferior-prolog-mode uses the autoloaded compilation-shell-minor-mode.
(declare-function compilation-forget-errors "compile" ())

Stefan Monnier's avatar
Stefan Monnier committed
392 393 394 395 396 397 398
(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)
399
      (compilation-forget-errors)
Stefan Monnier's avatar
Stefan Monnier committed
400 401 402
      (comint-send-string proc (concat "['" (file-relative-name file) "'].\n"))
      (pop-to-buffer (current-buffer)))))

Richard M. Stallman's avatar
Richard M. Stallman committed
403 404
(provide 'prolog)

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