TheSwamp

Code Red => AutoLISP (Vanilla / Visual) => Topic started by: David Hall on August 08, 2012, 05:05:30 PM

Title: Abort and return to command prompt
Post by: David Hall on August 08, 2012, 05:05:30 PM
In C# you can use Return to cancel out and return to command prompt, how do you do this in LISP?
Title: Re: Abort and return to command prompt
Post by: David Hall on August 08, 2012, 05:10:51 PM
Nevermind, I found it
(exit)
Title: Re: Abort and return to command prompt
Post by: CAB on August 08, 2012, 08:17:01 PM
I find it a rare occasion that I need EXIT?
Title: Re: Abort and return to command prompt
Post by: danallen on August 08, 2012, 08:42:03 PM
if a DCL can't be found? Saves having a wrapping if statement.
Title: Re: Abort and return to command prompt
Post by: Kerry on August 08, 2012, 08:53:58 PM
There is a difference in functionality :

.NET return
returns control to the calling method and can optionally return a value.

the lisp (exit)
returns the error message quit/exit abort and quits to the AutoCAD Command prompt.
.... it's handy to have a good *error* routine so that the environment will be cleaned up.
Title: Re: Abort and return to command prompt
Post by: BlackBox on August 08, 2012, 10:25:23 PM
As Kerry has suggested, I use a localized *error* sub-function for Commands that either change settings, or create external objects that must be released, in addition to ending an undo mark when applicable.

In combination with, or in lieu of, I usually use some sort of test function (IF + AND being my most common) to only perform the minimal task(s) necessary before the automated portion of my code. Less to 'handle' if the user hits escape.

HTH
Title: Re: Abort and return to command prompt
Post by: irneb on August 09, 2012, 03:24:37 AM
You could use the vl-exit-with-value (http://docs.autodesk.com/ACD/2011/ENU/filesALR/WS1a9193826455f5ff1a32d8d10ebc6b7ccc-68ce.htm) to get something similar to C#/C++'s return keyword. Unfortunately though it only works from inside a compiled VLX using a separate namespace (if you use it inside the document namespace - i.e. a same namespace VLX or a FAS/LSP file - it acts like exit but without calling the *error* function or returning a value)  :| ... why that should be you'd have to ask ADesk. My guess is it "exits the namespace, rather than the defun". Same goes for the vl-exit-with-error (http://docs.autodesk.com/ACD/2011/ENU/filesALR/WS1a9193826455f5ff1a32d8d10ebc6b7ccc-68d0.htm) it seems. At least from my quick tests here in Vanilla 2012:
Code - Auto/Visual Lisp: [Select]
  1. (defun test1 ()
  2.   (vl-exit-with-value "testing vl-exit-with-value")
  3.   1)
  4.  
  5. (defun test2 ()
  6.   (vl-exit-with-error "testing vl-exit-with-error")
  7.   2)
  8.  
  9. (defun c:test (/ *error*)
  10.   (defun *error* (msg) (princ "\nError:\t") (princ msg) (princ))
  11.   (princ (test1))
  12.   (princ (test2))
  13.   (princ))
Both those simply exit to command prompt without returning anything or calling the localized *error* defun at all.

I'd go with Kerry & RM's idea though! There's nothing worse than a user finding his OSnaps turned off after using your custom command. Even in C#/C++ you need to watch for this. If your defun doesn't alter stuff which should revert back, then you may not need to exit cleanly - but there's many times that you'd set some sysvar to make your programming work less or to make the user-interface more to your liking. In which case an error-handler is an absolute MUST.

Actually there's some debate about the use of return / break to stop a function / loop halfway through (even in C/C++/C#). Some deem it a form of "lazy programming". Their main gripe is exactly this "cleanup" idea - though they tend to use memory leaks as an example (which is less of an issue in lisp).

E.g. If your C function allocates memory dynamically (say using malloc or in C++ new) you should either return the pointer to that allocated memory or release it using free / delete. It just becomes very easy to forget to do so if you use return / break somewhere halfway through your code. I mean you look through your code and see that you've written the free / delete so memory shouldn't "leak". But you miss the line where you return / break earlier from the function / loop - if that return/break is reached the memory is never de-allocated, making your function eat RAM every time it's used.
Title: Re: Abort and return to command prompt
Post by: Lee Mac on August 09, 2012, 08:05:59 AM
I find it a rare occasion that I need EXIT?

Ditto, I can't remember ever using it...
Title: Re: Abort and return to command prompt
Post by: owenwengerd on August 09, 2012, 10:39:06 AM
Many years ago Tony and I argued in a very long thread about using (exit). I think that was on the AutoCAD discussion groups, but maybe it was on CompuServe. I use (exit) frequently, or at least I used to when I still wrote a lot of lisp functions.
Title: Re: Abort and return to command prompt
Post by: MP on August 09, 2012, 10:39:45 AM
Somewhat contrived use of (exit):

Code: [Select]
(defun _Document_Has_XRefs ( document / result )

    (vl-catch-all-apply
        (function
            (lambda ( )
                (vlax-for block (vla-get-blocks document)
                    (if (eq :vlax-true (vla-get-isxref block))
                        (progn
                            (setq result T)
                            (exit)
                        )
                    )
                )
            )       
        )       
    )

    result

)

Cheers.
Title: Re: Abort and return to command prompt
Post by: CAB on August 09, 2012, 10:44:37 AM
Quote
but maybe it was on CompuServe.
Now you are showing your age.  8-)
Title: Re: Abort and return to command prompt
Post by: dgorsman on August 09, 2012, 11:30:23 AM
I usually have a lot of "stuff" going on in the LISP functions, and trying to write error handlers to clean up everything everywhere is somewhat tedious (I use a global handler plus standardized handling for for general clean-up).  So I reserve (exit) calls to signal the somethings-REALLY-wrong-here happenings.
Title: Re: Abort and return to command prompt
Post by: irneb on August 09, 2012, 12:57:35 PM
I'm in agreement, exit may be used - it exists after all. It's just that you need to take note that stuff not handled by the garbage collector needs to be handled in the error handler. Whether you create a localized *error* or a common global version is your choice - as long as you don't run into situations like sysvars staying with temporary values, or vla-SelectionSets not deleted, or external ActiveX objects not released - those need to be handled by your error routine(s) if you do use exit. I'd actually say they need to be handled even if you don't use exit - it's just safer that way.

