TRY and CATCH

General Form:

TRY
...
[CATCH ERR exception1 [, exception2 ...]]
...
[CATCH]
...
END TRY

Where:

exception =any numeric expression reducing to an integer between 1 and 99, or any enumeration value in the range _KCML_USER_ERROR to _KCML_USER_ERROR_HIGH


The TRY/CATCH structured error handling statements allow centralized handling of run-time errors in the block of KCML code between the TRY and the first CATCH statement. There can be as many statements as necessary inside this block. There must be at least one CATCH ERR or CATCH statement between the TRY and the END TRY. A CATCH statement is either for a particular error code with an ERR clause specifying the particular error to be caught or it is a final CATCH statement which will handle any sort of error. There can be multiple CATCH ERR clauses but there can be only one final CATCH clause and, if present, it must be the last clause before the END TRY. Again each CATCH clause can have as many statements as are necessary to handle the applicable error.

TRY blocks can be nested. If an error cannot be handled in the executing block then KCML will search for an outer block and attempt to handle the error there. An error will also be promoted if it occurs during the execution of a CATCH clause. TRY blocks with a final CATCH clause will only invoke an outer handler if an error occurs in the statements within that clause.

TRY blocks can contain subroutine calls to any depth and errors in such routines will be handled unless the routines define their own handlers as TRY blocks can also nest in subroutines or WHILE/REPEAT loops provided all of the block is inside the loop or function.

DEFSUB 'DoSums(a, b)
	LOCAL DIM r
	TRY
		r = 'divide(a, b)
	CATCH
		REM this will catch any other problem
		'log_error(ERR)
		r = 0
	END TRY
	RETURN r
END SUB
DEFSUB 'divide(quotient, divisor)
	REM this handles obvious problems locally
	LOCAL DIM dividend
	TRY
		dividend = quotient/divisor
	CATCH ERR 62
		REM division by zero results in infinity
		dividend = 9E99
	CATCH ERR 63
		REM however zero divided by zero is 0
		dividend = 0
	END TRY
	RETURN dividend
END SUB

THROW can be used to force a runtime error in KCML, commonly to rethrow an error caught in an inner TRY block so that a handler in an outer block can process it.

The error expressions referenced in a CATCH ERR are evaluated at resolve time following the same rules as expressions in DIM statements. They must reduce to either an integer between 1 and 99 inclusive, representing a regular KCML error code, or an enumeration value (constant from a DEF ENUM block) between _KCML_USER_ERROR and _KCML_USER_ERROR_HIGH representing a user defined exception from a THROW. IF there is more than one expression on a CATCH ERR then the associated clause will be invoked if any of the expressions corresponds to the error that terminated the TRY block. Unlike the ERROR statement, all errors are considered recoverable.

If an error occurs in a program (or is explicitly thrown with THROW ERR) and there is no CATCH or CATCH ERR handler to catch it then a runtime error will occur which will either PANIC the program or force a development user into the KCML workbench. For exceptions this cannot happen without the KCML7 compiler generating errors warning that this could happen. See Exception Handling for more information.

When developing in the workbench if a runtime error occurs in a TRY block that could be caught by an outer CATCH outside the current subroutine then the error will be actioned by the workbench and execution will stop in the debugger at the point of the error. However if execution is resumed with CONTINUE or STEP then it will restart inside the CATCH. This gives the programmer a chance to see why and where the error occurred before switching to a completely different location. Errors produced by THROW are not treated this way and will be caught by an outer handler in the usual way.

It is a resolve time error to use any form of GOTO or indeed any line number reference within a TRY block.

Compatibility

TRY/CATCH/THROW was introduced in KCML 6.20 as a subset of a more general error handling mechanism that will debut with KCML 7. Previous error handling mechanisms such as ON ERROR and ERROR DO are now deprecated and are not allowed at $COMPLIANCE level 3 and above.

'KCML_Exception_Get_Log Usage in TRY

In TRY block, a DEFRECORD KCML_EXCEPTION_LOG could be registered in a TRY block by calling 'KCML_Exception_Get_Log internal function. If an exception is caught, the error message details will be assigned to the input string DEFRECORD. The following is an example of using 'KCML_Exception_Get_Log in TRY block.
PRIVATE DEF ENUM LAM
DIM _LAM_Error=_KCML_USER_ERROR + 1
END ENUM
'main()
DEFSUB 'main()
	LOCAL DIM rException$_KCML_EXCEPTION_LOG
	TRY
		'KCML_Exception_Get_Log(BYREF rException$)
		THROW ERR _LAM_Error
	CATCH
		STOP
		REM rException$ stored the error message within this TRY block
	END TRY
END SUB
In KCML 07.28.00.30249 the function was extended to take a pool handle as an optional second parameter. If set then a stack trace back will also be generated showing how the code was reached. This can be seein in the following example:
 00010 $COMPLIANCE 3
     : PRIVATE DEF ENUM LAM
     :     DIM _LAM_Error=_KCML_USER_ERROR + 1
     : END ENUM
     : 'main()
     : DEFSUB 'main()
     :     'main2()
     : END SUB
     : 
     : DEFSUB 'main2()
     :     LOCAL DIM rException$_KCML_EXCEPTION_LOG
     :     LOCAL DIM p AS PoolHandle
     :     p = 'KCML_Debug_GetReturnStackEx(GET POOL)
     :     // 'ShowStack(p)
     :     TRY
     :         'KCML_Exception_Get_Log(BYREF rException$, GET POOL)
     :         'Sub2()
     :         CATCH
     :         REM rException$ stored the error message within this TRY block
     :         PRINT FLD(rException$.KCML_EXCEPTION_LOG_ErrorMajor)
     :         PRINT FLD(rException$.KCML_EXCEPTION_LOG_ErrorMinor)
     :         PRINT FLD(rException$.KCML_EXCEPTION_LOG_LineNumber)
     :         PRINT FLD(rException$.KCML_EXCEPTION_LOG_StatementNumber)
     :         PRINT FLD(rException$.KCML_EXCEPTION_LOG_SourceFilename$)
     :         PRINT FLD(rException$.KCML_EXCEPTION_LOG_ErrorContext$)
     :         'ShowStack(FLD(rException$.KCML_EXCEPTION_LOG_Stack))
     :         END TRY
     : END SUB
     : 
     : DEFSUB 'ShowStack(Stack AS PoolHandle)
     :     LOCAL DIM i
     :     IF (Stack <> 0)
     :         FOR i = 1 TO DIM(SYM(*Stack)$(), 1)
     :             PRINT FLD(SYM(*Stack)$(i).kcml_debug_returnstackentry_name$)
     :             END FOR
     :     END IF
     : END SUB
     : 
     : DEFSUB 'Sub2()
     :     'Sub3()
     : END SUB
     : 
     : DEFSUB 'Sub3()
     :     'Sub4()
     : END SUB
     : 
     : DEFSUB 'Sub4()
     :     THROW ERR _LAM_Error
     : END SUB

See also:

Exception Handling, THROW, ON ERROR, SELECT ERROR, ERR, ERROR, ERR$(