NCL Home > Documentation > Manuals > Reference

NCL statements

Statements are the fundamental element of NCL. Everything NCL does happens only after a statement is entered. Statements are not restricted to being a single line of source, and statements can be nested within statements. There are 17 different kinds of statements: assignment, procedure call, function definition, procedure definition, block, do, if-then, if-then-else, break, continue, setvalues, getvalues, return, record, new, stop, and quit.

There are two very important things to understand about statements. First, each statement only executes after it has been parsed and is found to be free of syntax errors. Second, if a runtime error occurs while executing a statement, execution of the statement and any statements in which the erroneous statement is nested is immediately terminated.

Comments

Everything to the right of a semicolon (';') on a line is considered to be a comment:

    ; this entire line is a comment
    x = 5

    x = 5  ; this comment is on a line with executable code

Block comments were introduced with NCL version 6.4.0, whereby multiple lines of code may be commented by bracketing the text with /; and ;/ characters:

    /;
       These lines 
       of text are
       considered comments
    ;/

Blocks

Blocks provide a way to group a list of commands. Since blocks are statements, the statements within the begin and end do not get executed until the end statement is parsed and the source is determined to be free of syntax errors. Scripts enclosed in a block are parsed and, if there are no syntax errors, executed. When using NCL by either loading scripts or piping scripts into NCL, begin and end blocks should be used. This reduces error cascading, and syntax errors can be found before costly statements preceding the error are executed.

    begin
        statement list
    end 

if statements

The are two kinds of if statements: the if-then statement and the if-then-else statement. These function like if statements in other popular programming languages. The following is the syntax for the NCL if statements:

    if(scalar_logical_expression) then
        statement list
    end if

    if( scalar_logical_expression) then
        statement list
    else
        statement list
    end if 

The scalar_logical_expression is an expression made of relational operators. For if statements, the result of the logical expression within the parentheses must result in a single scalar value and must not be a missing value.

If statements may be nested within other if statements. It is also legal to have an "else" and an "if" on the same line, although each new "if" is considered to be a separate nested if statement. Every "if" must have a matching "end if", properly nested. In this way the "case" statement is emulated in NCL:

   if (scalar_logical_expression) then
       statement list
   else if (scalar_logical_expression) then
       statement list
   else if (scalar_logical_expression) then
       statement list
   else
       statement list
   end if
   end if
   end if

Version NCL 6.5.0 introduced the elseif keyword, which simplifies the above nested if-then-else clauses to:

   if (scalar_logical_expression) then
       statement list
   elseif (scalar_logical_expression) then
       statement list
   elseif (scalar_logical_expression) then
       statement list
   else
       statement list
   end if

Important note: never search for a missing value using a logical expression. For example if statements of the following form should not be used:

    if( a(i) .eq. a@_FillValue) then
        . . .

The above statement will return a missing value when a is equal to the value of the attribute _FillValue. This will in turn cause an error message and premature termination of the script. In NCL when expressions, even logical expressions, contain a missing value the missing value is propagated to the result, except when a lazy evaluation condition is met (see below).

Instead, use the ismissing function to check for a missing value:

    if(ismissing(a(i))) then
        . . .

For more information, see the discussion on Expressions and missing values.

Logical operators operate on entire arrays of values, when this is the case they produce array results, and each element in the array result could be True, False, or a missing value. Since if statements require the results of the logical expression to be a single scalar logical value a FATAL error message is generated and execution is terminated if the conditional part of the if statement is not scalar. Therefore there are three functions that help with the above problems. The functions any and all return a single True scalar value if any element in a logical array is True, or if all elements in a logical array are True, respectively. The function ismissing returns an array of True or False values at locations where the input is either a missing value or a normal value Combined with lazy conditional expression evaluation, the ismissing function can filter possible error conditions before they occur.

Lazy expression evaluation means that if the first term of an .AND. expression is False, or the first term of an .OR. expression is True, the entire expression--regardless of the result of the remaining terms--is False and True respectively. When this is the case, NCL does not evaluate the remaining terms. This is a very useful tool for avoiding error conditions. For example, consider the integer i and the array a. Lazy evaluation can avoid indexing the array a with i when i is out-of-range. The following example demonstrate this:

    if((i .lt. dimsizes(a)) .and. (a(i) .gt. 10 )) then 
        . . . 
    end if
For further discussion of this topic, see Lazy Evaluation.

Loops

