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