;; promela-mode.el --- major mode for editing PROMELA program files ;; $Revision: 1.11 $ $Date: 2001/07/09 18:36:45 $ $Author: engstrom $ ;; Author: Eric Engstrom ;; Maintainer: Eric Engstrom ;; Keywords: spin, promela, tools ;; Copyright (C) 1998-2003 Eric Engstrom / Honeywell Laboratories ;; ... Possibly insert GPL here someday ... ;;; Commentary: ;; This file contains code for a GNU Emacs major mode for editing ;; PROMELA (SPIN) program files. ;; Type "C-h m" in Emacs (while in a buffer in promela-mode) for ;; information on how to configure indentation and fontification, ;; or look at the configuration variables below. ;; To use, place promela-mode.el in a directory in your load-path. ;; Then, put the following lines into your .emacs and promela-mode ;; will be automatically loaded when editing a PROMELA program. ;; (autoload 'promela-mode "promela-mode" "PROMELA mode" nil t) ;; (setq auto-mode-alist ;; (append ;; (list (cons "\\.promela$" 'promela-mode) ;; (cons "\\.spin$" 'promela-mode) ;; (cons "\\.pml$" 'promela-mode) ;; ;; (cons "\\.other-extensions$" 'promela-mode) ;; ) ;; auto-mode-alist)) ;; If you wish for promela-mode to be used for files with other ;; extensions you add your own patterned after the code above. ;; Note that promela-mode adhears to the font-lock "standards" and ;; defines several "levels" of fontification or colorization. The ;; default is fairly gaudy, so I can imagine that some folks would ;; like a bit less. FMI: see `font-lock-maximum-decoration' ;; This mode is known to work under the following versions of emacs: ;; - XEmacs: 19.16, 20.x, 21.x ;; - FSF/GNU Emacs: 19.34 ;; - NTEmacs (FSF): 20.[67] ;; That is not to say there are no bugs specific to one of those versions :-) ;; Please send any comments, bugs, patches or other requests to ;; Eric Engstrom at engstrom@htc.honeywell.com ;; To-Do: ;; - compile/syntax-check/verify? (suggested by R.Goldman) ;; - indentation - splitting lines at logical operators (M. Rangarajan) ;; [ This might "devolve" to indentation after "->" or ";" ;; being as is, but anything else indent even more? ] ;; :: SomeReallyLongArrayRef[this].typedefField != SomeReallyLongConstant -> /* some-comment */ ;; [ Suggestion would be to break the first line after the !=, therefore: ] ;; :: SomeReallyLongArrayRef[this].typedefField ;; != SomeReallyLongConstant -> /* some-comment */ ;; [ at this point I'm not so sure about this change... EE: 2001/05/19 ] ;;; ------------------------------------------------------------------------- ;;; Code: ;; NOTE: same as CVS revision: (defconst promela-mode-version "$Revision: 1.11 $" "Promela-mode version number.") ;; ------------------------------------------------------------------------- ;; The following constant values can be modified by the user in a .emacs file (defconst promela-block-indent 2 "*Controls indentation of lines within a block (`{') construct") (defconst promela-selection-indent 2 "*Controls indentation of options within a selection (`if') or iteration (`do') construct") (defconst promela-selection-option-indent 3 "*Controls indentation of lines after options within selection or iteration construct (`::')") (defconst promela-comment-col 32 "*Defines the desired comment column for comments to the right of text.") (defconst promela-tab-always-indent t "*Non-nil means TAB in Promela mode should always reindent the current line, regardless of where in the line point is when the TAB command is used.") (defconst promela-auto-match-delimiter t "*Non-nil means typing an open-delimiter (i.e. parentheses, brace, quote, etc) should also insert the matching closing delmiter character.") ;; That should be about it for most users... ;; unless you wanna hack elisp, the rest of this is probably uninteresting ;; ------------------------------------------------------------------------- ;; help determine what emacs we have here... (defconst promela-xemacsp (string-match "XEmacs" (emacs-version)) "Non-nil if we are running in the XEmacs environment.") ;;;(defconst promela-xemacs20p (and promela-xemacsp (>= emacs-major-version 20)) ;; "Non-nil if we are running in an XEmacs environment version 20 or greater.") ;; ------------------------------------------------------------------------- ;; promela-mode font faces/definitions ;; make use of font-lock stuff, so say that explicitly (require 'font-lock) ;; BLECH! YUCK! I just wish these guys could agree to something.... ;; Faces available in: ntemacs emacs xemacs xemacs xemacs ;; font-lock- xxx -face 20.6 19.34 19.16 20.x 21.x ;; -builtin- X ;; -constant- X ;; -comment- X X X X X ;; -doc-string- X X X ;; -function-name- X X X X X ;; -keyword- X X X X X ;; -preprocessor- X X X ;; -reference- X X X X ;; -signal-name- X X!20.0 ;; -string- X X X X X ;; -type- X X X X X ;; -variable-name- X X X X X ;; -warning- X X ;;; Compatibility on faces between versions of emacs-en (unless promela-xemacsp (defvar font-lock-preprocessor-face 'font-lock-preprocessor-face "Face name to use for preprocessor statements.") ;; For consistency try to define the preprocessor face == builtin face (condition-case nil (copy-face 'font-lock-builtin-face 'font-lock-preprocessor-face) (error (defface font-lock-preprocessor-face '((t (:foreground "blue" :italic nil :underline t))) "Face Lock mode face used to highlight preprocessor statements." :group 'font-lock-highlighting-faces))) (defvar font-lock-reference-face 'font-lock-reference-face "Face name to use for constants and reference and label names.") ;; For consistency try to define the reference face == constant face (condition-case nil (copy-face 'font-lock-constant-face 'font-lock-reference-face) (error (defface font-lock-reference-face '((((class grayscale) (background light)) (:foreground "LightGray" :bold t :underline t)) (((class grayscale) (background dark)) (:foreground "Gray50" :bold t :underline t)) (((class color) (background light)) (:foreground "CadetBlue")) (((class color) (background dark)) (:foreground "Aquamarine")) (t (:bold t :underline t))) "Font Lock mode face used to highlight constancs, references and labels." :group 'font-lock-highlighting-faces))) ) ;; send-poll "symbol" face is custom to promela-mode ;; but check for existence to allow someone to override it (defvar promela-fl-send-poll-face 'promela-fl-send-poll-face "Face name to use for Promela Send or Poll symbols: `!' or `?'") (copy-face (if promela-xemacsp 'modeline 'region) 'promela-fl-send-poll-face) ;; some emacs-en don't define or have regexp-opt available. (unless (functionp 'regexp-opt) (defmacro regexp-opt (strings) "Cheap imitation of `regexp-opt' since it's not availble in this emacs" `(mapconcat 'identity ,strings "\\|"))) ;; ------------------------------------------------------------------------- ;; promela-mode font lock specifications/regular-expressions ;; - for help, look at definition of variable 'font-lock-keywords ;; - some fontification ideas from -- [engstrom:20010309.1435CST] ;; Pat Tullman (tullmann@cs.utah.edu) and ;; Ny Aina Razermera Mamy (ainarazr@cs.uoregon.edu) ;; both had promela-mode's that I discovered after starting this one... ;; (but neither did any sort of indentation ;-) (defconst promela-font-lock-keywords-1 nil "Subdued level highlighting for Promela mode.") (defconst promela-font-lock-keywords-2 nil "Medium level highlighting for Promela mode.") (defconst promela-font-lock-keywords-3 nil "Gaudy level highlighting for Promela mode.") ;; set each of those three variables now.. (let ((promela-keywords (eval-when-compile (regexp-opt '("active" "assert" "atomic" "break" "d_step" "do" "dproctype" "else" "empty" "enabled" "eval" "fi" "full" "goto" "hidden" "if" "init" "inline" "len" "local" "mtype" "nempty" "never" "nfull" "od" "of" "pcvalue" "printf" "priority" "proctype" "provided" "run" "show" "skip" "timeout" "trace" "typedef" "unless" "xr" "xs")))) (promela-types (eval-when-compile (regexp-opt '("bit" "bool" "byte" "short" "int" "unsigned" "chan"))))) ;; really simple fontification (strings and comments come for "free") (setq promela-font-lock-keywords-1 (list ;; Keywords: (cons (concat "\\<\\(" promela-keywords "\\)\\>") 'font-lock-keyword-face) ;; Types: (cons (concat "\\<\\(" promela-types "\\)\\>") 'font-lock-type-face) ;; Special constants: '("\\<_\\(np\\|pid\\|last\\)\\>" . font-lock-reference-face))) ;; more complex fontification ;; add function (proctype) names, lables and goto statements ;; also add send/receive/poll fontification (setq promela-font-lock-keywords-2 (append promela-font-lock-keywords-1 (list ;; ANY Pre-Processor directive (lazy method: any line beginning with "#[a-z]+") '("^\\(#[ \t]*[a-z]+\\)" 1 'font-lock-preprocessor-face t) ;; "Functions" (proctype-s and inline-s) (list (concat "\\<\\(" (eval-when-compile (regexp-opt '("run" "dproctype" "proctype" "inline"))) "\\)\\>[ \t]*\\(\\sw+\\)?") ;;'(1 'font-lock-keyword-face nil) '(2 'font-lock-function-name-face nil t)) ;; Labels and GOTO labels '("^\\(\\sw+\\):" 1 'font-lock-reference-face) '("\\<\\(goto\\)\\>[ \t]+\\(\\sw+\\)" ;;(1 'font-lock-keyword-face nil) (2 'font-lock-reference-face nil t)) ;; Send, Receive and Poll '("\\(\\sw+\\)\\(\\[[^\\?!]+\\]\\)?\\(\\??\\?\\|!?!\\)\\(\\sw+\\)" (1 'font-lock-variable-name-face nil t) (3 'promela-fl-send-poll-face nil t) (4 'font-lock-reference-face nil t) ) ))) ;; most complex fontification ;; add pre-processor directives, typed variables and hidden/typedef decls. (setq promela-font-lock-keywords-3 (append promela-font-lock-keywords-2 (list ;; ANY Pre-Processor directive (lazy method: any line beginning with "#[a-z]+") ;;'("^\\(#[ \t]*[a-z]+\\)" 1 'font-lock-preprocessor-face t) ;; "defined" in an #if or #elif and associated macro names '("^#[ \t]*\\(el\\)?if\\>" ("\\<\\(defined\\)\\>[ \t]*(?\\(\\sw+\\)" nil nil (1 'font-lock-preprocessor-face nil t) (2 'font-lock-reference-face nil t))) '("^#[ \t]*ifn?def\\>" ("[ \t]*\\(\\sw+\\)" nil nil (1 'font-lock-reference-face nil t))) ;; Filenames in #include <...> directives '("^#[ \t]*include[ \t]+<\\([^>\"\n]+\\)>" 1 'font-lock-string-face nil t) ;; Defined functions and constants/types (non-functions) '("^#[ \t]*define[ \t]+" ("\\(\\sw+\\)(" nil nil (1 'font-lock-function-name-face nil t)) ("\\(\\sw+\\)[ \t]+\\(\\sw+\\)" nil nil (1 'font-lock-variable-name-face) (2 'font-lock-reference-face nil t)) ("\\(\\sw+\\)[^(]?" nil nil (1 'font-lock-reference-face nil t))) ;; Types AND variables ;; - room for improvement: (i.e. don't currently): ;; highlight user-defined types and asociated variable declarations (list (concat "\\<\\(" promela-types "\\)\\>") ;;'(1 'font-lock-type-face) ;; now match the variables after the type definition, if any '(promela-match-variable-or-declaration nil nil (1 'font-lock-variable-name-face) ;; nil t) (2 font-lock-reference-face nil t))) ;; Typedef/hidden types and declarations '("\\<\\(typedef\\|hidden\\)\\>[ \t]*\\(\\sw+\\)?" ;;(1 'font-lock-keyword-face nil) (2 'font-lock-type-face nil t) ;; now match the variables after the type definition, if any (promela-match-variable-or-declaration nil nil (1 'font-lock-variable-name-face nil t) (2 'font-lock-reference-face nil t))) ))) ) (defvar promela-font-lock-keywords promela-font-lock-keywords-1 "Default expressions to highlight in Promela mode.") ;; Font-lock matcher functions: (defun promela-match-variable-or-declaration (limit) "Match, and move over, any declaration/definition item after point. Matches after point, but ignores leading whitespace characters. Does not move further than LIMIT. The expected syntax of a declaration/definition item is `word' (preceded by optional whitespace) optionally followed by a `= value' (preceded and followed by more optional whitespace) Thus the regexp matches after point: word [ = value ] ^^^^ ^^^^^ Where the match subexpressions are: 1 2 The item is delimited by (match-beginning 1) and (match-end 1). If (match-beginning 2) is non-nil, the item is followed by a `value'." (when (looking-at "[ \t]*\\(\\sw+\\)[ \t]*=?[ \t]*\\(\\sw+\\)?[ \t]*,?") (goto-char (min limit (match-end 0))))) ;; ------------------------------------------------------------------------- ;; "install" promela-mode font lock specifications ;; FMI: look up 'font-lock-defaults (defconst promela-font-lock-defaults '( (promela-font-lock-keywords promela-font-lock-keywords-1 promela-font-lock-keywords-2 promela-font-lock-keywords-3) ;; font-lock stuff (keywords) nil ;; keywords-only flag nil ;; case-fold keyword searching ;;((?_ . "w") (?$ . ".")) ;; mods to syntax table nil ;; mods to syntax table (see below) nil ;; syntax-begin (font-lock-mark-block-function . mark-defun)) ) ;; "install" the font-lock-defaults based upon version of emacs we have (cond (promela-xemacsp (put 'promela-mode 'font-lock-defaults promela-font-lock-defaults)) ((not (assq 'promela-mode font-lock-defaults-alist)) (setq font-lock-defaults-alist (cons (cons 'promela-mode promela-font-lock-defaults) font-lock-defaults-alist)))) ;; ------------------------------------------------------------------------- ;; other promela-mode specific definitions (defconst promela-defun-prompt-regexp "^[ \t]*\\(\\(active \\)?d?proctype\\|init\\|inline\\|never\\|trace\\|typedef\\|mtype\\s-+=\\)[ \t][^{]*" "Regexp describing the beginning of a Promela top-level definition.") (defvar promela-mode-syntax-table nil "Syntax table in use in PROMELA-mode buffers.") (if promela-mode-syntax-table () (setq promela-mode-syntax-table (make-syntax-table)) (modify-syntax-entry ?\\ "\\" promela-mode-syntax-table) (modify-syntax-entry ?/ ". 14" promela-mode-syntax-table) (modify-syntax-entry ?* ". 23" promela-mode-syntax-table) (modify-syntax-entry ?+ "." promela-mode-syntax-table) (modify-syntax-entry ?- "." promela-mode-syntax-table) (modify-syntax-entry ?= "." promela-mode-syntax-table) (modify-syntax-entry ?% "." promela-mode-syntax-table) (modify-syntax-entry ?< "." promela-mode-syntax-table) (modify-syntax-entry ?> "." promela-mode-syntax-table) (modify-syntax-entry ?& "." promela-mode-syntax-table) (modify-syntax-entry ?| "." promela-mode-syntax-table) (modify-syntax-entry ?. "_" promela-mode-syntax-table) (modify-syntax-entry ?_ "w" promela-mode-syntax-table) (modify-syntax-entry ?\' "\"" promela-mode-syntax-table) ) (defvar promela-mode-abbrev-table nil "*Abbrev table in use in promela-mode buffers.") (if promela-mode-abbrev-table nil (define-abbrev-table 'promela-mode-abbrev-table '( ;; Commented out for now - need to think about what abbrevs make sense ;; ("assert" "ASSERT" promela-check-expansion 0) ;; ("d_step" "D_STEP" promela-check-expansion 0) ;; ("break" "BREAK" promela-check-expansion 0) ;; ("do" "DO" promela-check-expansion 0) ;; ("proctype" "PROCTYPE" promela-check-expansion 0) ))) (defvar promela-mode-map nil "Keymap for promela-mode.") (if promela-mode-map nil (setq promela-mode-map (make-sparse-keymap)) (define-key promela-mode-map "\t" 'promela-indent-command) (define-key promela-mode-map "\C-m" 'promela-newline-and-indent) ;(define-key promela-mode-map 'backspace 'backward-delete-char-untabify) (define-key promela-mode-map "\C-c\C-p" 'promela-beginning-of-block) ;(define-key promela-mode-map "\C-c\C-n" 'promela-end-of-block) (define-key promela-mode-map "\M-\C-a" 'promela-beginning-of-defun) ;(define-key promela-mode-map "\M-\C-e" 'promela-end-of-defun) (define-key promela-mode-map "\C-c(" 'promela-toggle-auto-match-delimiter) (define-key promela-mode-map "{" 'promela-open-delimiter) (define-key promela-mode-map "}" 'promela-close-delimiter) (define-key promela-mode-map "(" 'promela-open-delimiter) (define-key promela-mode-map ")" 'promela-close-delimiter) (define-key promela-mode-map "[" 'promela-open-delimiter) (define-key promela-mode-map "]" 'promela-close-delimiter) (define-key promela-mode-map ";" 'promela-insert-and-indent) (define-key promela-mode-map ":" 'promela-insert-and-indent) ;; ;; this is preliminary at best - use at your own risk: (define-key promela-mode-map "\C-c\C-s" 'promela-syntax-check) ;; ;;(define-key promela-mode-map "\C-c\C-d" 'promela-mode-toggle-debug) ;;(define-key promela-mode-map "\C-c\C-r" 'promela-mode-revert-buffer) ) (defvar promela-matching-delimiter-alist '( (?( . ?)) (?[ . ?]) (?{ . "\n}") ;(?< . ?>) (?\' . ?\') (?\` . ?\`) (?\" . ?\") ) "List of pairs of matching open/close delimiters - for auto-insert") ;; ------------------------------------------------------------------------- ;; Promela-mode itself (defun promela-mode () "Major mode for editing PROMELA code. \\{promela-mode-map} Variables controlling indentation style: promela-block-indent Relative offset of lines within a block (`{') construct. promela-selection-indent Relative offset of option lines within a selection (`if') or iteration (`do') construct. promela-selection-option-indent Relative offset of lines after/within options (`::') within selection or iteration constructs. promela-comment-col Defines the desired comment column for comments to the right of text. promela-tab-always-indent Non-nil means TAB in PROMELA mode should always reindent the current line, regardless of where in the line the point is when the TAB command is used. promela-auto-match-delimiter Non-nil means typing an open-delimiter (i.e. parentheses, brace, quote, etc) should also insert the matching closing delmiter character. Turning on PROMELA mode calls the value of the variable promela-mode-hook with no args, if that value is non-nil. For example: ' (setq promela-mode-hook '(lambda () (setq promela-block-indent 2) (setq promela-selection-indent 0) (setq promela-selection-option-indent 2) (local-set-key \"\\C-m\" 'promela-indent-newline-indent) ))' will indent block two steps, will make selection options aligned with DO/IF and sub-option lines indent to a column after the `::'. Also, lines will be reindented when you hit RETURN. Note that promela-mode adhears to the font-lock \"standards\" and defines several \"levels\" of fontification or colorization. The default is fairly gaudy, so if you would prefer a bit less, please see the documentation for the variable: `font-lock-maximum-decoration'. " (interactive) (kill-all-local-variables) (setq mode-name "Promela") (setq major-mode 'promela-mode) (use-local-map promela-mode-map) (set-syntax-table promela-mode-syntax-table) (setq local-abbrev-table promela-mode-abbrev-table) ;; Make local variables (make-local-variable 'case-fold-search) (make-local-variable 'paragraph-start) (make-local-variable 'paragraph-separate) (make-local-variable 'paragraph-ignore-fill-prefix) (make-local-variable 'indent-line-function) (make-local-variable 'indent-region-function) (make-local-variable 'parse-sexp-ignore-comments) (make-local-variable 'comment-start) (make-local-variable 'comment-end) (make-local-variable 'comment-column) (make-local-variable 'comment-start-skip) (make-local-variable 'comment-indent-hook) (make-local-variable 'defun-prompt-regexp) (make-local-variable 'compile-command) ;; Now set their values (setq case-fold-search t paragraph-start (concat "^$\\|" page-delimiter) paragraph-separate paragraph-start paragraph-ignore-fill-prefix t indent-line-function 'promela-indent-command ;;indent-region-function 'promela-indent-region parse-sexp-ignore-comments t comment-start "/* " comment-end " */" comment-column 32 comment-start-skip "/\\*+ *" ;;comment-start-skip "/\\*+ *\\|// *" ;;comment-indent-hook 'promela-comment-indent defun-prompt-regexp promela-defun-prompt-regexp ) ;; Turn on font-lock mode ;; (and promela-font-lock-mode (font-lock-mode)) (font-lock-mode) ;; Finally, run the hooks and be done. (run-hooks 'promela-mode-hook)) ;; ------------------------------------------------------------------------- ;; Interactive functions ;; (defun promela-mode-version () "Print the current version of promela-mode in the minibuffer" (interactive) (message (concat "Promela-Mode: " promela-mode-version))) (defun promela-beginning-of-block () "Move backward to start of containing block. Containing block may be `{', `do' or `if' construct, or comment." (interactive) (goto-char (promela-find-start-of-containing-block-or-comment))) (defun promela-beginning-of-defun (&optional arg) "Move backward to the beginning of a defun. With argument, do it that many times. Negative arg -N means move forward to Nth following beginning of defun. Returns t unless search stops due to beginning or end of buffer. See also 'beginning-of-defun. This is a Promela-mode specific version since default (in xemacs 19.16 and NT-Emacs 20) don't seem to skip comments - they will stop inside them. Also, this makes sure that the beginning of the defun is actually the line which starts the proctype/init/etc., not just the open-brace." (interactive "p") (beginning-of-defun arg) (if (not (looking-at promela-defun-prompt-regexp)) (re-search-backward promela-defun-prompt-regexp nil t)) (if (promela-inside-comment-p) (goto-char (promela-find-start-of-containing-comment)))) (defun promela-indent-command () "Indent the current line as PROMELA code." (interactive) (if (and (not promela-tab-always-indent) (save-excursion (skip-chars-backward " \t") (not (bolp)))) (tab-to-tab-stop) (promela-indent-line))) (defun promela-newline-and-indent () "Promela-mode specific newline-and-indent which expands abbrevs before running a regular newline-and-indent." (interactive) (if abbrev-mode (expand-abbrev)) (newline-and-indent)) (defun promela-indent-newline-indent () "Promela-mode specific newline-and-indent which expands abbrevs and indents the current line before running a regular newline-and-indent." (interactive) (save-excursion (promela-indent-command)) (if abbrev-mode (expand-abbrev)) (newline-and-indent)) (defun promela-insert-and-indent () "Insert the last character typed and re-indent the current line" (interactive) (insert last-command-char) (save-excursion (promela-indent-command))) (defun promela-open-delimiter () "Inserts the open and matching close delimiters, indenting as appropriate." (interactive) (insert last-command-char) (if (and promela-auto-match-delimiter (not (promela-inside-comment-p))) (save-excursion (insert (cdr (assq last-command-char promela-matching-delimiter-alist))) (promela-indent-command)))) (defun promela-close-delimiter () "Inserts and indents a close delimiter." (interactive) (insert last-command-char) (if (not (promela-inside-comment-p)) (save-excursion (promela-indent-command)))) (defun promela-toggle-auto-match-delimiter () "Toggle auto-insertion of parens and other delimiters. See variable `promela-auto-insert-matching-delimiter'" (interactive) (setq promela-auto-match-delimiter (not promela-auto-match-delimiter)) (message (concat "Promela auto-insert matching delimiters " (if promela-auto-match-delimiter "enabled" "disabled")))) ;; ------------------------------------------------------------------------- ;; Compilation/Verification functions ;; all of this is in serious "beta" mode - don't trust it ;-) (setq promela-compile-command "spin " promela-syntax-check-args "-a -v " ) ;;(setq compilation-error-regexp-alist ;; (append compilation-error-regexp-alist ;; '(("spin: +line +\\([0-9]+\\) +\"\\([^\"]+\\)\"" 2 1)))) (defun promela-syntax-check () (interactive) (compile (concat promela-compile-command promela-syntax-check-args (buffer-name)))) ;; ------------------------------------------------------------------------- ;; Indentation support functions (defun promela-indent-around-label () "Indent the current line as PROMELA code, but make sure to consider the label at the beginning of the line." (beginning-of-line) (delete-horizontal-space) ; delete any leading whitespace (if (not (looking-at "\\sw+:\\([ \t]*\\)")) (error "promela-indent-around-label: no label on this line") (goto-char (match-beginning 1)) (let* ((space (length (match-string 1))) (indent (promela-calc-indent)) (wanted (max 0 (- indent (current-column))))) (if (>= space wanted) (delete-region (point) (+ (point) (- space wanted))) (goto-char (+ (point) space)) (indent-to-column indent))))) ;; Note that indentation is based ENTIRELY upon the indentation of the ;; previous line(s), esp. the previous non-blank line and the line ;; starting the current containgng block... (defun promela-indent-line () "Indent the current line as PROMELA code. Return the amount the by which the indentation changed." (beginning-of-line) (if (looking-at "[ \t]*\\sw+:") (promela-indent-around-label) (let ((indent (promela-calc-indent)) beg shift-amt (pos (- (point-max) (point)))) (setq beg (point)) (skip-chars-forward " \t") (setq shift-amt (- indent (current-column))) (if (zerop shift-amt) (if (> (- (point-max) pos) (point)) (goto-char (- (point-max) pos))) (delete-region beg (point)) (indent-to indent) (if (> (- (point-max) pos) (point)) (goto-char (- (point-max) pos)))) shift-amt))) (defun promela-calc-indent () "Return the appropriate indentation for this line as an int." (save-excursion (beginning-of-line) (let* ((orig-point (point)) (state (promela-parse-partial-sexp)) (paren-depth (nth 0 state)) (paren-point (or (nth 1 state) 1)) (paren-char (char-after paren-point))) ;;(what-cursor-position) (cond ;; Indent not-at-all - inside a string ((nth 3 state) (current-indentation)) ;; Indent inside a comment ((nth 4 state) (promela-calc-indent-within-comment)) ;; looking at a pre-processor directive - indent=0 ((looking-at "[ \t]*#\\(define\\|if\\(n?def\\)?\\|else\\|endif\\)") 0) ;; If we're not inside a "true" block (i.e. "{}"), then indent=0 ;; I think this is fair, since no (indentable) code in promela ;; exists outside of a proctype or similar "{ .. }" structure. ((zerop paren-depth) 0) ;; Indent relative to non curly-brace "paren" ;; [ NOTE: I'm saving this, but don't use it any more. ;; Now, we let parens be indented like curly braces ;;((and (>= paren-depth 1) (not (char-equal ?\{ paren-char))) ;; (goto-char paren-point) ;; (1+ (current-column))) ;; ;; Last option: indent relative to contaning block(s) (t (goto-char orig-point) (promela-calc-indent-within-block paren-point)))))) (defun promela-calc-indent-within-block (&optional limit) "Return the appropriate indentation for this line, assume within block. with optional arg, limit search back to `limit'" (save-excursion (let* ((stop (or limit 1)) (block-point (promela-find-start-of-containing-block stop)) (block-type (promela-block-type-after block-point)) (indent-point (point)) (indent-type (promela-block-type-after indent-point))) (if (not block-type) 0 ;;(message "paren: %d (%d); block: %s (%d); indent: %s (%d); stop: %d" ;; paren-depth paren-point block-type block-point ;; indent-type indent-point stop) (goto-char block-point) (cond ;; Indent (options) inside "if" or "do" ((memq block-type '(selection iteration)) (if (re-search-forward "\\(do\\|if\\)[ \t]*::" indent-point t) (- (current-column) 2) (+ (current-column) promela-selection-indent))) ;; indent (generic code) inside "::" option ((eq 'option block-type) (if (and (not indent-type) (re-search-forward "::.*->[ \t]*\\sw" (save-excursion (end-of-line) (point)) t)) (1- (current-column)) (+ (current-column) promela-selection-option-indent)) ) ;; indent code inside "{" ((eq 'block block-type) (cond ;; if we are indenting the end of a block, ;; use indentation of start-of-block ((equal 'block-end indent-type) (current-indentation)) ;; if the start of the code inside the block is not at eol ;; then indent to the same column as the block start +some ;; [ but ignore comments after "{" ] ((and (not (promela-effective-eolp (1+ (point)))) (not (looking-at "{[ \t]*/\\*"))) (forward-char) ; skip block-start (skip-chars-forward "{ \t") ; skip whitespace, if any (current-column)) ;; anything else we indent +promela-block-indent from ;; the indentation of the start of block (where we are now) (t (+ (current-indentation) promela-block-indent)))) ;; dunno what kind of block this is - sound an error (t (error "promela-calc-indent-within-block: unknown block type: %s" block-type) (current-indentation))))))) (defun promela-calc-indent-within-comment () "Return the indentation amount for line, assuming that the current line is to be regarded as part of a block comment." (save-excursion (beginning-of-line) (skip-chars-forward " \t") (let ((indenting-end-of-comment (looking-at "\\*/")) (indenting-blank-line (eolp))) ;; if line is NOT blank and next char is NOT a "*' (if (not (or indenting-blank-line (= (following-char) ?\*))) ;; leave indent alone (current-column) ;; otherwise look back for _PREVIOUS_ possible nested comment start (let ((comment-start (save-excursion (re-search-backward comment-start-skip)))) ;; and see if there is an appropriate middle-comment "*" (if (re-search-backward "^[ \t]+\\*" comment-start t) (current-indentation) ;; guess not, so indent relative to comment start (goto-char comment-start) (if indenting-end-of-comment (current-column) (1+ (current-column))))))))) ;; ------------------------------------------------------------------------- ;; Misc other support functions (defun promela-parse-partial-sexp (&optional start limit) "Return the partial parse state of current defun or from optional start to end limit" (save-excursion (let ((end (or limit (point)))) (if start (goto-char start) (promela-beginning-of-defun)) (parse-partial-sexp (point) end)))) ;;(defun promela-at-end-of-block-p () ;; "Return t if cursor is at the end of a promela block" ;; (save-excursion ;; (let ((eol (progn (end-of-line) (point)))) ;; (beginning-of-line) ;; (skip-chars-forward " \t") ;; ;;(re-search-forward "\\(}\\|\\b\\(od\\|fi\\)\\b\\)" eol t)))) ;; (looking-at "[ \t]*\\(od\\|fi\\)\\b")))) (defun promela-inside-comment-p () "Check if the point is inside a comment block." (save-excursion (let ((origpoint (point)) state) (goto-char 1) (while (> origpoint (point)) (setq state (parse-partial-sexp (point) origpoint 0))) (nth 4 state)))) (defun promela-inside-comment-or-string-p () "Check if the point is inside a comment or a string." (save-excursion (let ((origpoint (point)) state) (goto-char 1) (while (> origpoint (point)) (setq state (parse-partial-sexp (point) origpoint 0))) (or (nth 3 state) (nth 4 state))))) (defun promela-effective-eolp (&optional point) "Check if we are at the effective end-of-line, ignoring whitespace" (save-excursion (if point (goto-char point)) (skip-chars-forward " \t") (eolp))) (defun promela-check-expansion () "If abbrev was made within a comment or a string, de-abbrev!" (if promela-inside-comment-or-string-p (unexpand-abbrev))) (defun promela-block-type-after (&optional point) "Return the type of block after current point or parameter as a symbol. Return one of 'iteration `do', 'selection `if', 'option `::', 'block `{' or `}' or nil if none of the above match." (save-excursion (goto-char (or point (point))) (skip-chars-forward " \t") (cond ((looking-at "do\\b") 'iteration) ;;((looking-at "od\\b") 'iteration-end) ((looking-at "if\\b") 'selection) ;;((looking-at "fi\\b") 'selection-end) ((looking-at "::") 'option) ((looking-at "[{(]") 'block) ((looking-at "[})]") 'block-end) (t nil)))) (defun promela-find-start-of-containing-comment (&optional limit) "Return the start point of the containing comment block. Stop at `limit' or beginning of buffer." (let ((stop (or limit 1))) (save-excursion (while (and (>= (point) stop) (nth 4 (promela-parse-partial-sexp))) (re-search-backward comment-start-skip stop t)) (point)))) (defun promela-find-start-of-containing-block (&optional limit) "Return the start point of the containing `do', `if', `::' or `{' block or containing comment. Stop at `limit' or beginning of buffer." (save-excursion (skip-chars-forward " \t") (let* ((type (promela-block-type-after)) (stop (or limit (save-excursion (promela-beginning-of-defun) (point)))) (state (promela-parse-partial-sexp stop)) (level (if (looking-at "\\(od\\|fi\\)\\b") 2 (if (zerop (nth 0 state)) 0 1)))) ;;(message "find-start-of-containing-block: type: %s; level %d; stop %d" ;; type level stop) (while (and (> (point) stop) (not (zerop level))) (re-search-backward "\\({\\|}\\|::\\|\\b\\(do\\|od\\|if\\|fi\\)\\b\\)" stop 'move) ;;(message "looking from %d back-to %d" (point) stop) (setq state (promela-parse-partial-sexp stop)) (setq level (+ level (cond ((or (nth 3 state) (nth 4 state)) 0) ((and (= 1 level) (looking-at "::") (not (equal type 'option))) -1) ((looking-at "\\({\\|\\(do\\|if\\)\\b\\)") -1) ((looking-at "\\(}\\|\\(od\\|fi\\)\\b\\)") +1) (t 0))))) (point)))) (defun promela-find-start-of-containing-block-or-comment (&optional limit) "Return the start point of the containing comment or the start of the containing `do', `if', `::' or `{' block. Stop at limit or beginning of buffer." (if (promela-inside-comment-p) (promela-find-start-of-containing-comment limit) (promela-find-start-of-containing-block limit))) ;; ------------------------------------------------------------------------- ;; Debugging/testing ;; (defun promela-mode-toggle-debug () ;; (interactive) ;; (make-local-variable 'debug-on-error) ;; (setq debug-on-error (not debug-on-error))) ;;(defun promela-mode-revert-buffer () ;; (interactive) ;; (revert-buffer t t)) ;; ------------------------------------------------------------------------- ;;###autoload (provide 'promela-mode) ;;---------------------------------------------------------------------- ;; Change History: ;; ;; $Log: promela-mode.el,v $ ;; Revision 1.11 2001/07/09 18:36:45 engstrom ;; - added comments on use of font-lock-maximum-decoration ;; - moved basic preprocess directive fontification to "level 2" ;; ;; Revision 1.10 2001/05/22 16:29:59 engstrom ;; - fixed error introduced in fontification levels stuff (xemacs only) ;; ;; Revision 1.9 2001/05/22 16:21:29 engstrom ;; - commented out the compilation / syntax check stuff for now ;; ;; Revision 1.8 2001/05/22 16:18:49 engstrom ;; - Munched history in preparation for first non-Honeywell release ;; - Added "levels" of fontification to be controlled by the std. variable: ;; 'font-lock-maximum-decoration' ;; ;; Revision 1.7 2001/04/20 01:41:46 engstrom ;; Revision 1.6 2001/04/06 23:57:18 engstrom ;; Revision 1.5 2001/04/04 20:04:15 engstrom ;; Revision 1.4 2001/03/15 02:22:18 engstrom ;; Revision 1.3 2001/03/09 19:39:51 engstrom ;; Revision 1.2 2001/03/01 18:07:47 engstrom ;; Revision 1.1 2001/02/01 xx:xx:xx engstrom ;; migrated to CVS versioning... ;; Pre-CVS-History: ;; 99-10-04 V0.4 EDE Fixed bug in end-of-block indentation ;; Simplified indentation code significantly ;; 99-09-2x V0.3 EDE Hacked on indentation more while at FM'99 ;; 99-09-16 V0.2 EDE Hacked, hacked, hacked on indentation ;; 99-04-01 V0.1 EDE Introduced (less-than) half-baked indentation ;; 98-11-05 V0.0 EDE Created - much code stolen from rexx-mode.el ;; Mostly just a fontification mode - ;; (indentation is HARD ;-) ;; ;; EOF promela-mode.el