Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Introduction

User-defined routines (UDR) are functions that perform specific actions that you can define in your SIL™ programs for a later use. These can considerably improve the readability and maintainability of your code.

Syntax
Info

This page explains how to create and use user-defined functions (UDFs) in Simple Issue Language (SIL) programs to organize code into reusable components.

You can create and use user-defined functions in your SIL programs to encapsulate reusable logic. They help improve code organization, readability, and maintainability by breaking complex operations into smaller, manageable pieces.

Syntax and basic usage

A function is defined using the following syntax:

Code Block
languagejava
function <name>(<type> param1, <type> param2, ...) {
    Instruction1;
    ...
    InstructionN;
    return <value>;
}

noteWhen creating a function, keep in mind the following key points:

  • The

name
  • <name> of the

UDR
  • function cannot contain spaces, and they cannot start with a number

  • Parameters can be any valid SIL type.

  • The return statement is optional.

Contents:

Table of Contents
minLevel1
maxLevel3
outlinefalse
styledefault
typelist
printabletrue

Example

This example function calculates issue priority based on severity and environment. It uses conditional logic to demonstrate parameter passing.

Code Block
function
zero(){
 calculatePriority(string severity, string environment) {
    if (severity == "Critical" && environment == "Production") {
        return "Highest";
    } else if (severity == "Critical" && environment == "Development") {
        return "High";
    } else if (severity == "Major" && environment == "Production") {
        return "High";
    } else {
        return 
0; } number a
"Medium";
    }
}

// Usage examples:
string newPriority = 
zero();
Warning

Definition of UDRs must be done before the code, even though it is not used anywhere up to that point. Therefore, the following code is invalid.

Code Blocknumber i;
calculatePriority("Critical", "Production");  // Returns "Highest"

Description:

The function calculatePriority("Critical", "Production") receives "Critical" and "Production" as inputs and checks the first condition against an actual Jira issue. If the condition is met, it changes the priority of the Jira issue to Highest. If the condition is not met, the function continues by checking the next condition and so on.

This function can be reused throughout your SIL program whenever you need to calculate priority based on severity and environment. For example, when issues are created, when severity or environment fields are updated, as part of validation rules, or in bulk update operations.


Functions definition

SIL has strict rules about where function definitions can appear in your code.

Note

Important:

  • All functions must be defined before any executable code.

  • You can declare global variables and constants before the function definitions.

The correct order must be:

  1. Global variable declarations (with initialization if needed), including here any constant declarations

  2. Function definitions

  3. Executable code

This rule helps ensure that all functions are available when needed and prevents potential issues with code organization and variable scoping.

Invalid definition example

Code Block
// This is INVALID:
number i;                 // Variable declaration
const number pi = 3.14;   // Constant declaration
i = 0;
//this is the error line, i is initialized to a constant and
                    // Error line, this is code execution 
extra
- 
code,
NOT 
not
ALLOWED 
allowed
HERE
function circleArea(number r) {
    return r * r * pi;
}
Warning

However, you are allowed to declare global variables and constants before function definitions:

Code Block

The error occurs because you can't have executable code (like i = 0; where i is initialized to a constant) before function definitions. 

Valid definition example

Code Block
// This is VALID:
number i = 0;            //
global var
 Global variable declaration with initialization
, ok

const number pi = 3.14;  // Constant declaration

//
constant, ok
 Functions must be defined after declarations but before executable code
function circleArea(number r) {
    return (i + r) * (i + r) * pi;
}

// Executable code comes after all function definitions
number r = 10;
runnerLog("Area of radius " + r + " is " + circleArea(r));       //returns Area of radius 10 is 314
i++;
runnerLog("Area of radius " + (r + i) + " is " + circleArea(r));  // returns Area of radius 11 is 379.94

Running the above code in

the Power Scripts for JIRA (formerly known as JJUPIN) Runner gadget:

Image Removed

Parameters

The list of parameters in the definition of a UDR can be of any length (including 0) and their respective types can be any valid SIL™ type.

Example

Code Block

SIL Manager returns the following (in the Editor console):

The screenshot shows the results in the Editor console after the code is executed.Image Added


