S-Lang Programmer's Guide Version 0.1 John E. Davis Wed Dec 18 18:51:55 1996 1 Introduction =============== S-Lang (pronounced ``sssslang'') is a powerful stack based interpreter that supports a C-like syntax. It has been designed from the beginning to be easily embedded into a program to make it extensible. S-Lang also provides a way to quickly develop and debug the application embedding it in a safe and efficient manner. Since S-Lang resembles C, it is easy to recode S-Lang procedures in C if the need arises. The S-Lang language features both global variables and local variables, branching and looping constructs, as well as user defined functions. Unlike many interpreted languages, S-Lang allows functions to be dynamically loaded (function autoloading). It also provides constructs specifically designed for error handling and recovery as well as debugging aids (e.g., tracebacks). The core language currently implements signed integer, string, and floating point data types. Applications may also create new types specific to the application (e.g., complex numbers). In addition, S-Lang supports multidimensional arrays those types as well as any application defined types. The syntax of the language is quite simple and is very similar to C. Unlike C, S-Lang variables are untyped and inherit a type upon assignment. The actual type checking is performed at run time. In addition, there is limited support for pointers. 2 Variables ============ S-Lang is an untyped language and only requires that an variable be declared before it is used. Variables are declared using the `variable' keyword followed by a comma separated list of variable names, e.g., variable larry, curly, moe; As in C, all statements must end with a semi-colon. Variables can be declared to be either global or local. Variables defined inside functions are of the local variety and have no meaning outside the function. It is legal to execute statements in a variable declaration list. That is, variable x = 1, y = sin (x); are legal variable declarations. This also provides a convenient way of initializing a variable. The variable's type is determined when the variable is assigned a value. For example, in the above example, `x' is an integer and `y' is a float since `1' is an integer and the `sin' function returns a floating point type. 3 Functions ============ Like variables, functions must be declared before they may be used. The `define' keyword is used for this purpose. For example, define factorial (); is sufficient to declare a function named `factorial'. Unlike `variable' keyword, the `define' keyword does not accept a list of names. Usually, the above form is used only for recursive functions. The function name is almost always followed by a parameter list and the body of the function, e.g., define my_function (x, y, z) { } Here `x', `y', and `z' are also implicitly declared as local variables. In addition, the function body must be enclosed in braces. Functions may return zero, one or more values. For example, define sum_and_diff (x, y) { variable sum, diff; sum = x + y; diff = x - y; return (sum, diff); } is a function returning two values. Please note when calling a function that returns a value, the value returned cannot be ignored. See the section below on assignment statements for more information about this important point. 4 Statements and Expressions ============================= A statement may occur globally outside of functions or locally within functions. If the expression occurs inside a function, it is executed only when the function is called. However, statements which occur outside a function context are evaluated immediately. All statements must end in a semi-colon. 4.1 Assignment Statements -------------------------- An assignment statement follows the syntax: = ; Whitespace is required on both sides of the equal sign. For example, x = sin (y); is correct but x =sin(y); x= sin(y); x=sin(y); will generate syntax errors. Often, functions return more than one value. For example, define sum_and_diff (x, y) { return x + y, x - y; } returns two values. The most general assignment statement syntax is ( , , ..., ) = ; e.g., (s, d) = sum_and_diff (10, 2); To ignore one of the return values, simply omit the variable name from the list. For example, (s, ) = sum_and_diff (10, 2); may be used if one is only interested in the first return value. Some functions return a variable number of values. Usually, the first value will indicate the actual number of return values. For example, the `fgets' function returns either one or two values. If the first value is zero, there is no other return value. In this case, one must use another form of assignment since the previously discussed forms are inadequate. For example, n = fgets (fd); if (n != 0) { s = (); . . } In this example, the first value returned is assigned to `n' and tested. If it is non-zero, the second return value is assigned to `s'. The empty set of parenthesis is required. Please note that RETURN VALUES CANNOT BE IGNORED. There are several ways of dealing with a return value when one does not care about it. For example, the function `fflush' returns a value. However, most C programs that call this function almost always ignore the return value. In S-Lang, one can use any of the following forms: variable dummy; dummy = fflush (fd); () = fflush (fd); fflush (fd); pop (); The second form is perhaps the most clear way of indicating that the return value is being ignored. 4.2 Binary Operators --------------------- S-Lang supports a variety of binary operators. These include the usual arithmetic operators (`+', `-', `*', `/', and `mod'), the comparison operators (`>', `>=', `<', `<=', `!=', and `==') as well boolean operators (`or' and `and') and bitwise operators (`|', `&', `xor', `shl' and `shr'). Like the assignment operator, these operators must also be surrounded by whitespace. That is, x = y + z; is a legal statement but `x = y+z;' is not legal. To use these operators effectively, in addition to understanding the meaning of the operation, one must also understand the precedence level of the operator. In S-Lang, there are only three levels of precedence. The highest level consists of the `*', `/', and `mod' operators. The second level consists of the `+' and `-' operators. All other binary operators fall into the last level of precedence. Within a precedence level, operators are evaluated left to right. Parenthesis may be used to change the order of evaluation. For example, the expression: a == b or c == d IS NOT the same as: (a == b) or (c == d) since `==' and `or' share the same level of precedence. In fact, the expression without parenthesis is evaluated left to right and is equivalent to `((a == b) or a) == c'. Finally, S-Lang supports the increment and decrement operators `++' and `--', and the arithmetic assignment operators `+=' and `-='. Presently, these operators only work with integer types and a type mismatch error will result from the use of these operators with other types. These following table shows the meaning of these operators. Expression Meaning ---------- --------- ++x; x = x + 1; x++; x = x + 1; --x; x = x - 1; x--; x = x - 1; x += n; x = x + n; x -= n; x = x - n; Note that S-Lang does not distinguish between `x--' and `--x' since neither of these forms return a value as they do in C. With this in mind, do not use constructs such as: while (i--) .... % test then decrement while (--i) .... % decrement first then test Instead, use something like while (i, i--) .... % test then decrement while (i--, i) .... % decrement first then test These operators work only on simple scalar variables. In particular, `++(x)' is NOT the same as `++x' and will generate an error. Whenever possible, these latter four operations should be used since they execute 2 to 3 times faster than the longer forms. 4.2.1 Short Circuit Boolean Evaluation ....................................... The boolean operators `or' and `and' ARE NOT SHORT CIRCUITED as they are in some languages. S-Lang uses the `orelse' and `andelse' operators for short circuit boolean evaluation. However, these are not binary operators. Expressions of the form: and and ... and can be replaced by the short circuited version using `andelse': andelse {} {} {} ... {} A similar syntax holds for the `orelse' operator. For example, consider the statement: if ((x != 0) and (1 / x < 10)) do_something (); Here, if `x' were to have a value of zero, a division by zero error would occur because even though `x != 0' evaluates to zero, the `and' operator is not short circuited and the `1 / x' expression would be evaluated. For this case, the `andelse' operator could be used to avoid this problem: if (andelse {x != 0} {1 / x < 10}) do_something (); 4.3 Unary Operators -------------------- The UNARY operators operate only upon a single integer. They are defined by the following table below. In this table, the variable `i' is an integer type and `x' represents either a floating point or integer variable. Unary Expression Meaning ---------------- ------------------------------------------------- not (i) if i is non-zero return zero else return non-zero ~(i) bitwise not sqr(x) the square of x mul2(x) multiplies x by 2 chs (x) change the sign of x -x same as chs (x) sign (x) +1 if x > 0, -1 if x < 0, and 0 if x equals 0 abs (x) absolute value of x Note the following points: * All unary operators except `not' and `' operator on both integer and floating point types. * The `!' operator used in C is not used in S-Lang, `not' must be used instead. * The bitwise not operator `' must enclose its argument in parenthesis. `i' will be flagged as a syntax error. * Some applications which embed S-Lang may overload these operators to work with application defined data types. 4.4 Data Types --------------- Currently, S-Lang only supports integer, floating point (double precision), and character string data types. It is possible for an application that embeds S-Lang to define other, application specific, data types (e.g., complex numbers). In addition, the language supports arrays of any of these types (including application specific types). 4.4.1 Integers ............... Unsigned integers are not supported. An integer can be specified in one of several ways: * As a decimal integer consisting of the characters `0' through `9', e.g., `127'. The number cannot begin with a leading `0'. That is, `0127' is not the same as `127'. * Using hexidecimal (base 16) notation consisting of the characters `0' to `9' and `A' through `F'. The hexidecimal number must be preceded by the characters `0x'. For example, `0x7F' is the same thing as decimal `127'. * In Octal notation using characters `0' through `7'. The Octal number must begin with a leading `0'. For example, `0177' is the same thing as `127' decimal. * Using character notation containing a character enclosed in single quotes as `'a''. The value of the integer specified this way will lie in the range 0 to 256 and will be determined by the ascii value of the character in quotes. For example, i = '0'; results in a value of 48 for `i' since the character `0' has an ascii value of 48. Strictly speaking, S-Lang has no character type. Any integer may be preceded by a minus sign to indicate that it is a negative integer. 4.4.2 Floating Point Numbers ............................. Floating point numbers must contain either a decimal point or an exponent (or both). Here are examples of specifying the same floating point number: 12., 12.0, 12e0, 1.2e1, 120e-1, .12e2 Note that `12' is NOT a floating point number since it contains neither a decimal point nor an exponent. If fact `12' is an integer. 4.4.3 Strings .............. A literal string must be enclosed in double quotes as in: "This is a string". Although there is no imposed limit on the length of a string, literal strings must be less than 256 characters. It is possible to go beyond this limit by string concatenation. Any character except a newline (ascii 10) or the null character (ascii 0) may appear in the definition of the string. The backslash is a special character and is used to include special characters (such as a newline character) in the string. The special characters recognized are: \" -- double quote \' -- single quote \\ -- backslash \a -- bell character \t -- tab character \n -- newline character \e -- escape (S-Lang extension) \xhhh -- character expressed in HEXIDECIMAL notation \ooo -- character expressed in OCTAL notation \dnnn -- character expressed in DECIMAL (S-Lang extension) For example, to include the double quote character as part of the string, it is to be preceded by a backslash character, e.g., "This is a \"quote\"" 4.5 Mixing integer and floating point arithmetic. -------------------------------------------------- If a binary operation (+, -, * , /) is performed on two integers, the result is an integer. If at least one of the operands is a float, the other is converted to float and the result is float. For example: 11 / 2 --> 5 (integer) 11 / 2.0 --> 5.5 (float) 11.0 / 2 --> 5.5 (float) 11.0 / 2.0 --> 5.5 (float) Finally note that only integers may be used as array indices, for loop control variables, shl, shr, etc bit operations. Again, if there is any doubt, use the conversion functions `int' and `float' where appropriate: int (1.5) --> 1 (integer) float(1.5) --> 1.5 (float) float (1) --> 1.0 (float) 4.6 Conditional and Branching Statements ----------------------------------------- S-Lang supports a wide variety of looping (`while', `do while', `loop', `for', `forever', and `_for') and branching (`if', `!if', `else', `andelse', `orelse', and `switch') statements. These constructs operate on code statements grouped together in blocks. A block is a sequence of S-Lang statements enclosed in braces and may contain other blocks. However, a block cannot include function declarations; function declarations must take place at the top level. In the following, `statement' refers to either a single S-Lang statement or to a block of statements and `{ block }' refers to a block of statements. 4.6.1 if, if-else .................. if (expression) statement; Evaluates `statement' if the result of `expression' is non-zero. The `if' statement can also be followed by an `else': if (expression) statement; else statement; 4.6.2 !if .......... !if (expression) statement; Evaluates `statement' if `expression' is evaluates to zero. Note that there is no `!if-else' statement. 4.6.3 orelse, andelse ...................... These constructs were discussed earlier. The syntax for the `orelse' statement is: orelse { block } { block } ... { block }. This causes each of the blocks to be executed in turn until one of them returns a non-zero integer value. The result of this statement is the integer value returned by the last block executed. For example, orelse { 0; } { 6; } { 2; } {3; } returns `6' since the second block returns the non-zero result `6' and the last two block will not get executed. The syntax for the `andelse' statement is: andelse { block } { block } ... { block }. Each of the blocks will be executed in turn until one of them returns a zero value. The result of this statement is the integer value returned by the last block executed. For example, andelse { 6; } { 2; } { 0; } {4; } returns `0' since the third block will be the last to execute. 4.6.4 while ............ while (expression) statement; Repeat `statement' while `expression' returns non-zero. For example, j = 20; i = 10; while (i) { j = j + i; i = i - 1; } will cause the block to execute 10 times. 4.6.5 do-while ............... do statement; while (expression); Execute `statement' then test `expression'. Repeat while `expression' returns non-zero. This guarantees that `statement' will be executed at least once. 4.6.6 for .......... for (expr1; expr2; expr3) statement; Evaluate `expr1' first. Then loop executing `statement' while `expr2' returns non-zero. After every evaluation of `statement' evaluate `expr3'. For example, variable i, sum; sum = 0; for (i = 1; i <= 10; i++) sum += i; computes the sum of the first 10 integers. 4.6.7 loop ........... loop (n) statement; Evaluate `statement' `n' times. If `n' is less than zero, `statement' is not executed. 4.6.8 forever .............. forever statement; Loop evaluating statement forever. Forever means until either a `break' or `return' statement is executed. 4.6.9 switch ............. The switch statement deviates the most from its C counterpart. The syntax is: switch (x) { ... : ...} . . { ... : ...} Here the object `x' is pushed onto the stack and the sequence of blocks is executed. The `:' operator is a S-Lang special symbol which means to test the top item on the stack, if it is non-zero, the rest of the block is executed and control then passes out of the switch statement. If the test is false, execution of the block is terminated and the process repeats for the next block. The special keyword `case' may be used to compare the value of objects. It returns non-zero if the objects correspond to the same object and zero otherwise. For example: variable x = 3; switch (x) { case 1: print("Number is one.")} { case 2: print("Number is two.")} { case 3: print("Number is three.")} { case 4: print("Number is four.")} { case 5: print("Number is five.")} { pop(); print ("Number is greater than five.")} Here `x' is assigned a value of 3 and the `switch' statement pushes the 3 onto the stack. Control then passes to the first block. The first block uses the `case' construct to compare the top top stack item (3) with 1. This test will result with zero at the top of the stack. The `:' operator will then pop the top stack item and if it is zero, control will be passed to the next block where the process will be repeated. In this case, control will pass to the second block and on to the third block. When the `:' operator is executed for the third block, a non-zero value will be left on the top of the stack and the `print' function will be called. Control then passes onto the statement following the last block of the `switch' statement. Note that, in this example, the last block does not test the value of `x' against anything. Instead, if this block is executed, the top stack item (the value of `x' in this case) will be removed from the stack by the `pop' function and the rest of the block executed. Unlike most other languages with some form of switch statement, `x' does not have to be a simple integer. For example, the following is perfectly acceptable: variable x; x = "three"; switch (x) { case "one": print("Number is 1.")} { case "two": print("Number is 2.")} { case "three": print("Number is 3.")} { case "four": print("Number is 4.")} { case "five": print("Number is 5.")} { pop(); print ("Number is greater than 5.")} Again, the `case' function is used to test the top stack item and the last block serves as a ``catch-all''. 4.6.10 break, return, continue ............................... S-Lang also includes the non-local transfer functions `return', `break', and `continue'. The `return' statement causes control to return to the calling function while the `break' and `continue' statements are used in the context of loop structures. Here is an example: define fun () { forever { s1; s2; .. if (condition_1) break; if (condition_2) return; if (condition_3) continue; .. s3; } s4; .. } Here, a function `fun' has been defined that includes a `forever' loop which consists of statements `s1', `s2',...,`s3' and 3 boolean conditions. As long as `condition_1', `condition_2', and `condition_3' return 0, statements `s1', `s2',...,`s3' will be repeatedly executed. However, if `condition_1' returns a non-zero value, the `break' statement will get executed, and control will pass out of the `forever' loop to the statement immediately following the loop which in this case is `s4'. Similarly, if `condition_2' returns a non-zero number, `return' will cause control to pass back to the caller of `fun'. Finally, the `continue' statement will cause control to pass back to the start of the loop, skipping the statement `s3' altogether. 4.7 Arrays ----------- Arrays are created using the function call `create_array'. The type of the array and the size of the array are specified by parameters to this function. The calling syntax is: x = create_array (, i_1, i_2 ... i_dim, dim); Here a `dim' dimensional array of type specified by `' is created. The size of the array in the nth dimension is specified by the parameters `i_1'...`i_n' parameter. The `' parameter may be any one of the values given in the following table: Parameter Type of array --------- ------------- 's' array of strings 'f' array of floats 'i' array of integers 'c' array of characters Other integer values for the type may be given for applications which defined application specific types to create arrays of those types. In the current implementation, `dim' cannot be larger than 3. Also note that space is dynamically allocated for the array and that, upon assignment, copies of the array are NEVER used. Rather, references to the array are used by the assignment statements. For example: variable a = create_array ('f', 10, 20, 2); variable b = a; This creates a 2 dimensional 10 x20 array of 200 floats and assigns it to `a'. The second statement makes the variable `b' also refer to the array specified by variable `a'. Accessing a specific element of the array may be accomplished by placing the ``coordinates'' of the element in square brackets. For example, to access the (3, 4) element of the above array use `a[3, 4]'. Note that this differs from the way the C language specifies array access and that, like the C language, array subscripts start from 0. Finally, array notation may also be used for extracting characters from a string. For example, if one has: variable ch, s = "Hello World"; then `ch = s[0]' could be used to extract the first character from the string `s'. However, this syntax cannot be used to replace characters in the string, i.e., `s[0] = ch' is illegal and will generate an error. For the latter case, one must either use the `strsub' function or use a character array. Examples: Here is a function that computes the trace (sum of the diagonal elements) of a square 2 dimensional n xn array: define array_trace (a, n) { variable sum = 0, i; for (i = 0; i < n; i++) sum = sum + a[i, i]; return sum; } This fragment creates a 10 x10 integer array, sets its diagonal elements to 5, and then computes the trace of the array: variable a, j, the_trace; a = create_array('i', 10, 10, 2); for (j = 0; j < 10; j++) a[j, j] = 5; the_trace = array_trace(a, 10); Note: The array syntax should be used consistently and should not be mixed with lower-level stack manipulations. For example, consider the array `a' in the previous example. While a[j, j] = 5; works fine, 5; % push 5 onto stack a[j, j] = (); will not work. 4.8 Stack Operators -------------------- The use of local variables greatly simplifies the task of maintaining the stack. Nevertheless, S-Lang is really a stack based language and there are times when they are useful. pop % removes the top object from the stack dup % duplicates the top object on the stack exch % exchanges top 2 objects on the stack These operators work on all data types -- they are not limited to integers. 5 Advanced Topics ================== This section should be thoroughly understood by anyone serious about using the S-Lang language. 5.1 Loading Files: evalfile and autoload ----------------------------------------- Most S-Lang based applications will load a startup file which consists of S-Lang function definitions. The file may load other files of S-Lang code via the `evalfile' intrinsic function. This function takes one string argument (the filename) and returns a non-zero value if the file was successfully loaded, otherwise it returns zero. For example, !if (evalfile("my_functs.sl")) error("Error loading File!"); instructs the interpreter to load the file `my_functs.sl' and returns an error message upon failure. A nice feature found in S-Lang and not found in many interpreters in the ability to automatically load functions when they are used. For example, consider the JED editor which embeds S-Lang as its extension language. JED includes a set of S-Lang routines defined in a file `info.sl' which read GNU info files. In particular, JED's online documentation is in info format. It is extremely unlikely that one would read the online documentation every time one edits a file. Thus, it is not normally necessary to load this file of S-Lang code. Since the main entry point into the info reader is the function `info_run_info', JED includes the line autoload("info_run_info", "info.sl"); in its main startup file (`site.sl'). This line lets the S-Lang interpreter know that when the `info_run_info' function is called, the file `info.sl' is to be loaded first. 5.2 Error Handling ------------------- Many intrinsic functions signal errors in the event of failure. This is done internally in the underlying C code by setting `SLang_Error' to a non-zero value. Once this happens, S-Lang will start to return to top level by ``unwinding'' the stack. However, there are times when some cleanup needs to be done. This is facilitated in S-Lang through the concept of error blocks. An error block is a block of code that gets executed in the event of an error. An error block is declared by the `ERROR_BLOCK' directive. As an example, consider the following: define example () { ERROR_BLOCK {print("Error Block executed!"); } while (1); % executes forever } Here a function called `example' has been defined. It assumes an intrinsic function `print' has been defined and that there is some way for the user to signal a quit condition which the underlying C code will trap and raise `SLang_Error' to a non-zero value. The `while' loop will execute forever until an error condition is signaled. When that happens, the error block will get executed. Consider another example from the JED editor. When the user starts up JED with no filename, a message is displayed in the `*scratch*' buffer until the user hits a key: define startup_hook() { !if (strcmp("*scratch*", whatbuf())) return; insert("This is the JED editor.\n\nFor help, hit Control-H Control-H"); bob(); update(1); () = input_pending(300); % wait up to 300 seconds for input erase_buffer(); } This is a function that JED automatically calls upon startup. If the buffer is the `*scratch*' buffer, a short help message is displayed; otherwise the function returns. Then JED will wait 300 seconds or until the user hits a key at which point it will erase the buffer. This will work fine unless the user does something to generate an error. For example, errors will be generated of the user presses the Ctrl-G key to signal a quit condition, tries to delete past the beginning of the buffer, etc... If an error is generated, S-Lang will abort before erasing the buffer. This will leave the help message on the screen and in the buffer which is not what is desired. To prevent this, an error block is used: define startup_hook() { !if (strcmp("*scratch*", whatbuf())) return; ERROR_BLOCK {erase_buffer();} insert("This is the JED editor.\n\nFor help, hit Control-H Control-H"); bob(); update(1); () = input_pending(300); % wait up to 300 seconds for input EXECUTE_ERROR_BLOCK; } Here an error block has been declared and consists of the single statement `erase_buffer()'. If an error occurs the buffer will be erased. The statement `EXECUTE_ERROR_BLOCK' is a S-Lang directive that says to go ahead and execute the error block even if no error has occurred. Error blocks will not get executed for all errors. For example, if there is a memory allocation error or an error associated with the stack, it would make no sense to call the error block. Once an error has been caught by an error block, the error can be cleared by the `_clear_error' function. After the error has been cleared, execution resumes at the next statement at the level of the error block following the statement that generated the error. For example, consider: define make_error () { error ("Error condition created."); print ("This statement is not executed."); } define test () { ERROR_BLOCK { _clear_error (); } make_error (); print ("error cleared."); } Note that although the error was triggered in the `make_error' function, the error was cleared in the `test' function. As a result, execution resumes after the statement that makes the call to `make_error' since this statement is at the same level as the error block that cleared the error. Here is another example that illustrates how multiple error blocks work. define test () { variable n = 0, s = ""; ERROR_BLOCK { print (Sprintf ("s = %s, n = %d", s, n, 2)); _clear_error (); } forever { ERROR_BLOCK { s = strcat (s, "0"); _clear_error (); } if (n == 0) error (""); ERROR_BLOCK { s = strcat (s, "1"); } if (n == 1) error (""); n++; } } Here, three error blocks have been declared. One has been declared outside the `forever' loop and the other two have been declared inside the `forever' loop. Each time through the loop, the variable `n' is incremented and a different error block is triggered. The error block that gets triggered is the last one encountered. On the first time through the loop, `n' will be zero and the first error block in the loop will be executed. This error block clears the error and execution will resume following the `if' statement that triggered the error. The variable `n' will be incremented to 1 and, on the second cycle through the loop, and the second `if' statement will trigger an error and the second error block will execute. This time, the error is not cleared and the `forever' loop will abort causing the error block outside the `forever' loop to fire. This block prints out the values of the variables `s' and `n'. It will clear the error and execution resumes on the statement following the `forever' loop. In the end, `s = 01, n = 1' will be printed. To summarize, `ERROR_BLOCK' is a directive that declares an error block. The `EXECUTE_ERROR_BLOCK' directive indicates that the error block is to be executed at this point--- no error is necessary. In addition, error blocks may be defined at multiple levels. As the stack unwinds in response to an error condition, all error blocks in scope will get executed. The function `_clear_error' can be used to clear the error condition. Emacs elisp programmers should note the similarity to the Emacs elisp function `unwind-protect'. 5.3 Exit and User Blocks ------------------------- Error blocks get executed as the result of an error or when the directive `EXECUTE_ERROR_BLOCK' is encountered. There are two other types of blocks that are useful: exit blocks and user blocks. 5.3.1 Exit Blocks .................. An exit block is declared using the directive `EXIT_BLOCK'. It is executed when the function returns. For example, consider: define test () { variable n = 0; EXIT_BLOCK { print ("Exit block called."); } forever { if (n == 10) return; n++; } } Here, the function contains an exit block and a `forever' loop. The loop will terminate via `return' when `n' is 10. When it returns, the exit block will be called. Exit blocks are very useful for cleaning up when a function returns via an explicit call to `return' deep within the function. 5.3.2 User Blocks .................. A user block is similar to a function within a function. Up to 5 user blocks may be declared per function. Unlike functions, user blocks do not take arguments but do have access the function's local variables. User blocks are denoted by the directives `USER_BLOCK0', `USER_BLOCK1', `USER_BLOCK2', `USER_BLOCK3', and `USER_BLOCK4'. The directives `X_USER_BLOCK0', `X_USER_BLOCK1', `X_USER_BLOCK2', `X_USER_BLOCK3', and `X_USER_BLOCK4' may be used to call the user blocks. Here is an example: define silly () { variable s1, s2, ch = 'a'; USER_BLOCK0 { s1 = (); Sprintf ("%s & %c", s1, ch, 2); ch++; } s2 = ""; s2 = X_USER_BLOCK0 (s2); s2 = X_USER_BLOCK0 (s2); s2 = X_USER_BLOCK0 (s2); s2 = X_USER_BLOCK0 (s2); print (s2); } Here a user block has been declared. Although user blocks are not functions, as the example demonstrates, they can be used as functions. This is possible because S-Lang is a stack based language and parameters are passed via the stack. 5.4 Aliases ------------ S-Lang supports a limited concept of a pointer known as an `alias'. Variables can be declared to be aliases for global functions or global variables. Consider the following three functions: define first () { print("First"); } define second () { print("Second"); } define first_second() { variable f; f = &first; f(); % Line 1 (see text) f = &second; f(); % Line 2 (see text) } Here three functions have been defined. The functions `first' and `second' should be quite clear. However, the function `first_second' looks somewhat strange because of the appearance of the `&' character. When the interpreter encounters a function or variable name immediately preceded by the `&' character, it pushes the address of the OBJECT referenced by the name onto the stack rather than pushing its value (variable) or executing it (function). The object referred to must be a global object, either a global variable or a global function. Thus `&first' pushes the address of the function `first' on the stack rather than calling `first'. The local variable `f' is then assigned this address. Note that after the assignment, `f' is neither a string type nor an integer type. Rather, it becomes a function type and and is synonymous with the function `first', i.e., it is an alias for `first'. Hence the line labeled ``Line 1'' above simply calls the function `first'. A similar statement holds for ``Line 2'' which results in the function `second' getting called. Finally note that aliases may be passed as arguments. Consider: define execute_function (f) { f(); } Then execute_function(&first); execute_function(&second); will work as in the above example. This concept should be clear to most C programmers or to LISP programmers who will interpret the `&' prefixing the object name as simply quoting the object. 5.5 Functions returning multiple values ---------------------------------------- There are some functions which return multiple values. For example, S-Lang's implementation of the `fgets' function takes a single argument, a handle an to open file, and usually returns two values: the number of characters read followed by the character string itself. The question immediately arises about how to handle such a function. The answer to this question is to understand the stack. Consider the following function which ``types'' out a file to the standard output device. define display_file(file) { variable n, fp, buf; fp = fopen(file); if (fp == -1) error("fopen failed."); while (n = fgets(fp), n > 0) { buf = (); % <----- stack () = fputs(buf, stdout); % ignore return value. } if (fclose(fp) <= 0) error("fclose failed."); } The `fgets' function returns 2 items to the stack only when it reads one or more characters from the file. If it encounters the end of the file, it returns 0 and nothing else. If an error occurs when reading, it returns -1. The line containing the comment above illustrates how to assign the top stack item to a variable. Note also, that the two lines: buf = (); () = fputs(buf, 1); can be replaced by the single line: () = fputs((), 1); It is also permissible to replace `buf = ()' by simply `=buf'. Note that there is no space between the `=' and `buf' in the latter form. Someone might simply suggest to do it like it is done in C, i.e., n = fgets(buf, fp); However, this is impossible in S-Lang, and it is not even desirable. S-Lang is an interpreted language and one of the reasons for using it over the C language is to free oneself from problems which can arise in the above `C' expression. Finally, note that, when used as an argument, `()' is simply a disguise for the the top stack item. Consider the following code fragment: variable div_1, div_2; 12; div_1 = () / 4; % push 12 on stack then do division 4; div_2 = 12 / (); % push 4 on stack then do division The value of `div_1' will be the expected result of 12 / 4 = 3; however, `div_2' will have the value of 4 / 12! The reason is that after parsing, `()' will be replaced by the top stack item and in the second case, 12 will be the top stack item, not 4. If this point is unclear, it is better to simply avoid the use of `()' in this manner.