This is one of the STIPPLE Documentation pages.

STIPPLE Routine and Object Declarations

After the all of the prelude and type declarations, come the module object and routine declarations. Object declarations specify the names of individual objects that are globally accessible. Routine declarations specify the code for a procedure or iterator.

The object declaration

The syntax for an object declaration is:
object_declaration
object_name
type_name
The object declaration specifies both an object name and its associated type. If the type is parameterized, it must be followed by a list of parameter type names enclosed in square brackets. The type name is followed by an optional expression.

At program start-up time, all objects are first initialized to the appropriate initial object. Next, the object expressions are evaluated in a compilation specific order. (Usually, the order that the modules are specified on the link command line.)

The routine Declaration

The syntax for a routine declaration is:
routine_declaration
procedure_declaration
iterator_declaration
routine_types_clause
named_routine_type_clause
named_procedure_type_clause
named_iterator_type_clause
procedure_name
iterator_name
needs_clause
typed_object_or_routine
needs_object_clause
object_name
needs_procedure_clause
needs_iterator_type_clause
needs_type_name
takes_clause
returns_clause
yields_clause
signals_clause
signal_name_clause
signal_name
type_reference
type_name
There are two kinds of routines, procedures and iterators. The first line of a routine declaration starts with the either keyword procedure or iterator followed by the routine name, followed by an optional type name with optional parameter names. A routine declaration is followed by an optional routine_types clause, an optional needs clause, a required takes clause, a required returns clause, for iterators only, a required yields clause, an optional signals clause, followed by one or more statements. Only iterators are permitted to have a yields clause; they are explicitly disallowed for procedures. The needs clause is only permitted for parameterized routines.

The language mandates the order of these clauses in order to improve uniformity between code.

The routine_types Clause

The routine_types clause of a routine declaratation is used to define routine variable types for subsequent use within routine declaration.

For example:

    procedure at_exit
	routine_types
	    procedure exit_routine_type
		takes_nothing
		returns_nothing
	takes
	    exit_routine exit_routine_type
	returns_nothing

	#: This procedure will register {exit_routine}
	#, to be called when the program is exiting.
								
defines a procedure which takes as its only argument, exit_routine, a procedure variable that will eventually be invoked upon exit. The signature of the procedure variable is specified by the routine_types clause to be a routine that takes no arguments and returns no values.

The routine_types clause can reference any types imported into the module or defined within the module. In addition, for parameterized routines, the routine_types clause can reference any of the routine parameter types.

An example of a routine_types clause that references a parameter type is shown below:

    procedure on_expose_set@window1[co_type]
	routine_types
	    procedure expose_routine_type
		takes window, region, co_type
		returns_nothing
	takes
	    window window
	    expose_routine expose_routine_type
	returns_nothing

	#: This procedure stores the procedure variable
	#, to call when a region is exposed on {window}.
								
In the example immediately above, the routine_types clause defines the expose_routine_type procedure variable type whose third argument type is the type co_type, the parameter name from window1[co_type].

The needs Clause

The needs clause specifies a template of procedures and/or objects. This template is called the needs record. The needs record is implicitly passed along with any procedure that is parameterized. Hence, the needs clause can only be specified for parameterized procedures. It is a compiler error to specify a needs clause for a non-parameterized procedure. The needs keyword is followed by one or more name-procedure/object pairs. The procedure/object declaration must reference one or more parameter names. The initial object for each parameter in the procedure's parameter list is implicitly a part of each needs record; thus, it is a compiler error to specify initial objects in a needs clause (see Why have implicit initial objects in needs clauses.) The procedures/objects of a variadic_needs clause are accessed from inside of the procedure body by name.

The takes Clause

The takes clause specifies the name and types of all procedure arguments. Each name is treated exactly like a variable within the body of the procedure. When a procedure is called, the procedure arguments are evaluated and assigned to the variable names defined in the takes clause.

For example, consider the code fragment that calls the procedure named add,

    ...
    x := add(y + 10, z - 17)
    ...
								
where add is the following procedure:
    procedure add
	takes
	    a unsigned
	    b unsigned
	returns unsigned
	return a + b
								
The procedure call sequence is equivalent to:
    a := y + 10
    b := z - 17
								
where a and b are variables in the add procedure and x and y are evaluated in the environment at the call site.

The variadic Clause

{Please ignore the following for now...}

  variadic_clause
     -->	variadic variadic_size_variable { [ variadic_parameter_name, ... ] } ø
		¿	{ variadic_name signature_type_reference }+
		<
		{ variadic_needs_clause }
  variadic_size_variable
     -->	identifier
  variadic_parameter_name
     -->	identifier
  variadic_name
     -->	identifier
  variadic_needs_clause
     -->	variadic_needs ¤ need_clause
								