NCL provides two kinds of do loops: a while loop that repeats until its scalar_logical_expression evaluates to False, and a traditional do loop that loops from a start value through an end value incrementing or decrementing a loop variable either by one or by a specified stride. With all kinds of loops, the keywords break and continue can be used. Break causes the loop to abort, and continue causes the loop to proceed directly to the next iteration without executing the remaining statements in the block.

    do

    For the do loop, a variable loop identifier, a scalar numeric start expression, a scalar numeric end expression, and an optional positive scalar numeric stride expression are required. The identifier can be either a) a defined variable or variable subsection or b) an undefined name. Before the loop starts, the start_expr value is assigned to the identifier. Then after each iteration of the loop, either the identifier is incremented or the identifier is assigned its next value based on the stride specified and the direction. The following is an example of a loop without specification of the stride. In this kind of loop, the identifier is incremented by one after each iteration. If the start expression is greater than the end expression, no iterations of the loop occur. If the start expression is equal to the end, then one iteration occurs. To loop backward, see the next example.

        do loop_identifier = scalar_start_expr , end_expr
             statement list 
        end do
    

    The following shows how to specify a loop with an explicit stride. The following form is also necessary to loop backwards. With this form, if start is greater than end, then the identifier is decremented by the stride value, which must always be positive. If the start is less than the end, then the identifier is incremented by the stride value.

        do loop_identifier = scalar_start_expr , scalar_end_expr ,  scalar_stride_expr
             statement list 
        end do
    

    Any numeric type, including double or float, is allowed for the loop identifier and the scalar start, end, and stride expressions, but the usual NCL coercion rules apply as follows: if the loop identifier variable is defined prior to the beginning of the do loop, then the scalar start expression and the stride expression (if any) must both be coercible to the type of the loop identifier variable. Otherwise, the loop identifier variable takes the type of the scalar start expression and the scalar stride expression (if any) must be coercible to the type of the start expression. The loop identifier variable, which is incremented or decremented during execution of the loop, need not have the same type as the scalar end expression. When these types differ, the loop completion test performed at each iteration converts the applicable values to the double type prior to making the test.

    while

    This example shows the while loop syntax. The scalar_logical_expression adheres to the same restrictions placed on the conditional expression of the if statement. Specifically, it must be a scalar logical value and not be a missing value. Also lazy evaluation of .AND. and .OR. statements occurs.

        do while (scalar_logical_expression)
             statement list 
        end do
    

Assignment

The assignment statement can be used to assign values of any type to variables, subsections of variables, coordinate variables, and attributes. In addition, the assignment statement is used to assign string values to dimension names. Several NCL language constructs are used to identify what type of assignment is to take place. The constructs '=>', '->', '!', '&', and '@' are used to specify file group assignment, file variable assignment, dimension name assignment, coordinate variable assignment, and attribute assignment, respectively. Without these constructs, normal-value-to-variable assignment occurs.

The following passages cover the syntax of the various assignment statements. The section on variable assignment covers the semantics of variable-to-variable assignment. Variable-to-variable assignment copies not only a variable's value but all attributes, dimensions, and coordinate variables associated with that variable. The variable assignment section as well as the properties of variables are a very important sections to read and understand.

Dimension name assignment

To assign a dimension name to a defined variable or file variable, the variable name is followed by the '!' operator followed by the dimension number to which the new name is to be assigned.

Examples:

    a!0 = "Dimension1"
    thefile->a!0 = "Dimension1"

Coordinate variable assignment

To associate a coordinate variable with a dimension of a variable, the variable name is followed by the '&' operator, followed by the name of the dimension to which the coordinate variable is to belong. Three restrictions apply to coordinate variables. First, the dimension to which the coordinate variable is being assigned must have a name. Second, the value being assigned must be a single dimension array of the same size as the named dimension it is being associated with. Finally, if coordinate is going to be used, the values in the coordinate variable must be monotonically increasing or decreasing. If the value assigned is non-monotonic or contains missing values, the assignment still occurs but a WARNING message is generated and attempts to use coordinate subscripting will fail with a FATAL message.

Examples:

    a&Dimension1 = (/.1,.2,.3,.4,.5,.../)
    thefile->a&Dimension1 = (/.1,.2,.3,.4,.5,.../)
Using string references:

    dimname = "Dimension1"
    a&$dimname$ = (/.1,.2,.3,.4,.5,.../)
Using file string references:

    dimname = "Dimension1"
    varname = "a"
    thefile->$varname$&$dimname$ = (/.1,.2,.3,.4,.5,.../)

Attribute assignment

