;;;; c-mode.jl -- Primitive mode for editing C source
;;;  Copyright (C) 1993, 1994 John Harper <jsh@ukc.ac.uk>

;;; This file is part of Jade.

;;; Jade 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 2, or (at your option)
;;; any later version.

;;; Jade 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 Jade; see the file COPYING.  If not, write to
;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

(provide 'c-mode)

(defvar c-mode-tab 4
  "Size of indentation for c-mode")

(defvar c-mode-keymap (make-keylist))
(bind-keys c-mode-keymap
  "{" 'c-open-brace
  "}" 'c-close-brace
  ":" 'c-colon
  "TAB" 'indent-line)

(defvar c-mode-ctrl-c-keymap (make-keylist))
(bind-keys c-mode-ctrl-c-keymap
  "Ctrl-\\" 'c-backslash-region)

;;;###autoload
(defun c-mode ()
  "C Mode:\n
Simple mode for editing C source code. Its main feature is to be able to
indent lines to their (probably) correct depth.\n
Special commands are,\n
  `{', `}', `:'			Insert the character then indent the line
  `TAB'				Indent the current line
  `Ctrl-c Ctrl-\\'		Aligns backslash characters at the end
				of each line in the current block.
  `ESC Ctrl-b'			Move backwards one expression.
  `ESC Ctrl-f'			Move forward one expression."
  (interactive)
  (when major-mode-kill
    (funcall major-mode-kill (current-buffer)))
  (setq mode-name "C"
	major-mode 'c-mode
	major-mode-kill 'c-mode-kill
	mode-comment-fun 'c-insert-comment
	mode-indent-line 'c-indent-line
	mode-forward-exp 'c-forward-exp
	mode-backward-exp 'c-backward-exp
	ctrl-c-keymap c-mode-ctrl-c-keymap
	keymap-path (cons 'c-mode-keymap keymap-path))
  (eval-hook 'c-mode-hook))

(defun c-mode-kill ()
  (setq mode-name nil
	major-mode nil
	major-mode-kill nil
	mode-comment-fun nil
	mode-indent-line nil
	mode-forward-exp nil
	mode-backward-exp nil
	ctrl-c-keymap nil
	keymap-path (delq 'c-mode-keymap keymap-path)))

(defun c-open-brace ()
  (interactive)
  (insert "{")
  (indent-line))

(defun c-close-brace ()
  (interactive)
  (insert "}")
  (indent-line))

(defun c-colon ()
  (interactive)
  (insert ":")
  (indent-line))

(defun c-indent-line (&optional pos)
  "Indent the line at POS (or the cursor) assuming that it's C source code."
  (set-indent-pos (c-indent-pos pos)))

(defun c-indent-pos (&optional line-pos)
  "*Attempts* to guess the correct indentation for this line. Returns the
position for the first non-space in the line."
  (setq line-pos (line-start line-pos))
  (let*
      ((ind-pos (c-indent-pos-empty line-pos)))
    (when (not (empty-line-p line-pos))
      (cond
       ((regexp-match-line "^[\t ]*({|}|case .*:|default *:)" line-pos)
	(prev-tab 1 ind-pos c-mode-tab))
       ((regexp-match-line "^[\t ]*([a-zA-Z0-9_]*:|#)" line-pos)
	(set-pos-col ind-pos 0))))
    ind-pos))

(defun c-indent-pos-empty (&optional line-pos)
  "Returns the position for the first non-space in the line. Bases its guess
upon the assumption that the line is empty.
All positions depend on the indentation of the previous line(s)."
  (setq line-pos (line-start line-pos))
  (let*
      ((p-line-pos (prev-line 1 (copy-pos line-pos))))
    (while (or (empty-line-p p-line-pos)
	       (regexp-match-line "^([a-zA-Z0-9_]+:|#)" p-line-pos))
      (unless (prev-line 1 p-line-pos)
	(return)))
    (let*
	((ind-pos (indent-pos p-line-pos)))
      (set-pos-line ind-pos (pos-line line-pos))
      (cond
       ((regexp-match-line "{|case .*:|default[\t ]*:|do($| )|else|(if|for|while|switch)[\t ]*\\(.*\\)" p-line-pos)
	(next-tab 1 ind-pos c-mode-tab))
       ((regexp-match-line ";" p-line-pos)
	(prev-line 1 p-line-pos)
	(while (or (empty-line-p p-line-pos)
		   (regexp-match-line "^([a-zA-Z0-9_]+:|#)" p-line-pos))
	  (unless (prev-line 1 p-line-pos)
	    (return)))
	(when (and (regexp-match-line
		    "do($| )|else|(if|for|while|switch)[\t ]*\\(.*\\)"
		    p-line-pos)
		   (not (regexp-match-line " {[\t ]*(/\\*.*\\*/|)[\t ]*$"
					   p-line-pos)))
	  (prev-tab 1 ind-pos c-mode-tab)))
       ((regexp-match-line "^[\t ]*/\\*" p-line-pos)
	(unless (regexp-match-line "\\*/" p-line-pos)
	  (right-char 3 ind-pos)))
       ((regexp-match-line "^[\t ]*\\*/ *$" p-line-pos)
	(left-char 1 ind-pos))
       ((regexp-match-line "\\*/" p-line-pos)
	(left-char 3 ind-pos)))
      ind-pos)))

;;;###autoload
(defun c-backslash-area (start end)
  "Insert (or align) backslash characters at the end of each line in between
START and END except for the last line."
  (interactive "-m\nM")
  (let
      ((max-width 0)
       (pos (copy-pos start))
       tmp)
    (while (<= pos end)
      (setq tmp (char-to-glyph-pos (line-end pos)))
      (when (> (pos-col tmp) max-width)
	(setq max-width (pos-col tmp)))
      (setq pos (next-line 1 pos)))
    (setq max-width (1+ max-width))
    (unless (= (% max-width tab-size) 0)
      (setq max-width (* (1+ (/ max-width tab-size)) tab-size)))
    (set-pos-line pos (pos-line start))
    (set-pos-col pos max-width)
    (while (< pos end)
      (if (regexp-match-line "\\\\ *$" pos)
	  (progn
	    (setq tmp (char-to-glyph-pos (match-start)))
	    (cond
	     ((> (pos-col tmp) max-width)
	      (delete-area (glyph-to-char-pos pos) (match-start)))
	     ((< (pos-col tmp) max-width)
	      (goto-char (match-start))
	      (indent-to max-width))))
	(goto-char (line-end pos))
	(indent-to max-width)
	(insert "\\"))
      (setq pos (next-line 1 pos)))
    (goto-char end)))

;;;###autoload
(defun c-insert-comment ()
  (interactive)
  (find-comment-pos)
  (insert "/*  */")
  (goto-left-char 3))


;; Experimental expression stuff

(defun c-forward-exp (&optional number pos)
  (unless number
    (setq number 1))
  (while (> number 0)
    ;; first, skip empty lines & comments
    (while (looking-at "[\t\f ]*$|[\t\f ]*/\\*.*$" pos)
      (if (looking-at "[\t\f ]*/\\*" pos)
	  (progn
	    (unless (find-next-regexp "\\*/" pos)
	      (error "Comment doesn't end!"))
	    (setq pos (match-end)))
	(setq pos (next-line 1 (line-start pos)))
	(when (> pos (buffer-end))
	  (error "End of buffer"))))
    ;; Check for a cpp line
    (if (regexp-match-line "^[\t ]*#" pos)
	(setq pos (line-end pos))
      ;; now any other whitespace
      (when (looking-at "[\t\f ]+" pos)
	(setq pos (match-end)))
      ;; Skip weird stuff
      (while (looking-at "[!*~&<>/+%?:^-]+" pos)
	(setq pos (match-end))
	(when (equal pos (line-end pos))
	  (setq pos (next-char 1 pos))))
      (let
	  ((c (get-char pos)))
	(cond
	 ((member c '(?\" ?\'))
	  ;; move over string/character
	  (if (setq pos (find-next-char c (next-char 1 pos)))
	      (while (= (get-char (prev-char 1 (copy-pos pos))) ?\\ )
		(unless (setq pos (find-next-char c (next-char 1 pos)))
		  (error "String doesn't end!")))
	    (error "String doesn't end!"))
	  (setq pos (next-char 1 pos)))
	 ((member c '(?\( ?\[ ?\{))
	  ;; move over brackets
	  (unless (setq pos (match-brackets pos))
	    (error "Expression doesn't end!"))
	  (setq pos (next-char 1 pos)))
	 ((member c '(?, ?\; ?:))
	  (setq pos (next-char 1 pos)
		number (1+ number)))
	 ((member c '(?\) ?\] ?\}))
	  (error "End of containing expression"))
	 (t
	  ;; a symbol?
	  (if (looking-at "[a-zA-Z0-9_]+" pos)
	      (setq pos (match-end))
	    (unless (setq pos (find-next-regexp "[][a-zA-Z0-9_ \t\f()<>{}'\"]"
						pos))
	      (error "Can't classify expression"))
	    (setq number (1+ number))))))
      (setq number (1- number))))
  pos)
  
(defun c-backward-exp (&optional number orig-pos no-blocks)
  (unless number
    (setq number 1))
  (unless orig-pos 
    (setq orig-pos (cursor-pos)))
  (let
      ((pos (copy-pos orig-pos))
       tmp)
    (while (> number 0)
      ;; skip preceding white space
      (unless (setq pos (find-prev-regexp "[^\t\f ]" (prev-char 1 pos)))
	(error "No expression!"))
      (setq tmp (prev-char 1 (copy-pos pos)))
      (while (looking-at "\\*/" tmp)
	;; comment to skip
	(unless (setq tmp (find-prev-regexp "/\\*" tmp))
	  (error "Comment doesn't start!"))
	(unless (setq tmp (find-prev-regexp "[^\t\f ]" (prev-char 1 tmp)))
	  (error "Beginning of buffer"))
	(setq pos tmp))
      ;; Check for a cpp line
      (if (regexp-match-line "^[\t ]*#" pos)
	  (setq pos (line-start pos))
	(let
	    ((c (get-char pos)))
	  (cond
	   ((member c '(?\) ?\] ?\}))
	    (when (or (/= c ?\}) (not no-blocks))
	      (unless (setq pos (match-brackets pos))
		(error "Brackets don't match"))))
	   ((member c '(?\" ?\'))
	    (if (setq pos (find-prev-char c (prev-char 1 pos)))
		(while (= (get-char (prev-char 1 (copy-pos pos))) ?\\ )
		  (unless (setq pos (find-prev-char c (prev-char 1 pos)))
		    (error "String doesn't start!")))
	      (error "String doesn't start!")))
	   ((member c '(?\; ?: ?,))
	    ;; loop again
	    (setq number (1+ number)))
	   ((member c '(?\( ?\[ ?\{))
	    (error "Start of containing expression"))
	   (t
	    ;; a symbol?
	    (if (looking-at "[a-zA-Z0-9_]" pos)
		(unless (setq pos (find-prev-regexp "(^#[\t ]*|)[a-zA-Z0-9_]+"
						    pos))
		  (error "Can't classify expression"))
	      (unless (setq pos (find-prev-regexp "[][a-zA-Z0-9_ \t\f()<>{}'\"]"
						  pos))
		(error "Can't classify expression"))
	      (setq number (1+ number)))))
	  (when (member (get-char (prev-char 1 (copy-pos pos))) '(?! ?* ?- ?~))
	    ;; unary operator, skip over it
	    (setq pos (prev-char 1 pos))))
	(setq number (1- number))))
    pos))
