3.8. YCP Program Structure

Now that we have learned how data can be stored and evaluated in YCP, we will take a look at the surrounding code structure that can be realized. Code structure is created by means of blocks and statements.

3.8.1. Comments

Despite not being “reallystatements, comments do (and should) belong to the overall structure of a YCP program. There are two kinds of comments:

  • Single-line comments

    Single-line comments may start at any position on the line and reach up to the end of this line. They are introduced with “//”.

  • Multi-line comments

    Multi-line comments may also start at any position on the line but they may end on another line below the starting line. Consequently there must be a start tag (“/*”) and an end tag (“*/”) as is shown below.

Example 3.14. Comments

{
   // A single-line comment ends at the end of the line.

   /*
     Multi-line comments
     may span several lines.
   */

   y2milestone("This program runs without error");
}
          

3.8.2. Variable Declaration

Synopsis: data_type variable_name = initial_value;

Variable declarations in YCP are simliar to C. Before you can use a variable, you must declare it. With the declaration you appoint the new variable to be of a certain data_type which means you can assign only values of that specific type. To avoid any errors caused by unintialized variables, a declaration must imply a suitable value assignment.

Note: A variable declaration may occur at several points in the code which determines its validity (accessability) in certain program regions (see Section 3.8.15, “Variable Scopes and blocks”).

Example 3.15. Variable Declaration

{
  integer int_num     = 42;
  float   float_num   = 42.0;
  string  TipOfTheDay = "Linux is best!";
  integer sum         = 4 * (num + 8);
}
          

3.8.3. Variable Assignment

Synopsis: variable_name = value;

An assignment statement is almost the same as a declaration statement. Just leave out the declaration. It is an error to assign a value to a variable that has not already been declared or to a variable of different data type.

Example 3.16. Variable Assignment

{
  integer number = 0;

  number = number + 1;
  number = 2 * number;
  number = "Don't assign me to integers!";     // This will cause an error!!!
}
          

3.8.4. Conditional Branch

Synopsis: if (condition) then_part [ else else_part ]

Depending on condition only one of the code branches then_part and else_part is executed. The else_part is optional and may be omitted. Both then_part and else_part may either be single statements or a sequence of statements enclosed in curly brackets, i.e. a block. The then_part is executed if and only if condition evaluates to true, the else_part otherwise. It is an error if condition evaluates to something other than true or false.

Example 3.17. Conditional branch

{ 
   integer a = 10;

   if ( a > 10 )  
      y2milestone("a is greater than 10");
   else
   {
      // Multiple statements require a block...
      
      y2milestone("a is less than or equal to 10");
      a = a * 10;
  }
}
          

3.8.5. while() Loop

Synopsis: while (condition) loop_body

The while() loop executes the attached loop_body again and again as long as condition evaluates to true. The loop_body may be either a single statement or a block of statements.

Because condition is checked at the top of loop_body, it may not be executed at all if condition is false right from start.

Example 3.18. while() Loop

{
   integer a = 0;

   while (a < 10) a = a + 1;
 
   while (a >= 0)
   {
      y2milestone("Current a: %1", a);
      a = a - 1;
   }
}
	  

3.8.6. do..while() Loop

Synopsis: do loop_body while (condition);

The do...while() loop executes the attached loop_body again and again as long as condition evaluates to true. The loop_body may be either a single statement or a block of statements.

Because condition is checked at the bottom of loop_body, it is executed at least once, even if condition is false right from start.

Example 3.19. do...while() Loop

{
   integer a = 0;

   do 
   {
      y2milestone("Current a: %1", a);
      a = a + 1;
   } while (a <= 10);
}
	  

3.8.7. repeat..until() Loop

Synopsis: repeat loop_body until (condition);

The repeat...until() loop executes the attached loop_body again and again as long as condition evaluates to false. The loop_body may be either a single statement or a block of statements.

Because condition is checked at the bottom of loop_body, it is executed at least once, even if condition is true right from start.

repeat...until() is similar to do...while() except that condition is logically inverted. The example below has been converted from the do...while() example above.

Example 3.20. repeat...until() Loop

{
   integer a = 0;

   repeat 
   {
      y2milestone("Current a: %1", a);
      a = a + 1;
   } until (a > 10);
}
	  

3.8.8. break Statement

Synopsis: break;

The break statement is used within loops to exit immediately. The execution is continued at the first statement after the loop.

Example 3.21. break statement