For me, the exit is rather useless - it's more like the abort / exit functions of C++. If it worked exactly as the C return keyword works, then it might have been more useful. As it stands, exit means (to me at least) that something's gone wrong and I need to cancel everything (exactly as if the user pressed ESC).
Title: Re: Abort and return to command prompt
Post by: BlackBox on August 09, 2012, 01:02:48 PM
As it stands, exit means (to me at least) that something's gone wrong and I need to cancel everything (exactly as if the user pressed ESC).

... And this is where I chose to rely on *error* instead, as if the user pressed ESC, then anything needing to be handled (i.e., sysvars, external objects, etc.) are handled regardless.

For successful completion of code execution I send (*error* nil), whereas 'if' certain criteria is not met first, a CONDitional 'else' expression is used to report to the user what they did wrong (also using *error*, simply changing the MSG argument).
Title: Re: Abort and return to command prompt
Post by: David Hall on August 09, 2012, 01:05:30 PM
What I am doing is checking a value for inserting a switch in vertical space.  What I needed was a way to tell the operator they attempted to insert a part below the NESC safety clearance.  Here is the code

Code: [Select]
(if (= (strcase BsHgt) "LOW")
    (progn
      (if (< *LOWBUS* 98)
   (progn
     (aLERT "Too Low!")
     (exit)
   )
      )


    )
  )


Now that we have mentioned the error routine, I dont have one.  I rarely write in LISP, so I could use good examples.
Title: Re: Abort and return to command prompt
Post by: David Hall on August 09, 2012, 01:07:02 PM
the above could be combined using the LISP version of AND, once I learn how
Title: Re: Abort and return to command prompt
Post by: irneb on August 09, 2012, 01:12:45 PM
Somewhat contrived use of (exit):

Code: [Select]
(defun _Document_Has_XRefs ( document / result )

    (vl-catch-all-apply
        (function
            (lambda ( )
                (vlax-for block (vla-get-blocks document)
                    (if (eq :vlax-true (vla-get-isxref block))
                        (progn
                            (setq result T)
                            (exit)
                        )
                    )
                )
            )       
        )       
    )

    result

)

Cheers.
I think this would always return nil. Seeing as the first block in the blocks collection would be the model space - which isn't an xref. And thus the exit is called on the first block. Though you do mention it's a contrived usage  ;)