To assign an attribute to a variable, the variable name is followed by the '@' operator, followed by the name of the attribute. Attributes must have a single dimension but can be any size. Examples:

    a@units = "Degrees C"
    a@_FillValue = -9999.0
    thefile->a@units = "Degrees C"
Using string references:

    attnames = (/"units","_FillValue"/)
    a@$attnames(0)$ = "Degrees C"
    a@$attnames(1)$ = -9999.0
Using file string references:

    attnames = (/"units","_FillValue"/)
    varname = "a"
    thefile->$varname$&$attnames(0)$ = "Degrees C"
    thefile->$varname$&$attnames(1)$ =  -9999.0
The _FillValue attribute is a special reserved attribute. It allows the user to associate a missing value with a variable. This allows values to be filtered when the array of values is an operand to an algebraic expression. When set, all of the missing values in the value array referenced by the variable are changed to the new missing value.

Reassignment

The reassignment statement is available in version 6.1.1 or later.

The reassignment statement can be used to assign values of any type to variables (which may be already definde in other type or shape),

The section on variable reassignment covers the semantics of variable-to-variable and value-only reassignment. Variable-to-variable reassignment copies not only a variable's value but all attributes, dimensions, and coordinate variables associated with that variable.

Procedures

NCL procedures, in many ways, are the same as in most programming languages, but NCL procedures also have some distinct differences. The key differences are in how the types and dimension sizes for parameters are specified and handled by NCL. In NCL, parameters can be specified to be very constrained and require a specific type, number of dimensions, and a dimension size for each dimension, or parameters can have no type or dimension constraints. Parameters in NCL are always passed by reference, meaning changes to their values, attributes, dimension names, and coordinate variables within functions change their values outside of the functions. There is one very important exception that does generate a WARNING message: when an input parameter must be coerced to the correct type for the procedure, NCL is not able to coerce data in the reverse direction so the parameter is not affected by changes made to it within the procedure. See the discussion on coercion.

Parameters that are subsections of variables (i.e. subscripted elements) are passed by reference. Which means when the procedure finishes, the values of the subsection are mapped back into their original locations in the referenced variable.

External Procedures

External procedures are procedures that exist in external shared libraries. These procedures are loaded during execution of the external statement. To call one of these procedures or functions, you preceded the procedure name and argument list with the name provided to the external statement followed by two colons. For example:

    external elib "./elib.so"
    . . .
    elib::eproc(param0,param1)

For more information on calling external procedures, see the documentation on WRAPIT.

Function and procedure definitions

Functions and procedures are defined using a similar syntax; the only difference is that the keyword procedure is used instead of the keyword function. To define a new function, the proper keyword is entered, followed by the symbol name of the function, followed by a list of declarations that can optionally be followed by a list of local variable names. Finally, a block of statements enclosed in begin and end follows.

function function_name (  declaration_list  )
local local_identifier_list
begin
     statement list 
    return(return_value)
end

The local statement and function and procedure scope

It is very important to note that if a variable intended to be local to the function or procedure does not occur in the local list, then the function may find that variable name outside of the scope of the function or procedure.

It is also important to note the consequences of not putting a local variable into the local list. There are two possibilities. First, if the variable is defined at the time of function definition and is in the function's scope, the variable will be referenced. If the variable is not defined at the time of definition, it will be an undefined variable local to the function.

Placing the local variable's name in the local list assures that the variable truly is local only to the current function. The scope rules for NCL are identical to Pascal. The main NCL prompt is the bottom level of NCL's scope. Function and procedure blocks are the next level. When a variable is referenced in a function, NCL first looks within the scope of the function for that variable. If it doesn't exist, NCL looks in the scope containing the function and continues to search outward until either the bottom level is reached or a variable of the correct name that is not local to its own function is found. If it is not found, then an undefined variable reference error message is generated.

Local variable list syntax:

    local  identifier_name  ,  identifier_name  , . . .

The local variable list is very important. If a local variable name is not in the local list then and the same identifier is defined in a lower scope level, then the variable reference in the function or procedure will refer to that variable. Sometimes this may be the desired effect. However, if the variable is to be local, it should be referenced in the local list.

Constraining the type and dimensionality of input parameters

As noted previously, parameters can be constrained or unconstrained. The following shows the syntax of the variable declaration. The square brackets ( '[' and ']' ) are used to denote optional parts of the declaration syntax and are not part of the structure of the declaration list. Each declaration in the declaration list is separated by a comma.