Parameters definition

Here’s what you need to know about parameters definition in a function:

  • Parameters can be of any valid SIL type (string, number, boolean, etc.)

  • Parameters are passed by value; changes inside the function don't affect the original variables.

  • The parameter list can be of any length; it can be empty.

Examples

This example illustrates how different parameter types are defined and used in a function.

Code Block
// Function with no parameters
function zero() {
    return 0;
}

// Function showing different parameter types
function 
doSomething
processData(
    string 
s,
name,           // Single string parameter
    number 
n1, number
age,            // Single number parameter
    number[] 
n2,
scores,       // Array of numbers
    boolean 
flag, string [] oneMore){ .... }

UDRs use a "pass-by-value" policy. This means that even though you modify the value of a parameter in your function, on exit the value will be lost.

Example

Excerpt Code Blockfunction increment(number a){ a = a + 1; // the value of a is only modified locally
isActive,      // Boolean parameter
    string[] comments      // Array of strings
) {
    // Using the parameters
    runnerLog("Processing data for: " + name);
    runnerLog("Age: " + age);
    runnerLog("First score: " + scores[0]);
    runnerLog("Status: " + isActive);
    runnerLog("First comment: " + comments[0]);
}

// Example usage:
string studentName = "John";
number studentAge = 20;
number[] testScores = {95, 87, 92};
boolean active = true;
string[] feedback = {"Good work", "Needs improvement"};

processData(studentName, studentAge, testScores, active, feedback);

Running the example usage code in the SIL Manager returns the following result:

Cfm background
widthSwitchauto
heightSwitchwrap text
backgroundColor#F1F2F4
imagePositionCenter
textColorPaletteDefault
textColor#091E42
imageOpacity1
minHeight1
an.spaceKeyPSJ
borderRadius0.03
width1
sideSpacing24
imageSizecover
idphlweaey13
opacity1
alignmentstart
backgroundColorPaletteDefault

Processing data for: John
Age: 20
First score: 95
Status: true
First comment: Good work
DONE!

Pass-by-value behavior

Parameters in UDFs are passed by value in the following way:

  • A copy of the value is passed to the function.

  • Changes to the parameter inside the function don't affect the original variable.

  • The original variable keeps its value after the function exits.

  • Pass-by-value behavior is the same for all SIL basic types, arrays, and structs.

Example

This example demonstrates pass-by-value behavior for a UDF that takes a number parameter and adds 10 to it.

Code Block
function updateScore(number score) {
    // Because of pass-by-value, 'score' is a local copy
    score = score + 10;                // Any changes to score parameter won't affect the original value
    runnerLog("Inside function, score is: " + score);
    return 
a
score;
}