Edit:
*allowing time for post edit before posting correction*
Sorry ... yes I missed the progn when reading this.  :-[ Stupid of me. Sorry MP! It's novel I must say!
Title: Re: Abort and return to command prompt
Post by: David Hall on August 09, 2012, 01:14:39 PM
do you have a non-vlisp example?  I haven't learned all the Vlisp commands
Title: Re: Abort and return to command prompt
Post by: Lee Mac on August 09, 2012, 01:20:59 PM
I think this would always return nil. Seeing as the first block in the blocks collection would be the model space - which isn't an xref. And thus the exit is called on the first block.

*allowing time for post edit before posting correction*
Title: Re: Abort and return to command prompt
Post by: David Hall on August 09, 2012, 01:31:51 PM
This was what I used as an example
http://www.theswamp.org/index.php?topic=50.msg366#msg366 (http://www.theswamp.org/index.php?topic=50.msg366#msg366)
Title: Re: Abort and return to command prompt
Post by: irneb on August 09, 2012, 01:48:50 PM
do you have a non-vlisp example?  I haven't learned all the Vlisp commands
Here's  simplistic idea to show something which happens quite often:
Code - Auto/Visual Lisp: [Select]
  1. (defun c:DrawOneLine  (/ pt1 pt2 osmode cmdecho *error*)
  2.   (defun *error*  (msg)
  3.     (and osmode (setvar "OSMODE" osmode))
  4.     (and cmdecho (setvar "CMDECHO" cmdecho))
  5.     (cond ((not msg))
  6.           ((wcmatch (strcase msg) "*EXIT*,*ABORT*,*QUIT*"))
  7.           (t (princ msg)))
  8.     (princ))
  9.   (if (and (setq pt1 (getpoint "Pick line's start point: "))
  10.            (setq pt2 (getpoint pt1 "Pick line's end point: ")))
  11.     (progn (setq osmode  (getvar "OSMODE")
  12.                  cmdecho (getvar "CMDECHO"))
  13.            (setvar "OSMODE" 0)
  14.            (setvar "CMDECHO" 0)
  15.            (command "_.LINE" pt1 pt2 "")))
  16.   (*error* nil))
There are better way to write the above. But it serves much like RenderMan's situation - i.e. use the *error* routine to clean up even if there was no error.

Say the above is started transparently during another command. The line command would fail with a "Function canceled" error message. The *error* defun would still ensure that osmode & cmdecho are reset back to what they were.

Also if the user presses ESC (or you add an exit, perhaps due to a point not inside a defined area) that message won't be displayed on the command-line. If you want it displayed then omit that wcmatch line, or modify for other messages.
Title: Re: Abort and return to command prompt
Post by: Eddie D. on August 09, 2012, 01:58:15 PM
Not related to your topic, CMDRDUH, but it's good to see someone else on The Swamp that does substation design!
Title: Re: Abort and return to command prompt
Post by: Kerry on August 09, 2012, 04:20:17 PM
the above could be combined using the LISP version of AND, once I learn how

Perhaps something like this David

Code - Auto/Visual Lisp: [Select]
  1.  
  2.         (= (strcase bshgt) "LOW")
  3.         (< *LOWBUS* 98)
  4.     )
  5.     (progn
  6.         (alert "Too Low!")
  7.         (exit)
  8.     )
  9. )
  10.  
  11.  

Just yell if you need help

Stay well,
Kerry
Title: Re: Abort and return to command prompt
Post by: David Hall on August 09, 2012, 04:25:05 PM
Thats the ticket Kerry! thanks.  I knew it would be easy.  I am still thinking in .Net and cant remember the old LISP ways.
Title: Re: Abort and return to command prompt
Post by: Lee Mac on August 09, 2012, 05:22:17 PM
Here is how I might code it:

Code - Auto/Visual Lisp: [Select]
  1.     (   (and (= (strcase bshgt) "LOW") (< *LOWBUS* 98))
  2.         (alert "Too Low!")
  3.     )
  4.     (   t
  5.         <Rest of Program>
  6.     )
  7. )

This avoids the need to use exit to force an error since the program will cease evaluation of the cond expression when a test expression returns a non-nil value. Of course, in this particular case where there is only a single condition to be tested, an if expression could also be used in place of the cond expression, however, usually there is more than one error condition to be checked, and so cond becomes more suitable (and readable) than multiple nested if statements.

In fact, I use this construct in almost all of my programs which utilise DCL, as, in my opinion, it is cleaner than using exit, doesn't necessarily require an *error* handler, and furthermore retains the flow of the code (i.e. the program is being evaluated line by line rather than aborting).

