PDA

View Full Version : Error Handling in LISP/Net



irneb
2014-01-13, 08:03 AM
One thing I'm still scratching my head around is a way to return a normal Lisp-like error through a DotNet exception class. I.e. I'd like to be able to use vl-catch-all-apply to check for errors as with most already built-in lisp functions, and keep the command-line as clean as possible (i.e. silent exit of error to use alternative code).

peter
2014-01-13, 10:45 PM
One thing I'm still scratching my head around is a way to return a normal Lisp-like error through a DotNet exception class. I.e. I'd like to be able to use vl-catch-all-apply to check for errors as with most already built-in lisp functions, and keep the command-line as clean as possible (i.e. silent exit of error to use alternative code).

Good one!

One thing I have been trying to adopt throughout all of my code (.NET or LISP) is to trap errors at the level of each function.

In LISP that means returning a value or a T_atom for success and nil for failure.

I like to wrap my code with and (and ...) and that way it drops out immediately upon hitting an error.

I do that in .NET with checking the return value from each function similarly.

I have a function for LISP that I use called Errortrap that wraps each function call with an error trapping function that returns a value or T for success and nil for failure

(errortrap (quote (setq x (/ 1 0))) ; returns nil because you can't divide by zero.




(defun ErrorTrap (symFunction / objError result)
(if (vl-catch-all-error-p
(setq objError (vl-catch-all-apply
'(lambda (X)(set X (eval symFunction)))
(list 'result))))
nil
(if result result 'T)
)

irneb
2014-01-14, 11:18 AM
Yes, I know the return value/T and nil on error idea. But then what about an error message. E.g. say the exception was due to user cancelling on input. Usually in lisp you'd silently blank such error messages by making a custom *error* defun and checking the message for its contents. But with DotNet's exception it seems it's an all-or-nothing idea: Either you throw the exception and let it trace the entire DotNet stack to the command-line, or you catch the exception and return nil to Lisp (difficult to allow lisp to search the "message").

1st prize would be if a custom LispException class can be made which when not caught would return a vl-error to whatever lisp evaluation called it. Also throwing Lisp into its *error* function with the message passed as argument (unless the call was wrapped within a vl-catch-all-apply). There has to be a way, since it is obviously implemented inside ARX from other built-in functions (thus the divide by zero type error) - the / function is probably inside the vl.arx file (or built into acad.exe) - anyone know if there's some callable function which could do this (perhas through PInvoke)?

2nd prize would be to return some specialized object value (similar to the vl-error code) within which the error message is contained and can be extracted. But this might cause hassles, since then you leave it up to the lisp code to check if such object is returned instead of nil or and error stopping the code.

Joint 2nd prize: use the return nil idea and (at least) set some global variable to the error message. Doesn't actually throw an error, so the *error* defun (if used for something else) would become redundant in this case. Would need to document this global var so Lisp-side users of the DLL would need to know where to check.

In the last 2 methods consistency is lost. I.e. DotNet's LispFunctions work different when erroring than any other functions. Could confuse Lispers as they now have a 3rd way in which they have to check for errors. This is my reason for not liking the return-nil idea as much as a full-fledged lisp-error.

peter
2014-01-14, 04:51 PM
I started a new thread on Error Handling

Lets move the conversation on this over there.


P=

peter
2014-01-14, 04:52 PM
This stores the last error in a global variable that could be checked and reset.

I tend not to use *Error* very much but occasionally

I use the value/T and nil methodology to handle the errors in the flow.

P=

I find that .net is very unstable when handling errors, whereas LISP is almost bulletproof.

I would rather handle errors in LISP... IMHO probably just because I am more used to it.

Lets start a new thread on Error Handling.... OK?

P





(defun ErrorTrap (symFunction / objError result)
(if (vl-catch-all-error-p
(setq objError (vl-catch-all-apply
'(lambda (X)(set X (eval symFunction)))
(list 'result))))
(progn
(setq strGlobalErrorMessage (strcat
(vl-catch-all-error-message objError)
(princ "\nWhile evaluating the expression: ")
(vl-princ-to-string symfunction)
)
)
nil
)
(if result result 'T)
)
)

BlackBox
2014-01-14, 05:44 PM
I started a new thread on Error Handling

Lets move the conversation on this over there.


Just passing through on my short lunch break....

I've moved the other related posts here, so someone just finding this thread doesn't start in the middle of a neat topic.



A quick $0.02 - I would love for other native Objects to be exposed as valid return Types for LispFunctions (i.e., COM Objects, vl-Error, etc.), however, ADN staff tell me this is not possible.

Cheers

irneb
2014-01-15, 04:59 AM
Just as an aside, error handling in VB.DotNet is a bit inconsistent. The legacy On Error Goto (from old VB6/VBA) is still allowed due to backward compatibility. The trouble of course is when you convert the VB code to other DotNet languages - no other language uses the Goto statements. They've been omitted from nearly all "new" languages since they tend to cause complicated and messy control flow - making the code difficult to understand. MSDN recommends using the try-catch approach instead, since you can achieve the same thing you wanted to do with On Error Goto, only in a more structured and understandable manner. The only thing I know of which On Error can do and try-catch can't is On Error Resume Next - but is that really something you think is correct code (it would be something like a try-ignore statement)?

Some further dicussion on this: http://forums.devx.com/showthread.php?54248-On-Error-Goto-versus-Try-Catch-Fail

irneb
2014-01-15, 06:21 AM
Another idea could be to create a temporary volatile ename to use as a return value on errors. Now, to figure out how to accomplish this in DotNet instead of ARX as per Owen's suggestion here: http://www.theswamp.org/index.php?topic=41851.msg469841#msg469841

peter
2014-01-15, 04:53 PM
OK...

I use the On Error Goto OnError method because it is cleaner than try/catch.

It is 3 lines of code compared to 5.

Because I only want the function to return a value/True or nothing/nil and I test for success in the calling function.

Whatever the error is... I want my program to continue and I can deal with the error later.

Being I have programmed basic since 1974 and used Goto statements a long time, I like it.

If you see it and think a try catch is cleaner, you are more than welcome to switch it and repost or convert to other languages.

I switched from try/catch to this more simple syntax as I was refining some of my code a while back.

There are no good writes, just rewrites.

P=

I will try to include the try catch syntax to make it easier to translate.

Let me show you what I see.



Public Function TestFunction(ByVal rbfLISPArguments As ResultBuffer)
' 5 lines of code to handle errors
Try
' the code
Return tpvTrue
Catch ex As System.Exception
End Try

Return tpvNil
End Function




' 3 lines of code to handle errors
Public Function TestFunction(ByVal rbfLISPArguments As ResultBuffer)
On Error GoTo OnError
' the code
Return tpvTrue
OnError: Return tpvNil
End Function


What I would like to see is

On Error Return Nothing ... or something like that

irneb
2014-01-16, 11:25 AM
Sorry, not trying to say it's wrong. I also "started" with Basic in DOS - while still in school. And I did remember that (as long as you keep your wits bout you ;)) the goto's actually gave you a lot of funky capabilities. It's just that these days they're considered bad practise. BTW, the try-catch-finally is implemented (after compilation) as assembly branches - i.e. the exact same thing as a goto statement. The thinking is that it's just a more structured source-code, thus less chance of something going wrong inadvertently.

BlackBox
2014-01-16, 05:22 PM
Especially given the inherent requirement of 'more lines of code' in .NET vs. LISP, etc., I wouldn't fret the extra few lines personally... Then again, I haven't coded in VB in at least a few years anyways (all of my Exchange Apps are in C#). LoL

peter
2014-01-17, 07:59 PM
Moved topic of Common LISP Object System syntax to different thread.

We can continue to discuss error handling here.

irneb
2014-01-20, 11:47 AM
Just thinking a bit, wondering if this would work. Note the idea from Gile's code (as BlackBox's link) and using the P/Invoke or the newer Application.Invoke.
using System;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;

namespace ALispDotNetLink {
/// <summary>
/// Throw a generic exception to be handled by the Lisp Environment
/// </summary>
public class LispException : System.Exception {
/// <summary>
/// Constructor
/// </summary>
/// <param name="msg">The error message</param>
public LispException(string msg) : base(msg) { }

/// <summary>
/// Pass the exception to the Lisp Environment instead of stack-tracing the DotNet assembly
/// </summary>
/// <returns>Always null</returns>
public object SendLispError() {
try {
ResultBuffer rb = new ResultBuffer();
rb.Add(new TypedValue((int)LispDataType.Text, "vl-exit-with-error"));
rb.Add(new TypedValue((int)LispDataType.Text, this.Message));
LispEnvironment.Invoke(rb);
} catch (System.Exception) { }
return null;
}
}

/// <summary>
/// Throw a number-of-arguments exception to be handled by the Lisp Environment
/// </summary>
public class LispArgumentCountException : LispException {
/// <summary>
/// Constructor
/// </summary>
/// <param name="ToMany">Boolean value: true="too many"; false="too few"</param>
public LispArgumentCountException(bool ToMany) :
base(string.Format("too {0} arguments", (ToMany) ? "many" : "few")) { }
}

/// <summary>
/// Throw an unexpected argument type exception to be handled by the Lisp Environment
/// </summary>
public class LispArgumentException : LispException {
/// <summary>
/// Constructor
/// </summary>
/// <param name="s">Expected lisp argument type</param>
/// <param name="tv">The actual typed value passed in</param>
public LispArgumentException(string s, TypedValue tv)
:base(string.Format("invalid argument type: {0}: {1}", s,
tv.TypeCode == (int)LispDataType.Nil ? "nil" : tv.Value)) {}
}
}Haven't tested it yet, will need to do so later today.

Actually the vl-exit-with-error doesn't seem to have any influence on Lisp's error itself - though it does close the defun at that point. Will have to look at what can be dome otherwise.

irneb
2014-01-20, 12:47 PM
Other than that, the only things looking promising is using P/Invoke on some of acad's loaded DLLs:

void __cdecl error_output(char * __ptr64) in VL.CRX
void __cdecl AcEdPromptSelection::InputCallbackArgs::setErrorMessage(class AcString const & __ptr64) __ptr64 in accore.dll

irneb
2014-01-20, 08:02 PM
Actually the vl-exit-with-error doesn't seem to have any influence on Lisp's error itself - though it does close the defun at that point. Will have to look at what can be dome otherwise.As I thought, this doesn't work. The vl-exit-with-error function only operates on separate namespaces. Will have to try and see what I can do with the other 2 p/invoke functions. The vl.crx one would necessitate VLisp to be loaded through vl-load-com, and the one from accore.dll is a bit strange - I've never used a class method through p/invoke before, but willing to give it a try.

Edit: BTW, I used Dll Export Viewer (http://www.nirsoft.net/utils/dll_export_viewer.html) to find those other 2. There's a whole plethora of functions with error in their name, but these 2 seemed as if they might be doing what I'm after.

peter
2014-01-21, 07:36 AM
I have played around with pinvoke before.

Successfully created new ones.

Thanks for your work on the error handling...

I also want to continue our discussion of common lisp...

Peter

irneb
2014-01-21, 12:49 PM
Neither of those 2 seemed to work at all. But I didn't give up - seems there's a fail function exported in VL.CRX which accepts a char-array as a string. It seems to do the job (half-way) when used in ACad Vanilla 2014:

The code to import the fail function. Note I'm exposing it in my LispEnvironment class (also contains get/set/invoke):
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("vl.crx", EntryPoint = "?fail@@YAXPEAD@Z",
CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
extern static private void fail([In, MarshalAs(UnmanagedType.LPStr)] string message);

Then the exception class, as previous, except for the SendLispError method:
/// <summary>
/// Pass the exception to the Lisp Environment instead of stack-tracing the DotNet assembly
/// </summary>
/// <returns>Always null</returns>
public object SendLispError() {
try { LispEnvironment.Fail(this.Message); } catch { }
return null;
}

And then lastly an example use:
namespace ALispDotNetLink
{
/// <summary>
/// Description of TestError.
/// </summary>
public class TestError
{
[LispFunction ("err-vl")]
public object errVL(ResultBuffer arguments) {
try {
if (arguments == null)
throw new LispArgumentCountException(false);
TypedValue[] args = arguments.AsArray();
if (args.Length < 1)
throw new LispArgumentCountException(false);
if (args[0].TypeCode != (int)LispDataType.Text)
throw new LispArgumentException("stringp", args[0]);
return args[0];
} catch (LispException e) {
return e.SendLispError();
}
}
}
}

It's not exactly "perfect" but it does work the way I wanted it - though there are some extra stuff printed to the command-line:
Command: (defun test (toEval / *error*) (defun *error* (msg) (princ "This is my error: ") (princ msg) (princ)) (princ "Result: ") (princ (eval toEval)) (princ "\n\nNo errors!!!") (princ))
TEST
Command: (test '(/ 1 0))
Result: This is my error: divide by zero
Command: (test '(/ 5 2))
Result: 2
No errors!!!
Command: netload
Command: (test '(err-vl "testing"))
Result: testing
No errors!!!
Command: (test '(err-vl 1))
Result: This is my error: invalid argument type: stringp: 1nil
No errors!!!
"\n;;; CNTX trace stack warn\n""\n;;; CNTX trace stack warn\n"
Command: (test '(err-vl 2 3))
Result: This is my error: too many argumentsnil
No errors!!!
; error: Exception occurred: 0xC0000005 (Access Violation)
; warning: unwind skipped on exception
; error: Exception occurred: 0xC0000005 (Access Violation)
"\n;;; CNTX trace stack warn\n"
So for some strange reason, the *error* function does get called, but the currently running function isn't stopped. And then on the 2nd such error the entire thing fails - Access violation.

But then after waiting a bit it then seems to work - sort-of (not even displaying that CNTX message):
Command: (test '(err-vl "testing again"))
Result: testing again
No errors!!!
Command: (test '(err-vl 1))
Result: This is my error: invalid argument type: stringp: 1nil
No errors!!!
Command: (test '(err-vl 1 2))
Result: This is my error: too many argumentsnil
No errors!!!
Command: (test '(err-vl nil))
Result: This is my error: invalid argument type: stringp: nilnil
No errors!!!
Command: (test '(err-vl 1 2))
Result: This is my error: too many argumentsnil
No errors!!!
Command: (test '(err-vl nil))
Result: This is my error: invalid argument type: stringp: nilnil
No errors!!!

It seems the fail is simply calling the *error* defun with the message, but doesn't actually reset the lisp environment. I'll have to research a bit more, but I think I'm getting close now.

irneb
2014-02-24, 07:26 AM
I've just thought of another way of doing this. Similar to returning nil on errors, but allowing for nil as an accepted return value (as it should be, since it could mean a false or not-found or empty - which aren't necessarily "errors").

My idea was to use some "special" value which cannot be used anywhere else. I was thinking (a while ago) to make some temporary ename and pass that as an ObjectId so it could be checked in Lisp - similar to vl-catch-all-error-p does. However, the method started to become too cumbersome - not to mention it needed to be implemented in ARX (not DotNet).

So now, I've though: "Is there any possible value which can be returned from a LispFunction, which Lisp cannot possibly use in any "normal" situation?" And it seems there is: double.NaN (i.e. a double value which refers to an error condition "Not-a-Number"). Here's the test function
[LispFunction ("test-NAN")]
public static object testNAN(ResultBuffer arguments) {
return double.NaN;
}And when run in acad:
Command: (test-NAN)
-1.#IND

Then to make an is-error function I used this:
[LispFunction ("test-input")]
public static object testInput(ResultBuffer arguments) {
Document activeDocument =
Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
Editor activeEditor = activeDocument.Editor;
try {
if (arguments == null) throw new ArgumentNullException();
TypedValue[] args = arguments.AsArray();
if (args.Length < 1) throw new ArgumentException("No arguments passed in");
activeEditor.WriteMessage(string.Format("{0} arguments passed in\n", args.Length));
foreach (TypedValue element in args) {
activeEditor.WriteMessage(string.Format("\t{0}\t{1}\n",
LispDataType.GetName(typeof(LispDataType), element.TypeCode),
element.Value));
}
return true;
} catch (System.Exception e) {
activeEditor.WriteMessage(e.Message);
return null;
}
}And then ran it inside acad:
Command: (test-input (test-NAN))
1 arguments passed in
Double NaN
TSo it would be possible to test for such from Lisp.

But even easier - it would be possible to test for a NaN value direct inside Lisp. See these tests in acad:
Command: (= (test-NAN) 0)
nil

Command: (<= (test-NAN) 0)
T

Command: (>= (test-NAN) 0)
T

Command: (and (>= (test-NAN) 0) (<= (test-NAN) 0) (/= (test-NAN) 0))
TNotice the last item makes no logical sense? So the "is-error" function could simply be something like this:
(defun is-error (value)
(and (>= value 0) (<= value 0) (/= value 0)))Thereafter it would simply require that the DotNet needs to set the ErrNo and/or somewhere place the error message so it can be inspected from the Lisp side. And as optional extra the normal *error* can then be called as per my previous posts.

peter
2014-02-26, 07:53 AM
I wanted to demonstrate a style of writing LISP that utilizes the 'and' expression.

It is why I like to have my expressions return a value or T for success and nil for failure.

To demonstrate.

Someone asked me yesterday how to explode all blocks in a block definition.

I has an old function using is if statements.




;___________________________________________________________________________________________________________
;
; Function to explode all components in a block definition ;___________________________________________________________________________________________________________

(defun ExplodeItemsInBlock (objBlockDefinition / intCount objLast blnRerun)
(setq intCount (vla-get-count objBlockDefinition))
(while (not blnReRun)
(setq blnRerun T
intCount 0
)
(while (< intCount (vla-get-count objBlockDefinition))
(setq objLast (vla-item objBlockDefinition intCount))
(if (and (wcmatch (vla-get-objectname objLast) "AcDbBlockReference,AcDbMInsertBlock")
(errortrap '(vla-update objLast))
)
(progn
(errortrap '(vla-update objLast))
(if (errortrap '(vla-explode objLast))
(progn
(errortrap '(vla-delete objLast))
(setq blnRerun nil intCount (vla-get-count objBlockDefinition))
)
)
)
)
(setq intCount (1+ intCount))
)
)
)

; Standard error trap that returns nil for error or a value or T for success.

(defun ErrorTrap (symFunction / objError result XYZ123)
(if (vl-catch-all-error-p
(setq objError (vl-catch-all-apply
'(lambda (XYZ123)(set XYZ123 (eval symFunction)))
(list 'result))))
nil
(if result result 'T)
)
)


Now this is a rewrite of the code above using the 'and' syntax.



;___________________________________________________________________________________________________________
;
; Function to explode all components in a block definition rewritten to use 'and' syntax ;___________________________________________________________________________________________________________

(defun ExplodeItemsInBlock (objBlockDefinition / intCount objLast blnRerun)
(setq intCount 0)
(while (< intCount (vla-get-count objBlockDefinition))
(and (setq objLast (vla-item objBlockDefinition intCount))
(wcmatch (vla-get-objectname objLast) "AcDbBlockReference,AcDbMInsertBlock")
(errortrap '(vla-update objLast))
(errortrap '(vla-explode objLast))
(errortrap '(vla-delete objLast))
(ExplodeItemsInBlock objBlockDefinition)
(setq intCount (vla-get-count objBlockDefinition))
)
(setq intCount (1+ intCount))
)
(princ)
)


Which one do you prefer?

P=

irneb
2014-02-26, 11:52 AM
The last one definitely looks cleaner.
The trouble I have with only restricting yourself to nil/T as return value is that then you cannot make your DotNet LispFunction actually return useful data - you'll need to set some global variable if you want to return any data back to Lisp. For that reason I'd then want a error token instead of simply a "yes it succeeded" or "no something went wrong" value.

That way, you can still use it as per your example - your error trap would simply return nil if the function's return value is the token error. E.g.
(defun is-error (value)
(and (= (type value) 'REAL) (>= value 0) (<= value 0) (/= value 0)))

(defun errortrap (toEval / result)
(not (or (vl-catch-all-error-p (setq result (vl-catch-all-apply 'eval (list toEval))))
(is-error result))))

peter
2014-02-26, 11:34 PM
It is not just T/nil it is (T or any value) / nil.

It is just the 'and' and 'or' expressions recognize nil as no and anything else as yes.

The error trap returns T or a value for success and nil for failure.

Error message can also displayed using this variation of the error trap function



(defun ErrorTrap (symFunction / objError result)
(if (vl-catch-all-error-p
(setq objError (vl-catch-all-apply
'(lambda (XYZ)(set XYZ (eval symFunction)))
(list 'result))))
(and
DEBUG
(princ
(strcat "\nError: "
(vl-catch-all-error-message objError)
". While evaluating the expression: "
(vl-princ-to-string symfunction) "\n"
)
)
nil
)
(or result
'T
)
)
)


Example of code



Command: (setq DEBUG 1)
Command: (errortrap '(/ 1 0))

Error: divide by zero. While evaluating the expression: (/ 1 0)
nil

irneb
2014-02-27, 05:29 AM
I'd rather want control over the result. E.g. you might want to have a nil returned if the result is used to test if your code needs to continue down a specific path. So I'd add an argument to the errortrap function to indicate a default return (instead of nil / T) e.g.
(defun net-error (value)
(and (numberp value) (>= value 0) (<= value 0) (/= value 0)))

(defun error-trap (toEval default / result error debug)
(defun debug (DotNet)
(if *DEBUG*
(princ (strcat "\nError: "
(cond (DotNet *NET-ERROR-MESSAGE*)
((vl-catch-all-error-p error) (vl-catch-all-error-message error))
("Unknown error"))
". While evaluating the expression: "
(vl-princ-to-string toEval)
"\n")))
default)
(cond ((vl-catch-all-error-p
(setq error (vl-catch-all-apply
(function (lambda (output) (set output (eval toEval))))
(list 'result))))
(debug nil))
((net-error result) (debug t))
((null result) default)
(result)))Then using it:

_$ (error-trap '(/ 1 0) T)

Error: divide by zero. While evaluating the expression: (/ 1 0)
T
_$ (error-trap '(test-NAN) T)

Error: Testing DotNet LispFunction error token. While evaluating the expression: (test-nan)
T
_$ (error-trap '(test-NAN) nil)

Error: Testing DotNet LispFunction error token. While evaluating the expression: (test-nan)
nil
_$ (error-trap '(test-NAN) 12345)

Error: Testing DotNet LispFunction error token. While evaluating the expression: (test-nan)
12345

peter
2014-02-27, 05:41 AM
OK, I can see some advantage with adding a default success return variable.

I have to think about whether I want to adopt it.

It would be good if that could be an optional argument.

I still kinda like the simplicity of nil.

Its all good.

Did you check out the new ADOX thread?

irneb
2014-02-27, 08:40 AM
It would be good if that could be an optional argument.Yes indeed. Though how to accomplish an optional argument for this in AutoLisp is beyond me. All I can think which might work is to redo that error trap routine in ObjectARX, since you cannot do it through DotNet (some of the arguments would cause errors, since DotNet does not allow stuff like ActiveX objects passed to/fro ALisp).