xref: /386bsd/usr/local/lib/emacs/19.25/lisp/cmacexp.el (revision a2142627)
1;;; cmacexp.el --- expand C macros in a region
2
3;; Copyright (C) 1992, 1994 Free Software Foundation, Inc.
4
5;; Author: Francesco Potorti` <pot@cnuce.cnr.it>
6;; Version: $Id: cmacexp.el,v 1.14 1994/05/03 22:17:03 kwzh Exp $
7;; Adapted-By: ESR
8;; Keywords: c
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software; you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation; either version 2, or (at your option)
15;; any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs; see the file COPYING.  If not, write to
24;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
25
26;; USAGE =============================================================
27
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.
31;;
32;; When called with a C-u prefix, c-macro-expand replaces the selected
33;; region with the expansion.  Both the preprocessor name and the
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.
40
41;; A c-macro-expansion function is provided for non-interactive use.
42
43;; INSTALLATION ======================================================
44
45;; Put the following in your ~/.emacs file.
46
47;; If you want the *Macroexpansion* window to be not higher than
48;; necessary:
49;;(setq c-macro-shrink-window-flag t)
50;;
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;;
56;; If you often use a particular set of flags:
57;;(setq c-macro-cppflags "-I /usr/include/local -DDEBUG"
58;;
59;; If you want the "Preprocessor arguments: " prompt:
60;;(setq c-macro-prompt-flag t)
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
69;; - A lot of user visible changes.  See above.
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.
75
76;; ACKNOWLEDGEMENTS ==================================================
77
78;; A lot of thanks to Don Maszle who did a great work of testing, bug
79;; reporting and suggestion of new features.  This work has been
80;; partially inspired by Don Maszle and Jonathan Segal's.
81
82;; BUGS ==============================================================
83
84;; If the start point of the region is inside a macro definition the
85;; macro expansion is often inaccurate.
86
87
88(provide 'cmacexp)
89
90(defvar c-macro-shrink-window-flag nil
91  "*Non-nil means shrink the *Macroexpansion* window to fit its contents.")
92
93(defvar c-macro-prompt-flag nil
94  "*Non-nil makes `c-macro-expand' prompt for preprocessor arguments.")
95
96(defvar c-macro-preprocessor "/lib/cpp -C"
97  "The preprocessor used by the cmacexp package.
98
99If you change this, be sure to preserve the -C (don't strip comments)
100option, or to set an equivalent one.")
101
102(defvar c-macro-cppflags ""
103  "*Preprocessor flags used by c-macro-expand.")
104
105(defconst c-macro-buffer-name "*Macroexpansion*")
106
107(defun c-macro-expand (start end subst)
108  "Expand C macros in the region, using the C preprocessor.
109Normally display output in temp buffer, but
110prefix arg means replace the region with it.
111
112`c-macro-preprocessor' specifies the preprocessor to use.
113Prompt for arguments to the preprocessor \(e.g. `-DDEBUG -I ./include')
114if the user option `c-macro-prompt-flag' is non-nil.
115
116Noninteractive args are START, END, SUBST.
117For use inside Lisp programs, see also `c-macro-expansion'."
118
119  (interactive "r\nP")
120  (let ((inbuf (current-buffer))
121	(displaybuf (if subst
122			(get-buffer c-macro-buffer-name)
123		      (get-buffer-create c-macro-buffer-name)))
124	(expansion ""))
125    ;; Build the command string.
126    (if c-macro-prompt-flag
127	(setq c-macro-cppflags
128	      (read-string "Preprocessor arguments: "
129			   c-macro-cppflags)))
130    ;; Decide where to display output.
131    (if (and subst
132	     (and buffer-read-only (not inhibit-read-only))
133	     (not (eq inbuf displaybuf)))
134	(progn
135	  (message
136	   "Buffer is read only: displaying expansion in alternate window")
137	  (sit-for 2)
138	  (setq subst nil)
139	  (or displaybuf
140	      (setq displaybuf (get-buffer-create c-macro-buffer-name)))))
141    ;; Expand the macro and output it.
142    (setq expansion (c-macro-expansion start end
143				       (concat c-macro-preprocessor " "
144					       c-macro-cppflags) t))
145    (if subst
146	(let ((exchange (= (point) start)))
147	  (delete-region start end)
148	  (insert expansion)
149	  (if exchange
150	      (exchange-point-and-mark)))
151      (set-buffer displaybuf)
152      (setq buffer-read-only nil)
153      (buffer-flush-undo displaybuf)
154      (erase-buffer)
155      (insert expansion)
156      (set-buffer-modified-p nil)
157      (if (string= "" expansion)
158	  (message "Null expansion")
159	(c-macro-display-buffer))
160      (setq buffer-read-only t)
161      (setq buffer-auto-save-file-name nil)
162      (bury-buffer displaybuf))))
163
164
165;; Display the current buffer in a window which is either just large
166;; enough to contain the entire buffer, or half the size of the
167;; screen, whichever is smaller.  Do not select the new
168;; window.
169;;
170;; Several factors influence window resizing so that the window is
171;; sized optimally if it is created anew, and so that it is messed
172;; with minimally if it has been created by the user.  If the window
173;; chosen for display exists already but contains something else, the
174;; window is not re-sized.  If the window already contains the current
175;; buffer, it is never shrunk, but possibly expanded.  Finally, if the
176;; variable c-macro-shrink-window-flag is nil the window size is *never*
177;; changed.
178(defun c-macro-display-buffer ()
179  (goto-char (point-min))
180  (c-mode)
181  (let ((oldwinheight (window-height))
182	(alreadythere			;the window was already there
183	 (get-buffer-window (current-buffer)))
184	(popped nil))			;the window popped changing the layout
185    (or alreadythere
186	(progn
187	  (display-buffer (current-buffer) t)
188	  (setq popped (/= oldwinheight (window-height)))))
189    (if (and c-macro-shrink-window-flag	;user wants fancy shrinking :\)
190	     (or alreadythere popped))
191	;; Enlarge up to half screen, or shrink properly.
192	(let ((oldwin (selected-window))
193	      (minheight 0)
194	      (maxheight 0))
195	  (save-excursion
196	    (select-window (get-buffer-window (current-buffer)))
197	    (setq minheight (if alreadythere
198				(window-height)
199			      window-min-height))
200	    (setq maxheight (/ (screen-height) 2))
201	    (enlarge-window (- (min maxheight
202				    (max minheight
203					 (+ 2 (vertical-motion (point-max)))))
204			       (window-height)))
205	    (goto-char (point-min))
206	    (select-window oldwin))))))
207
208
209(defun c-macro-expansion (start end cppcommand &optional display)
210  "Run a preprocessor on region and return the output as a string.
211Expand the region between START and END in the current buffer using
212the shell command CPPCOMMAND (e.g. \"/lib/cpp -C -DDEBUG\").
213Be sure to use a -C (don't strip comments) or equivalent option.
214Optional arg DISPLAY non-nil means show messages in the echo area."
215
216;; Copy the current buffer's contents to a temporary hidden buffer.
217;; Delete from END to end of buffer.  Insert a preprocessor #line
218;; directive at START and after each #endif following START that are
219;; not inside a comment or a string.  Put all the strings thus
220;; inserted (without the "line" substring) in a list named linelist.
221;; If START is inside a comment, prepend "*/" and append "/*" to the
222;; #line directive.  If inside a string, prepend and append "\"".
223;; Preprocess the buffer contents, then look for all the lines stored
224;; in linelist starting from end of buffer.  The last line so found is
225;; where START was, so return the substring from point to end of
226;; buffer.
227  (let ((inbuf (current-buffer))
228	(outbuf (get-buffer-create " *C Macro Expansion*"))
229	(filename (if (and buffer-file-name
230			   (string-match (regexp-quote default-directory)
231					 buffer-file-name))
232		      (substring buffer-file-name (match-end 0))
233		    (buffer-name)))
234	(mymsg (format "Invoking %s%s%s on region..."
235		       c-macro-preprocessor
236		       (if (string= "" c-macro-cppflags) "" " ")
237		       c-macro-cppflags))
238	(uniquestring "???!!!???!!! start of c-macro expansion ???!!!???!!!")
239	(startlinenum 0)
240	(linenum 0)
241	(startstat ())
242	(startmarker ""))
243    (unwind-protect
244	(save-excursion
245	  (save-restriction
246	    (widen)
247	    (set-buffer outbuf)
248	    (setq buffer-read-only nil)
249	    (erase-buffer)
250	    (set-syntax-table c-mode-syntax-table)
251	    (insert-buffer-substring inbuf 1 end))
252
253	  ;; We have copied inbuf to outbuf.  Point is at end of
254	  ;; outbuf.  Insert a space at the end, so cpp can correctly
255	  ;; parse a token ending at END.
256	  (insert " ")
257
258	  ;; Save sexp status and line number at START.
259	  (setq startstat (parse-partial-sexp 1 start))
260	  (setq startlinenum (+ (count-lines 1 (point))
261				(if (bolp) 1 0)))
262
263	  ;; Now we insert the #line directives after all #endif or
264	  ;; #else following START going backward, so the lines we
265	  ;; insert don't change the line numbers.
266	  ;(switch-to-buffer outbuf) (debug)	;debugging instructions
267	  (goto-char (point-max))
268	  (while (re-search-backward "\n#\\(endif\\|else\\)\\>" start 'move)
269	    (if (equal (nthcdr 3 (parse-partial-sexp start (point)
270						     nil nil startstat))
271		       '(nil nil nil 0 nil)) ;neither in string nor in
272					     ;comment nor after quote
273		(progn
274		  (goto-char (match-end 0))
275		  (setq linenum (+ startlinenum
276				   (count-lines start (point))))
277		  (insert (format "\n#line %d \"%s\"\n" linenum filename))
278		  (goto-char (match-beginning 0)))))
279
280	  ;; Now we are at START.  Insert the first #line directive.
281	  ;; This must work even inside a string or comment, or after a
282	  ;; quote.
283	  (let* ((startinstring (nth 3 startstat))
284		 (startincomment (nth 4 startstat))
285		 (startafterquote (nth 5 startstat))
286		 (startinbcomment (nth 7 startstat)))
287	    (insert (if startafterquote " " "")
288		    (cond (startinstring
289			   (char-to-string startinstring))
290			  (startincomment "*/")
291			  (""))
292		    (format "\n#line %d \"%s\"\n" startlinenum filename)
293		    (setq startmarker
294			  (concat uniquestring
295				  (cond (startinstring
296					 (char-to-string startinstring))
297					(startincomment "/*")
298					(startinbcomment "//"))
299				  (if startafterquote "\\")))))
300
301	  ;; Call the preprocessor.
302	  (if display (message mymsg))
303	  (call-process-region 1 (point-max) "sh" t t nil "-c"
304			       (concat cppcommand " 2>/dev/null"))
305	  (if display (message (concat mymsg "done")))
306
307	  ;; Find and delete the mark of the start of the expansion.
308	  ;; Look for `# nn "file.c"' lines and delete them.
309	  (goto-char (point-min))
310	  (search-forward startmarker)
311	  (delete-region 1 (point))
312	  (while (re-search-forward (concat "^# [0-9]+ \""
313					    (regexp-quote filename)
314					    "\"") nil t)
315	    (beginning-of-line)
316	    (let ((beg (point)))
317	      (forward-line 1)
318	      (delete-region beg (point))))
319
320	  ;; Compute the return value, keeping in account the space
321	  ;; inserted at the end of the buffer.
322	  (buffer-substring 1 (max 1 (- (point-max) 1))))
323
324      ;; Cleanup.
325      (kill-buffer outbuf))))
326
327;;; cmacexp.el ends here
328