unofficial mirror of help-gnu-emacs@gnu.org
 help / color / mirror / Atom feed
From: Phil Carmody <thefatphil_demunged@yahoo.co.uk>
To: help-gnu-emacs@gnu.org
Subject: Re: jumping through ifdefs
Date: Fri, 13 Jun 2008 18:52:19 +0300	[thread overview]
Message-ID: <87od65ib7w.fsf@nonospaz.fatphil.org> (raw)
In-Reply-To: 5774a661-d829-49fd-a433-2a9d8c0d39cc@26g2000hsk.googlegroups.com

Rafal Kurcz <pawlaczus@yahoo.com> writes:

> Hello
> I have the following code:
>
> #ifdef LINUX
>
> "another nested ifdefs"
>
> #else //LINUX
>
> "another nested ifdefs"
>
> #endif //LINUX
>
> I would like to cycle through "LINUX" ifdefs by single emacs command.
> I tried forward-ifdef, down-ifdef and next-ifdef from hide-ifdef-mode
> but they don't work as I expect.
> They either omit the "else" statement or stop at nested ifdefs.
>
> Thank You for help


I wrote this about 15 years ago (emacs 18?), and it still 
seems to work (in 22 and 21 at least). Or at least the stuff
I've needed to use recently still works. There's a whole pile 
of stuff you haven't asked for, and a fair chunk of it is 
now in the core emacs lisp libraries, so really ought to 
be purged. However, if it's useful, feel free to prune it 
and use it.


--- 8< ---

;;; BEGIN Short-cuts for preprocessor stuff in C mode

;; We use symbolic names for the 4 types of directive we can find
(defconst pc-hash-if 1)
(defconst pc-hash-else 2)
(defconst pc-hash-elif 3)
(defconst pc-hash-endif 4)

(defvar pc-auto-modernise-ifdef nil "non nil will cause modernisation to update a buffer") 
(defvar pc-last-directive-type nil "type of last directive found")
(defvar pc-last-directive-deftype nil "n?def iff last type was #(el)?ifn?def, else nil")
(defvar pc-last-directive-cond nil "condition string of last directive")


