]> git.eshelyaron.com Git - emacs.git/commitdiff
Properly check in the total rewrite installed months ago.
authorRichard M. Stallman <rms@gnu.org>
Mon, 7 Feb 1994 05:39:56 +0000 (05:39 +0000)
committerRichard M. Stallman <rms@gnu.org>
Mon, 7 Feb 1994 05:39:56 +0000 (05:39 +0000)
lisp/progmodes/cmacexp.el

index dd2c6df2c6721e4d338e90e8aa8c9d7f54a5b38e..8fa710c913f019caac0031347628a79152e35a9d 100644 (file)
@@ -1,8 +1,10 @@
-;;; cmacexp.el --- C preprocessor macro expansion
+;;; cmacexp.el --- expand C macros in a region
 
-;; Copyright (C) 1988 Free Software Foundation, Inc.
+;; Copyright (C) 1992 Free Software Foundation, Inc.
 
-;; Maintainer: FSF
+;; Author: Francesco Potorti` <pot@cnuce.cnr.it>
+;; Version: $Id: cmacexp.el 1.2 1992/09/15 11:34:56 pot Exp $
+;; Adapted-By: ESR
 ;; Keywords: c
 
 ;; This file is part of GNU Emacs.
 
 ;;; Commentary:
 
-;; This package gives you the ability to run the C macro preprocessor
-;; on the current region, expanding macros within it.  This can be useful
-;; when you're not sure of the value or expansion of such macros and want
-;; to make sure they're doing what you think they're doing.
+;; In C mode C-M-x is bound to c-macro-expand.  The result of the
+;; expansion is put in a separate buffer.  The buffer is put in
+;; view-mode if the Inge Frick's view.el is installed.  A user option
+;; allows the window displaying the buffer to be optimally sized.
 ;;
-;; This package supports the following option variables:
+;; When called with a C-u prefix, c-macro-expand replaces the selected
+;; region with the expansion.  With two C-u's the user is offered to
+;; change the flags to the preprocessor (while the results of the
+;; expansion go to a separate buffer).  Preprocessor arguments default
+;; to the last ones entered.  Both the preprocessor name and the
+;; initial flag defaults can be set by the user.  Setting
+;; c-macro-always-prompt to a non-nil value allows one to be always
+;; prompted for the flags, regardless of the prefix used.
+
+;; A c-macro-expansion function is provided for non-interactive use.
+;; A still experimental function c-macro-eval is provided.  It aims at
+;; evaluating the contents of a region by using calc (by Dave
+;; Gillespie).  Select a region and type C-x C-e (if you followed the
+;; suggestions in the INSTALLATION section) or type M-x c-ma RET v
+;; RET.  If you have calc installed, the computed value of the
+;; expression will appear in the message area.  If you give an
+;; interactive C-u prefix the computed value will be shown in signed,
+;; unsigned, hex and boolean representations.  Two C-u's allow to
+;; change the preprocessor flags via prompt.  c-macro-eval works well
+;; for constant expressions, but see the BUG section.
+
+;; A patch to calc 2.02 has been written by Dave Gillespie.  It can
+;; be downloaded via anonymous ftp at fly.cnuce.cnr.it:pub/calc.diff.
+
+;; INSTALLATION ======================================================
+
+;; Put this file on your load-path, byte compile it for increased
+;; speed and put part or all of the following in your ~/.emacs file.
+
+;; To make a directory ~/emacs be in front of your load-path:
+;;(setq load-path (cons (expand-file-name "~/emacs") load-path))
+;;
+;; Suggested keybindings (work only in c-mode):
+;;(define-key c-mode-map "\C-\M-x" 'c-macro-expand)
+;;(define-key c-mode-map "\C-x\C-e" 'c-macro-eval)
+;;
+;; If you want the *Macroexpansion* window to be not higher than
+;; necessary: 
+;;(setq c-macro-shrink-window-p t)
 ;;
-;; c-macro-preprocessor --- program to be used for macro expansion.
-;; c-macro-options --- command-line options to pass it.
+;; If you use a preprocessor other than /lib/cpp (be careful to set a
+;; -C option or equivalent in order to make the preprocessor not to
+;; strip the comments):
+;;(setq c-macro-preprocessor "gpp -C")
+;;
+;; If you often use a particular set of flags, and want them to be
+;; the default:
+;;(setq c-macro-default-cppflags "-I /usr/include/local -DDEBUG"
+;;
+;; If you always want the "Preprocessor arguments: " prompt,
+;; regardless of the arguments provided:
+;;(setq c-macro-always-prompt-p t)
+;;
+;; If you want to experiment with the C constant expressions
+;; evaluation feature:
+;;(autoload 'c-macro-eval "cmacexp"
+;;  "C constant expressions evaluation.  Requires calc.  Experimental." t)
+
+;; BUG REPORTS =======================================================
+
+;; Please report bugs, suggestions, complaints and so on to
+;; pot@cnuce.cnr.it (Francesco Potorti`).
+
+;; IMPROVEMENTS OVER emacs 18.xx cmacexp.el ==========================
+
+;; - A lot of user visible changes.  See above.
+;; - #line directives are inserted, so __LINE__ and __FILE__ are
+;;   correctly expanded.  Works even with START inside a string, a
+;;   comment or a region #ifdef'd away by cpp. cpp is invoked with -C,
+;;   making comments visible in the expansion.
+;; - All work is done in core memory, no need for temporary files.
+;; - The /lib/cpp process is run synchronously.  This fixes an
+;;   infinite loop bug on Motorola Delta (cpp waiting forever for
+;;   end-of-file, don't know why).  Fixes a similar intermittent
+;;   problem on SunOS 4.1.
+
+;; ACKNOWLEDGEMENTS ==================================================
+
+;; A lot of thanks to Don Maszle who did a great work of testing, bug
+;; reporting and suggestion of new features, to Inge Fricks for her
+;; help with view.el and to Dave Gillespie for his suggestions on
+;; calc's use.  This work has been partially inspired by Don Maszle
+;; and Jonathan Segal's.
+
+;; By the way, I recommend you Inge Frick's view.el.  It works like
+;; the standard view, but *it is not recursive* and has some more
+;; commands.  Moreover it is a minor mode, so you preserve all your
+;; major mode keybindings (well, not always :).  Mail me to obtain a
+;; copy, or get it by anonymous ftp in fly.cnuce.cnr.it:pub/view.el.
+
+;; BUGS ==============================================================
+
+;; calc 2.02 does not handle the C operators "->", ".", "*" (as a
+;; prefix), the composite assignement operators "+=" etc.  It cannot
+;; handle the "," operator and will be confused by ";".  Almost all
+;; these can be defined as no-ops using the Calc's Syntax Tables
+;; feature.  The built-in calc functions will cause problems in
+;; certain circumstances.  c-macro-eval behaves correctly only on
+;; expressions not containing such operators.  Does not distinguish
+;; among integer and real division.
+
+;; If the start point of the region is inside a macro definition the
+;; macro expansion is often inaccurate.
 
 ;;; Code:
 
-(defvar c-macro-preprocessor "/lib/cpp"
-  "*Command to be used for C preprocessing.")
-
-(defvar c-macro-options nil
-  "*List of options to use in C preprocessing.
-Each string in the list becomes a separate argument to the preprocessor.
-These arguments precede the filename.
-Use the `-I' option here to specify directories for header files.")
-
-(defun c-macro-expand (beg end)
-  "Display the result of expanding all C macros occurring in the region.
-The expansion is entirely correct because it uses the C preprocessor.
-You can use the variables `c-macro-preprocessor' and `c-macro-options'
-to customize how preprocessing is done, or specify header file directories
-and macros to predefine."
-  (interactive "r")
-  (let ((outbuf (get-buffer-create "*Macroexpansion*"))
-       (tempfile "%%macroexpand%%")
-       expanded
-       process
-       last-needed)
-    (setq expanded (expand-file-name tempfile))
-    (save-excursion
-      (set-buffer outbuf)
-      (erase-buffer))
-    (setq process (apply 'start-process "macros" outbuf c-macro-preprocessor
-                        c-macro-options))
-    (set-process-sentinel process '(lambda (&rest x)))
-    (save-restriction
-      (widen)
-      (save-excursion
-       (goto-char beg)
-       (beginning-of-line)
-       (setq last-needed (point))
-       (if (re-search-backward "^[ \t]*#" nil t)
-           (progn
-             ;; Skip continued lines.
-             (while (progn (end-of-line) (= (preceding-char) ?\\))
-               (forward-line 1))
-             ;; Skip the last line of the macro definition we found.
-             (forward-line 1)
-             (setq last-needed (point)))))
-      (write-region (point-min) last-needed expanded nil 'nomsg)
-      ;; Output comment ender in case last #-directive is inside a comment.
-      ;; Also, terminate any string that we are in.
-      (write-region "*//*\"*/\n" nil expanded t 'nomsg)
-      (write-region beg end (concat expanded "x") nil 'nomsg)
-      (process-send-string process (concat "#include \"" tempfile "\"\n"))
-      (process-send-string process "\n")
-      (process-send-string process (concat "#include \"" tempfile "x\"\n"))
-      (process-send-eof process)
-      ;; HPUX seems to want two eofs.
-      (process-send-eof process))
-    (while (eq (process-status process) 'run)
-      (accept-process-output))
-    (delete-file expanded)
-    (delete-file (concat expanded "x"))
-    (display-buffer outbuf)
-    (save-excursion
-      (set-buffer outbuf)
-      (goto-char (point-max))
-      (forward-line -1)
-      (delete-region (point) (point-max))
-      (re-search-backward "\n# 1 ")
-      (forward-line 2)
-      (while (eolp) (delete-char 1))
-      (delete-region (point-min) (point)))
-    (display-buffer outbuf)))
-
-;;; cmacexp.el ends here
+(defvar c-macro-shrink-window-p nil
+  "*Non-nil means shrink the *Macroexpansion* window to fit its contents.")
+
+(defvar c-macro-always-prompt-p nil
+  "*Non-nil means always prompt for preprocessor arguments.")
+
+(defvar c-macro-preprocessor "/lib/cpp -C" "\
+The preprocessor used by the cmacexp package.
+
+If you change this, be sure to preserve the -C (don't strip comments)
+option, or to set an equivalent one.")
+
+(defvar c-macro-default-cppflags ""
+  "Default cpp flags used by c-macro-expand.")
+
+(defconst c-macro-buffer-name "*Macroexpansion*")
+
+(defun c-macro-expand (start end &optional flag) "\
+Expand all C macros occurring in the region using c-macro-preprocessor.
+Normally display output in temp buffer.
+Prefix arg means replace the region with it.
+Prompt for a string of arguments to the preprocessor, (e.g.
+-DDEBUG -I ./include) when prefixed with two C-u's.
+
+It is intended for interactive use only.
+For non interactive use, see the c-macro-expansion function."
+
+  (interactive "r\nP")
+  (let* ((subst (and flag (not (equal flag '(16)))))
+        (inbuf (current-buffer))
+        (displaybuf (if subst
+                        (get-buffer c-macro-buffer-name)
+                      (get-buffer-create c-macro-buffer-name)))
+        (expansion ""))
+    ;; Build the command string.
+    (if (or c-macro-always-prompt-p (equal flag '(16)))
+       (setq c-macro-default-cppflags
+             (read-string "Preprocessor arguments: "
+                          c-macro-default-cppflags)))
+    ;; Decide where to display output.
+    (if (and subst
+            buffer-read-only
+            (not (eq inbuf displaybuf)))
+       (progn
+         (message
+          "Buffer is read only: displaying expansion in alternate window")
+         (sit-for 2)
+         (setq subst nil)
+         (or displaybuf
+             (setq displaybuf (get-buffer-create c-macro-buffer-name)))))
+    ;; Expand the macro and output it.
+    (if (interactive-p) (message (c-macro-default-message)))
+    (setq expansion
+         (c-macro-expansion start end
+                            (concat c-macro-preprocessor " "
+                                    c-macro-default-cppflags)))
+    (message (concat (c-macro-default-message) "done"))
+    (if subst
+       (let ((exchange (= (point) start)))
+         (delete-region start end)
+         (insert expansion)
+         (if exchange
+             (exchange-point-and-mark)))
+      (set-buffer displaybuf)
+      (setq buffer-read-only nil)
+      (buffer-flush-undo displaybuf)
+      (erase-buffer)
+      (insert expansion)
+      (set-buffer-modified-p nil)
+      (if (string= "" expansion)
+         (message "Null expansion")
+       (c-macro-display-buffer inbuf))
+      (setq buffer-read-only t)
+      (bury-buffer displaybuf))))
+
+
+;; Display the current buffer in a window which is either just large
+;; enough to contain the entire buffer, or half the size of the
+;; screen, whichever is smaller.  Put the current buffer in view-mode
+;; if the Inge Frick's view-mode is installed, with buffer to return
+;; to set to RETBUF (if sensible). Do not select the new window.
+;;
+;; Several factors influence window resizing so that the window is
+;; sized optimally if it is created anew, and so that it is messed
+;; with minimally if it has been created by the user.  If the window
+;; chosen for display exists already but contains something else, the
+;; window is not re-sized.  If the window already contains the current
+;; buffer, it is never shrunk, but possibly expanded.  Finally, if the
+;; variable c-macro-shrink-window-p is nil the window size is *never*
+;; changed.
+(defun c-macro-display-buffer (retbuf)
+
+  (goto-char (point-min))
+  (c-mode)
+  (require 'view)                      ;load view.el
+  (let ((oldwinheight (window-height))
+       (alreadythere                   ;the window was already there
+        (get-buffer-window (current-buffer)))
+       (popped nil)                    ;the window popped changing the layout 
+       (niceview                       ;is this Inge Fricks's view.el?
+        (boundp 'view-kill-when-finished)))
+
+    (or alreadythere
+       (progn
+         (display-buffer (current-buffer) t)
+         (setq popped (/= oldwinheight (window-height)))))
+    (if niceview
+       (view-mode 1))                  ;set view mode
+    (if (and c-macro-shrink-window-p   ;user wants fancy shrinking :\)
+            (or alreadythere popped))
+       ;; Enlarge up to half screen, or shrink properly.
+       (let ((oldwin (selected-window))
+             (minheight 0)
+             (maxheight 0))
+         (save-excursion
+           (select-window (get-buffer-window (current-buffer)))
+           (setq minheight (if alreadythere
+                               (window-height)
+                             window-min-height))
+           (setq maxheight (/ (screen-height) 2))
+           (enlarge-window (- (min maxheight
+                                   (max minheight
+                                        (+ 2 (vertical-motion 1000000))))
+                              (window-height)))
+           (goto-char (point-min))
+           (select-window oldwin))))))
+
+
+(defun c-macro-expansion (start end cppcommand) "\
+Expands the region between START and END in the current buffer using
+the shell command CPPCOMMAND (e.g. \"/lib/cpp -C -DDEBUG\").  Be sure
+to use a -C (don't strip comments) or equivalent option.
+Returns the output as a string."
+
+;; Copy the current buffer's contents to a temporary hidden buffer.
+;; Delete from END to end of buffer.  Insert a preprocessor #line
+;; directive at START and after each #endif following START that are
+;; not inside a comment or a string.  Put all the strings thus
+;; inserted (without the "line" substring) in a list named linelist.
+;; If START is inside a comment, prepend "*/" and append "/*" to the
+;; #line directive.  If inside a string, prepend and append "\"".
+;; Preprocess the buffer contents, then look for all the lines stored
+;; in linelist starting from end of buffer.  The last line so found is
+;; where START was, so return the substring from point to end of
+;; buffer. 
+  (let ((inbuf (current-buffer))
+       (outbuf (get-buffer-create " *C Macro Expansion*"))
+       (filename (if (and buffer-file-name
+                          (string-match (regexp-quote default-directory)
+                                        buffer-file-name))
+                     (substring buffer-file-name (match-end 0))
+                   (buffer-name)))
+       (linenum 0)
+       (linelist ()))
+    (unwind-protect
+       (save-excursion
+         (save-restriction
+           (widen)
+           (set-buffer outbuf)
+           (setq buffer-read-only nil)
+           (erase-buffer)
+           (set-syntax-table c-mode-syntax-table)
+           (insert-buffer-substring inbuf 1 end))
+
+         ;; We have copied inbuf to outbuf.  Point is at end of
+         ;; outbuf.  Insert a space at the end, so cpp can correctly
+         ;; parse a token ending at END. 
+
+         (insert " ")
+
+         ;; Now we insert the #line directives after all #endif or
+         ;; #else following START. 
+         ;(switch-to-buffer outbuf) (debug)    ;debugging instructions
+         (while (re-search-backward "\n#\\(endif\\|else\\)\\>" start 'move)
+           (if (equal (nthcdr 3 (parse-partial-sexp 1 (point)))
+                      '(nil nil nil 0)) ;neither in string nor in
+                                        ;comment nor after quote
+               (progn
+                 (goto-char (match-end 0))
+                 (setq linenum (count-lines 1 (point)))
+                 (setq linelist
+                       (cons (format "\n# %d \"%s\"\n" linenum filename)
+                             linelist))
+                 (insert (car linelist))
+                 (skip-chars-backward "^#")
+                 (insert "line")
+             (goto-char (match-beginning 0)))))
+
+         ;; We are at START.  Insert the first #line directive.  This
+         ;; must work even inside a string or comment, or after a
+         ;; quote.
+         (setq linenum (+ (count-lines 1 (point))
+                          (if (bolp) 1 0)))
+         (setq linelist
+               (cons
+                (let* ((startstat (parse-partial-sexp 1 start))
+                       (startinstring (nth 3 startstat))
+                       (startincomment (nth 4 startstat))
+                       (startafterquote (nth 5 startstat)))
+                  (concat (if startafterquote " ")
+                          (cond (startinstring "\"") (startincomment "*/"))
+                          (format "\n# %d \"%s\"\n" linenum filename)
+                          (cond (startinstring "\"") (startincomment "/*"))
+                          (if startafterquote "\\")))
+                linelist))
+         (insert (car linelist))
+         (skip-chars-backward "^#")
+         (insert "line")
+
+         ;; Call the preprocessor.
+         (call-process-region 1 (point-max) "sh" t t nil "-c"
+                              (concat cppcommand " 2>/dev/null"))
+
+         ;; Look for the `# nn "file.c"' lines from the last to the first
+         ;; and delete them.
+         (setq linelist (reverse linelist))
+         (while (progn
+                  (if (search-backward (car linelist) nil t)
+                      (replace-match ""))
+                  (setq linelist (cdr linelist))))
+         
+         ;; Compute the return value, keeping in account the space
+         ;; inserted at the end of the buffer.
+         (buffer-substring (point) (max (point) (- (point-max) 1))))
+
+      ;; Cleanup.
+      (kill-buffer outbuf))))
+
+
+;; Experimental.  With an argument, print signed, unsigned, hex and
+;; boolean representations.
+(defun c-macro-eval (start end &optional flag) "\
+Expand region using cpp and evaluate it using calc.
+Interactively print value in minibuffer and push it on the kill ring.
+With a C-u argument shows the evaluation in a variety of formats.
+With two C-u's prompts the user for a string of flags to the preprocessor.
+
+Non interactively returns value of region between START and END
+as a string.  Several formats are used if optional FLAG is non-nil."
+
+  (interactive "r\nP")
+  (or (fboundp 'calc-eval)
+      (require 'calc))
+  (if (or c-macro-always-prompt-p (equal flag '(16)))
+      (setq c-macro-default-cppflags
+           (read-string "Preprocessor arguments: "
+                        c-macro-default-cppflags)))
+
+  ;; Expand the region.
+  (if (interactive-p) (message (c-macro-default-message)))
+  (let ((evaluation
+        (c-macro-expansion start end
+                           (concat c-macro-preprocessor " "
+                                   c-macro-default-cppflags)))
+       (evalbuf (get-buffer-create " *Macro Evaluation*")))
+    (unwind-protect
+       (save-excursion
+         (set-buffer evalbuf)
+         (setq buffer-read-only nil)
+         (erase-buffer)
+         (insert evaluation)
+
+         ;; Evaluate expression(s).
+         (if (interactive-p)
+             (message "Invoking calc..."))
+         (setq evaluation
+               (let ((calc-eval-error t))
+                 (calc-eval (list (buffer-string) 'calc-language 'c))))
+         (erase-buffer)
+         (cond
+          (flag
+           (insert (calc-eval (list evaluation
+                                    'calc-language 'c
+                                    'calc-simplify-mode 'binary))
+                   "(u)" " == "
+                   (calc-eval (list evaluation
+                                    'calc-language 'c
+                                    'calc-word-size (- calc-word-size)
+                                    'calc-simplify-mode 'binary))
+                   "(d)" " == "
+                   (calc-eval (list evaluation
+                                    'calc-language 'c
+                                    'calc-number-radix 16
+                                    'calc-simplify-mode 'binary))
+                   "(x)")
+           (save-excursion
+             (insert " == " (calc-eval (list evaluation
+                                             'calc-language 'c
+                                             'calc-number-radix 16
+                                             'calc-simplify-mode 'binary))))
+           (while (re-search-forward "0x\\([^,]+\\)\\(, \\|\\'\\)" nil t)
+             (if (string= "0"
+                          (buffer-substring (match-beginning 1)
+                                            (match-end 1)))
+                 (replace-match "FALSE\\2")
+               (replace-match "TRUE\\2"))))
+          (t
+           (insert evaluation)))
+
+         ;; Output the evaluation.
+         (if (interactive-p)
+             (progn
+               (copy-region-as-kill 1 (point-max))
+               (message (buffer-string)))
+           (buffer-string)))
+      (kill-buffer evalbuf))))
+
+(defun c-macro-default-message ()
+  (format "Invoking %s%s%s on region..."
+         c-macro-preprocessor
+         (if (string= "" c-macro-default-cppflags) "" " ")
+         c-macro-default-cppflags))
+
+(provide 'cmacexp)
+
+;;; cmacexp.el ends here.