yet another *error* question

Discussion in 'AutoCAD' started by Mark Propst, Aug 22, 2003.

  1. Mark Propst

    Mark Propst Guest

    Hi all you smart dudes and dudettes!

    ...example
    command1 calls func1 which calls func2
    func1 for example opens textfiles x y and z or sets variables 1 2 and 3
    func2 opens text files a b and c or sets variables 4 5 and 6

    ....method1
    func2 has a local *error* func that closes a b and c or resets 4 5 and 6
    func1 has a local *error* func that closes x y and z or resets 1 2 and 3

    ....method2
    command1 has a local *error* func that closes x y and z or resets 1 2 and 3
    and also closes a b and c or resets 4 5 and 6

    ....questions
    are methods 1 and 2 both valid?
    if not, why is one or both not valid?
    if so, is one preferred?
    if not, which is correct? (if either)
    or can only the top level function(command) have an error func?

    tia
    Mark
     
    Mark Propst, Aug 22, 2003
    #1
  2. Mark Propst

    John Uhden Guest

    I guess the answer depends on how you want things to work. My (dubious) opinion
    is to employ one (1) *error* function in the "master" function, which to me is a
    C: function, and should be written to handle what its sub-functions do,
    correctly or not.
     
    John Uhden, Aug 23, 2003
    #2
  3. Mark Propst

    Mark Propst Guest

    I'll bet on your *dubious* opinion any day!

    I didn't know if you could have more than one 'layer' of error funcs.
    so each sub could take care of its own clean up and thus be more portable
    across routines, rather than the "master" function having to look 'inside'
    all the sub funcs to see what it has to include in it's 'master' error func.
    I'm thinking if an 'inner' function fails and has an error func that error
    func may run and be the last thing to happen, rather than passing 'upstairs'
    to the calling function's error handler, and so forth up the line to the
    'master' error function.

    I guess I'm kind of thinking 'out loud' cause I haven't tried experimenting
    yet.
    gotta go find some time

    maybe you could use one of those dynamic error handlers and each function
    could adderr it's own error code and delerr as it's last successful
    operation????

    off to the lab....he he he the crazy scientist mumbles to himself.....
    :)

    Thanks
    Mark
     
    Mark Propst, Aug 23, 2003
    #3
  4. I didn't know if you could have more than one 'layer' of error funcs.
    Very good. That's how its supposed to work. Portability
    or reusablity require that a procedure or function not
    know anything about the calling context, since it may be
    called from different places.

    Unfortunately, Visual LISP has some very basic flaws in this
    area (exception handling), which make it next to impossible
    to have the same kind of localized, layered, non-exclusive
    exception handling, as most other languages permit.

    The right way to solve the problem you cite is via exception
    handling and termination handling, but AutoDesk did a very
    lousy job at implementing the former in Visual LISP, and did
    not even consider the latter, at all.

    To help understand why I've completely lost my patince with
    LISP, here's a basic example in Object Pascal/pseudo code.

    To address the problem you cite in a language that has the
    required functionality (such as OP, C++ or any .NET language),
    I would use try/finally or try/except to deal with exceptions
    that require cleanup in multiple places or levels:

    Procedure Proc1;
    begin
    Open(file1); // open a file
    try
    Proc2; // call another procedure
    finally
    Close(file1); // close the file
    end;
    end;

    Procedure Proc2;
    begin
    Open(file2); // open another file
    try
    Proc3; // call another procedure
    finally
    Close(file2); // close the file
    end;
    end;

    Procedure Proc3;
    begin
    Open(File3);
    try
    // <some process that may fail here>
    finally
    Close(File3);
    end;
    end;

    Procedure Main;
    begin
    try
    Proc1;
    except
    Print 'An error occured, all files closed.';
    end;
    end;

    In the above, Proc1 calls Proc2, and Proc2 calls Proc3.

    Each Procedure opens a file before it calls the next
    Procecure, and closes the file after the called proc
    returns. Regardless of whether an exception is raised
    anywhere in that code, or not, all three files will be
    closed.

    That's because the code that appears between the
    'finally' and 'end' statements is guaranteed to
    execute, uncondtionally, even if an error occurs.

    This is called "termination handling" (in contrast to
    'exception handling', which is similar), a practice
    that's commonly used in most professional development
    languages that are in widespread use today (standard
    equipment in Pascal, C++, and all .NET languages).

    Unfortunately, Visual LISP's (vl-catch-xxxx) family
    of error handling functions do not make this kind of
    termination handling, or even structured exception
    handling easy to emulate, and I will have no part of
    trying to simulate what you see above, in VLISP.
     
    Tony Tanzillo, Aug 23, 2003
    #4
  5. Mark Propst

    Mark Propst Guest

    thanks, that's what I figured and as my little test proved to me.

    (defun test()
    (defun *error*(msg)
    (print"error1")
    (print msg)
    )
    (print "test1")
    (test2)
    )
    (defun test2()
    (defun *error*(msg)
    (print"error2")
    (print msg)
    )
    (print "test2")
    (test3)
    )
    (defun test3()
    (defun *error*(msg)
    (print"error3")
    (print msg)
    )
    (print "test3")
    (setq bad (/ 6 0))
    (print "you won't see this")
    )

    result:
    "test1"
    "test2"
    "test3"

    "error3"
    "divide by zero"
    _$
    Thanks again
    Mark
     
    Mark Propst, Aug 23, 2003
    #5
  6. Mark Propst

    Doug Broad Guest

    Mark,
    I believe you understand Tony's point but the test functions you
    show don't prove much. You didn't localize any of the *error*
    functions, not that that would have changed the results in any way.

    A partial workaround to AutoLISP weaknesses noted is possible
    but shouldn't be necessary and is hardly worth demonstrating
    since, except for interrupted file i/o, most error functions posted
    in this NG just restore system variables. With VLISP, it is entirely
    possible to avoid changing system variables. Therefore there is
    less need for the *error* functions than there was when command
    methods were the only choice.

    If you want to implement a nested error structure, make the
    *error* symbol local to the top level function. Then use a
    function to push sublevel error handlers on to an evaluation stack.
    Subfunction *error* statements should be pushed on to the stack in LIFO
    order. Though not nearly as neat and clean as the wonderful
    PASCAL language permits, it could provide much of its functionality.

    Challenge: Code the following sub-functions: pusherror and
    poperror in the context below. Pusherror should build the
    *error* function but not destroy the statements already there.
    Poperror should pop the local error handler off at the end
    of the subfunction. It should conditionally execute the popped
    error function. Remember that functions can be lists with the
    first argument being the argument list. You do not need defun
    to build functions.

    Limitations: All nested user defined functions must use the
    pusherror / poperror structure or the whole house of cards fails.

    (defun test( / *error* a )
    (setq a 1)
    ;;at each level turn over the task of error/cleanup to pusherror/poperror
    (pusherror (list '(print"error1")'(print msg)(list 'print a)))
    (test2)
    ;;poperror only necessary if cleanup desired.
    )

    (defun test2( / a)
    (setq a 2)
    (pusherror (list '(print"error2")'(print msg) (list 'princ a)))
    (test3)
    (poperror nil) ;;nil means do not execute *error* as cleanup.
    )

    (defun test3( / a bad)
    (setq a 3)
    (pusherror (list '(print"error3")'(print msg)(list 'princ a)))
    (princ "test3")
    (setq bad (/ 6 0))
    (print "you won't see this")
    (poperror nil)
    )

    Results should look like:
    (test)
    test3

    "error3"
    "divide by zero" 3
    "error2"
    "divide by zero" 2
    "error1"
    "divide by zero"
    1

    Of course, with more complex versions of pusherror and poperror,
    the error message could be printed only once. It should be possible
    to interpret from the above at least a method of closing files.

    I look forward to seeing your solution.

    Regards,
    Doug
     
    Doug Broad, Aug 23, 2003
    #6
  7. | Mark -
    |
    | Just my (perhaps feeble) opinion, but I think functions should always
    | clean up after themselves, so if something may be left untidy due to
    | abnormal termination, the function should have a _local_ error handler
    | to deal with that situation.
    |
    | (Where is Robert Bell?):)
    |

    Lurking... ;^)
     
    R. Robert Bell, Aug 24, 2003
    #7
  8. This would be true for general use "toolbox" style functions for myself
    also.

    Application specific subrs might use a localized error handler, but more
    likely use the application-level error handler.

    --
    R. Robert Bell, MCSE
    www.AcadX.com


    | Further thought,
    |
    | my preference is to avoid designing functions which _will_ require a
    | local *error* function :)
    |
    | trivial e.g., If a function requires a real as argument, then validate
    | the data before attempting to calculate with it, either before passing
    | it to the function, or as part of the function itself.
    |
    | etc.
    |
     
    R. Robert Bell, Aug 24, 2003
    #8
  9. Comments below the responses below (eek).


    | Hi Herman,
    | Good to hear from you. See responses below:
    |
    | | > Mark -
    | >
    | > Just my (perhaps feeble) opinion, but I think functions should always
    | > clean up after themselves, so if something may be left untidy due to
    | > abnormal termination, the function should have a _local_ error handler
    | > to deal with that situation.
    |
    | The valid point made about AutoLISP weakness was that the calling
    functions
    | don't really have a chance to clean up after themselves. It is not enough
    | to clean up at a particular level. The function at each level of the
    operating
    | stack should be given the opportunity to cleanup after itself. For
    AutoLISP,
    | without special programming, upper level functions are not given that
    chance.
    |


    A valid point, yes, and comment was even made earlier about
    (vl-catch-all-apply). As you obliquely mentioned, by using a properly
    constructed vl-catch-all-apply handler at the subr level, a return of nil
    could be used to permit the upper level functions to finish quietly.

    Contrived sample:

    (defun BadFile (/ FileH Success)
    (setq FileH (open "C:\\Temp\\Test.txt" "w"))
    (vl-Catch-All-Apply
    (function
    (lambda ()
    (write-line (rtos "A") FileH)
    (setq Success T))))
    (close FileH)
    Success)

    (defun C:Test (/ *Error*)
    (defun *Error* (msg)
    (princ " <bonk!>")
    (princ))
    (princ "\nI'm happy...")
    (if (BadFile) (princ "\nOk, you can go."))
    (princ " I'm happy...")
    (/ 1.0 0.0)
    (princ))


    | > if an error occurs in func, the local *error* will be called. Then,
    | > since func has ended (abnormally, in this case) all its local symbols,
    | > including local functions, go out of scope and are inaccessible. IOW,
    | > the local *error* function runs once, then dies.
    |
    | This is true, but without special programming, LISP does not integrate the
    | cleanup aspects of the local *error* of the calling function. Only the
    | lowest level gets cleaned up.
    |
    | <snip>
    |
    | > Based on what I just tried this evening (R2002) it appears to me that
    | > VLISP closes file handles if the symbol goes out of scope.
    | >
    | <snip>
    |
    | I think you will find that VLISP does not actually close the file,
    although, for
    | some reason, it allows further appends to occur. Try this experiment.
    While
    | Autocad is still running (but not program test2), try to delete the file
    created
    | with MS Explorer. Now close ACAD, reopen, rewrite the LISP to not error
    | out and close the file. Then try to delete the file with the Explorer.
    Note that,
    | unless the file is closed, another application may not use the file until
    ACAD
    | closes.


    I'm glad you pointed that out. I'm surprised that Herman missed that part of
    the file open trouble. 8^)


    | Tony raises a valid point of AutoLISP weakness. I only offered a partial
    | remedy. PASCAL, as demonstrated is certainly superior.
    |
    | The method of creating the error handlers via pusherror and
    | poperror must involve either defun-q or setq to construct the *error*'s.
    | So that each level can be popped. I would only add that consideration
    | should be made in looping situtations to avoid unneccessary activity in
    | creating and destroying error handlers.
    |
    | Regards,
    | Doug
    |
    |
     
    R. Robert Bell, Aug 24, 2003
    #9
  10. NNOOOOOOO!!!!!!

    It is sooo irritating not to be able to delete a file just because the
    "sloppiness" is possible.

    8^)

    --
    R. Robert Bell, MCSE
    www.AcadX.com



    |
    | Which to me implies that if you do not anticipate another program needing
    access
    | to the file while Acad, or your LISP program, is still running, you can
    blissfully
    | indulge in a bit of sloppiness, & let VLISP do its thing w.r.t. file
    handles &
    | know that your (write-line)s will still happen. One less thing you need to
    worry
    | about. <nomex zipped up>
    |
    |
     
    R. Robert Bell, Aug 24, 2003
    #10
  11. Mark Propst

    Doug Broad Guest

    Hi Robert,
    Glad you joined in. Tony's point about the lack of layered error handling
    in VLISP rang true to me. Even though clever tricks and hacks can bring
    us closer to a good working system, there is still too much work to replicate
    what is built in in other languages. If a programmer is trying to maximize
    his programming profits, he would naturally select the tools that are the most
    powerful, reliable, and readable. Since I am not a full time programmer, I'm
    stuck with making what I have work as well as it can.

    Your illustration of vl-catch... was relevant. It certainly has applications for
    making cleaner exits to busted code. Thanks for demonstrating that
    possibility. I wasn't suggesting vl-catch.... for the pusherror and poperror.
    Those two functions were intended just to rewrite and conditionally execute the
    *error* function based on the current level. By building the *error* function as
    a list with the format

    ( (<argument list>) <statement group n> <statement group n-1>.... <statement group
    1>)
    or
    ((msg)(progn ...)(progn....)(progn...))


    using setq, it is possible to push each level's cleanup code on to the *error* stack
    and then pop it off at each function end. Poperror could conditionally execute
    only <statement group n> or just eliminate it from the list. At the top level,
    poperror
    would set *error* to nil.

    I didn't post the actual functions because I thought others would have more
    fun experimenting with the idea themselves. The functions as I wrote them are
    simple and short.

    The writers of AutoLISP could have helped by allowing the *error* handlers
    in each level to execute separately and in reverse sequential order. The stack
    is available to the LISP interpreter and these facilities could have been built in.
    Since we don't have access to that stack, we can only simulate it with
    a copy.

    Regards,
    Doug
     
    Doug Broad, Aug 24, 2003
    #11
  12. Mark Propst

    Doug Broad Guest

    Robert,
    Unfortunately AutoCAD itself is guilty of this too.
    Ever tried to debug a hatch pattern code and be forced
    to shut down AutoCAD because it didn't close the pattern
    file after it reported an error? I have.

    Herman,
    Such problems are relatively unimportant for those of us
    that are writing and using the programs for ourselves but
    are very important to those who are selling their programs
    to others. For vendors, it is a matter of professional pride
    that their code not interfere and leave messes. I also
    imagine that such attention to those kinds of details is a
    coping mechanism to prevent constant service requests and
    criticisms.

    Your other points are well taken about most toolbox functions
    not having or needing their own error handlers. I also agree
    with you about checking input such as making sure that denominators
    are not 0.

    The nomex went over my head. ??? ;-)

    Regards,
    Doug
     
    Doug Broad, Aug 24, 2003
    #12
  13. Mark Propst

    Doug Broad Guest

    Mark,
    You're certainly welcome. I admit that I didn't know how to interpret
    your "dynamic" comment. Have fun coming up with your own variety.
    Thanks for the use of your thread.

    Regards,
    Doug
     
    Doug Broad, Aug 25, 2003
    #13
  14. Mark Propst

    John Uhden Guest

    Following up...

    Maybe I'm just a simple person, but it just seems a whole lot simpler to have
    one *error* function, local to the top level call, that accounts for the
    possible errors in the sub-functions it employs, AND the sub-functions should be
    written in concert to minimize the error possibilities but to expose any symbols
    required for the singular *error* function to handle appropriately.

    Thank you all for the intellectual offerings to this thread. It has made me
    more comfortable with the approach I've been using.
     
    John Uhden, Aug 25, 2003
    #14
  15. Mark Propst

    Mark Propst Guest

    ok, here's one version...
    I cheated cause I'm not using defun-q to change the error function
    definition...just a simple setq cons list and an extra function to evaluate
    it
    so again I'm probably disqualified from the contest
    but it does seem to print the end result you specified.
    I'll keep trying to get the defun-q think working when I get time

    (defun test( / *error* a errlst)
    (setq a 1)
    (debugon)
    (defun *error* (msg)
    (if msg ;meets conditional test...ommited for simplicity
    (if errlst(evalerrlist errlst))
    );i
    (princ)
    )
    ;;at each level turn over the task of error/cleanup to pusherror/poperror
    (pusherror (list '(print"error1")'(print msg)(list 'princ a)))
    (test2)
    (princ)
    ;;poperror only necessary if cleanup desired.
    )

    (defun test2( / a)
    (setq a 2)
    (pusherror (list '(print"error2")'(print msg) (list 'princ a)))
    (test3)
    (poperror nil) ;;nil means do not execute *error* as cleanup.
    )

    (defun test3( / a bad)
    (setq a 3)
    (pusherror (list '(print"error3")'(print msg)(list 'princ a)))
    (princ "test3")
    (setq bad (/ 6 0))
    (poperror nil)
    )

    (defun pusherror(arg)
    (if arg
    (if errlst
    (setq errlst (cons arg errlst ))
    (setq errlst (list arg))
    )

    )
    );d


    (defun poperror(arg)
    (if (not arg)
    (if errlst
    (setq errlst (cdr errlst ))
    )
    (evalerrlist errlst)
    )
    )

    (defun evalerrlist(errlst)
    (if errlst
    (mapcar
    '(lambda (lst)
    (mapcar 'eval lst)
    )
    errlst
    )
    )
    (princ)
    )
     
    Mark Propst, Aug 25, 2003
    #15
  16. Mark Propst

    Doug Broad Guest

    Hi Mark,
    Good job. That seems to work fine. Since it looks like you're the
    only one interested, I post my versions of pusherror and poperror.
    I haven't researched past attempts like you have so I hope I don't
    step on anyone's toes by recreating prior posts. This post does not
    imply in any way that your version is any worse.

    Don't think anything that works could be considered cheating.
    Perhaps you can post a link to the one's by Bobby and Vlad.

    John and Robert had good points about application level error
    handlers. Most of the subroutines that I write that work as part of a
    specific application and subfunctions are merely written to make
    the top level code shorter and more readable. If subfunctions are
    involved in heavy looping activity, there would be a large performance
    penalty to constantly build and unbuild error handlers.

    You ask good questions and its a pleasure to have interesting topics
    to discuss.

    ;;Pusherror-bare bones style. D. C. Broad, Jr. 8/22/03
    ;;Intended to build nested *error* functions. Assumes that any
    ;;prior *error* function was built with defun-q or setq. Will
    ;;choke if started with a prior SUBR style function bound to *error*.
    (defun pusherror (statements)
    (setq *error*
    (cond
    (*error*
    (cons '(msg)
    (cons
    (cons 'progn statements)
    (list (cons 'progn (cdr *error*))))))
    (t
    (cons '(msg) statements)))))

    ;;Poperror-bare bones style. D. C. Broad, Jr. 8/22/03
    (defun poperror (execute)
    (if execute (eval (cons '(msg) (assoc 'progn *error*))))
    (setq *error*
    (cons '(msg)
    (cdr (MEMBER (assoc 'progn *error*)*error*)))))

    ;;Enjoy.

    Regards,
    Doug


    <snip>
     
    Doug Broad, Aug 26, 2003
    #16
Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.