Error
Handling
See Also: Exception Handling, ucError, ucErrorMessage, ucRaiseError
You have two general options for handling errors in uCalc. You can 1) check for errors with ucError after each uCalc routine, or 2) create a centralized error handler to handle all errors wherever they arise.
ucError
This routine is basically a leftover from version 2.0 of uCalc, where you might check the value of ucError after every call to routines such as ucParse, ucEval, or ucEvaluate, etc. If you use an error handling routine, you will typically not need ucError.
Example A: Error handling with ucError
Visual Basic
Dim MyExpression As
String MyExpression
= "5+4/x" Print ucEvalStr(MyExpression) If ucError
Then Print ucErrorMessage() uCalc uc_ErrorClear End If |
Error handling callback routines
uCalc Fast Math Parser allows you to create error handling callback routines, which handle errors/exceptions wherever they my occur, whether during the parsing stage, or during the evaluation stage. With a centralized error handler, it is not necessary to individually check for errors after each uCalc definition or evaluation, although that option is still there. Your error handling callback routine(s) will be invoked the moment an error occurs.
Example B: Defining a centralized error handler
The following centralized error handler displays an error message, with ucErrorMessage(), wherever and whenever an error or exception occurs. This code needs to be defined only once (although you are free to define multiple error handlers, which will be called in sequence when an error occurs). The function below includes some additional information beyond the error message, such as the offending symbol, with ucErrorSymbol(), and error location, with ucErrorLocation(), where applicable; in the current version, the error location is only given for errors raised at parse time, and not during evaluation. This example can be found in the demo program as well. ucErrorMessage() can be called outside of an error handling callback, whereas calls to ucErrSymbol() and ucErrLocation() are valid only in error handling callbacks.
Note: The callback function should generally return ucAbort. ucAbort is defined as a constant whose value is 0. Some compilers return a 0 by default, in which case setting the return value to ucAbort is optional. ucAbort causes the parser to stop parsing or evaluating. Other options are ucResume, and ucReRaise, which are explained in the next sections.
// The following line can be placed in
TForm1::FormCreate() ucAddErrorHandler(MyErrorHandler); // The following code can be placed
just before TForm1::FormCreate() // IMPORTANT: Always remember to
use stdcall for uCalc
callbacks (in C++) DWORD _stdcall MyErrorHandler(long t) { ShowMessage("Error Handler message: " + ucErrorMessage(0,
t) + '\n' +
"Offending symbol: " + ucErrorSymbol(t) + '\n' +
"Error Location: " + IntToStr(ucErrorLocation(t))
+ '\n' + '\n'); return ucAbort; } |
// The following line can be placed in
Form1_Load() ucAddErrorHandler(MyErrorHandler); // The following code can be placed
just before Form1_Load() static int MyErrorHandler(int t) { MessageBox.Show("Error Handler message: " + uCalc.ucErrorMessage(0, t) + '\n' +
"Offending symbol: " + uCalc.ucErrorSymbol(t) + '\n' +
"Error Location: " + uCalc.ucErrorLocation(t).ToString()
+ '\n' + '\n');
return uCalc.ucAbort; } |
// The following line can be placed in
TForm1.FormCreate() ucAddErrorHandler(@MyErrorHandler); // The following code can be placed
just before TForm1.FormCreate() // IMPORTANT: Always remember to
use stdcall for uCalc
callbacks function MyErrorHandler(t:
Longword): Longword; stdcall; begin ShowMessage('Error Handler message: ' + ucErrorMessage(0,
t) + Chr(13) +
'Offending symbol: ' + ucErrorSymbol(t)
+ Chr(13) +
'Error Location: ' + IntToStr(ucErrorLocation(t)) + Chr(13) + Chr(13));
MyErrorHandler := ucAbort; end; |
' The following line can be placed in PBMain() ucAddErrorHandler(CodePtr(MyErrorHandler)) ' The following callback routine can be
pasted just before PBMain() Function MyErrorHandler(ByVal t As Long) As Long MsgBox "Error Handler message: " + ucErrorMessage(0,
t) + Chr$(13) _ + "Offending
symbol: " + ucErrorSymbol(t)
+ Chr$(13) _ +
"Error Location: " + Str$(ucErrorLocation(t))
+ Chr$(13) + Chr$(13)
MyErrorHandler = %ucAbort End Function |
' The following line can be placed in Form_Load() ucAddErrorHandler(AddressOf MyErrorHandler) ' The following callback routine goes
in a separate module, such as DemoVB.Bas Function MyErrorHandler(ByVal t As Long) As Long MsgBox "Error Handler message: " + ucErrorMessage(0,
t) + Chr$(13) _ +
"Offending symbol: " + ucErrorSymbol(t) + Chr$(13)
_ +
"Error Location: " + Str$(ucErrorLocation(t))
+ Chr$(13) + Chr$(13) MyErrorHandler = ucAbort End Function |
Note: Under managed code in C#, VB.NET and VC++, it may be best to declare a delegate declared as a static variable (or equivalent) with a permanent address and pass that instead of passing the address directly as done above. The demo program that comes with the uCalc FMP download uses delegates.
ucResume allows your error handling callback routine to take corrective action, and resume parsing or evaluating, instead of aborting the process.
Example C: Using ucResume to allow auto variable definitions
uCalc requires a variable to be defined prior to appearing in an expression. However, if you want variables to automatically be defined implicitly as soon as they are featured in an expression, you can create an error handling routine like the one below, which defines the variable that wasn't recognized in the expression, and then resumes parsing, once the variable is defined.
Visual Basic 6
' The following line can be placed in Form_Load() ucAddErrorHandler(AddressOf AutoVariableDef) ' The following callback routine goes
in a separate module, such as DemoVB.Bas Function AutoVariableDef(ByVal t As Long) As Long If ucError(t) = uc_Err_Undefined_Identifier
Then ucDefineVariable ucErrorSymbol(t),
0, t AutoVariableDef = ucResume End If End Function |
ucReRaise
allows your compiler to catch uCalc exceptions.
Using a centralized error handling routine like the one above is one way to catch FPU exceptions (such as Overflow, Division by 0, etc). Alternatively, you may let your compiler catch them instead. Or you can even do both. If you want your compiler to catch exceptions from uCalc, you should either have one error handler that returns ucReRaise, or if you have multiple error handlers, the one that returns ucReRaise should be the last one to be defined. ucReRaise is designed to work only with FPU exceptions.
Example D: Re-raising an error
In this example, a uCalc error handler is defined, which returns ucReRaise in order to allow the host Delphi compiler to catch the exception, using try / except. If you enter an expression such as 1/0, and the Division by 0 FPU setting is not masked, then a message box will display: This is handled by Try/Except in Delphi.
Delphi
function MyErrorHandlerB(t:
Longword): Longword; stdcall; begin
// Additional code may go here if necessary Result :=
ucReRaise; end; procedure TForm1.btnEvalClick(Sender: TObject); begin try txtResult.Text := ucEvalStr(txtExpression.Text); except ShowMessage('This is handled by Try/Except in Delphi'); end; end; // The following line can go in
TForm1.FormCreate() uCalc(uc_AddErrorHandler, 0, Longword(@MyErrorHandlerB)); |
Dealing with multiple error handlers
You are allowed to have multiple error handling callback routines. However, if there is more than one, they must be coordinated. Perhaps you may have different handlers for different categories of errors. Or 3rd party add-on libraries might come with their own error handlers. Some handlers may want to perform an action and resume the parsing or evaluation process, while others might want to abort the process. It is a good idea to design an error handler with consideration for other handlers, even if the others aren't implemented yet.
When multiple error handlers are defined, the action (ucAbort, ucResume, or ucReRaise) of the last handler to be defined is the one that has an effect. Also, one error handler may perform corrective action, which may clear the error value, causing uc_Err_None to be the return value of ucError() for subsequent handlers. With that in mind, examples B and C can be coordinated as demonstrated below in such a way that the DisplayError() handler displays an error message box, unless the error is uc_Err_Undefined_Identifier, in which case the AutoVariableDef() handler, which gets called first, takes care of defining the variable.
The handler in example B was renamed DisplayError below. An IF statement was added to it, which checks the value of ucError(). If it is uc_Err_None, which means that the error was cleared, then parsing resumes (indicated by DisplayError = ucResume) without displaying an error message. The order in which the two handlers were defined matters. If you switched the order, then the undefined identifier would automatically be defined as a variable, but an error message would also be displayed.
Visual Basic 6
' The following line can be placed in Form_Load() ucAddErrorHandler(AddressOf AutoVariableDef) ucAddErrorHandler(AddressOf DisplayError) ' The following callback routine goes
in a separate module, such as DemoVB.Bas Function AutoVariableDef(ByVal t As Long) As Long
If ucError(t) = uc_Err_Undefined_Identifier
Then
ucDefineVariable ucErrorSymbol(t),
0, t
AutoVariableDef = ucResume
End If End Function Function DisplayError(ByVal t As Long) As Long
If ucError(t) = uc_Err_None
Then
DisplayError = ucResume
Else
MsgBox "Error Handler message: " +
ucErrorMessage(0, t) + Chr$(13)
_ + "Offending symbol: " + ucErrorSymbol(t) + Chr$(13) _ + "Error Location: " + Str$(ucErrLocation(t)) + Chr$(13) + Chr(13)
DisplayError = ucAbort
End If End Function |
ucErrorMessage([ErrorNumber [, ThreadHandle]])
This function retrieves the text associated with an error number. If the ErrorNumber argument is omitted, or equal to 0, then the current error message is returned. When you invoke ucErrorMessage from a callback routine, you should always include the second argument. This argument is the one value that is passed as an argument to your callback routine. See the example above, and also the ucErrorMessage topic.
Pre-defined constants for ErrorNumber are:
uc_Err_None
uc_Err_Dynamically_Defined
uc_Err_Syntax_Error
uc_Err_Undefined_Identifier
uc_Err_Unknown_Error
uc_Err_Unrecognized_Token
uc_Err_Unrecognized_Command
uc_Err_Datatype_Mismatch
uc_Err_Invalid_Argument_Count
uc_Err_Invalid_Definition
uc_Err_CodeBlock_Error
uc_Err_Undefined_Callback
uc_Err_ErrMsgAlreadyDefined
uc_Err_ItemCannotBeModified
uc_Err_Unrecognized_Member
uc_Err_Unbalanced_Quote
uc_Err_Array_Bounds_Exceeded
uc_Err_Float_Denormal_Operand
uc_Err_Float_Divide_By_Zero
uc_Err_Float_Inexact_Result
uc_Err_Float_Invalid_Operation
uc_Err_Float_Overflow
uc_Err_Float_Stack_Check
uc_Err_Float_Underflow
uc_Err_Integer_Divide_By_Zero
uc_Err_Integer_Overflow
uc_Err_Privileged_Instruction
You can set or modify a message to be associated with an error number using ucSetErrorMessage. For instance:
ucSetErrorMessage uc_Err_Syntax_Error, "Syntax error"
ucSetErrorMessage
600, "My custom error message"
Although you may create a series of new error messages with ucSetErrorMessage, you also have the option of using ucRaiseErrorMessage (explained further down) from your callback to return a customized error message, which will be temporarily set for the occasion.
ucErrorLocation
If there is a symbol name associated with an error, such as a name in an expression that is not recognized as a defined function or variable, then your callback can retrieve this name with ucErrorSymbol(). This function takes one argument, which represents the handle of the given thread as passed to the error handling callback routine. ucErrorLocation() returns the character location of the error. ucErrorSymbol and ucErrorLocation are valid only in an error handling callback; and only for parse-time errors (such as Syntax error, etc). See Example B.
You can raise an error using ucRaiseError or ucRaiseErrorMessage. ucEraiseError sets the numeric value of the error you want to raise, while ucRaiseErrorMessage lets you set a customized error message.
Example E: Raising an error with ucRaiseErrorMessage
This example creates a native function named MyArea, which returns the product of multiplying the two arguments. If either of the arguments is a negative number, then ucRaiseErrorMesage raises an error using the error message that is supplied to it. Once defined, MyArea(3, 4) would return 12, and if you are using ucEvalStr, MyArea(-5, 3) would return the message: Length cannot be negative.
// The following line can be placed in
TForm1::FormCreate() ucDefineFunction("Native:
MyArea(Length, Width)", MyArea); // The following callback routine can
be placed just before TForm1::FormCreate() // IMPORTANT: Always remember to use stdcall for uCalc callbacks (in C++) void _stdcall MyArea(DWORD Expr) { long
double MyLength, MyWidth; MyLength = ucArg(Expr, 1); MyWidth = ucArg(Expr, 2); if(MyLength < 0) ucRaiseErrorMessage(Expr, "Length cannot be negative"); if(MyWidth < 0) ucRaiseErrorMessage(Expr, "Width cannot be negative"); ucReturn(Expr, MyLength * MyWidth); } |
// The following line can be placed in
Form1_Load() uc.ucDefineFunction("Native:
MyArea(Length, Width)", d_MyArea); // The following callback routine can be placed just before Form1_Load() public uCalc.ucCallback d_MyArea = new uCalc.ucCallback(MyArea); static void MyArea(int Expr) { double MyLength, MyWidth; MyLength = uCalc.ucArg(Expr, 1); MyWidth = uCalc.ucArg(Expr, 2); if(MyLength < 0) uCalc.ucRaiseErrorMessage(Expr, "Length cannot be negative"); if(MyWidth < 0) uCalc.ucRaiseErrorMessage(Expr, "Width cannot be negative"); uCalc.ucReturn(Expr, MyLength * MyWidth); } |
// The following line can be placed in
TForm1.FormCreate() ucDefineFunction('Native:
MyArea(Length, Width)', @MyArea); // The following code can be placed
just before TForm1.FormCreate() // IMPORTANT: Always remember to use stdcall for uCalc callbacks procedure MyArea(Expr: Longword); stdcall; var MyLength, MyWidth:
Extended; begin MyLength := ucArg(Expr, 1); MyWidth := ucArg(Expr, 2); if MyLength < 0 then ucRaiseErrorMessage(Expr, 'Length cannot be negative'); if MyWidth < 0
then ucRaiseErrorMessage(Expr, 'Width cannot be negative'); ucReturn(Expr, MyLength * MyWidth); end; |
' The following line can be placed in PBMain() ucDefineFunction
"Native: MyArea(Length, Width)", CodePtr(MyArea) ' The following callback routine can be placed just before PBMain() Sub MyArea(ByVal Expr As ucExpression) Dim MyLength As Extended, MyWidth
As Extended MyLength = ucArg(Expr, 1) MyWidth = ucArg(Expr, 2) If MyLength < 0 Then ucRaiseErrorMessage Expr, "Length cannot be negative" If MyWidth < 0
Then ucRaiseErrorMessage
Expr, "Width cannot be negative" ucReturn(Expr, MyLength * MyWidth) End Sub |
' The following line can be placed in Form_Load() ucDefineFunction
"Native: MyArea(Length, Width)", AddressOf MyArea ' The following callback routine goes in a separate module, such as DemoVB.Bas Sub MyArea(ByVal Expr As Long) Dim MyLength As Double, MyWidth As
Double MyLength = ucArg(Expr, 1) MyWidth = ucArg(Expr, 2) If MyLength < 0 Then ucRaiseErrorMessage Expr, "Length cannot be negative" If MyWidth < 0
Then ucRaiseErrorMessage
Expr, "Width cannot be negative" ucReturn Expr, MyLength * MyWidth End Sub |
' The following line can be placed in
Form1_Load() Static d_MyArea As ucCallback
= New ucCallback(AddressOf
MyArea) ucDefineFunction("Native:
MyArea(Length, Width)", d_MyArea) ' The following callback routine goes in a separate module, such as DemoNET.vb Sub MyArea(ByVal Expr As Integer) Dim MyLength As Double, MyWidth As
Double MyLength = ucArg(Expr, 1) MyWidth = ucArg(Expr, 2) If MyLength < 0 Then ucRaiseErrorMessage(Expr, "Length cannot be negative") If MyWidth < 0
Then ucRaiseErrorMessage(Expr, "Width cannot be negative") ucReturn(Expr, MyLength * MyWidth) End Sub |
Clearing an error
Generally, the error value is cleared prior to each new uCalc transaction. The exception to this is ucEvaluate. The error value is not cleared when you call ucEvaluate. To clear an error, use uCalc uc_ErrorClear. See Example A. This is generally necessary only if you are using ucError after each call instead of a centralized error handler.
Raising an error in an expression
You can raise an error in an expression, using Error()
Example F: Raising an error in an expression
Visual Basic
ucDefineFunction
"Area(x) = iif(x < 0, Error('Area argument cannot be negative'),
x^2)" Print ucEvalStr("Area(5)") ' Returns 25 Print ucEvalStr("Area(-5)")
' Returns this error: Area argument cannot be negative |
New or enhanced in version 3.0
· See also What’s New.
Note: ucErrSymbol, ucErrLocation, and ucErrMsg were renamed ucErrorSymbol, ucErrorLocation, and ucErrorMessage respectively. The old names are preserved for backwards compatibility
New or enhanced in version 2.96