// Test the function
number 
b
testScore = 
0; number c = increment(b
75;       //testScore variable created and set to 75
runnerLog("Before function call: " + testScore); //
the value of b does not change print(b);
shows 75

number newScore = updateScore(testScore);

runnerLog("After function call:");
runnerLog("Original score: " + testScore);   // Still 
this
75 
prints
- 
0
unchanged
print(c);
runnerLog("New score: " + newScore);         // 85 
this
- 
prints 1
returned value

If run in the SIL Manager, the function returns the following result:

Cfm background
widthSwitchauto
heightSwitchwrap text
backgroundColor#F1F2F4
imagePositionCenter
textColorPaletteDefault
textColor#091E42
imageOpacity1
minHeight1
an.spaceKeyPSJ
borderRadius0.03
width1
sideSpacing24
imageSizecover
idphlweaey13
opacity1
alignmentstart
backgroundColorPaletteDefault

Before function call: 75
Inside function, score is: 85
After function call:
Original score: 75
New score: 85
DONE!

Example description:

  • First, the function gets a copy of the testScore = 75 value to work with.

  • Inside the function, it adds 10 to that copy, making it 85. The function prints this value: "Inside function, score is: 85"

  • After the function exits, both the original score (75) and the modified score (85) are printed.

Constant Parameters

Parameters of

user-defined routines

UDFs can be made read-only

in the scope of the routine by adding the keyword "const" before the parameter definition in the signature of the routine.

by using the const keyword within the function.

Example

Code Block
function f(const string s) {
	...
}

Default Parameters

Parameters

may

can have defaults. This feature was introduced in

5

SIL v5.8.0.0.

 Default parameters should not be followed by another param without a default value.

Code Block
function f(int i, string arg = "this is default") {
    return "[" + i + "]" + arg;
}
//function f(int i = 1001, string arg) - incorrect, because 'arg' does not have a default
//function f(int i = 1001, string arg = "default") - correct, we can call it now by f() and receive the default params.

return f(0, "first string") + " >>>" + f(1); //f(1) will provide argument 'arg' the default value

Variable Visibility

There are three categories of variables that can be used in a UDR:

Local variables

These are the variables you define in the body of the UDR. These can be used throughout the body of the UDR. On exit, the values of these variables are lost.

Code Blockfunction example()

Note: Parameters with default values must appear after parameters without defaults.

The default value is used only when you don't provide a value for that parameter when calling the function. When you provide a value, it overrides the default.

Example

Code Block
// Function with a default parameter - 'name' will use "Guest" if no value is provided
function greet(string name = "Guest") {
    runnerLog("Hello, " + name);
}

// You can call this function two ways:

// 1. Without providing a parameter - will use the default "Guest"
greet();                  // Outputs: "Hello, Guest"

// 2. Providing a value - will use your provided value instead of default
greet("John");           // Outputs: "Hello, John"

Variable visibility in UDFs

When writing UDFs, you can work with three different categories of variables:

  • Local variables that you create inside the function

  • Parameter variables that you pass into the function

  • Global variables that are available throughout your program

Understanding how and when you can use each category of variable helps you write more effective and maintainable code.

Variable category

Definition

Example

Local

These are variables you create inside your UDF. They:

  • Only exist inside that specific UDF

  • Are created when the UDF starts running

  • Can only be used within that UDF

  • Are removed when the UDF finishes running

Code Block
function calculateTotal() {
    number 
a
price = 
3;
100;        // Local variable
    number 
b
tax = 
a
price 
+
* 
10;
0.1;  // Another local variable
    return price + tax;
} // price and 
use
tax 
here
no 
variables
longer 
a
exist 
and
after 
b
this 
}
point

Parameter

Variables

These are

the values passed to the UDR

variables that get passed into your UDF in the list of parameters.

Because SIL™ uses a

 Important characteristics:

  • Use the "pass-by-value" policy

, even though you modify the value of these variables in the body of the function, on exit, their original values will be restored.
  • Changes to parameters inside the body of the UDF don’t affect the original value

  • Original values are preserved after the UDF exits

Code Block
function 
increment
adjustPrice(number 
a
price) {
    
a
price = 
a
price + 
1
10;    // 
the
Only 
value
changes 
of
the 
a
copy 
is
inside 
only
the 
modified locally
UDF
    return 
a
price;
}

number 
b
itemPrice = 
0
50;
number 
c
newPrice = 
increment
adjustPrice(
b
itemPrice);
// the value of b does not change

print(
b
itemPrice);    // 
this
Still 
prints
shows 
0
50
print(
c
newPrice);     // 
this
Shows 
prints 1
60

Global

Variables

These are the variables

that are already defined and can be used

right away (issue fields, customfields and any variables defined before the routine). You can use issue fields and custom fields anywhere in your code

immediately (including in the

UDR

UDF body)

without having

; they are accessible throughout the entire program and you don’t have to declare them. These include:

  • Variables defined outside any UDF

  • Standard issue fields (e.g.keysummarydescription)

  • Custom fields

Code Block
number totalItems = 0;    // Global variable

function 
printKey
updateInventory() {
    totalItems = totalItems + 1;     // Uses global variable
    print("Issue: " + key);          // Uses standard issue field
}
Notice that the

The key variable is a standard issue field that you could

otherwise

use anywhere in your

SIL™

SIL program without having to declare it.