{
   integer a = 0;

   repeat 
   {
      y2milestone("Current a: %1", a);
      a = a + 1;
      if (a == 7) break;              // Exit the loop here, if a equals 7.
   } until (a > 10);                  // Value 10 will never be reached.

   y2milestone("Final a: %1", a);     // This prints 7.
} 
          

3.8.9. continue Statement

Synopsis: continue;

The continue statement is used within loops to abandon the current loop cycle immediately. In contrast to break it doesnt exit the loop but jumps to the conditional clause that controls the loop. So for a while() loop, it jumps to the beginning of the loop and checks the condition. For a do...while() loop or repeat...until() loop, it jumps to the end of the loop end checks the condition.

Example 3.22. continue statement

{
   integer a = 0;

   while (a < 10)
   { 
      a = a + 1;
      if (a % 2 == 1) continue;     // % is the modulo operator.
      y2milestone("This is an even number: %1", a);
   }
}
            

3.8.10. return Statement

Synopsis: return [ return_value ];

The return statement immediately leaves the current function or a current toplevel block (that contains it) and optionally assigns a return_value to this block. If blocks are nested, i.e. if the current block is contained in another block, the return statement leaves all nested blocks and defines the value of the outermost block.

However, if a block is used in an expression other than a block, and that expression is contained in an outer block, the return statement of the inner block won't leave the outer block but define the value of the inner block. This behaviour is a as one would expect. For example in the iteration builtins in Section 3.8.16, “Applying Expressions To Lists And Maps”,

Example 3.23. return statement

{
   // This block evaluates to 42.

   return 42;
   y2milestone("This command will never be executed");
}

{
   // This block evaluates to 18

   while (true)
   {
      return 18;
   }
}

{ 
   // This program evaluates to 3:

   integer a = 1 + { return 2; };
   return a;
}
            

3.8.11. Function definition

Synopsis: data_type function_name ( [ typed_parameters ] ) function_body

A function definition creates a new function in the current namespace named function_name with a parameter list typed_parameters that has function_body attached for evaluation. The function_body must return a value of type data_type and the arguments passed upon function call must match the type definitions in typed_parameters.

Example 3.24. Function definition

{
   void nothing()
    {
      y2milestone("doing nothing, returning nothing");
   }

   integer half( integer value )
    {
      return value / 2;
   }

   // This renders: ...nothing: nil  -  half: 21...

   y2milestone("nothing: %1  -  half: %2", nothing(), half(42) );
}
	  

3.8.12. Function declaration

Synopsis: data_type function_name ( [ typed_parameters ] );

A function declaration allows you to declare only a header of a function without its body. It's main purpose is for indirect recursion etc. You have to provide a function definition with exactly the same arguments later in the same file. A new function will be declared in the current namespace named function_name with a parameter list typed_parameters.

Example 3.25. Function declaration

{
   void nothing();
   
   integer half( integer value )
    {
      return value / 2;
   }

   // This renders: ...nothing: nil  -  half: 21...

   y2milestone("nothing: %1  -  half: %2", nothing(), half(42) );

   void nothing()
   {
      y2milestone("doing nothing, returning nothing");
   }

}
	  

3.8.13. include Statement

Synopsis: include " included file";

The include statement allows you to insert contents of a file at the given place in the current file. If the current file is a module, the contents of the included file will become a part of the module.

This is useful for dividing a large file into number of pieces. However, if a file is included more than once in a single block, the 2nd, 3rd etc. include statements are ignored.

The included file can be a relative or an absolute file name. Relative names are looked up with /usr/share/YaST2/include as a base.

Example 3.26. Include a file

// this will include /usr/share/YaST2/include/program/definitions.ycp

#include "program/definitions.ycp";
	  

3.8.14. import Statement

Synopsis: import " name space";

Not written yet...

3.8.15. Variable Scopes and blocks

In contrast to many other programming languages, YCP variables can be defined at (almost) any point in the code, namely between other statements. Given that, there must be some rules regarding the creation, destruction and validity of variables. Generally variables are valid (accessible) within the block they are declared in. This also covers nested blocks that may exist in this current block. The valid program region for a variable is called a scope.

Example 3.27. Variable scopes and blocks

{
   // Declared in the outer block
   integer outer = 42;

   {
      // Declared in the inner block
      integer inner = 84;

      // This is OK.
      // Log: ...IN: inner: 84 - outer: 42
      //
      y2milestone("IN: inner: %1 - outer: %2", inner, outer);
   }

   // This yields an error because "inner" is not defined any more.
   // 
   y2milestone("OUT: inner: %1 - outer: %2", inner, outer);
}
	  