Declaration list element syntax:

    variable_name [  dimension_size_list ] [ : data_type ]

Unlike the preceding syntax specification, the characters '[' and ']' are literal elements of the syntax for the dimension size list. They are used to separate dimension sizes. Each pair of brackets can contain an integer representing the size of the dimension or a star to indicate that the dimension may be any size. The number of pairs of brackets is the number of dimensions the parameter will be constrained to.

Dimension size list syntax:

    [ dimension_size ]
or

    [ * ]

If the data type is not specified then any data type will be accepted for the argument. If an individual type such as float is specified explicitly then only values of type float or that can be automatically coerced to float will be accepted. However, if coercion occurs a warning will be generated saying that modifications to the value will not be propagated back to the reference variable. See Coercion for a table listing the automatic type coercions that are permitted.

Rather than restricting an argument to a specific type you can more generally require that it be a numeric value by substituting the keyword numeric for the data type. Since the addition of a number of new integer data types in version 5.2.0, the keyword numeric has been supplemented with two new keywords, snumeric and enumeric. Here are their meanings:

numeric
numeric types defined prior to version 5.2.0: float, double, byte, short, integer, and long.

snumeric
(super-numeric) float, double, and signed and unsigned integer types of any size (i.e. all currently defined numeric types).

enumeric
(extra-numeric) only those types added since version 5.2.0: ubyte, ushort, uint, ulong, int64, and uint64.

Use of any of these keywords eliminates the warning message about propagation of modified values back to the reference variable. However, if you do want to modify the value inside the procedure or function, you should be aware that errors can occur during assignment if coercion fails. Just as a simple example, if a routine has an input numeric type argument called x, and within the routine is the statement x = 2, an error will occur if you try to pass in a byte or a short type value. That is because the number 2 is input as an integer which is not coercible to byte or short. In this case you could fix the problem by changing the statement to x = 2b, causing the value 2 to be input as a byte type, which is coercible to any of the numeric types. But for most real-world situations it will probably not be so simple to allow any numeric type for a value that is modified within a routine. You will avoid this difficulty as long as you treat the variable specified by the argument as read-only within the routine.

Data type syntax:

     : data_type | numeric | snumeric | enumeric 

Visualization blocks

Visualization blocks are the interface between the HLU library and NCL. There are three types: create, setvalues, and getvalues. These set or retrieve HLU resources from HLU objects. Creating graphics with the HLUs can range from very simple to very complex. Only the syntax for the interface is discussed here. More information on HLU usage as well as NCL examples can be found at the following locations:

setvalues

Use setvalues to modify resources in an HLU object after it has been created. setvalues takes an NCL resource list in the same fashion that create does. It then sets each resource to its corresponding value. Note: obj_reference can be one or more HLU objects. If more than one the resources are repeatedly applied to each HLU object.

    setvalues obj_reference
        resource_string :  value_expression
        resource_string :  value_expression
    end setvalues

getvalues

Use getvalues to retrieve resource values from HLU objects. It is different than the setvalues and create NCL resource list because it requires an identifier rather than an expression after the colon. getvalues functions identically to the assignment with respect to how it assigns values to the identifier.

    getvalues obj_reference
        resource_string : identifier
        resource_string : identifier
    end getvalues

Load a shared library

The external command loads a specially created shared library. Shared libraries are one way in which NCL can be extended to call custom C and Fortran functions and procedures. The syntax for the external command is:

    external string_name path_name
The process for creating these shared libraries is described in the extending NCL section. The syntax for calling functions and procedures defined in external shared libraries is described in the procedures section of this page. The rules for call procedures also apply to functions.

Load script from a file

The load command is used to load external scripts from files. These external scripts can either be sets of functions and procedures or actual NCL programs. Currently, file_path must be a constant value; string expressions will generate syntax error messages. You can think of the load command as being analogous to the C #include statement. The load command executes immediately when it is encountered. It cannot be executed conditionally. The following is an example:

    load "file_path"
In order to conditionally load a script or use a string variable to reference a file path see the loadscript procedure.

Record commands

The record command will cause all syntactically correct commands entered at the command line to be saved in the file pointed to by the file_path constant string value. The "stop record" command terminates this process.

    record "file_path"
    statements
    . . .
    stop record

Quit an NCL interactive session or script

The quit command immediately exits from an NCL interactive session or script. Like the load command it is processed immediately when encountered and cannot be executed conditionally. NCL takes care of closing any open graphics or data files properly before exiting. In order to exit a script conditionally see either the exit or status_exit procedures.