My 2p  :-)
Title: Re: Abort and return to command prompt
Post by: owenwengerd on August 09, 2012, 06:21:52 PM
Here is how I might code it:

Code - Auto/Visual Lisp: [Select]
  1.     (   (and (= (strcase bshgt) "LOW") (< *LOWBUS* 98))
  2.         (alert "Too Low!")
  3.     )
  4.     (   t
  5.         <Rest of Program>
  6.     )
  7. )

I think this makes code more readable:

Code - Text: [Select]
  1. (if (and (= (strcase bshgt) "LOW") (< *LOWBUS* 98))
  2.   (progn (alert "Too Low!") (exit)))
  3.  
  4. (if (failed-p (test1))
  5.   (exit))
  6.  
  7. (if (failed-p (test2))
  8.   (exit))
  9.  

I want the error condition to cause an immediate failure without getting propagated and without being hidden intside a fancy structure (or indented 20 spaces), and I want it to be obvious when scanning the code 30 years later what the control flow is here.
Title: Re: Abort and return to command prompt
Post by: Lee Mac on August 09, 2012, 06:33:28 PM
I shan't argue with your evident programming expertise Owen and I respect your opinion, but I can't say that this structure:

Code - Auto/Visual Lisp: [Select]
  1. (if (failed-p (test1))
  2.     (progn
  3.         (princ "\nTest1 failed.")
  4.         (exit)
  5.     )
  6. )
  7.  
  8. (if (failed-p (test2))
  9.     (progn
  10.         (princ "\nTest2 failed.")
  11.         (exit)
  12.     )
  13. )
  14.  
  15. (if (failed-p (test2))
  16.     (progn
  17.         (princ "\nTest1 failed.")
  18.         (exit)
  19.     )
  20. )

is any more readable than:

Code - Auto/Visual Lisp: [Select]
  1.     (   (failed-p (test1))
  2.         (princ "\nTest1 failed.")
  3.     )
  4.     (   (failed-p (test2))
  5.         (princ "\nTest2 failed.")
  6.     )
  7.     (   (failed-p (test3))
  8.         (princ "\nTest3 failed.")
  9.     )
  10. )

I don't think the level of indentation plays any part since both would have to be indented to the level at which the conditions are to be tested in any case.
Title: Re: Abort and return to command prompt
Post by: Kerry on August 09, 2012, 10:21:29 PM
< .. >
I want the error condition to cause an immediate failure without getting propagated and without being hidden intside a fancy structure (or indented 20 spaces), and I want it to be obvious when scanning the code 30 years later what the control flow is here.

I concur !


I shan't argue with your evident programming expertise Owen and I respect your opinion, but I can't say that this structure:

Code - Auto/Visual Lisp: [Select]
  1. (if (failed-p (test1))
  2.     (progn
  3.         (princ "\nTest1 failed.")
  4.         (exit)
  5.     )
  6. )
  7.  
  8. (if (failed-p (test2))
  9.     (progn
  10.         (princ "\nTest2 failed.")
  11.         (exit)
  12.     )
  13. )
  14.  
  15. (if (failed-p (test2))
  16.     (progn
  17.         (princ "\nTest1 failed.")
  18.         (exit)
  19.     )
  20. )

is any more readable than:

Code - Auto/Visual Lisp: [Select]
  1.     (   (failed-p (test1))
  2.         (princ "\nTest1 failed.")
  3.     )
  4.     (   (failed-p (test2))
  5.         (princ "\nTest2 failed.")
  6.     )
  7.     (   (failed-p (test3))
  8.         (princ "\nTest3 failed.")
  9.     )
  10. )

I don't think the level of indentation plays any part since both would have to be indented to the level at which the conditions are to be tested in any case.

Lee, You're assuming the assertion tests can all be collected together.
I've never had a situation where that can be achieved that I recall.
... Particularly when dealing with code that is any considerable length.

Regards
Kerry
Title: Re: Abort and return to command prompt
Post by: Kerry on August 09, 2012, 10:27:16 PM

Just for an option.
This is really old .. and it is called innumerable times each day.

