1. Programming Basics

In this chapter, you will find the basic constructs and some basics for programming in the ACF-language.

  1. Programming Basics
    1. Structure
    2. Function Declaration
    3. Variable Declarations
    4. Parameter Declarations and Local Declarations
    5. Auto Declarations
    6. Loops
      1. For loops
      2. While loops
      3. Repeat...Until loops
    7. Conditionals
      1. IF...THEN...ELSE...END IF
      2. Conditional expressions
      3. CASE Statements
    8. Calling Other ACF Functions
      1. Functions and the Return Value
      2. Return Value and Recursion
    9. Error Handling
      1. Try / Catch

1.1. Structure

The structure in the source file is as follows:

Package <package name> "Description - can be several lines"

<global declarations within the package>

function firstfunction ( <declaration of parameters, comma-separated> ) 
    <Declarations and statements>
end

function nextfunction ( <declaration of parameters, comma-separated> ) 
    <Declarations and statements>
end

...
...

function lastfunction ( <declaration of parameters, comma-separated> ) 
    <Declarations and statements>
end

1.2. Function Declaration

The function declaration consists of the keyword "function" followed by a parameter declaration list enclosed in parentheses. All the parameters must be declared by type.

function abc ( int a, float b )

It is usually not necessary to declare the return parameter since the return statement in the function auto-declares the return value. However, if needed, you can have a standard declaration of the function name itself below the initial function declaration.

To register the function as a plugin function, you can use the FunctionID command inside the function, generally near the top.

1.3. Variable Declarations

Declarations are the same for local and global, with one exception. Global declarations cannot have an initial value. All program execution takes place inside functions, and thus, they have to initialize the globals they use. The types can be found in the Datatypes document.

Variable names are not case-sensitive, start with a letter between 'a' and 'z,' can contain numbers and underscores. Variables cannot start with a '$'-sign, as those are FileMaker variables, and the FileMaker evaluation engine is used to evaluate them. It's faster to use local or global variables where you can, as the plugin evaluates them, and we do not need the extra time to call the FM evaluation engine to get or set their value.

Global variables:

<type> global1, global2, global3;

Local variables:

<type> local1, local2, local3;

Or local variables with initializers (can be mixed with non-initializers) - integer variables in this example:

int local1 = 1, local2 = 33 * 4, local3 = 200, local4;

Strings with initializers. The variable svar2 will contain 80 dashes as a string multiplied by a number that repeats that string the given number of times.

string svar = "abc", svar2 = "-" * 80, svar3, svar4;

Local variables that do not have any initializers will be set to zero. Variables can be declared anywhere before they are used. However, they cannot be declared inside if- or loop constructs. Move the declaration outside such constructs to ensure that they are initialized before use.

1.4. Parameter Declarations and Local Declarations

Declaration of a parameter-list (cannot have initializers) are locals. You can write to them, but this will not affect the value of the variable in the calling program. Parameters are then read-only but can be modified locally in the function. Local variables are gone when you come to the end statement. The scope of locals is inside the function where they are declared, after the declaration.

1.5. Auto Declarations

In assignment or loop control variables, they can be auto-declared. The type of the variable will be that of the calculation result. The compiler will issue a warning about auto declarations. The purpose of this warning is to alert about potentially misspelled variables.

Example of misspelled variable:

int default_size;
defalt_size = 23;
WARNING Auto declared 'defalt_size' as INTEGER at Line 254, pos 22.

Here you see that the assignment statement warns about the auto declaration, where the variable was misspelled. Variables on the right side of "=" must be declared or previously auto-declared. Undeclared identifiers used here will give fatal errors (no produced code).

1.6. Loops

There are three types of loops.

  1. For loops
  2. While loops (while)
  3. Repeat until loops.

1.6.1. For loops

for (<loop control identifier> = <start value>, <end value>, <optional step value>)
    <statements>
end for

The "end for" can also be spelled "endfor."

Example:

int i;
for (i = 1; i <= 10)
    a = b * c + i;
    c++;
end for

Here, the loop runs ten times, with 'i' running from 1 to 10, and 'a' is always calculated.

1.6.2. While loops

While loops are much like the FileMaker script step "loop - end loop."

while (<while expression>)
    <statements>
end while

Example 1:

bool finished = false;
while (!finished)
    <some statements>
    ...
end while

Example 2:

int a, b = 3, c = 4;
while (a < c)
    a = b * c;
    c = 4 * a;
end while

1.6.3. Repeat...Until loops

Repeat until loops differ from while loops in having the condition at the end instead of the beginning, meaning that the variables of the condition do not need to have a value before the loop.

repeat
    ...
    <some statements>
    ...
until <exit expression>;

Currently, there is no equivalent to the "exit loop if" statement in FileMaker scripts. Instead, use if-constructs to avoid execution of the remainder of the loop if you find you are finished somewhere in the middle. Some languages have a "break" statement feature, which is planned for a later release of the plugin.

1.7. Conditionals

1.7.1. IF...THEN...ELSE...END IF

The IF structure is pretty standard and found in most programming languages. Note the "then" at the end of the lines of "if" and "elseif" statements.

Syntax:

if ( <boolean expression> ) then
    <statements>
    ...
elseif ( <boolean expression> ) then
    <statements>
    ...
elseif ( <boolean expression> ) then
    <statements>
    ...
...
else
    <statements>
    ...
end if

The elseif and the else parts are, of course, optional. You can have as many elseif statements as needed. You can nest "if" constructs to any desired number of levels, along with other structural parts.

1.7.2. Conditional expressions

Introduced in Plugin version 1.7.0.15

