Copyright up-date.
[bpt/emacs.git] / lisp / progmodes / cmacexp.el
CommitLineData
4af1f509 1;;; cmacexp.el --- expand C macros in a region
c0274f38 2
9596811a 3;; Copyright (C) 1992, 1994, 1996 Free Software Foundation, Inc.
9750e079 4
4af1f509 5;; Author: Francesco Potorti` <pot@cnuce.cnr.it>
4af1f509 6;; Adapted-By: ESR
e5167999
ER
7;; Keywords: c
8
c17d15d0
JB
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
e5167999 13;; the Free Software Foundation; either version 2, or (at your option)
c17d15d0
JB
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
b578f267
EN
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24;; Boston, MA 02111-1307, USA.
c17d15d0 25
89fb82d6 26;; USAGE =============================================================
e41b2db1 27
074521be
RS
28;; In C mode C-C C-e is bound to c-macro-expand. The result of the
29;; expansion is put in a separate buffer. A user option allows the
30;; window displaying the buffer to be optimally sized.
e41b2db1 31;;
4af1f509 32;; When called with a C-u prefix, c-macro-expand replaces the selected
89fb82d6 33;; region with the expansion. Both the preprocessor name and the
074521be
RS
34;; initial flag can be set by the user. If c-macro-prompt-flag is set
35;; to a non-nil value the user is offered to change the options to the
36;; preprocessor each time c-macro-expand is invoked. Preprocessor
37;; arguments default to the last ones entered. If c-macro-prompt-flag
38;; is nil, one must use M-x set-variable to set a different value for
39;; c-macro-cppflags.
4af1f509
RS
40
41;; A c-macro-expansion function is provided for non-interactive use.
4af1f509
RS
42
43;; INSTALLATION ======================================================
44
074521be 45;; Put the following in your ~/.emacs file.
4af1f509 46
4af1f509 47;; If you want the *Macroexpansion* window to be not higher than
4ff3fcc7 48;; necessary:
074521be 49;;(setq c-macro-shrink-window-flag t)
e41b2db1 50;;
4af1f509
RS
51;; If you use a preprocessor other than /lib/cpp (be careful to set a
52;; -C option or equivalent in order to make the preprocessor not to
53;; strip the comments):
54;;(setq c-macro-preprocessor "gpp -C")
55;;
89fb82d6
RS
56;; If you often use a particular set of flags:
57;;(setq c-macro-cppflags "-I /usr/include/local -DDEBUG"
4af1f509 58;;
89fb82d6 59;; If you want the "Preprocessor arguments: " prompt:
074521be 60;;(setq c-macro-prompt-flag t)
4af1f509
RS
61
62;; BUG REPORTS =======================================================
63
64;; Please report bugs, suggestions, complaints and so on to
65;; pot@cnuce.cnr.it (Francesco Potorti`).
66
67;; IMPROVEMENTS OVER emacs 18.xx cmacexp.el ==========================
68
3332766c 69;; - A lot of user and programmer visible changes. See above.
4af1f509
RS
70;; - #line directives are inserted, so __LINE__ and __FILE__ are
71;; correctly expanded. Works even with START inside a string, a
72;; comment or a region #ifdef'd away by cpp. cpp is invoked with -C,
73;; making comments visible in the expansion.
74;; - All work is done in core memory, no need for temporary files.
4af1f509
RS
75
76;; ACKNOWLEDGEMENTS ==================================================
77
78;; A lot of thanks to Don Maszle who did a great work of testing, bug
074521be
RS
79;; reporting and suggestion of new features. This work has been
80;; partially inspired by Don Maszle and Jonathan Segal's.
4af1f509 81
89fb82d6 82;; BUGS ==============================================================
4af1f509
RS
83
84;; If the start point of the region is inside a macro definition the
85;; macro expansion is often inaccurate.
e41b2db1 86
89fb82d6 87
a9132c1f
RS
88(require 'cc-mode)
89
89fb82d6 90(provide 'cmacexp)
c17d15d0 91
00ed33e7
RS
92(defgroup c-macro nil
93 "Expand C macros in a region."
94 :group 'c)
4af1f509 95
4af1f509 96
00ed33e7
RS
97(defcustom c-macro-shrink-window-flag nil
98 "*Non-nil means shrink the *Macroexpansion* window to fit its contents."
99 :type 'boolean
100 :group 'c-macro)
101
102(defcustom c-macro-prompt-flag nil
103 "*Non-nil makes `c-macro-expand' prompt for preprocessor arguments."
104 :type 'boolean
105 :group 'c-macro)
106
107(defcustom c-macro-preprocessor
68dff3ca 108 ;; Cannot rely on standard directory on MS-DOS to find CPP.
b4ec679c
RS
109 (cond ((eq system-type 'ms-dos) "cpp -C")
110 ;; Solaris has it in an unusual place.
111 ((and (string-match "^[^-]*-[^-]*-\\(solaris\\|sunos5\\)"
112 system-configuration)
113 (file-exists-p "/opt/SUNWspro/SC3.0.1/bin/acomp"))
114 "/opt/SUNWspro/SC3.0.1/bin/acomp -C -E")
4ff3fcc7 115 ((file-exists-p "/usr/ccs/lib/cpp") "/usr/ccs/lib/cpp -C")
b4ec679c 116 (t "/lib/cpp -C"))
9c32788e 117 "The preprocessor used by the cmacexp package.
4af1f509 118
440c10f8 119If you change this, be sure to preserve the `-C' (don't strip comments)
00ed33e7
RS
120option, or to set an equivalent one."
121 :type 'string
122 :group 'c-macro)
123
124(defcustom c-macro-cppflags ""
125 "*Preprocessor flags used by `c-macro-expand'."
126 :type 'string
127 :group 'c-macro)
4af1f509
RS
128
129(defconst c-macro-buffer-name "*Macroexpansion*")
130
d322f009 131;;;###autoload
9c32788e
RS
132(defun c-macro-expand (start end subst)
133 "Expand C macros in the region, using the C preprocessor.
134Normally display output in temp buffer, but
135prefix arg means replace the region with it.
136
137`c-macro-preprocessor' specifies the preprocessor to use.
138Prompt for arguments to the preprocessor \(e.g. `-DDEBUG -I ./include')
139if the user option `c-macro-prompt-flag' is non-nil.
4af1f509 140
89fb82d6 141Noninteractive args are START, END, SUBST.
9c32788e 142For use inside Lisp programs, see also `c-macro-expansion'."
4af1f509
RS
143
144 (interactive "r\nP")
89fb82d6
RS
145 (let ((inbuf (current-buffer))
146 (displaybuf (if subst
147 (get-buffer c-macro-buffer-name)
148 (get-buffer-create c-macro-buffer-name)))
074521be 149 (expansion ""))
4af1f509 150 ;; Build the command string.
9c32788e 151 (if c-macro-prompt-flag
89fb82d6 152 (setq c-macro-cppflags
4af1f509 153 (read-string "Preprocessor arguments: "
89fb82d6 154 c-macro-cppflags)))
4af1f509
RS
155 ;; Decide where to display output.
156 (if (and subst
6c61e6a9 157 (and buffer-read-only (not inhibit-read-only))
4af1f509
RS
158 (not (eq inbuf displaybuf)))
159 (progn
160 (message
161 "Buffer is read only: displaying expansion in alternate window")
162 (sit-for 2)
163 (setq subst nil)
164 (or displaybuf
165 (setq displaybuf (get-buffer-create c-macro-buffer-name)))))
166 ;; Expand the macro and output it.
89fb82d6
RS
167 (setq expansion (c-macro-expansion start end
168 (concat c-macro-preprocessor " "
074521be 169 c-macro-cppflags) t))
4af1f509
RS
170 (if subst
171 (let ((exchange (= (point) start)))
172 (delete-region start end)
173 (insert expansion)
174 (if exchange
175 (exchange-point-and-mark)))
176 (set-buffer displaybuf)
177 (setq buffer-read-only nil)
3332766c 178 (buffer-disable-undo displaybuf)
4af1f509
RS
179 (erase-buffer)
180 (insert expansion)
181 (set-buffer-modified-p nil)
182 (if (string= "" expansion)
183 (message "Null expansion")
89fb82d6 184 (c-macro-display-buffer))
4af1f509 185 (setq buffer-read-only t)
89fb82d6 186 (setq buffer-auto-save-file-name nil)
4af1f509
RS
187 (bury-buffer displaybuf))))
188
189
190;; Display the current buffer in a window which is either just large
191;; enough to contain the entire buffer, or half the size of the
89fb82d6
RS
192;; screen, whichever is smaller. Do not select the new
193;; window.
4af1f509
RS
194;;
195;; Several factors influence window resizing so that the window is
196;; sized optimally if it is created anew, and so that it is messed
197;; with minimally if it has been created by the user. If the window
198;; chosen for display exists already but contains something else, the
199;; window is not re-sized. If the window already contains the current
200;; buffer, it is never shrunk, but possibly expanded. Finally, if the
9c32788e 201;; variable c-macro-shrink-window-flag is nil the window size is *never*
4af1f509 202;; changed.
89fb82d6 203(defun c-macro-display-buffer ()
4af1f509
RS
204 (goto-char (point-min))
205 (c-mode)
4af1f509
RS
206 (let ((oldwinheight (window-height))
207 (alreadythere ;the window was already there
208 (get-buffer-window (current-buffer)))
4ff3fcc7 209 (popped nil)) ;the window popped changing the layout
4af1f509
RS
210 (or alreadythere
211 (progn
212 (display-buffer (current-buffer) t)
213 (setq popped (/= oldwinheight (window-height)))))
9c32788e 214 (if (and c-macro-shrink-window-flag ;user wants fancy shrinking :\)
4af1f509
RS
215 (or alreadythere popped))
216 ;; Enlarge up to half screen, or shrink properly.
217 (let ((oldwin (selected-window))
218 (minheight 0)
219 (maxheight 0))
220 (save-excursion
221 (select-window (get-buffer-window (current-buffer)))
222 (setq minheight (if alreadythere
223 (window-height)
224 window-min-height))
32cdf3f6 225 (setq maxheight (/ (frame-height) 2))
4af1f509
RS
226 (enlarge-window (- (min maxheight
227 (max minheight
89fb82d6 228 (+ 2 (vertical-motion (point-max)))))
4af1f509
RS
229 (window-height)))
230 (goto-char (point-min))
231 (select-window oldwin))))))
232
233
074521be 234(defun c-macro-expansion (start end cppcommand &optional display)
9c32788e 235 "Run a preprocessor on region and return the output as a string.
89fb82d6
RS
236Expand the region between START and END in the current buffer using
237the shell command CPPCOMMAND (e.g. \"/lib/cpp -C -DDEBUG\").
074521be
RS
238Be sure to use a -C (don't strip comments) or equivalent option.
239Optional arg DISPLAY non-nil means show messages in the echo area."
4af1f509
RS
240
241;; Copy the current buffer's contents to a temporary hidden buffer.
242;; Delete from END to end of buffer. Insert a preprocessor #line
243;; directive at START and after each #endif following START that are
244;; not inside a comment or a string. Put all the strings thus
245;; inserted (without the "line" substring) in a list named linelist.
246;; If START is inside a comment, prepend "*/" and append "/*" to the
247;; #line directive. If inside a string, prepend and append "\"".
248;; Preprocess the buffer contents, then look for all the lines stored
249;; in linelist starting from end of buffer. The last line so found is
250;; where START was, so return the substring from point to end of
4ff3fcc7 251;; buffer.
4af1f509
RS
252 (let ((inbuf (current-buffer))
253 (outbuf (get-buffer-create " *C Macro Expansion*"))
254 (filename (if (and buffer-file-name
255 (string-match (regexp-quote default-directory)
256 buffer-file-name))
257 (substring buffer-file-name (match-end 0))
258 (buffer-name)))
074521be
RS
259 (mymsg (format "Invoking %s%s%s on region..."
260 c-macro-preprocessor
261 (if (string= "" c-macro-cppflags) "" " ")
262 c-macro-cppflags))
32cdf3f6 263 (uniquestring "??? !!! ??? start of c-macro expansion ??? !!! ???")
074521be 264 (startlinenum 0)
4af1f509 265 (linenum 0)
074521be 266 (startstat ())
440c10f8 267 (startmarker "")
3332766c 268 (exit-status 0)
767d12f2 269 (tempname (make-temp-file
fc416240 270 (expand-file-name "cmacexp"
77f90711
EZ
271 (or small-temporary-file-directory
272 temporary-file-directory)))))
4af1f509
RS
273 (unwind-protect
274 (save-excursion
275 (save-restriction
276 (widen)
32cdf3f6
KH
277 (let ((in-syntax-table (syntax-table)))
278 (set-buffer outbuf)
279 (setq buffer-read-only nil)
280 (erase-buffer)
281 (set-syntax-table in-syntax-table))
4af1f509
RS
282 (insert-buffer-substring inbuf 1 end))
283
284 ;; We have copied inbuf to outbuf. Point is at end of
32cdf3f6
KH
285 ;; outbuf. Inset a newline at the end, so cpp can correctly
286 ;; parse a token ending at END.
287 (insert "\n")
4af1f509 288
074521be
RS
289 ;; Save sexp status and line number at START.
290 (setq startstat (parse-partial-sexp 1 start))
291 (setq startlinenum (+ (count-lines 1 (point))
292 (if (bolp) 1 0)))
293
4af1f509 294 ;; Now we insert the #line directives after all #endif or
074521be
RS
295 ;; #else following START going backward, so the lines we
296 ;; insert don't change the line numbers.
4af1f509 297 ;(switch-to-buffer outbuf) (debug) ;debugging instructions
074521be 298 (goto-char (point-max))
4af1f509 299 (while (re-search-backward "\n#\\(endif\\|else\\)\\>" start 'move)
074521be
RS
300 (if (equal (nthcdr 3 (parse-partial-sexp start (point)
301 nil nil startstat))
9c32788e 302 '(nil nil nil 0 nil)) ;neither in string nor in
074521be 303 ;comment nor after quote
4af1f509
RS
304 (progn
305 (goto-char (match-end 0))
074521be
RS
306 (setq linenum (+ startlinenum
307 (count-lines start (point))))
308 (insert (format "\n#line %d \"%s\"\n" linenum filename))
309 (goto-char (match-beginning 0)))))
310
311 ;; Now we are at START. Insert the first #line directive.
312 ;; This must work even inside a string or comment, or after a
4af1f509 313 ;; quote.
074521be
RS
314 (let* ((startinstring (nth 3 startstat))
315 (startincomment (nth 4 startstat))
316 (startafterquote (nth 5 startstat))
317 (startinbcomment (nth 7 startstat)))
318 (insert (if startafterquote " " "")
319 (cond (startinstring
320 (char-to-string startinstring))
321 (startincomment "*/")
322 (""))
074521be 323 (setq startmarker
521fffcd 324 (concat "\n" uniquestring
074521be
RS
325 (cond (startinstring
326 (char-to-string startinstring))
327 (startincomment "/*")
328 (startinbcomment "//"))
521fffcd
FP
329 (if startafterquote "\\")))
330 (format "\n#line %d \"%s\"\n" startlinenum filename)))
4af1f509
RS
331
332 ;; Call the preprocessor.
074521be 333 (if display (message mymsg))
440c10f8 334 (setq exit-status
68dff3ca
RS
335 (call-process-region 1 (point-max)
336 shell-file-name
337 t (list t tempname) nil "-c"
338 cppcommand))
074521be 339 (if display (message (concat mymsg "done")))
eb426f3a
RS
340 (if (= (buffer-size) 0)
341 ;; Empty output is normal after a fatal error.
342 (insert "\nPreprocessor produced no output\n")
343 ;; Find and delete the mark of the start of the expansion.
344 ;; Look for `# nn "file.c"' lines and delete them.
345 (goto-char (point-min))
346 (search-forward startmarker)
347 (delete-region 1 (point)))
074521be
RS
348 (while (re-search-forward (concat "^# [0-9]+ \""
349 (regexp-quote filename)
350 "\"") nil t)
351 (beginning-of-line)
352 (let ((beg (point)))
353 (forward-line 1)
354 (delete-region beg (point))))
4af1f509 355
440c10f8 356 ;; If CPP got errors, show them at the beginning.
68dff3ca
RS
357 ;; MS-DOS shells don't return the exit code of their children.
358 ;; Look at the size of the error message file instead, but
359 ;; don't punish those MS-DOS users who have a shell that does
360 ;; return an error code.
361 (or (and (or (not (boundp 'msdos-shells))
362 (not (member (file-name-nondirectory shell-file-name)
363 msdos-shells)))
364 (eq exit-status 0))
365 (zerop (nth 7 (file-attributes (expand-file-name tempname))))
440c10f8
RS
366 (progn
367 (goto-char (point-min))
68dff3ca
RS
368 ;; Put the messages inside a comment, so they won't get in
369 ;; the way of font-lock, highlighting etc.
370 (insert
371 (format "/* Preprocessor terminated with status %s\n\n Messages from `%s\':\n\n"
372 exit-status cppcommand))
373 (goto-char (+ (point)
374 (nth 1 (insert-file-contents tempname))))
375 (insert "\n\n*/\n")))
440c10f8
RS
376 (delete-file tempname)
377
4af1f509
RS
378 ;; Compute the return value, keeping in account the space
379 ;; inserted at the end of the buffer.
074521be 380 (buffer-substring 1 (max 1 (- (point-max) 1))))
4af1f509
RS
381
382 ;; Cleanup.
383 (kill-buffer outbuf))))
384
89fb82d6 385;;; cmacexp.el ends here