Path: utzoo!utgpu!jarvis.csri.toronto.edu!rutgers!usc!cs.utexas.edu!uunet!watmath!watmsg!gjditchfield From: gjditchfield@watmsg.waterloo.edu (Glen Ditchfield) Newsgroups: gnu.emacs Subject: Re: Looking for Pascal mode (part 1 of 2) Keywords: Pascal, Gnu-Emacs Message-ID: <27519@watmath.waterloo.edu> Date: 5 Jul 89 15:39:18 GMT References: <349@node13.mecazh.UUCP> Sender: daemon@watmath.waterloo.edu Reply-To: gjditchfield@watmsg.waterloo.edu (Glen Ditchfield) Organization: U. of Waterloo, Ontario Lines: 681 This Pascal mode differs from other Pascal modes (and Modula-2 and Ada modes) that I've heard about in that template insertion is handled by the abbreviation expansion mechanism instead of control key sequences. This means that - customizing templates to your indentation style is easier. - template slots can be filled in any order you find convenient. - template abbreviations are (usually) the corresponding Pascal keyword, and hence are easy to remember. Part 2 will be a texinfo file describing Pascal mode. Glen Ditchfield gjditchfield@violet.uwaterloo.ca Office: DC 2517 Dept. of Computer Science, U of Waterloo, Waterloo, Ontario, Canada, N2L 3G1 "... and the rest, we will spend foolishly!" -- _Svengali_ ------------------------------------------------------------------------------- ;; Pascal code editing support for Gnu Emacs ;; Copyright (c) 1989 Glen J. Ditchfield ;; Last known address: gjditchfield@violet.uwaterloo.ca ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY. Neither the author nor any 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 Emacs General Public ;; License for full details. ;; Everyone is granted permission to copy, modify and redistribute ;; this file, but only under the conditions described in the ;; GNU Emacs 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. ;; Please mail bug reports and feature requests (and bug fixes!) to the ;; above address. I would be delighted to see any utility functions you ;; write for this mode. If I add them to future versions, you will get ;; credit for them. ;; This package was inspired by published descriptions of DEC's Language ;; Sensitive Editor and the Cedar editor, (neither of which have I ever ;; seen running, mind you) and by a Gosling Emacs package written by Erik ;; Fortune and distributed by Scott Menter. ;; 87-06-29: added `\<' (beginning of word) and `/>' (end of word) to ;; keywords used in pascal-in-parm, to prevent false matches. ;; 87-07-06: if the first thing in a file was a routine, and a nested routine ;; was created inside it, pascal-in-parm claimed that the nested routine ;; was in a parameter list. Fixed. ;; 87-07-21: do-pascal-fill replaces do-auto-fill as the fill hook. It keeps ;; fill mode from breaking lines inside strings. ;; High voltage functions replaced by pascal-auto-newline. ;; 87-08-05: Pascal-forward-sexp, pascal-backward-sexp added and bound to ;; keys. pascal-indent-line modified to adjust indentation after `end' ;; and `until' statements. ;; 87-08-13: Hooks no longer know what abbreviation invoked them; they just ;; use last-abbrev-text. `record' template added. ;; 87-09-04: pascal-beginnify-region stirred in. ;; 89-06-05 Elision support moved out to pascal-elide. Pascal-edit-string ;; removed. (defconst pascal-comment-start "{ " "*Preferred comment opener") (defconst pascal-comment-end "\t}" "*Preferred comment closer") (defconst pascal-indent-level 3 "*Indentation depth") (defconst pascal-auto-newline nil "*If non-nil, `;' at the end of a line causes a newline to be inserted") (defvar pascal-mode-map (make-sparse-keymap) "Keymap used in Pascal mode.") (define-key pascal-mode-map "\C-j" 'newline-check-and-indent) (define-key pascal-mode-map "\C-m" 'newline) (define-key pascal-mode-map "\177" 'backward-delete-char-untabify) (define-key pascal-mode-map ";" 'electric-pascal-semi) (define-key pascal-mode-map "\t" 'pascal-indent-line) (define-key pascal-mode-map "\e\t" 'pascal-outdent-line) (define-key pascal-mode-map "\C-c\C-b" 'pascal-beginnify-region) (define-key pascal-mode-map "\e\^f" 'pascal-forward-sexp) (define-key pascal-mode-map "\e\^b" 'pascal-backward-sexp) (define-key pascal-mode-map "\e\C-e" 'end-of-pascal-block) (defun pascal-mode () "Major mode for editing Pascal programs. Indentation is done by abbreviation expansion; see M-x list-abbrevs for the current abbreviations. Delete converts tabs to spaces as it moves back. \\{pascal-mode-map} " (interactive) (use-local-map pascal-mode-map) (setq major-mode 'pascal-mode) (setq mode-name "Pascal") (abbrev-mode 1) (if (not pascal-mode-abbrev-table) (progn (if (file-exists-p abbrev-file-name) (quietly-read-abbrev-file "")) (if (not pascal-mode-abbrev-table) (define-abbrev-table 'pascal-mode-abbrev-table ' (("if" "" pascal-if-hook 0) ("ie" "" pascal-if-else-hook 0) ("else" "" pascal-else-hook 0) ("ei" "" pascal-else-if-hook 0) ("for" "" pascal-for-hook 0) ("while" "" pascal-while-hook 0) ("with" "" pascal-with-hook 0) ("case" "" pascal-case-hook 0) ("var" "" pascal-var-hook 0) ("const" "" pascal-const-hook 0) ("type" "" pascal-type-hook 0) ("begin" "" pascal-begin-hook 0) ("int" "integer" nil 0) ("fun" "" pascal-function-hook 0) ("repeat" "" pascal-repeat-hook 0) ("prog" "" pascal-program-hook 0) ("proc" "" pascal-procedure-hook 0)))))) (setq local-abbrev-table pascal-mode-abbrev-table) (if (not pascal-syntax-table) (progn (setq pascal-syntax-table (make-syntax-table)) (set-syntax-table pascal-syntax-table) (modify-syntax-entry ?\( "()1") (modify-syntax-entry ?\) ")(4") (modify-syntax-entry ?\* ". 23") (modify-syntax-entry ?\{ "(}") ; Making {} paired delimiters (modify-syntax-entry ?\} "){") ; makes forward-sexp skip over them. (modify-syntax-entry ?\[ "(]") (modify-syntax-entry ?\] ")[") (modify-syntax-entry ?\' "\"") (modify-syntax-entry ?/ ".") (modify-syntax-entry ?+ ".") (modify-syntax-entry ?- ".") (modify-syntax-entry ?= ".") (modify-syntax-entry ?< ".") (modify-syntax-entry ?> ".") ) (set-syntax-table pascal-syntax-table)) (make-local-variable 'indent-line-function) (setq indent-line-function 'pascal-indent-line) (make-local-variable 'comment-start) (setq comment-start pascal-comment-start) (make-local-variable 'comment-end) (setq comment-end pascal-comment-end) (make-local-variable 'comment-start-skip) (setq comment-start-skip "{ *\\|(\\*+ *") (make-local-variable 'paragraph-separate) (setq paragraph-separate "^[ \t]*$\\|^^L") (make-local-variable 'paragraph-start) (setq paragraph-start "^[ \t]*$\\|^^L") (make-local-variable 'parse-sexp-ignore-comments) (setq parse-sexp-ignore-comments nil) ; ... because comments are sexps (run-hooks 'pascal-mode-hook) (if (eq auto-fill-hook 'do-auto-fill) (setq auto-fill-hook 'do-pascal-fill)) ) (defvar pascal-syntax-table nil) (defvar pascal-mode-abbrev-table nil "Abbrev table in use in Pascal-mode buffers.") ;; Search backwards for a line with 0 < indent < limit. Return it's indentation ;; (or 0 if no such line exists). (defun pascal-old-indent (limit) (save-excursion (beginning-of-line 0) ;start of previous line (while (and (not (bobp)) (or (zerop (current-indentation)) (<= limit (current-indentation)))) (beginning-of-line 0)) (current-indentation)) ) (defun electric-pascal-semi () "Insert ; and perform \\[newline-check-and-indent] if pascal-auto-newline." (interactive) (insert-string ";") (if (and pascal-auto-newline (eolp)) (newline-check-and-indent))) (defun pascal-indent-line () "Indent current line as Pascal code." (interactive) (let (indent) ;1st guess at proper indentation (save-excursion (skip-chars-backward " \t\n\^l\^m") (beginning-of-line 1) (if (looking-at "[ \t]*\\(end\\|until\\)\\>") (progn ;If the previous statement ended a compound statement, use the ;indentation of the statement's starting line. (re-search-forward "end\\|until") (pascal-backward-sexp) (setq indent (current-indentation)) ) ;Otherwise, use the previous non-zero indentation. ;If the previous line does not appear to be a complete statement, ;comment, or #include, increase the indentation. (forward-line 1) (setq indent (pascal-old-indent 9999)) (beginning-of-line) (skip-chars-backward " \t\n\^l\^m") (backward-char 2) (if (not (looking-at ".;\\|.}\\|\*)")) (progn (beginning-of-line) (if (/= ?# (char-after (point))) (setq indent (+ indent pascal-indent-level)) ) ) ) ) ) (if (looking-at "^$") ;empty line? (indent-to indent) (let ((pos (- (point-max) (point))) ;distance from end of buffer beginning) (beginning-of-line) ;If point was inside a line, the (setq beginning (point)) ;text after it must be shifted right. (skip-chars-forward " \t") (delete-region beginning (point)) (indent-to indent) (goto-char (- (point-max) pos) ) ) ) ) ) (defun pascal-outdent-line () "Decrease the indentation of the current line. The indentation used is that of the last line above the current one with an indentation less than the current line's." (interactive) (let ((indent (pascal-old-indent (current-indentation))) (pos (- (point-max) (point))) beginning) (beginning-of-line) (setq beginning (point)) (skip-chars-forward " \t") (delete-region beginning (point)) (indent-to indent) (goto-char (- (point-max) pos)) ) ) ;; Insert the given string into the buffer at point, and add the current ;; indentation of the line point was on to every new line in the inserted ;; area. When finished, leave point at the position marked by ! in the ;; inserted string if there is one; otherwise, leave point at the end of ;; the inserted area. (defun insert-indented-string (str) (let ((start-point (point)) (start-indent (current-indentation)) end-point) (insert-string str) (setq end-point (point)) (beginning-of-line) ;if the end of the inserted string is not on the same line as the start, ;end-point must be adjusted because the line will be indented. (if (< start-point (point)) (setq end-point (+ start-indent end-point))) ;add the original indentation to every new line in the inserted string. (while (< start-point (point)) (indent-to start-indent) (previous-line 1) (beginning-of-line) ) (if (search-forward "!" end-point "") (delete-backward-char 1)) ) ) (defun re-search-back-skip-str (pattern) (let ((qpattern (concat "'\\|" pattern)) search-result) (setq search-result (re-search-backward qpattern 1 t)) (while (and search-result (looking-at "'")) (search-backward "'") (setq search-result (re-search-backward qpattern 1 t)) ) search-result) ) ;; Point can be in a comment (state 1), a string (state 2), or program text ;; (state 4). Strings can't span line breaks, and comments shouldn't if ;; comment-multi-line is not t, so the beginning of a line can have state set ;; {4} or {1,4}. The function returns the state it thinks point is in; it ;; may not be accurate. (defun pascal-point-state () (let ((state-set (if comment-multi-line 5 4)) (old-point (point)) char-found ) (beginning-of-line) ;look for string and comment delimiters (while (re-search-forward "'\\|(\\*\\|\\*)\\|{\\|}" old-point t) (setq char-found (char-after (- (point) 1))) (cond ((= char-found ?\' ) ;toggle between program and string states. (setq state-set (elt '(t 1 4 5 2 3 6) state-set))) ((or (= char-found ?\) ) (= char-found ?\} )) ;change comment states to program states (setq state-set (elt '(t 4 2 6 t 4 2) state-set))) (t ;found comment start ;change program states to comment states (setq state-set (elt '(t 1 2 3 1 1 3) state-set))) ) ) ; If state-set contains more than one state, look back for comment ;delimiters to decide whether point is in a multi-line comment. ;This doesn't handle nested comments. (if (memq state-set '(3 5 6)) (progn (beginning-of-line) (re-search-back-skip-str "*)\\|(\\*\\|{\\|}") (if (looking-at "{\\|(\\*") (setq state-set (elt '(t t t 1 t 1 4) state-set)) (setq state-set (elt '(t t t 2 t 4 2) state-set))) ) ) (goto-char old-point) state-set ) ) (defun newline-check-and-indent () "Notifies typist if the end of line is inside a character string, or if it is inside a comment and comment-multi-line is nil; then it calls newline-and-indent." (interactive "*") (let ((state (pascal-point-state))) (cond ((and (= state 1) (not comment-multi-line)) (beep) (message "Closing comment delimiter needed.")) ((= state 2) (beep) (message "Closing quote needed.")) ) ) (newline-and-indent)) ;; A fill hook that does not break strings across line boundaries. If the ;; fill column is in a string, and there are no spaces on the line before ;; the string other than the indentation, the line should break at the end ;; of string, but doesn't. This seems to be a bug with do-auto-fill. (defun do-pascal-fill () (if (/= (pascal-point-state) 2) (let ((curr-pt (point))) (move-to-column fill-column) (if (= (pascal-point-state) 2) (let ((old-fill-column fill-column)) (search-backward "'") (setq fill-column (current-column)) (goto-char curr-pt) (if (not (do-auto-fill)) (newline-and-indent)) (setq fill-column old-fill-column)) (goto-char curr-pt) (do-auto-fill)) ) ) ) ;; Skip backwards over whitespace, including comments. ;; Point is left to the left of the first black character. (defun skip-backward-whitespace () (skip-chars-backward " \t\n\^l\^m") (backward-char 1) (while (= (pascal-point-state) 1) (re-search-backward "(\\*\\|{") (skip-chars-backward " \t\n\^l\^m") (backward-char 1)) ) (defun pascal-beginnify-region (start end) "Wrap a begin statement around the region." (interactive "r") (copy-to-register 1 start end t) ; delete contents of region (insert-indented-string pascal-begin-str) (insert-string " ") ; replace ! with a space and the region (setq end (point)) (insert-indented-string (get-register 1)) (goto-char end) (skip-chars-forward " \t\n\^l\^m") (delete-region end (point)) (goto-char start) (if (looking-at "\\B") ; if point is at the end of a token, (insert-string " ")) ; put a space between it and `begin'. ) ;; move to the end of the next sexp (comment, (-) pair, [-] pair, begin-end, ;; case-end, repeat-until e, string, or record-end). (defun pascal-forward-sexp () "Move forward across one balanced expression, record type, begin statement, case statement or repeat statement." (interactive) (let ((search-pat "\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|[{(']") open-list open-end) (skip-chars-forward " \t\n\^l\^m") (cond ((looking-at "\\\\|\\\\|\\\\|\\") (setq open-list (list (if (looking-at "rec") ?R (char-after (point))))) (forward-word 1) (while (and open-list (re-search-forward search-pat nil t)) (setq open-end (match-end 0)) (goto-char (match-beginning 0)) (cond ((looking-at "[({']") ; string, or possible comment (forward-sexp 1)) ((looking-at "rec") ; record type (setq open-list (cons ?R open-list)) (goto-char open-end)) ((= (char-after (point)) ?c) ; case ;Ignore `case' in (variant) records. (if (/= (car open-list) ?R) (setq open-list (cons (char-after (point)) open-list))) (goto-char open-end)) ((looking-at "[br]") ; begin or repeat (setq open-list (cons (char-after (point)) open-list)) (goto-char open-end)) ((= (char-after (point)) ?u) ; until (if (= (car open-list) ?r) (setq open-list (cdr open-list)) (error "unmatched `begin' or `case'?")) (goto-char open-end)) (t ; end (if (or (= (car open-list) ?b) (= (car open-list) ?c) (= (car open-list) ?R)) (setq open-list (cdr open-list)) (error "unmatched `repeat'?")) (goto-char open-end)) ) ) (if open-list (error "unbalanced parenthesis or statements")) ) (t (forward-sexp 1) ) ) ) ) ;; move back to the start of the previous sexp (comment, (-) pair, [-] pair, ;; string, begin-end, repeat-until, case-end, or record-end). It doesn't ;; handle variant records correctly, but the error is conservative. (defun pascal-backward-sexp () "Move backward across one balanced expression, record type, begin statement, case statement, or repeat statement." (interactive) (let ((search-pat "\\\\|\\\\|\\\\|\\\\|\\\\|\\\\|[})']") (open-list nil)) (backward-sexp 1) (if (looking-at "end\\>\\|until\\>") ;Point was at the end of a statement. (progn (setq open-list (cons (char-after (point)) open-list)) (while (and open-list (re-search-backward search-pat nil t)) (cond ((looking-at "[eu]") ; end or until (setq open-list (cons (char-after (point)) open-list))) ((looking-at "[)}']") ; string, or possible comment (forward-char 1) (backward-sexp 1)) ((looking-at "rep") ; repeat (if (= (car open-list) ?u) (setq open-list (cdr open-list)) (error "unmatched `until'?"))) (t ; begin, record or case (if (= (car open-list) ?e) (setq open-list (cdr open-list)) (error "unmatched `end'?"))) ) ) (if open-list (error "unbalanced parenthesis or statements")) ) ) ) ) ;; Guess whether point is inside a routine's formal parameter list or not. ;; This is used to decide whether to expand the abbreviation 'var' as a var ;; parameter or a variable declaration section. This is tricky; consider ;; procedure foo( var p1:t1; procedure bar( var p2:t2); var p3:t3); ;; var ;; v1:t4; ;; I consider point to be in a parameter list if it is inside a () pair and if ;; the pair is preceded by 'procedure' or 'function' and a word. Various ;; other symbols are taken as signs that point could not have been in a ;; parameter list. (defun pascal-in-parm () "Return nil if point is not in a formal parameter list." (let ((escapes "(\\|'\\|\\\\|\\\\|\\") (pat ")\\|}\\|(\\|'\\|\\\\|\\\\|\\")) (save-excursion ;look back for an unmatched "(", a keyword that can't be in a ;parameter list, or the beginning of the buffer. (while (and (re-search-backward pat 1 1) (not (looking-at escapes))) (forward-char 1) (pascal-backward-sexp) ) (cond ((looking-at "(") (backward-word 1) (condition-case nil ;in case point is at start of buffer (skip-backward-whitespace) (error nil)) (backward-word 1) (looking-at "\\\\|\\"))) ) ) ) ;; If the previous statement ends with a `;', remove that `;'. Intended for ;; use when inserting 'else' clauses. (defun no-prev-semi () (save-excursion (skip-backward-whitespace) (if (looking-at ";") (delete-char 1))) ) ;;; Structure templates. (defvar pascal-if-else-str "if! then begin\n \n end\nelse begin\n \n end;" "*If-then-else statement template; abbreviated 'ie'.") (defun pascal-if-else-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-if-else-str) (insert-string last-abbrev-text)) ) (defvar pascal-if-str "if! then begin\n \n end;" "*If-then statement template; abbreviated 'if'.") (defun pascal-if-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-if-str) (insert-string last-abbrev-text)) ) (defvar pascal-else-str "else begin\n !\n end;" "*else clause template; abbreviated 'else'.") (defun pascal-else-hook() (if (= 4 (pascal-point-state)) (progn (no-prev-semi) (insert-indented-string pascal-else-str)) (insert-string last-abbrev-text)) ) (defvar pascal-else-if-str "else if! then begin\n \n end;" "*else-if clause template; abbreviated 'ei.'") (defun pascal-else-if-hook () (if (= 4 (pascal-point-state)) (progn (no-prev-semi) (insert-indented-string pascal-else-if-str)) (insert-string last-abbrev-text)) ) (defvar pascal-while-str "while! do begin\n \n end;" "*while statement template; abbreviated 'while'.") (defun pascal-while-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-while-str) (insert-string last-abbrev-text)) ) (defvar pascal-for-str "for! := to do begin\n \n end;" "*for statement template; abbreviated 'for'.") (defun pascal-for-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-for-str) (insert-string last-abbrev-text)) ) (defvar pascal-repeat-str "repeat\n !\nuntil ;" "*repeat statement template; abbreviated 'repeat'.") (defun pascal-repeat-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-repeat-str) (insert-string last-abbrev-text)) ) (defvar pascal-with-str "with! do begin\n \n end;" "*with statement template; abbreviated 'with'.") (defun pascal-with-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-with-str) (insert-string last-abbrev-text)) ) (defvar pascal-case-str "case! of\n \n end;" "*case statement template; abbreviated 'case'.") (defun pascal-case-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-case-str) (insert-string last-abbrev-text)) ) (defvar pascal-begin-str "begin\n !\n end;" "*begin statement template; abbreviated 'begin'.") (defun pascal-begin-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-begin-str) (insert-string last-abbrev-text)) ) (defvar pascal-program-str "program!( input, output);\n \n begin\n \n end." "*Program template; abbreviated 'prog'.") (defun pascal-program-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-program-str) (insert-string last-abbrev-text)) ) (defvar pascal-procedure-str "procedure!();\n \n begin\n \n end;" "*Procedure template; abbreviated 'proc'.") (defvar pascal-procedure-parm-str "procedure!()" "*Procedure parameter template; abbreviated 'proc'.") (defun pascal-procedure-hook () (if (= 4 (pascal-point-state)) (if (pascal-in-parm) (insert-indented-string pascal-procedure-parm-str) (insert-indented-string pascal-procedure-str)) (insert-string last-abbrev-text)) ) (defvar pascal-function-str "function!():;\n \n begin\n \n end;" "*Function template; abbreviated 'fun'.") (defvar pascal-function-parm-str "function!():" "*Function parameter template; abbreviated 'fun'.") (defun pascal-function-hook () (if (= 4 (pascal-point-state)) (if (pascal-in-parm) (insert-indented-string pascal-function-parm-str) (insert-indented-string pascal-function-str)) (insert-string last-abbrev-text)) ) (defvar pascal-const-str "const\n " "*const declaration section template; abbreviated 'const'.") (defun pascal-const-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-const-str) (insert-string last-abbrev-text)) ) (defvar pascal-type-str "type\n " "*type declaration section template; abbreviated 'type'.") (defun pascal-type-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-type-str) (insert-string last-abbrev-text)) ) (defvar pascal-var-str "var\n " "*Variable declaration section template; abbreviated 'var'.") (defvar pascal-var-parm-str "var" "*Var parameter template; abbreviated 'var'.") (defun pascal-var-hook () (if (= 4 (pascal-point-state)) (if (pascal-in-parm) (insert-indented-string pascal-var-parm-str) (insert-indented-string pascal-var-str)) (insert-string last-abbrev-text)) ) (defvar pascal-record-str "record\n !\n end;" "*Record declaration template; abbreviated 'record'.") (defun pascal-record-hook () (if (= 4 (pascal-point-state)) (insert-indented-string pascal-record-str) (insert-string last-abbrev-text)) )