A variadic routine is one with a variadic clause (see Why variadic procedures.) The variadic clause in conjunction with the optional variadic_needs clause specifies a template, called a variadic record. A variadic routine invocation can pass zero, one, or more variadic records as arguments. The identifier immediately following the variadic keyword is called the variadic size variable; it has a type of unsigned and is assigned the number of variadic records passed into the variadic procedure. Following the variadic size variable is an optional parameter list enclosed in square brackets ("[...]".) Variadic parameter lists are discussed shortly. The lines following the variadic keyword define the fields and types that make up the variadic record in exactly the same format as a record type declaration.

A variadic procedure with T arguments in the takes clause and V arguments in the variadic clause must be invoked with T + nV arguments, for some non-negative value of n. n is the number of variadic records and is assigned the to the variadic size variable. The types of the first T arguments must match the corresponding types of the takes clause. The remaining nV arguments must match the corresponding types in the variadic clause in a round-robin fashion.

For example, the following two variadic procedures:

    procedure sequence
	takes
	    heap
	variadic size
	    value unsigned
	returns vector[integer]
	...

    procedure wire
	takes
	    heap
	variadic count
	    x integer
	    y integer
	returns wire
	...
								
can be invoked as follows:
    primes := sequence(heap1, 1, 2, 3, 5, 7, 11, 13)	# size = 7
    fibonacci := sequence(heap1, 1, 1, 2, 3, 5, 8, 13)	# size = 7
    empty := sequence(heap1)				# size = 0
    error := sequence(heap1, -1, -2, -3)		# Type error, must be unsigned
    wire1 := wire(heap1, 0, 0, 1, 1)			# count = 2
    none := wire(heap1)					# count = 0
    bad := wire(heap1, 1, 2, 3)				# Error, no y value for last pair
								
The values of variadic arguments are accessed using the variadic_with statement (see the variadic_with statement.)

The variadic_needs Clause

The variadic_needs clause specifies some routines and/or objects that are to be invisibly passed with each variadic record. The variadic_needs clause can be specified only if the variadic clause is parameterized. The variadic_needs keyword is followed by one or more name-routine/object pairs. The routine/object declaration must reference one or more parameter names. The initial object for each parameter in the variadic_needs clause is implicitly a part of each variadic record; thus, it is a compiler error to specify initial objects in a variadic_needs clause (see Why have implicit initial objects in needs clauses.) The routines/objects of a variadic_needs clause are accessed from inside of a variadic_with statement.

The following example is a simple implemention of the out_stream@print variadic procedure, which requires a variadic_needs clause:

    procedure print@out_stream
	takes
	    output out_stream
	    format_string string
	variadic count [type]
	    value type
	returns unsigned
	variadic_needs
	    procedure format@type
		takes type, out_stream, in_stream
		returns unsigned

	format_stream:: in_stream :=
	  format_string~in_stream_convert(heap@standard)
	index:: unsigned := 0
	width:: unsigned := 0
	loop
	    for chr:: character := format_stream~characters() as format_chrs
	    if chr = "%"
		if !format_stream.empty && format_stream~peek() = "%"
		    next chr := format_chrs
		    chr~print(output)
		    width++
		else_if index < count
		    variadic_with index++
		    width += value~format(output, format_stream)
		else
		    width += output~print(`')
	    else
	    	chr~print(output)
		width++
	if index < count
	    width += output~print(`<%d arguments not printed>', count - index)
	return width
								
This routine takes an output stream and a format string followed by a sequence of zero, one, or more arguments of any type. Each argument is put into a variadic record along with an associated routine called format@type. (In addition, the initial object, type@??, is implicitly passed in the variadic record.) Errors are printed by recursively calling itself. The actual implementation of this routine is more sophisticated in that it permits the printing of out-of-order arguments, and fixed width fields.

The returns Clause

The returns clause specifies the number and types of values returned by a procedure. However, the syntax

If a procedure does not return any values, the returns_nothing clause is specified.

Some procedures, like exit@system, have the attribute that they will never return. (This is different from returning no values.) A procedure that never returns specifies the returns_never keywords for the returns clause. Thus,

    procedure exit@system
	takes
	    return_code integer
	returns_never
	...
								
is the correct definition for the exit@system procedure.

The yields Clause

The yields clause is specifies the number and types of values yielded by an iterator. It is a compiler error to specify a yields clause for a procedure. They syntax of the yields clause is the same as the returns clause. In the rare case where an iterator does not yield any values, the yields_nothing clause is specified.

The signals Clause

The signals clause specifies the signal names and any associated return types for a procedure.

Signals are raised via the signals statement.

{This is a little too terse.}


From here you can go to either the next chapter on STIPPLE Statements or back to the table of contents.
Copyright (c) 1991 -- Wayne C. Gramlich. All rights reserved.