Exception handling

This page explains Simple Issue Language (SIL™ ) exception handling mechanisms, showing how to create and throw custom errors and catch them using try-catch blocks with different patterns. It provides guidance on handling both SIL and Java exceptions, from basic usage to nested error handling, along with some best practices for implementing error management in your SIL scripts.

SIL provides a practical approach to error handling that balances ease of use with error management capabilities. While the language is designed to handle minor issues gracefully by returning empty values where appropriate, there are situations where proper exception handling is necessary to ensure your scripts work reliably.

The language also lets you to both catch system-generated errors and create custom error handling logic. This flexibility allows you to manage errors in a way that makes sense for your particular use case, whether that's displaying user-friendly messages, logging issues for later review, or implementing recovery procedures.

There are two types of exceptions in SIL:

  • Java Exceptions that come from the underlying Java system that runs the SIL code.

  • SIL objects which are custom errors that you can create and throw in your own code using the throw keyword. This page focuses on this type of exception handling.

Throwing SIL objects

Any SIL value can be thrown as exception. Values can either be constants, variables or expressions.

The general syntax for throwing an exception from SIL is:

throw <value>;

Examples

// Example 1: Throwing a variable containing an error code number errorCode = 404; throw errorCode; // Throws the numeric value 404 // Example 2: Throwing a literal number throw 2; // Throws the number 2 directly // Example 3: Throwing a value from an array struct ErrorInfo { string fieldName; string message; } ErrorInfo[] array = { {"username", "Username is required"}, {"email", "Invalid email format"}, {"password", "Password too short"} }; throw array[0].fieldName; // Throws "username" // Example 4: Throwing a constructed error message using a structure struct UserData { string email; string field; } UserData structure; structure.email = "invalid@email"; structure.field = "email"; throw ("Field value invalid: " + structure.field); // Throws "Field value invalid: email" // Example 5: Throwing a simple string message throw "Generic error!"; // Throws the literal string "Generic error!"

Try-catch block

The try-catch block is SIL's main mechanism for handling errors. It lets you to:

  • Write code that might produce errors in a try block

  • Handle any errors that occur in one or more catch blocks

  • Continue program execution gracefully instead of crashing

Syntax and components

The general syntax for try-catch block is the following:

try { // try block component <// Code that might cause an error } catch <type> <variableName> { // typed catch block component // Handle specific type of error } catch { // general catch block component // Code to handle any error }

A try-catch block can have three possible parts:

Component

Required

Description

How it works

Component

Required

Description

How it works

Try block

  • You can have one try block per try-catch structure.

  • It contains the code that might produce an error.

  • Code in the try block executes normally until an error occurs, or the block completes successfully.

  • If an error occurs, execution immediately jumps to the catch blocks. Any remaining code in the try block is skipped.

Typed catch block

  • You can have multiple typed catches.

  • It looks for a catch clause that matches the type of the error that was trown (for example, string or number).

  • The variable name gives you access to the thrown value.

If you have nested try-catch blocks , they work like a series of safety nets, where an error that isn't caught by an inner catch block "falls through" until caught by an outer catch block. Here’s how it works:

  • The catch blocks are checked in the order they were declared.

  • First matching catch block handles the error.

  • If no match is found, error “falls through” to the outer catch.

  • This continues until a matching catch is found or the program crashes.

General catch block (catch-all)

  • Catches any type of error (SIL objects or Java exceptions).

  • It is usually placed last.

  • You can use the following functions to get error details:

    • lastExceptionClass() : gets the Java exception class name

    • lastExceptionMessage() :gets the error message


Common patterns

Basic example

This example illustrates how the try-catch mechanism works:

If you run the script in SIL Manager, the output would be:

Think of it like a play script:

  1. Actor says "Starting...".

  2. Actor sees "Error occurred" and immediately exits stage.

  3. String error handler comes on stage and announces the error.

  4. Number error handler never appears (not needed).

  5. Play continues with next line "Continues here".

Type matching and catch all examples

This example illustrates how these two catch components work:

The first example uses  catch number err which:

  • Only catches errors of the number type.

  • Gives you access to the thrown value through err.

  • Lets you work with the actual error value (2).

The second example uses catch which:

  • Catches any type of error.

  • Doesn't provide access to the thrown value.

  • More general but less specific handling

Nested try-catch example

In this example, you can see how the processUserData function handles user information (name, age, email) with three layers of error checking:

Here’s what happens if you test  with user.age = -5;:

  1. The middle layer catches the invalid age.

  2. Converts it to error code 400.

  3. The outer layer sees error code 400 and logs "Validation failed".

  4. As shown in the screenshot below, function returns false.

    This screenshot shows the validation error message and the thrown exception in SIL Manager Editor console.
    Test result in SIL Manager Editor console

Think of it like a bouncer checking your ID:

  • Inner layer: checks your ID details

  • Middle layer: verifies your age

  • Outer layer: makes final decision to let you in or not

Java exceptions

This example shows how to handle Java exceptions in SIL:

Output for this example might look like:

Here are some key points about this example:

  • runAs() is a SIL function that interacts with Java code.

  • When Java code throws an exception, it can only be caught by a catch-all block.

  • Use lastExceptionClass() and lastExceptionMessage() to get error details.

  • Typed catch blocks (like catch string) won't handle Java exceptions.


Best practices

While try-catch blocks are powerful, they should be used judiciously. Not every operation needs error handling, but when you do need it, make sure to apply some best practices to your code.

To enhance error specificity:

  • Catch specific errors before general ones.

  • Use typed catch blocks when you need the error value.

  • Use general catch blocks for unexpected errors.

To improve error handling:

  • Log error details for debugging.

  • Clean up resources in all error paths.

  • Provide meaningful error messages.

  • Don't catch errors you can't handle properly.

To streamline code organization:

  • Keep try blocks focused on specific operations.

  • Don't use try-catch for normal flow control.

  • Consider extracting complex error handling into separate functions.