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.

 

Borland C++ Builder

// 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;

}

 

C#

// 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;

}

 

Delphi

// 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;

 

PowerBASIC

' 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

 

Visual Basic 6

' 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

 

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

 

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.

 

 

ucErrorSymbol

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.

 

 

ucRaiseErrorMessage

 

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.

 

Borland C++ Builder

// 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);

}

 

C#

// 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);

}

 

Delphi

// 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;

 

PowerBASIC

' 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

 

Visual Basic

' 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

 

Visual Basic.NET

' 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