Best practices for using variables in UDFs

  • Keep in mind that local variables have the most limited access (just within the { and } of your UDF, while global variables have widest access.

  • Minimize the use of global variables to reduce code complexity.

  • Use meaningful parameter names to improve code readability.

  • Document any global variables used within UDFs.

  • Consider passing needed values as parameters rather than relying on their global state.

Return

Value

Return values can be used to communicate with the context that called the UDR or to halt its execution.

Examples

value

When a UDF finishes running, it can send a value back to where it was called from. You can also use return to stop the UDR from running further. This way, return values serve two purposes:

  • They provide a way to send results back to the code that called the UDF (like returning a calculation result.)

  • They allow you to exit the UDF at any point (for example, stop early if an error is found.)

Examples

This example returns a boolean result:

Code Block
function isEven(number 
a
value) {
    return (
a
value % 2 == 0);    // Returns true or false
}

This example returns a calculated value:

Code Block
function increment(number 
a
value) {
    return 
a
value + 1;    // Returns the input plus 1
}

number 
b
result = increment(2);
Notice that
    // result becomes 3

Important notes about returns

Return types are dynamic.

  • SIL determines the return type at runtime, so there is no need to declare the return type

of the return value; this will be evaluated at runtime. Therefore, even though the check on the following program will be ok, at runtime the value of d will NOT be modified because of the incompatibility between date (on the right-hand-side) and number (on the left-hand-side
  • in the UDF definition.

  • You cannot return two different types. This represents an error

  • Be careful with type compatibility when using the returned value. In the example below, the value of myDate will not be modified because there is an incompatibility between number (right-hand side, returned from the function) and date (left-hand side, the variable type).

    Code Block
    function increment(number 
a
  • value) {
        return 
a
  • value + 1;    // Returns a number
    }
    
    date 
d
  • myDate = increment(2);
NoteThere can be only one return value (at most). If you would like to return more values of the same type
  •     // WARNING: This will fail at runtime
                                  // Cannot convert number to date
  • A UDF can return at most one value.

Note: To return multiple values, consider using an array for multiple values of the same type or a

struct

structure for multiple values of different types.

  • You can

return simply from a routine without specifying
  • use return; without a value.

However, you should always remember that by design routines
  • UDFs always return a value, even if

it
  • return is undefined. The

following code is therefore valid
  • result of empty returns is an undefined value.

Examples

These are examples of empty returns behavior:

Code Block
function 
f(number a) { if(a
processStatus(number value) {
    // Case 1: value < 0
    if(value < 0) {
        print("Invalid: negative value");
        return;    // Exits here for negative numbers
    }
    
    // Case 2: value > 100
    if(value > 100) {
        print("Invalid: exceeds maximum");
        return;    // Exits here for numbers over 100
    }
    
    // Case 3: value between 1-100
    if(value > 0) {
        print("
positive"
Processing: " + value);
        return;    // Exits here for positive numbers up to 100
    }
    
    // Case 4: value equals 0
    if(
a
value == 0) { 
        print("
ZERO
Status: inactive"); 
        // No explicit return - function still exits after this
    }
}

//
[...................] string s =f(4); //s is still undefined, no value was returned if(isNull(s)) {   print("S IS NULL!"); //this will be printed } else {   print("S IS NOT NULL!"); }

Of course, the above code will print the text 'S IS NULL' in the log.

Contents

Table of ContentsmaxLevel3
 Let's see what happens in each case:
string result1 = processStatus(150);    
// Prints: "Invalid: exceeds maximum"
// result1 is undefined because of empty return

string result2 = processStatus(-5);     
// Prints: "Invalid: negative value"
// result2 is undefined because of empty return

string result3 = processStatus(50);     
// Prints: "Processing: 50"
// result3 is undefined because of empty return

string result4 = processStatus(0);      
// Prints: "Status: inactive"
// result4 is undefined because function ends without return

// All results are undefined, so all these will print:
print("Result 1 is null");  // Prints for value 150
print("Result 2 is null");  // Prints for value -5
print("Result 3 is null");  // Prints for value 50
print("Result 4 is null");  // Prints for value 0

Best practices for return values

  • Always consider what value your UDF should return.

  • Be consistent with return types within a single UDF.

  • Check for null/undefined values when using returned values.

  • Use meaningful return values that help explain the UDF's outcome.