3.8.16. Applying Expressions To Lists And Maps

Additionally to the structural language elements described so far, there are special commands that apply to lists and maps. What is special about these commands is that they apply an expression to the single elements of a list or map. This is done in a functional manner, i.e. the expression to be applied is passed as a parameter. Generally this executes faster than a procedural loop because the internal functionality is realized in a very effective way.

Furthermore some of these commands create lists from maps, maps from maps, maps from lists etc., so that they can be used to avoid the cumbersome assembling of these compound data types in a procedural loop.

3.8.16.1. foreach() Statement

Synopsis (list): any foreach( type variable, list<type>, (expression) );

Synopsis (map): any foreach( type_key variable_key, type_value variable_value, map<type_key, type_value>, (expression) );

This statement is a means to process the content of list or map in a sequential manner. It establishes an implicit loop over all entries of the list or map thereby executing the given expression with the respective entries. With lists the variable is a placeholder for the current entry. With maps, variable_key and variable_value are substituted for the respective key-value-pair.

Note 1: FIXME: Typing

Note 2: The return value of the last execution of expression determines the value of the whole foreach() statement.

Example 3.28. foreach() Loop

{
   // This yields 3
   foreach(integer value, [1,2,3], { return value; });

   // This yields 9
   foreach(integer key, integer value, $[1:1,2:4,3:9], 
           { y2milestone("value: %1", value); return value; });
}
	    

3.8.16.2. listmap() Statement

Synopsis: map<type1, type2> listmap( type3 variable, list<type3>, (expression returning map<type1, type2>) );

This statement is a means to process the content of list in a sequential manner. It establishes an implicit loop over all entries in list thereby executing the given expression with the respective entry. During execution variable is a placeholder for the current entry. For each element in list the expression is evaluated in a new context. The result of each evaluation MUST be a map with a single pair of key-value. All the returned key-value-pairs are assembled to form the new map that is returned.

Note: FIXME: Typing, break, continue

Example 3.29. listmap() statement

{
   // This results in $[1:"xy", 2:"xy", 3:"xy"]
   //
   map<integer, string> m1 = listmap(integer s, [1,2,3], ( $[s: "xy"]));

   // This results in $[11:2, 12:4, 13:6]
   //
   map<integer, integer> m2 = listmap(integer s, [1,2,3],
		    { integer a = s+10;
                        integer b = s*2;
			list ret = [a,b];
                        return(ret); });

   y2milestone("map 1: %1  - map 2: %2", m1, m2);
}
            

3.8.16.3. maplist() Statement

Synopsis (map): list<type1> maplist( type2 key, type3 value, map<type2, type3>, (expression returning type1) );

Synopsis (list): list<type1> maplist( type2 variable, list<type2>, (expression returning type1) );

This statement is a means to process the content of map or list in a sequential manner. It establishes an implicit loop over all entries in map or list thereby executing the given expression with the respective entries. With lists the variable is a placeholder for the current entry. With maps, key and value are substituted for the respective key-value-pair. For each element the expression is evaluated in a new context. All return values are assembled to form the new list that is returned.

Note: FIXME: Typing, break, continue

Example 3.30. maplist() statement

{
   // This results in [2, 4, 6]
   list<integer> l1 = maplist(integer s, [1,2,3], (s*2));

   // This results in [2, 6, 12]
   list<integer> l2 = maplist(integer k, integer v, $[1:2, 2:3, 3:4], (k*v));

   y2milestone("list 1: %1  - list 2: %2", l1, l2);
}
            

3.8.16.4. mapmap() Statement

Synopsis: map<type1, type2> mapmap( type3 key, type4 value, map<type3, type4>, (expression returning map<type1, type2>) );

This statement is a means to process the content of map in a sequential manner. It establishes an implicit loop over all entries in map thereby executing the given expression with key and value substituted for the respective key-value-pair. For each map element the expression is evaluated in a new context. The result of each evaluation MUST be a map with a single pair of key-value. All the returned key-value-pairs are assembled to form the new map that is returned.

Example 3.31. mapmap() statement

{
   // This results in $[11:"ax", 12:"bx"]
   //
   map<integer,string> m = mapmap(inetger k, string v, $[1:"a", 2:"b"], ([k+10, v+"x"]));

   y2milestone("map: %1", m);
}