(defun pc-get-directive-type (&optional quick)
  "Get the directive type and any associated condition
Return by default a list of an enumeration for the preprocessor directive
which point is on, a string if it is an old-style ifn?def, and finally any
associated condition on the line. Store these for later use in the variables
pc-last-directive-type, pc-last-directive-deftype and pc-last-directive-cond.
If optional parameter QUICK is non-null, then do not try to extract the
condition, and do not set the variables, and return only the atom denoting
which type of directive was found."

  (interactive "p")
  (save-excursion
    (let* ((not-at-bol (not (bolp)))
           (l-end (if not-at-bol nil
                    (end-of-line) 
                    (point)))
           (l-beg (if not-at-bol nil
                    (beginning-of-line)
                    (point)))
           (d-end (if not-at-bol nil
                    ;; 3 of the below will be nil, one will be a match
                    (re-search-forward "^#\\s *\\(\\(if\\)\\|\\(else\\)\\|\\(elif\\)\\|\\(endif\\)\\)"
                                       l-end t nil))))
      ;; (message "%S %S %S %S" not-at-bol l-end l-beg d-end)      
      (if (null d-end)
          (progn 
            (message (if not-at-bol
                         "Not at beginning of line"
                       "No directive found at point"))
            nil)
        (let* ((which-type (cond ((not (null (match-string 2))) 'pc-hash-if)
                                 ((not (null (match-string 3))) 'pc-hash-else)
                                 ((not (null (match-string 4))) 'pc-hash-elif)
                                 ((not (null (match-string 5))) 'pc-hash-endif)
                                 (t nil))))
          (if (not (null quick))
              ;; should we return an atom or a cons cell?
              which-type ; nothing more to do, return the number
            
            (set 'pc-last-directive-type which-type)
            
            (if (or (eq which-type 'pc-hash-else) 
                    (eq which-type 'pc-hash-endif))
                ;; No condition required - return value
                (progn 
                  (message "else or endif")
                  (list which-type
                        (set 'pc-last-directive-deftype nil)
                        (set 'pc-last-directive-cond nil)))
                  
              ;; else it's an if or an elif
              (let* ((ndef-bit (and (not (eq which-type 'pc-hash-elif))
                                    (looking-at "n?def")
                                    (match-string 0)))
                     ;; c-beg is the beginning of a comment, or null
                     (c-beg (search-forward "/*" l-end t nil))
                     ;; a-end i either the end of line or before the comment
                     (a-end (if (null c-beg) l-end (- c-beg 2)))
                     ;; aux is the condition of the directive
                     (aux (progn (goto-char d-end)
                                 ;; skip over leading whitespace 
                                 ;; and ignore trailing whitespace
                                 (re-search-forward "\\s *\\(.*\\S \\)"
                                                    a-end t nil)
                                 (match-string 1))))
                (message "ndef-bit = %S" ndef-bit)
                (message "c-beg = %S" c-beg)
                (message "a-end = %S" a-end)
                (message "aux = %S" aux)
                
                (list which-type 
                      (set 'pc-last-directive-deftype ndef-bit)
                      (set 'pc-last-directive-cond aux))
                ) ; end let
              ) ; endif condition required
            ) ; endif null quick 
          ) ; end let
        ) ; endif not null d-end
      ) ; end let
    ) ; end save excursion
  ) ; end defun


(defun pc-preprocessor-enclosure (&optional forward skip-elses)
  "Finds the beginning or end of the enclosing preprocessor block.
Defaults - search forward, and ignore #else and #elif entirely.
Optional parameter FORWARD, if null means search forwards.
Optional parameter SKIP-ELSES, can be t, null or something else.
If null one can be enclosed within a #if #el*, or #el* #endif block.
If t all #elses or #elifs are skipped. Any other value skips #else 
but not #elif.
"
  (interactive "p") 
  ;;  (message "dir=%s elses=%s"
  ;;           (if forward "forward" "back")
  ;;           (if skip-elses "skip" "handle"))
  (let* ((on-if (if forward 1 -1))
         (search-fn (if forward 
                        're-search-forward 
                      're-search-backward))
         (search-str (concat "#\\s *\\(if" 
                             (cond ((eq skip-elses t) "") ; skip all elses
                                   ((null skip-elses) "\\|elif\\|else")
                                   (t "\\|elif"))
                             "\\|endif\\)\\(.*\\)"))
         (depth 0)
         (tmp-dtype nil)
         found)
    (save-excursion
      ;; if at beginning of line, move to previous line. this stops repeats
      (and forward (bolp) (forward-line)) 
      (while (and (not found)
                  (funcall search-fn search-str nil t))
        ;;        (message "%d) %s" depth (match-string 0))
        (beginning-of-line)
        (or (and (set 'tmp-dtype (pc-get-directive-type t))
                 ;;                 (message "dtype OK %S" tmp-dtype)
                 (cond ((eq tmp-dtype 'pc-hash-if) ; decrease depth, to -1?
                        (eq (setq depth (+ depth on-if)) -1))

                       ((eq tmp-dtype 'pc-hash-endif) ; increase depth, to -1?
                        (eq (setq depth (- depth on-if)) -1))
                       
                       ;; We can't match anything else unless we are
                       ;; not skipping elses.
                       ;; i.e. is this bit not needed?
                       ((or (eq skip-elses t) ; skip all elses
                            (and skip-elses (eq tmp-dtype 'pc-hash-else)))
                        nil)
                       
                       ;; We don't change the depth, but this would
                       ;; have taken us to -1 - so DO IT!
                       ;; either set depth to -1 (a lie) or set found (better)
                       ((equal depth 0) t)

                       ;; Catch-all - not found
                       (t nil))
                 (set 'found (point)))  ; end and (set and cond)
            
            ;; we didn't find a match, then get to depth -1,
            ;; get the hell off this line!
            (and (message "skip to next") forward (forward-line))))
      
      ;;      (message "Terminated while with depth %d found=%s" 
      ;;             depth
      ;;           (if (null found) "nil" "found"))
      )                                 ; end save excursion
    (if found 
        (goto-char found)
      (message "Couldn't find a match")
      nil)))


(defun pc-preprocessor-to-if () (interactive) (pc-preprocessor-enclosure nil t))
(defun pc-preprocessor-to-endif () (interactive) (pc-preprocessor-enclosure t t))
(defun pc-preprocessor-up-to-else () (interactive) (pc-preprocessor-enclosure nil nil))
(defun pc-preprocessor-down-to-else () (interactive) (pc-preprocessor-enclosure t nil))
(defun pc-preprocessor-up-to-el_if () (interactive) (pc-preprocessor-enclosure t 1))


(defun pc-modernise-ifdef (&optional my-hash)
  "Modern ANSI-ify #ifdef and #ifndef into #if defined and #if !defined\
It returns if a replacement was made."
  (interactive)
  (save-excursion
    (beginning-of-line)
    (message "my-hash = %S" my-hash)
    (let* ((this-hash (if (null my-hash) (pc-get-directive-type nil) my-hash))
           (oldtype (and this-hash (nth 1 this-hash)))
           (oldtype-n (and oldtype (string-equal oldtype "ndef"))))
      (message "this-hash = %S" this-hash)
      (message "oldtype = %S" oldtype)
      (message "oldtype-n = %S" oldtype-n)
      (if (null oldtype)
          (progn (message (if (null this-hash)
                            "Not on a directive"
                              "Not an old-style #ifn?def"))
                 nil)
        (search-forward oldtype nil t)
        (replace-match (if (string-equal oldtype "def") " defined" " !defined") t t)
        (message "replacing old-style #if%s %s" oldtype (nth 2 this-hash))
        t)
      nil)))


(defun pc-electric-endif (&optional nocomment)
  "Insert an endif and a comment describing the condition which has ended.
The comment will not be inserted if optional NOCOMMENT is t."
  (interactive "p")
  (let ((insertion 
         (if (eq nocomment t)
             "#endif"
           (save-excursion
             (let* ((conditional (pc-preprocessor-enclosure nil nil))
                    (cond-exp (and conditional (pc-get-directive-type)))
                    (negate-cond nil))
               (message "Conditional is %S" cond-exp)
               (if (not (eq (car cond-exp) 'pc-hash-else))
                   nil ; (message "Phew! not an else")
                 (message "Is an else - finding and negating the condition")
                 (set 'negate-cond t)
                 (set 'conditional (pc-preprocessor-enclosure nil 1)) ; skip elses
                 (set 'cond-exp (pc-get-directive-type))
                 ;; get the matching if or elif
                 (message "Conditional is now NOT %S" cond-exp))

               ;; we are now at an if or an elif
               (if (null conditional)
                   (progn 
                     (message "No #if or #elif found")
                     nil)
                 (if (not (and pc-auto-modernise-ifdef
                               (nth 1 cond-exp)))
                     (message "Not #if, not #ifn?def, or not auto-correcting")
                   ;; We must correct this
                   (pc-modernise-ifdef cond-exp)
                   (set 'cond-exp (pc-get-directive-type))
                   (message "Modernised ifdef"))
                 (format "#endif /* end %s %s */" 
                         (if negate-cond "else not" "if")
                         (nth 2 cond-exp))))))))

    (if (null insertion)
        (progn 
          (message "No modification made")
          nil)
      (pc-ensure-blank-line)
      (insert insertion)
      t)))

(defun pc-electric-else (&optional nocomment) 
  "Insert an else and a comment describing the condition which has ended.
The comment will not be inserted if optional NOCOMMENT is t."
  (interactive "p")
  (let ((insertion 
         (if (eq nocomment t)
             "#else"
           (save-excursion
             (let* ((conditional (pc-preprocessor-enclosure nil 1))
                    (cond-exp (and conditional (pc-get-directive-type))))
               (message "Conditional is %S" cond-exp)

               ;; we are now at an if or an elif
               (if (null conditional)
                   (progn 
                     (message "No #if or #elif found")
                     nil)
                 (if (not (and pc-auto-modernise-ifdef
                               (nth 1 cond-exp)))
                     (message "Not #if, not #ifn?def, or not auto-correcting")
                   ;; We must correct this
                   (pc-modernise-ifdef cond-exp)
                   (set 'cond-exp (pc-get-directive-type))
                   (message "Modernised ifdef"))
                 (format "#else /* %s not %s */" 
                         (if (eq (car cond-exp) 'pc-hash-if) "else" "elif")
                         (nth 2 cond-exp))))))))

    (if (null insertion)
        (progn 
          (message "No modification made")
          nil)
      (pc-ensure-blank-line)
      (insert insertion)
      t)))

(defun pc-hashout-region (start end)
  "Use #if 0 to remove a chunk of code."
  (interactive "r")
  (let ((opoint (point))
        (beg start)
        (fin end))
    (goto-char end)
    (pc-ensure-blank-line)
    (insert (format "#endif /* #endif 0 (auto-hashout %d-%d) */" beg fin))
    (goto-char start)
    (pc-ensure-blank-line)
    (insert (format "#if 0 /* (auto-hashout %d-%d) */" beg fin))))


(defun pc-unhashout-smallest-enclosure ()
  "If strictly between a #if and a #endif, remove those directives"
  (interactive)
  (save-excursion
    (let* ((top (pc-preprocessor-enclosure t nil))
           (topif (and top (pc-get-directive-type t)))
           (bot (and (eq topif 'pc-hash-if)
                     (pc-preprocessor-enclosure nil nil)))
           (botendif (and bot (pc-get-directive-type t))))
      (and botendif
           (progn
             (goto-char top)
             (re-search-forward ".*\n")
             (replace-match ""))))))

(defun pc-preprocessor-h-exclude ()
  "If in a .h file, insert #if/#endif exclusions"
  (interactive)
  (save-excursion
    (let* ((fname buffer-file-name)
           (ucfname (upcase fname)))
      (beginning-of-buffer)
      ;; A rudimentary check for comments at the top of the file
      (if (looking-at "/*")
          (search-forward "*/"))
      (message ucfname)
      (pc-ensure-blank-line)
      (insert ucfname)
      (beginning-of-line)
      (re-search-forward "^/.*/")
      (replace-match "#if !defined(_" t nil)
      (re-search-forward "[.]H$")
      (replace-match "_H_INCLUDED)" t nil)
      (end-of-buffer)
      (pc-electric-endif))))


(progn
  ;; Why do these not work - perhaps I should move them?
  (defvar pc-c-hash-map nil "map for C preprocessor functions")
  (define-prefix-command 'pc-c-hash-map)

  (define-key pc-c-hash-map [?0] 'pc-hashout-region)

  ;; toggle auto-modernise
  (define-key
    pc-c-hash-map [?a] 
    '(lambda () 
       (interactive)
       (message "%sautomatically modernising #ifdef"
                (if (set 'pc-auto-modernise-ifdef 
                         (null pc-auto-modernise-ifdef))
                    ""
                  "Not "))))
  (define-key pc-c-hash-map [?m] 'pc-modernise-ifdef)
  (define-key pc-c-hash-map [?f] 'pc-electric-endif)
  (define-key pc-c-hash-map [?e] 'pc-electric-else)
  (define-key pc-c-hash-map [?l] 'pc-electric-elif)  
  (define-key pc-c-hash-map [?h] 'pc-preprocessor-h-exclude)
  (define-key pc-c-hash-map [down] 'pc-preprocessor-to-endif)
  (define-key pc-c-hash-map [up] 'pc-preprocessor-to-if)
  (define-key pc-c-hash-map [C-down] 'pc-preprocessor-down-to-else)
  (define-key pc-c-hash-map [M-down] 'pc-preprocessor-down-to-else)
  (define-key pc-c-hash-map [C-up] 'pc-preprocessor-up-to-else))

;;; END Short-cuts for preprocessor stuff in C mode


--- 8< ---

-- 
Dear aunt, let's set so double the killer delete select all.
-- Microsoft voice recognition live demonstration


      reply	other threads:[~2008-06-13 15:52 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-06-13 15:01 jumping through ifdefs Rafal Kurcz
2008-06-13 15:52 ` Phil Carmody [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87od65ib7w.fsf@nonospaz.fatphil.org \
    --to=thefatphil_demunged@yahoo.co.uk \
    --cc=help-gnu-emacs@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).