4.12.2 User-Defined Functions

Complicated filter scripts can often be simplified and made more manageable by defining new functions.

The function definitions can appear anywhere between the handler declarations in a filter program. The only requirement is that the function definition occurs before the place where the function is invoked.

The definition of a function named name looks like this:

 
[qualifier] func name (param-decl) returns data-type
do
  function-body
done

name is the name of the function to define, param-decl is a comma-separated list of parameter declarations. The syntax of the latter is the same as that of variable declarations (see section Variable declarations), i.e.:

 
type name

declares the parameter name having the type type. The type is string or number.

Optional qualifier declares the scope of visibility for that function (see section Scope of Visibility). It is similar to that of variables, except that functions cannot be local (i.e. you cannot declare function within another function).

The public qualifier declares a function that may be referred to from any module, whereas the static qualifier declares a function that may be called only from the current module (see section Modules). The default scope is ‘public’, unless specified otherwise in the module declaration (see section Declaring Modules).

For example, the following declares a function ‘sum’, that takes two numeric arguments and returns a numeric value:

 
func sum(number x, number y) returns number

Similarly, the following is a declaration of a static function:

 
static func sum(number x, number y) returns number

Parameters are referenced in the function-body by their name, using the syntax for variable references: %name. The value of a parameter can be altered using set statement, the same way as for variables.

A function can be declared to take a certain number of optional arguments. In function declaration, optional abstract arguments must be placed after the mandatory ones, and must be separated from them with a semicolon. The following example is a definition of function foo, which takes two mandatory and two optional arguments:

 
func foo(string msg, string email; number x, string pfx)

Mandatory arguments are: msg and email. Optional arguments are: x and pfx. The actual number of parameters supplied to the function is returned by a special construct $#. In addition, the special construct @arg evaluates to the ordinal number of variable arg in the list of formal parameters (the first argument has number ‘0’). These two constructs can be used to verify whether an argument is supplied to the function.

The actual parameter for argument n is supplied if the number of actual parameters ($#) is greater than its ordinal number in the declaration list (@n). So, to check if an optional argument arg is supplied, the following condition can be used:

 
func foo(string msg, string email; number x, string arg)
do
  if $# > @arg
    …
  fi

The default mailfromd installation provides a special macro for this purpose: see defined. Using it, the example above could be rewritten as:

 
func foo(string msg, string email; number x, string arg)
do
  if defined(arg)
    …
  fi

Within a function body, optional arguments are referenced exactly the same way as the mandatory ones. Attempt to dereference an optional argument for which no actual parameter was supplied, results in an undefined value, so be sure to check whether a parameter is passed before dereferencing it.

A function can also take variable number of arguments (such functions are called variadic). This is indicated by the use of ellipsis as the last abstract argument. The statement below defines a function foo taking one mandatory, one optional and any number of additional arguments:

 
func foo (string a ; string b, ...)

All actual arguments passed in a list of variable arguments are coerced to string data type. To refer to these arguments in the function body, the following construct is used:

 
$(expr)

where expr is any valid MFL expression, evaluating to a number n. This construct refers to the value of nth actual parameter from the variable argument list. Parameters are numbered from ‘1’, so the first variable parameter is $(1), and the last one is $($# - Nm - No), where Nm and No are numbers of mandatory and optional arguments to the function.

For example, the function below prints all its arguments:

 
func pargs (string text, ...)
do
  echo "text=%text"
  loop for number i 1,
       while %i <= $# - 1,
       set i %i + 1
  do
    echo "arg %i=" . $(%i)
  done
done

Note the loop limits. The last variable argument has number $# - 1, because the function takes one mandatory argument.

The function-body is any list of valid mailfromd statements. In addition to the statements discussed below (see section Statements) it can also contain the return statement, which is used to return a value from the function. The syntax of the return statement is

 
  return value

As an example of this, consider the following code snippet that defines the function ‘sum’ to return a sum of its two arguments:

 
func sum(number x, number y) returns number
do
        return %x + %y
done

The returns part in the function declaration is optional. A declaration lacking it defines a procedure, or void function, i.e. a function that is not supposed to return any value. Such functions cannot be used in expressions, instead they are used as statements (see section Statements). The following example shows a function that emits a customized temporary failure notice:

 
func stdtf()
do
  tempfail 451 4.3.5 "Try again later"
done

A function may have several names. An alternative name (or alias) can be assigned to a function by using alias keyword, placed after param-decl part, for example:

 
func foo()
alias bar
returns string
do
  …
done

After this declaration, both foo() and bar() will refer to the same function.

The number of function aliases is unlimited. The following fragment declares a function having three names:

 
func foo()
alias bar
alias baz
returns string
do
  …
done

Although this feature is rarely needed, there are sometimes cases when it may be necessary.

A variable declared within a function becomes a local variable to this function. Its lexical scope ends with the terminating done statement.

Parameters, local variables and global variables are using separate namespaces, so a parameter name can coincide with the name of a global, in which case a parameter is said to shadow the global. All references to its name will refer to the parameter, until the end of its scope is reached, where the global one becomes visible again. Consider the following example:

 
number x

func foo(string x)
do
  echo "foo: %x"
done

prog envfrom
do
  set x "Global"      
  foo("Local")
  echo %x
done

Running mailfromd --test with this configuration will display:

 
foo: Local
Global