The C-like core of SaC
The language kernel of SAC is a functional subset of C. In this context, the term “functional” refers to a rather straightforward mapping of language constructs to an applied Lambda-calculus. The meaning of programs is given by this mapping and by context-free substitutions, as defined by the applied Lambda-calculus. Still, this meaning is supposed to exactly coincide with that defined by the original host language's semantics, which is based on implicit control flow.
These requirements rule out global variables, pointers, and, hence, all kinds of data structures which rely on pointers. The control flow instructions break, continue, and goto must be dropped as well, but for the remaining language constructs found in C, transformation rules into an applied Lambda-calculus can actually be defined more or less straightforwardly.
SaC programs are sequences of top-level type and function definitions with a specific function main serving as the designated starting point for program execution. Function headers are almost identical to ANSI-C, the sole difference being that supports functions with multiple return values, similar to ID or SISAL . Hence, function definitions may start with sequences of return types, separated by commas, instead of a single return type only. The set of basic types is restricted to the four scalar data types known from C, which represent integer numbers, single and double precision floating point numbers and characters. In contrast to C, SaC explicitly distinguishes between integer and boolean values. Hence, a dedicated data type bool is added to the set of basic types. Furthermore, SAC supports function overloading , i.e., functions may share the same name as long as they differ with respect to the types of their formal parameters. The ordering of function definitions is irrelevant, i.e., functions may be applied before they are defined, thus making obsolete forward declarations of function prototypes.
Function bodies consist of variable declarations, assignments, and a trailing return-statement. Local variable declarations are optional in SAC; they may be omitted. An assignment is either a simple let-statement or a compound assignment. In the presence of functions with multiple return values, let-statements may in one conceptual step bind multiple variables to values. A compound assignment is either a conditional or one of the three loop constructs known from C. Branches of conditionals as well as loop bodies may either be single assignments or sequences of multiple assignments delimited by curly brackets. However, such local blocks may not contain additional variable declarations, i.e., there are no local scopes in other than function bodies. The return-statement may contain a sequence of expressions, separated by commas, rather than a single one only, to support functions with multiple return values.
The mapping of SAC function definitions to an applied Lambda-calculus is rather straightforward. A sequence of let-statements is mapped to nestings of let-blocks with the trailing return-statement defining the goal expression of the entire term. An if-clause is interpreted as a functional conditional with the continuation code being copied into both branches. Last but not least, loops are considered syntactic sugar for equivalent tail-end recursive functions.
To a large extent SAC expressions are almost identical to those available in C. Expressions can be constants, variable identifiers, or applications of built-in operators, primitive functions, or defined functions. SAC supports the usual arithmetic, relational, and boolean operators just as C does. The usual precedence rules apply, but expressions may be grouped by round brackets.
The primitive functions toi, tof, and tod allow for explicit conversion between the three numerical data types int, float, and double; there is no implicit conversion as in C. Additionally, SAC provides built-in minimum and maximum functions.
This relatively small and simple language kernel allows for the specification of clear and concise purely functional programs. Yet, their functional semantics, as defined by mapping them into an applied Lambda-calculus, coincides exactly with their usual imperative interpretation. This is illustrated in the following example program, which computes the greatest common denominator of two non-negative numbers, following the Euclidian algorithm. Rather than using the built-in modulo operator %, the function diffrest computes both the quotient and the remainder of its two arguments to illustrate the use of functions featuring multiple return values.
int gcd( int high, int low) { if (high ≤ low) { mem = low; low = high; high = mem; } while (low > 0) { quotient, remainder = diffrest( high, low); high = low; low = remainder; } return( high); } int, int diffrest( int x, int y) { quotient = x / y; remainder = x - quotient * y; return( quotient, remainder); } int main( ) { return( gcd( 22, 27)); }