unofficial mirror of help-gnu-emacs@gnu.org
 help / color / mirror / Atom feed
From: "Pascal J. Bourguignon" <pjb@informatimago.com>
To: help-gnu-emacs@gnu.org
Subject: Re: Compilation warnings of ELisp seem wrong and misleading
Date: Mon, 30 Mar 2015 19:08:26 +0200	[thread overview]
Message-ID: <87wq1ywgz9.fsf@kuiper.lan.informatimago.com> (raw)
In-Reply-To: mailman.3032.1427730039.31049.help-gnu-emacs@gnu.org

"Ludwig, Mark" <ludwig.mark@siemens.com> writes:

> "Pascal J. Bourguignon" wrote:
>
>> "Ludwig, Mark" <ludwig.mark@siemens.com> writes:
>> 
>> > Greetings,
>> >
>> > I've been using an ancient Emacs (19.29) on Solaris and finally
>> > got around to installing a current one (24.4).
>> >
>> > I find my custom Elisp generates warnings that seem pretty stupid.
>> > For example:
>> >
>> > emacs.el:255:10:Warning: reference to free variable `if'
>> > emacs.el:219:8:Warning: reference to free variable `save-excursion'
>> > emacs.el:331:41:Warning: reference to free variable `forward-char'
>> > emacs.el:261:17:Warning: reference to free variable `insert'
>> > emacs.el:261:17:Warning: reference to free variable `forward-sexp'
>> >
>> > Those are all valid functions.  For example, here are lines 255-258:
>> >
>> > 	(if (not (= ans ?q))
>> > 	    (progn
>> > 	      (goto-char found-start)
>> > 	      (delete-region found-start found-end)))
>> >
>> > This is inside a large-ish "let*" form (111 lines).
>
>> We cannot see either, because the meaning of a sexp is determined by its
>> surrounding form, which you didn't provide.
>> 
>> You probably have a parenthesis problem in your let* form which makes
>> lisp interpret some parts as being variable names instead of operators.
>
> No, I know how to balance parentheses.  I've been
> programming Emacs Lisp for almost 30 years.  I rely on Emacs
> for its outstanding job of showing structural problems
> through its automatic indentation (in all programming
> languages, but certainly within its own).  By all means, if
> there's anything wrong with that (if ...) form, please
> explain.
>
> For context, the "let*" is on line 239; the warning
> (mentioned above as being on line 255 column 10) is
> referring to the form after the comment "Make the
> substitution."  If you read the warnings carefully, you'll
> note that it warns about two different things on line 261,
> column 17.  That line is the one with the comment, 'Now it's
> "strlcpy" or whatever.'  Clearly, the byte-compiler is
> pretty confused....


No it is not confused.  
The warning are correct.  
However, it reports them at seemingly wrong places.

First, the code you gave has indeed 5 free variables.  It should not
compile and it should not run!  I had to add them to be able to compile
it, in a surrounding let.

Second, the code you gave is ill-formatted.  How hard is it to put the
closing parentheses on the same line (try to avoid ;-comments on the
last expression before a closing parenthesis), and how hard is it to
type M-q to have emacs indent your code correctly?!?

Next, the warning reported:

Compiling file /tmp/e.el at Mon Mar 30 18:38:30 2015
e.el:65:14:Warning: reference to free variable `if'
e.el:90:33:Warning: reference to free variable `save-excursion'
e.el:99:33:Warning: reference to free variable `forward-char'
e.el:32:12:Warning: reference to free variable `insert'
e.el:32:12:Warning: reference to free variable `forward-sexp'
e.el:110:33:Warning: reference to free variable `c-indent-exp'

correspond indeed to those symbols used in variable position, as cond
expressions!  Which can be clearly seen, when you have your code
indented properly, starting from line 85 below:


     1	(let ((from  "from")
     2	      (to1   "T")
     3	      (to2   "t")
     4	      (found-start 0)
     5	      (found-end   2))
     6	 (let* ((c1 (string-to-char to1))
     7	        (c2 (string-to-char to2))
     8	        (ans 0))
     9	
    10	   ;; Get one of the first characters from the two substitutions
    11	
    12	   (while (and (not (= ans c1))
    13	               (not (= ans c2))
    14	               (not (= ans ?q)))     ; Quit, don't substitute anything
    15	     (setq ans (read-char-exclusive
    16	                (concat "Replace " from " with "
    17	                        to1 " (will truncate silently) or "
    18	                        to2 " (dstat)? (" (char-to-string c1) "/" (char-to-string c2) "/q) "))))
    19	
    20	   ;; Make the substitution
    21	
    22	   (if (not (= ans ?q))
    23	       (progn
    24	         (goto-char found-start)
    25	         (delete-region found-start found-end)))
    26	
    27	   (cond ((= ans c1)
    28	          (insert to1)                ; Now it's "strlcpy" or whatever
    29	          )
    30	
    31	         ((= ans c2)
    32	          (insert "dstat = " to2) ; Now it's "dstat = nlsStrLCpy" or whatever
    33	          (back-to-indentation)
    34	          (setq ans 0)
    35	          (while (and (not (= ans ?d))
    36	                      (not (= ans ?e))
    37	                      (not (= ans ?n)))
    38	            (setq ans (read-char-exclusive "Use DSOK/EXIT/no form? ")))
    39	
    40	          (let ((if-pos (point)))
    41	
    42	            (cond ((= ans ?d)
    43	                   (insert "if (DSOK) ")
    44	                   (forward-sexp 3) ; After the closing parenthesis for the function call
    45	                   )
    46	
    47	                  ((= ans ?e)
    48	                   (insert "if (")
    49	                   (forward-sexp 3) ; After the closing parenthesis for the function call
    50	                   (insert ")")
    51	                   (newline-and-indent)
    52	                   (insert "goto EXIT") ; The semi-colon should end up after "EXIT"
    53	                   (c-indent-command) ; Make the "goto" be correctly indented under the "if"
    54	                   )
    55	
    56	                  ((= ans ?n)
    57	                   ;; no form
    58	                   ))
    59	
    60	            ;; Try to look ahead, in case the function call we just
    61	            ;; modified was a one-statement "block" in an "if," so
    62	            ;; we've just unintentionally modified the meaning of
    63	            ;; that "if" statement
    64	           
    65	            (if (not (= ans ?n))
    66	                (condition-case
    67	                 nil
    68	                 (progn
    69	                   (forward-sexp 1)   ; Skip over "else" if it's there
    70	                   (forward-sexp -1) ; Back in front of "else" if it's there
    71	                   (if (looking-at "else")
    72	                       (progn
    73	                         (goto-char if-pos) ; Go back to where we inserted "if"
    74	
    75	                         (cond ((= ans ?d)
    76	                                ;; Skip over: "if" "(DSOK)" "dstat" "= ..." "()"
    77	                                (forward-sexp 5)
    78	                                )
    79	
    80	                               ((= ans ?e)
    81	                                ;; Skip over:
    82	                                ;; "if" "(dstat = ...)" "goto" "EXIT"
    83	                                (forward-sexp 4))
    84	
    85	                               (if (not (looking-at ";"))
    86	                                   (error "First expected ';' not: \"%s\""
    87	                                          (buffer-substring (point)
    88	                                                            (progn (end-of-line 1)
    89	                                                                   (point)))))
    90	                               (save-excursion
    91	                                (goto-char if-pos) ; Go back to where we inserted "if"
    92	                                (insert "{
    93	"))
    94	                               (if (not (looking-at ";"))
    95	                                   (error "Second expected ';' not: \"%s\""
    96	                                          (buffer-substring (point)
    97	                                                            (progn (end-of-line 1)
    98	                                                                   (point)))))
    99	                               (forward-char 1)
   100	
   101	                               ;; We should be looking at the end of line
   102	                               (if (not (looking-at "\\s-*$"))
   103	                                   (error "Second expected end of line, not: \"%s\""
   104	                                          (buffer-substring (point)
   105	                                                            (progn (end-of-line 1)
   106	                                                                   (point)))))
   107	                               (insert "
   108	}")
   109	                               (forward-sexp -1)
   110	                               (c-indent-exp)
   111	                               ))))
   112	                 (error nil)          ;hand-rolled (ignore-errors ...)
   113	                 )))))))


You may indeed report an emacs bug, that the warnings refer the first
occurences of the symbol, instead of the erroneous occurences.


Finally, you should have expressions so long. You should write short
functions, and use them in shorter forms.


Your code should be replaced with a single simple function:

(defun do-something (from to1 to2 found-start found-end)
  (let* ((c1 (string-to-char to1))
         (c2 (string-to-char to2))
         (ans (get-one-of-the-first-characters-from-the-two-substitutions from to1 to2 c1 c2)))
    (unless (= ans ?q)
      (make-the-substitution ans from to1 to2 c1 c2 found-start found-end))))


;; which uses the two simple and clear functions:

(defun get-one-of-the-first-characters-from-the-two-substitutions (from to1 to2 c1 c2)
  (let ((ans 0))
    (while (and (/= ans c1)
                (/= ans c2)
                (/= ans ?q))          ; Quit, don't substitute anything
      (setq ans (read-char-exclusive
                 (concat "Replace " from " with "
                         to1 " (will truncate silently) or "
                         to2 " (dstat)? (" (char-to-string c1) "/" (char-to-string c2) "/q) "))))
    ans))


(defun make-the-substitution (ans from to1 to2 c1 c2 found-start found-end)
  (assert (/= ans ?q))
  (goto-char found-start)
  (delete-region found-start found-end)
  (cond ((= ans c1) (insert to1))
        ((= ans c2) (insert-dstat ans from to2 c2 found-start found-end))))


;; Also notice how they can very easily be tested alone:

;; (get-one-of-the-first-characters-from-the-two-substitutions "from" "T" "t" ?T ?t)
;; (make-the-substitution ?T "from" "T" "t" ?T ?t (point) (point))


;; With make-the-substitution itself using the following functions:

(defun get-dstat-choice ()
  (let ((ans 0))
    (while (and (/= ans ?d)
                (/= ans ?e)
                (/= ans ?n))
      (setq ans (read-char-exclusive "Use DSOK/EXIT/no form? ")))
    ans))

;; again, very easily testable: (get-dstat-choice)


(defun insert-dstat (ans from to2 c2 found-start found-end)
  (insert "dstat = " to2) ; Now it's "dstat = nlsStrLCpy" or whatever
  (back-to-indentation)
  (let ((ans    (get-dstat-choice))
        (if-pos (point)))
    (unless (= ans ?n)

      (case ans
        (?d
         (insert "if (DSOK) ")
         (forward-sexp 3))
        (?e
         (insert "if (")
         (forward-sexp 3) ; After the closing parenthesis for the function call
         (insert ")")
         (newline-and-indent)
         (insert "goto EXIT") ; The semi-colon should end up after "EXIT"
         (c-indent-command)))

      ;; Try to look ahead, in case the function call we just
      ;; modified was a one-statement "block" in an "if," so
      ;; we've just unintentionally modified the meaning of
      ;; that "if" statement
      
      (forward-sexp 1)  ; Skip over "else" if it's there
      (forward-sexp -1) ; Back in front of "else" if it's there
      (when (looking-at "else")
        (goto-char if-pos) ; Go back to where we inserted "if"
        (case ans
          (?d ;; Skip over: "if" "(DSOK)" "dstat" "= ..." "()"
           (forward-sexp 5))
          (?e
           ;; Skip over:
           ;; "if" "(dstat = ...)" "goto" "EXIT"
           (forward-sexp 4))
          (otherwise ;; perhaps you didn't mean otherwise, but end of
                     ;; the case?  You'll have to debug it here.
           (unless (looking-at ";")
             (error "First expected ';' not: \"%s\""
                    (buffer-substring (point)
                                      (progn (end-of-line 1)
                                             (point)))))
           (save-excursion
            (goto-char if-pos) ; Go back to where we inserted "if"
            (insert "{\n"))
           (unless (looking-at ";")
             (error "Second expected ';' not: \"%s\""
                    (buffer-substring (point)
                                      (progn (end-of-line 1)
                                             (point)))))
           (forward-char 1)
           ;; We should be looking at the end of line
           (unless  (looking-at "\\s-*$")
             (error "Second expected end of line, not: \"%s\""
                    (buffer-substring (point)
                                      (progn (end-of-line 1)
                                             (point)))))
           (insert "\n}")
           (forward-sexp -1)
           (c-indent-exp)))))))


;; you an then call your functions from the toplevel:

(let ((from  "from")
      (to1   "T")
      (to2   "t")
      (found-start 0)
      (found-end   2))
  (do-something from to1 to2 found-start found-end))



-- 
__Pascal Bourguignon__                 http://www.informatimago.com/
“The factory of the future will have only two employees, a man and a
dog. The man will be there to feed the dog. The dog will be there to
keep the man from touching the equipment.” -- Carl Bass CEO Autodesk


  parent reply	other threads:[~2015-03-30 17:08 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <mailman.3018.1427724507.31049.help-gnu-emacs@gnu.org>
2015-03-30 14:39 ` Compilation warnings of ELisp seem wrong and misleading Pascal J. Bourguignon
2015-03-30 15:40   ` Ludwig, Mark
2015-03-30 16:59     ` tomas
2015-03-30 19:09       ` Ludwig, Mark
     [not found]   ` <mailman.3032.1427730039.31049.help-gnu-emacs@gnu.org>
2015-03-30 17:08     ` Pascal J. Bourguignon [this message]
2015-03-31  0:53 ` Emanuel Berg
2015-04-01 11:45   ` Ludwig, Mark
2015-04-01 12:02     ` tomas
2015-04-02 10:20     ` Philipp Stephani
     [not found]   ` <mailman.3123.1427888711.31049.help-gnu-emacs@gnu.org>
2015-04-01 23:08     ` Emanuel Berg
2015-03-30 13:47 Ludwig, Mark

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=87wq1ywgz9.fsf@kuiper.lan.informatimago.com \
    --to=pjb@informatimago.com \
    --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).