Code - Auto/Visual Lisp: [Select]
  1.  
  2. ;;;------------------------------------------------------------------
  3. ;;;------------------------------------------------------------------
  4. ;;;
  5. ;;; Spit the Dummy if 'testStatement evaluates to nil
  6. ;;; argument must be quoted
  7. ;;;   (setq var 0)
  8. ;;;   (KDUB:assert 'var "Symbol 'VAR' has no value assigned.")
  9. ;;;   (KDUB:assert '(not(zerop var)) "Symbol 'VAR' evaluates to 0 ... this is illegal ... go to jail")
  10. ;;;   (KDUB:assert '(> var 0) nil)
  11. ;;;   (KDUB:assert '(< var 1) nil)
  12. (defun kdub:assert (teststatement message)
  13.   (if (not (eval teststatement))
  14.     (progn (kdub:display-message
  15.              " **** Assertion Failure"
  16.              (if message
  17.                message
  18.                (list (vl-prin1-to-string teststatement) " ==>> nil/null")
  19.              )
  20.            )
  21.            ;; decide what to do here later ?
  22.            (if kglobal:debug_on
  23.              (princ "  ")
  24.              (exit)
  25.            )
  26.     )
  27.   )
  28.   (princ)
  29. )
  30. ;;;------------------------------------------------------------------
  31. ;;;------------------------------------------------------------------
  32.  
  33.  
Title: Re: Abort and return to command prompt
Post by: Kerry on August 09, 2012, 10:45:47 PM

This goes with that :)

Code - Auto/Visual Lisp: [Select]
  1.  
  2. ;;;------------------------------------------------------------------
  3. ;;;
  4. ;;; (KDUB:display_formatted_message prefix mess )
  5. ;;; kwb 20020715
  6. ;|
  7. prefix : String or nil
  8. mess   : atom or list comprising STRing, INT, REAL,
  9.          SYMbol, ENAME, VLA-OBJECT, FILEid etc
  10. |;
  11. ;;;
  12. ;;; Print a formatted message to the command line
  13. ;;;
  14.  
  15. (defun kdub:display-message (prefix mess)
  16.           (list "\n"
  17.                 (if (= (type prefix) 'str)
  18.                   prefix
  19.                   "; **** ERROR"
  20.                 )
  21.                 ": "
  22.           )
  23.   )
  24.   (if (vl-consp mess)
  25.     (mapcar 'princ mess)
  26.     (princ mess)
  27.   )
  28.   (princ)
  29. )
  30.  
  31. ;;;------------------------------------------------------------------
  32. ;;;------------------------------------------------------------------
  33.  

.. thanks John boy :)
Title: Re: Abort and return to command prompt
Post by: owenwengerd on August 09, 2012, 11:10:19 PM
I don't think the level of indentation plays any part since both would have to be indented to the level at which the conditions are to be tested in any case.

In these contrived examples I can't disagree, but in a real application, IMO the indentation does become a factor because you inevitably have to nest multiple levels deep.

In your example, it's easier for someone to append code in the future after the (cond) without realizing that the appended code executes even during failure conditions. That's what I meant about "hiding" assertions inside a structure. There is no wrong way or right way, necessarily, but I think there is an unwarranted stigma to using (exit), and I hope you don't dismiss it out of hand. Part of my motivation is to dispel any myth that (exit) is a kludge that "real" programmers avoid.
Title: Re: Abort and return to command prompt
Post by: BlackBox on August 10, 2012, 02:20:19 AM
I do not feel that there is any stigma for or against using Exit; rather it's more likely that most simply use the best tool for the job at hand (which ends up not being Exit).

Most Visual LISP routines incorporate a change in sysvar, or external object which warrants a better option than Exit. In my limited experience, it is rare that I code a routine where Exit would be sufficiently effective.
Title: Re: Abort and return to command prompt
Post by: irneb on August 10, 2012, 02:56:40 AM
Usually ... when a function becomes so large that a cond structure becomes "lost" and/or obscured ... causing you to need exit: This means your function is too convoluted, at least for Lisp-best-practise. I've seen some, and I've written some myself (most notably my AutoIncr's main input function - from line 1657 here (http://caddons.svn.sourceforge.net/viewvc/caddons/General/AutoIncr.LSP?revision=66&view=markup)  :-[ - "when" I get the time I'm definitely going to re-write that, at the very least, into separate functions).

In the above AutoIncr sample I'd have to follow a method like MP's example in order to use the exit function - since I don't want the calling function to exit, only the current one. Which IMO might even make than function more convoluted, therefore the cond used is the best for that case. Though the contents of each cond makes the function lumber-some (never mind cumbersome). So in the "lisp-way" (though many languages advocate keeping each function minimal for ease of future support) would be to effectively extract each condition body into a separate defun of its own - then the condition would simply call that defun instead of inlining its code. That would make the cond structure a lot more readable for future edits.