( Packages otherwise source compatible with 1.6 version can run this in the runtime but must be compiled in 1.7.0.15 )

Like in standard custom functions in FileMaker, we now have inline if constructs as a shorthand for if-else-endif constructs. They are typically used in assignment statements but can be part of any expressions. We have decided to adopt the syntax for this found in C, C++, and PHP.

variable = <bool expression> ? <expression for true> : <expression for false> ;

The runtime will either evaluate the expression for true or false, dependent on the outcome of the boolean expression. The statement terminator ";" is needed for terminating the expression, but if it is a part of an expression in parenthesis, the end parenthesis will act as an expression terminator. Like this:

variable = 20 + (<bool expression> ? <expression for true> : <expression for false>)*3; 

As you see above, 20 adds the result of the conditional expression multiplied by 3.

1.7.3. CASE Statements

Introduced in Plugin version 1.7.0.15

( Packages otherwise source compatible with 1.6 version can run this in the runtime but must be compiled in 1.7.0.15 )

Syntax:

case
  :(<boolean expression>)
    <statements>
    ...
  :(<boolean expression>)
    <statements>
    ...
  :(<boolean expression>)
    <statements>
    ...
  default
    <statements>
    ...
end case

The default section is optional. Each branch should not be terminated by "break" as in many other languages. Each boolean expression will be evaluated in sequence until one expression evaluates to true. Then the statements in that branch will be executed, and finally continue after the end-case. If none of the boolean expressions evaluates to true, the default branch is executed.

The construct can be nested with any other constructs or other case constructs.

1.8. Calling Other ACF Functions

Other ACF functions in the same package can be called by using their name and parameters directly. The function needs to be defined before it is used (above in the package). For recursive calls, it might be necessary to declare the type of return value explicitly. We have planned to also allow this calling convention for functions in other packages too, but for now, the @ACF_run(....)@ method must be used for functions in other packages. The

type and number of parameters must match the declaration of the function.

All the ACF functions are functions. They return values using the return statement and must be called as functions. There is no procedural call convention where the return value is not stored or used. Even if you don't intend to use the return value, it must be assigned to a variable.

Example:

function abc ( int a ) 
   ...
   ...
  return a * ....; 
end

function def ( ... )
   int z, f; 
   f = 33; 
   z = abc ( f ); // This is OK
   z = 3 * abc ( f ) - 32; // This is also OK
   // But this procedural method will fail
   abc ( f);
   ...
   return ...; 
end

There is very little overhead in calling other ACF functions in the same package, and it can be a good programming practice to put repeating segments of code into separate functions. As they appear in the same source file, it is easy to follow the program logic where the use and the definition are close to each other.

Such internal functions will also appear in the ACF_GetAllPrototypes and thus can be called from FileMaker calculations directly. If this is not desired, we encourage you to use the output from this function in your package documentation, and edit this list to remove the entries that you don't want to make available from FileMaker Calculations.

1.8.1. Functions and the Return Value

The function returns its value with the return statement.

return myresultvariable; 

The return statement must be inserted before the END, but you can have as many as you like. However, the type of result is essential and should be the same for all return statements in a single function.

When one function invokes another function as part of a calculation, the compiler needs to know the result type to create the correct code. The return statement implicitly declares the return type of the function.

For example:

int a = 2, b = 4; 
return a / b; 

As a divide operation always creates a DOUBLE result, this function will have DOUBLE as its return type.

int a = 2, b = 4; 
return a * b; 

This one returns an INTEGER result, so the return type is INTEGER.

1.8.2. Return Value and Recursion

If you create a recursive function, the compiler does not know the return type before it comes to the recursive call. To solve this, you can declare the return type of the function explicitly.

The standard custom functions in FileMaker do not have loops, so loop techniques were created using recursion instead. Each iteration in the loop was a new recursive call. This technique is not necessary here, as we have loops. Loops are far more efficient and easier for the human eye to follow.

However, recursion used wisely can have its advantages in some special cases, like backtracking algorithms or dealing with hierarchical data structures, and so on. It is often used in conjunction with loops.

function calcthisandthat (int bb, float cc, string abc) 
    float calcthisandthat; 
    ...
    cc = calcthisandthat ( bb + 1, cc - 1, "next rec.."); 
    ...
    return bb * cc; 
end

1.9. Error Handling

There are two main methods for aborting a function due to invalid parameters or other error conditions. In the middle of some function, you find that there is an error, and further execution of the function is impossible.

One way to do it is to use the RETURN function with an error code/string.

However, that only returns the current function. If this is the function called from FileMaker directly, it's, of course, good enough. If you have a call hierarchy: Let's say you have function 'a', calling 'b', that then calls 'c'; the function 'c' discovers an error and wants to call off the whole thing. Instead of having 'b' check the result of 'c', and then 'a' check the result of 'b', you could use the "throw" statement, which discards the whole call hierarchy and returns the error to the FileMaker ACF_run function.

Example:

function c (...) 
    ...
    ...
    if ( abc > 10000 ) then
        throw "ERROR: Too great value of abc"; 
    end if
    ...
    ...
end

function b ( ...)
    ..
    ..
    c ( ... ); 
    ..
    ..
end

function a ( ...)
    ...
    ...
    b ( ... ); 
    ...
    ...
end 

// From the FileMaker Script: 
Set Variable ( $xx ; ACF_run ( "a"; .....))
if ( LeftWords ( $xx; 1) = "ERROR" )
    Show Custom Dialog ( ....)
    Halt Script
end if

1.9.1. Try / Catch

Provides structured exception handling in user code.

Read more about this topic in the Event handling section