;;; prolog-indent.el --- Indentation of Prolog code.
;;;****************************************************************************
;;; $Id: prolog-indent.el,v 0.00 1995/02/24 15:28:23 gerd Exp $
;;;****************************************************************************
;;;
;;; Description:
;;;
;;;     This file implements additional routines to the prolog mode
;;;     supplied with Emacs. The indentation is improved and some
;;;     electric keys are activated.
;;;
;;; Installation:
;;;     Put the following lines in your .emacs file:
;;;
;;;         (require 'prolog-indent)
;;;
;;;     This assumes that the file prolog-indent.el can be found on
;;;     the load-path:
;;;
;;;         (require 'cl)
;;;         (setq load-path (adjoin "PROLOG-INDENT-DIRECTORY" load-path))
;;;
;;; Bugs and Problems:
;;;     Comments inside Prolog goals are not supported.
;;;     Things might get mixed up for infix operators.
;;;
;;; To do:
;;;
;;; Changes:
;;;
;;; Author:
;;;     Gerd Neugebauer
;;;     Mainzer Str. 16
;;;     56321 Rhens (Germany)
;;;     Net: [email protected]
;;;
;;;****************************************************************************
;;; LCD Archive Entry:
;;; Not yet.
;;;****************************************************************************
;;;
;;; Copyright (C) 1995,1996 Gerd Neugebauer
;;;
;;; prolog-indent.el is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY.  No author or distributor
;;; accepts responsibility to anyone for the consequences of using it
;;; or for whether it serves any particular purpose or works at all,
;;; unless he says so in writing.  Refer to the GNU General Public
;;; License for full details.
;;;
;;; Everyone is granted permission to copy, modify and redistribute
;;; prolog-indent.el, but only under the conditions described in the
;;; GNU General Public License.  A copy of this license is
;;; supposed to have been given to you along with GNU Emacs so you
;;; can know your rights and responsibilities.  It should be in a
;;; file named COPYING. Among other things, the copyright notice
;;; and this notice must be preserved on all copies.
;;;

;;;----------------------------------------------------------------------------
;;; Load prolog.el if not already loaded.
(eval-when-compile (load-library "prolog"))
(if (null prolog-mode-map) (load-library "prolog"))


(defvar Prolog-prefix-op-regex "\\\\\\+"
 "Regular expression of a Prolog prefix operator of length 2.")
(defvar Prolog-infix1-op-regex "[^:/=]=\\|.[*+]\\|[^*]/\\|[^:]-\\|.^"
 "Regular expression of a Prolog infix operator")
(defvar Prolog-infix2-op-regex "\\(==\\|=<\\|>=\\|<<\\|>>\\|is\\)"
 "Regular expression of a Prolog infix operator of length 2.")
(defvar Prolog-infix3-op-regex "=[:\\\\]=\\|=\\.\\.\\|\\\\=="
 "Regular expression of a Prolog infix operator of length 3.")
(defvar Prolog-ascii-escape-regex "0'."
 "Regular expression of a Prolog ASCII characters (length 3).")

;;;----------------------------------------------------------------------------
(defun backward-Prolog-goal (&optional fwd)
 "Move point backward to the beginning of a Prolog goal.
This includes compound terms constructed with infix operators of low priority.
Some of those operators are built into this routine:

+ - * /
\\+
== =< >= << >> is
=.. =:= =\\\\== \\\\===

Comments inside a Prolog goal are not considered!
"
 (if fwd (forward-char fwd))
 (Prolog-backward-to-noncomment (point-min))
 (forward-char -1)
 (if (looking-at "!")  ; the cut is a goal even if it is no sexp
     nil
   (forward-char 1)
   (backward-sexp 1))
 (let ((beg (point)))
   (Prolog-backward-to-noncomment (point-min))
   (or (bobp) (forward-char -1))
   (or (bobp) (forward-char -1))
   (cond
    ((looking-at Prolog-prefix-op-regex) )
    ((looking-at Prolog-infix2-op-regex)
      (backward-Prolog-goal))
    ((looking-at Prolog-infix1-op-regex)
     (backward-Prolog-goal 1))
    ((looking-at Prolog-ascii-escape-regex)
      (backward-Prolog-goal))
    ( (progn (or (bobp) (forward-char -1))
             (looking-at Prolog-infix3-op-regex))
      (backward-Prolog-goal))
    ( t (goto-char beg))))
 (or (bobp)
     (progn
       (forward-char -1)
       (cond ((looking-at "[a-z_A-Z0-9'](")
              (backward-Prolog-goal 1))
             ( t (forward-char 1))))))

;;;----------------------------------------------------------------------------
(defun Prolog-indent (&optional lines)
 "Indent current line as Prolog code.
With argument, indent any additional lines along with this one."
 (interactive "p")
 (if (numberp lines)
     (let ((fwd (if (> lines 0) 1 -1)))
       (if (< lines 0) (setq lines (- 0 lines)))
       (while (> lines 1)
         (setq lines (- lines 1))
         (Prolog-indent-line)
         (forward-line fwd))))
 (Prolog-indent-line)
)

;;;----------------------------------------------------------------------------
(defun Prolog-indent-buffer ()
 "Indent the whole buffer as Prolog code."
 (interactive)
 (save-excursion
   (goto-char (point-min))
   (end-of-line)
   (while  (not (eobp))
     (Prolog-indent-line)
     (forward-line 1)
     (end-of-line)
     )))

;;;----------------------------------------------------------------------------
(defun Prolog-in-comment-p ()
 "Check whether point is inside a C-style comment."
 (let ((beg (if (bobp)
                (point)
              (save-excursion
                (beginning-of-line)
                (search-backward "/*" (point-min) 'move)
                (point))))
       (pos (if (bobp)
                (point-max)
              (save-excursion
                (beginning-of-line)
                (search-backward "*/" (point-min) 'move)
                (point)))))
   (< pos beg)))

;;;----------------------------------------------------------------------------
(defun Prolog-indent-line ()
 "Indent current line as Prolog code."
 (let (indent pos beg)
   (if (Prolog-in-comment-p)
       nil
     (setq indent (Prolog-indentation-level)
           pos (- (point-max) (point)))
     (beginning-of-line)
     (setq beg (point))
     (skip-chars-forward " \t")
     (if (zerop (- indent (current-column)))
         nil
       (delete-region beg (point))
       (indent-to indent))
     (if (> (- (point-max) pos) (point))
         (goto-char (- (point-max) pos)))
     )))

(defvar Prolog-colon-minus-indent tab-width
 "Indentation of :- if at the beginning of a line.")

(defvar Prolog-body-indent tab-width
 "Indentation of the body after :- or --> at the end of a line.")

(defvar Prolog-then-indent 2
 "Indentation of literals after ->")

;;;----------------------------------------------------------------------------
(defun Prolog-indentation-level ()
 "Compute the Prolog indentation level."
 (save-excursion
   (beginning-of-line)
   (skip-chars-forward " \t")
   (cond
    ((looking-at "%[^%]") comment-column)      ;Small comment starts
    ((looking-at "%%%")   0)                   ;Large comment starts
    ((looking-at "/\\*")  0)
    ((looking-at "?-")    0)
    ((looking-at "\\([)}]\\|]\\)")             ;Closing parenthesis
     (forward-char 1)                          ; is aligned at opening
     (backward-sexp)
     (current-column))
    ((looking-at ";")
     (Prolog-backward-to-noncomment (point-min))
     (backward-Prolog-goal)
     (max (- (current-column) prolog-indent-width) 0)
     )
    ((looking-at ":-\\|-->")
     (Prolog-backward-to-noncomment (point-min))
     (cond
      ((or (bobp)
           (= (preceding-char) ?.))
       0 )
      ( t Prolog-colon-minus-indent)))
    ((looking-at "->")
     (Prolog-backward-to-noncomment (point-min))
     (backward-Prolog-goal)
     (+ (current-column) prolog-indent-width))
    ((bobp)               0)
    (t
     (Prolog-backward-to-noncomment (point-min))
     (if (save-excursion
           (beginning-of-line 1)
           (skip-chars-forward " \t")
           (looking-at "%%[^%]"))
         (current-column)
       (let ((pos (point)))
         (skip-chars-backward " \t")
         (or (bobp) (forward-char -1))
         (or (bobp) (forward-char -1))                  ;Backward twice
         (cond
          ((looking-at ".;")
           (goto-char pos)
           (+ -1 (current-column) prolog-indent-width))
          ((looking-at ".,")
           (backward-Prolog-goal 1)
           (current-column))
          ((looking-at "->")
           (cond
            ((= (preceding-char) ?-)
             Prolog-body-indent
             )
            ( t
              (backward-Prolog-goal)
              (+ (current-column) Prolog-then-indent))))
          ((looking-at "[a-z_A-Z0-9'](")
           (forward-char 2)
           (current-column))
          ((looking-at ".[({]")
           (forward-char 1)
           (+ (current-column) prolog-indent-width))
          ((looking-at ":-") Prolog-body-indent)
          ((looking-at ".[^.]")
           (goto-char pos)
           (max (- (current-column) prolog-indent-width) 0))
          (t 0 ))
         ))))))

;;;----------------------------------------------------------------------------
;;; Taken from the prolog mode by Ken'ichi HANDA ([email protected])
(defun Prolog-backward-to-noncomment (lim)
 (let (opoint stop)
   (while (not stop)
     (skip-chars-backward " \t\n\f" lim)
     (setq opoint (point))
     (if (and (>= (point) (+ 2 lim))
              (= (preceding-char) ?/) (= (char-after (- (point) 2)) ?*))
         (search-backward "/*" lim 'mv)
       (let ((p (max lim (save-excursion (beginning-of-line) (point)))))
         (if (nth 4 (parse-partial-sexp p (point)))
             (search-backward "%" p 'mv)
           (goto-char opoint)
           (setq stop t)))))))


(defvar Prolog-boc-regexp "^['a-z]")

;;;----------------------------------------------------------------------------
(defun Prolog-beginning-of-clause ()
 "Return the position of the beginning of the clause or nil."
 (save-excursion
   (catch 'boc
     (while t
       (cond
        ((null (search-backward-regexp Prolog-boc-regexp (point-min) t))
         (throw 'boc nil))
        ((not (Prolog-in-comment-p))
         (throw 'boc (point))))))))

(defvar Prolog-eoc-regexp "\\.[ \t\f]*$")

;;;----------------------------------------------------------------------------
(defun Prolog-end-of-clause ()
 "Return the position of the beginning of the clause or nil."
 (save-excursion
   (catch 'boc
     (while t
       (cond
        ((null (search-forward-regexp Prolog-eoc-regexp (point-max) t))
         (throw 'boc nil))
        ((not (Prolog-in-comment-p))
         (skip-chars-backward " \t\f\n")
         (throw 'boc (point))))))))

;;;----------------------------------------------------------------------------
(defun Prolog-indent-region (beg end)
 (interactive "d\nm")
 (let (tmp)
   (if (null end)
       (message "Mark not set")
     (if (< end beg) (setq tmp end end beg beg tmp))
     (save-excursion
       (setq end (- (point-max) end))
       (goto-char beg)
       (Prolog-indent-line)
       (forward-line 1)
       (beginning-of-line)
       (while (> (- (point-max) (point)) end)
         (Prolog-indent-line)
         (forward-line 1)
         (beginning-of-line))))))

;;;----------------------------------------------------------------------------
(defun Prolog-indent-clause ()
 (interactive)
 (let ((beg (Prolog-beginning-of-clause))
       (end (Prolog-end-of-clause)))
   (if (and beg end) (Prolog-indent-region beg end))))

;;;----------------------------------------------------------------------------
(defun Prolog-fill-paragraph (&optional arg) (interactive)
 (if (Prolog-in-comment-p) (fill-paragraph arg) (Prolog-indent-clause)))

(defvar Prolog-electric t
 "Variable indicating if the Prolog electric characters should be active.")

;;;----------------------------------------------------------------------------
;;; Taken from the prolog mode by Ken'ichi HANDA ([email protected])
;;; Modified
(defun Prolog-electic-char (arg)
 "Insert character and correct line's indentation."
 (interactive "P")
 (if (or arg
         (not Prolog-electric))
     (self-insert-command (prefix-numeric-value arg))
     (progn
       (self-insert-command (prefix-numeric-value arg))
       (Prolog-indent-line))))

;;;----------------------------------------------------------------------------
;;; Activate some additional keys
(define-key prolog-mode-map "("    'Prolog-electic-char   )
(define-key prolog-mode-map ")"    'Prolog-electic-char   )
(define-key prolog-mode-map "{"    'Prolog-electic-char   )
(define-key prolog-mode-map "}"    'Prolog-electic-char   )
(define-key prolog-mode-map ";"    'Prolog-electic-char   )
(define-key prolog-mode-map "-"    'Prolog-electic-char   )
(define-key prolog-mode-map ">"    'Prolog-electic-char   )
(define-key prolog-mode-map "\t"   'Prolog-indent         )
(define-key prolog-mode-map "\M-q" 'Prolog-fill-paragraph )

;;;----------------------------------------------------------------------------
(defun prolog-indent-line (&optional arg) (interactive "P")
 (Prolog-indent (prefix-numeric-value arg)))

(provide 'prolog-indent)