This chapter introduces the language syntax and program constructs in terms of the "C" programming language. Since this is the language that PI most closely resembles, it will be easy for those who have worked in "C" to understand the concepts and constructs used by this language.Below is a sample program that shows the basic structure of a PI program. Many of the program structures and directives are features of the glossary file concept which is described in the an earlier chapter. These same features are reintroduced here in terms of their usage within PI.
Example 1: A simple program to calculate area
. Simple program to calculate the area of a circle. #include $Utools/Utools/include/pi.h
#define PI 3.14159#main(argc, argv)
local radius, a, p;radius = (float)argv[argc - 1];
/* Calculate and then write */
/* the result to the output */
a = area(radius);
p = format("Radius = %f, Area = %f\n",radius,a);
fputs(p, stdout);
)). The following function returns the area
. of a circle with the given radius.
#area(radius)
return ( #PI * radius * radius );
))To run this program using Print Control, execute the following command (shown in bold) from the shell prompt ($);
$ pcf /tmp/sample 3To run this program using UxFax2, execute the following command (shown in bold) from the shell prompt ($);
Radius = 3.000000, Area = 28.274310
$$ faxcmd run /tmp/sample 3In both instances above, the program has been saved as "/tmp/sample". This file is named as the first argument on the command line. The number "3" is being passed as the radius argument to the program, and it will be used to calculate the area which is displayed by the program.
Radius = 3.000000, Area = 28.274310
$
All lines beginning with a period in the left most column are comments. All of the comments which appear outside of glossary entries, such a the comment at the top of the file, and the two comment lines preceding the area() glossary entry will not even be encountered by the interpreter during execution.Any line with a comment period in the first column is ignored. Besides allowing the inclusion of comments, this provides a quick method of removing lines during debugging of a program, simply by inserting a period.
The language also supports "C" style comments, such as the two comment lines within the main() routine. These will be ignored by the interpreter.
The #include directive is a feature of glossary files. The file to be included is named after the directive. Unlike "C", the name appears without surrounding quotation marks or "<" or ">" symbols.Also, environment variable substitution will be performed on the pathname. The example program shows the $Utools variable being referenced. Although the inclusion of pi.h is not necessary within this program, it will be commonly included other PI programs.
Any defined glossary variables in an included file will also be accessible within the file which includes it. This facility may also be used to include other source files with common (interpreted) libarary functions. For example, in the above sample program, the #define directive and the area() routine could be removed and place in a separate file called "math.h". This file could then be named with a #include directive. e.g;
Library file: $Utools/Utools/include/math.h
#define PI 3.14159 #area(radius)
return ( #PI * radius * radius );
))
Example 2: A program using library functions
. Simple program to calculate the area of a circle. #include $Utools/Utools/include/math.h
#main(argc, argv)
local radius, a, p;radius = (float)argv[argc - 1];
/* Calculate and then write */
/* the result to the output */
a = area(radius);
p = format("Radius = %f, Area = %f\n",radius,a);
fputs(p, stdout);
))This above program is similar to the original example, except the area() function and definition of PI have been removed to a header file called math.h which is included. Also, since the glossary variable PI is visible within both files, it is also used as a constant within the main routine.
This directive is a feature of the glossary file concept, and is also the definition of a glossary variable. So the #define directive varies from it's normal usage within "C" in several aspects.It does not allow macro definitions. Macro definitions are just as well provided with function declarations. This is demonstrated in the previous example where area() is defined in an included header file.
The directive may only be used to declare constants which are substituted in-place wherever the defined variable name appears, preceded by a hash (#) symbol.
If the defined value includes spaces, the entire string must be surrounded in double quotes. Unlike "C", only the value which appears within the quotes is defined, so if it is used as a string constant within the program source, the quotation marks must be given again, e.g.;
#define Welcome "Welcome to Utopia\n"The value of the defined name is substitued in it's entirety, although there are a couple of extensions to the syntax which may be used when the defined variable is used within PI reports. These extensions affect the placement of the substsitued string by allowing the value to only over-write white space within the glossary entry. This is used to maintain the format of reports.
. . . .
fputs("#Welcome", stdout);
All functions and variables are declared as they are encountered during execution. Unlike "C", variables are not declared with a type. Only the name is declared; the type is determined by the value that it stores. But the declaraion of a variable involves two important things;In addition, the visibility and lifetime of a variable is also affected by whether it is declared as global or local.
- visibility
- lifetime
Local Variables are visible only within the compound statement (the local block) in which they are declared, or any nested compound statements (inner blocks). The variables are not visible outside of the local block. As soon as execution leaves the local block, the variable declaration is lost, and so it only has a lifetime for the execution of the local block.
Variable declaration is implicit. Any unrecognised symbol will be automatically declared as a local variable as it is encountered. But care needs to be taken when relying upon automatic declaration, because these variables are only visible fromwithin the block in which they are first encountered. i.e. Variables which are automatically declared within an inner block will not be visible in an outer block. e.g.
. The following will cause an error because y
. is only ever declared in the inner block.
#main()
for (x=0;x<10;x++) {
if (x == 0) y = x;
else y += x;
}
write(stderr, x, y);
)). The following is correct because both
. x and y are first declared in the outer block.
#main()
y = 0;
for (x=0;x<10;x++) {
if (x == 0) y = x;
else y += x;
}
write(stderr, x, y);
))Global Variables are only declared as they are encountered during normal execution of the program. Once declared though, they are visible within any program block. If a local block includes a declaration of a variable with the same name as an already declared global, the local variable will take precedence during the lifetime of that block. The variable has infinite lifetime once declared during execution of the program.
The following example shows the ideal method of declaring global variables within a program. By containing the global declaration and initiation of these variables within the one routine, that routine only needs to be called once at program startup.
Example 3: Ideal method of declaring global variables
. Declare all globals used by the program
#globals()
global status, message;
status = 1;
message = "Waiting"
))#main(argc,argv)
globals();
main_loop();
))Functions are declared as they are encountered during execution of the program. Once declared they have global visibility and lifetime. Only the function name is significant during declaration. The function arguments are matched when the function is called, and like a variable, a function may return any data type.
When functions are to be called indirectly, the function name must be declared before it is used. This is demonstrated in the following program which will give a different result depending upon the number of arguments given when the program is called.
#funct_a()
fputs("funct A\n",stdout);
))#funct_b()
fputs("funct B\n",stdout);
))#main(argc,argv)
global funct_a(), funct_b();list = {funct_a, funct_b};
argc = (argc > 2) ? 1 : 0;
list[argc]();
))In this example, the functions are declared as global. They may also be declared as local which will only give then local visibility. So any future indirect reference to these functions would have to be declared again. For efficiency, functions should be declared as global.
Basic Data Types
Variables and functions may assume any data type. i.e. any data type may be assigned to a variable, and a function may return any data type. The basic data types supported within PI are;A declared variable has a type of IS_NULL until another type is assigned to it. The data type will equate to the constant NULL in conditional expressions. NULL has no type and no value.
- IS_INTEGER A decimal integer (whole) number
- IS_FLOAT A floating point number
- IS_STRING An ASCII character string
The language allows conversion by casting between each of these basic data types, except that precision may be lost when converting from a float type to an integer type.
Arrays
Arrays variables are comprised of a variable number of elements of any type. An array element may also be an array. In this way multi-dimensioned arrays may be built up. Array dimensions are dynamic, and will be determined by the largest array index reference made during program execution.local a;In the above extract, when the variable "a" is declared it has no type. After the first statement it becomes a one dimension array, and the first element is an integer. After the second statement it is resized to a two element array, and the second element is a floating point number. And after the last statement it is resized as a five element array, with the fifth deing a string, while the third and fourth elements are null.
a[0] = 12;
a[1] = 2.2;
a[4] = "Text";local a;In this extract, the fourth element of the array will be a three element array, so a[3][0] will be the same as a[0], and a[3][1] will be the same as a[1], and a[3][2] will be the same as a[2].
a[0] = 12;
a[1] = 2.2;
a[2] = "Text";
a[3] = a;Complex Types
The inbuilt libraray functions introduce several different data types. These include;
The IS_FILE type is most popular within the standard library, since it is similar to the "file pointer" type in standard "C". This type is used in functions concerned with reading from and writing to standard I/O and file streams.
- IS_FILE A file stream
- IS_FUNCTION A function reference
- IS_POINTER A variable pointer
- IS_ISAM An ISAM file
The IS_FUNCTION data type results from an expression which yields an indirect function reference. An example of this is shown in the sample program in the previous section on "Declarations".
PI provides "address of" and "contents of" operations, just like standard C. These have limited application where it is necessary to work with indirect variable references. The "&" (address of) operator will yield an IS_POINTER data type, while a "*" (contents of) operator will expect an IS_POINTER data type.
The IS_ISAM data types is used to access ISAM data tables used within UxFax2. Most of the ISAM library functions use this data type when refering to opened data tables.
Constants are virtually identical to "C", and use standard notation for the three basic data types, integer, float and string. The NULL constant will yield a null data type; not zero as standard "C" does.Array constants use a similar notation to "C" when assigning initial values to structures. The array constant is a comma separated list of constants surroundex by "curly" braces. Each constant may also be an array constant, thus allowing the specification of multi-dimension array constants.
a = { 12, 2.2, NULL, NULL, "Text" };For example, the above source extract is identical to the first sample in the previous section on "Data Types" under the heading "Arrays". And the extract below is identical to the second sample in that same section;a = { 12, 2.2, "Text", { 12, 2.2, "Text" } };
PI provides an identical set of operators as standard "C". These are summarised in the following tables.Arithmetic
= Assignment a = b; Assign b to a. Conditional
+ Addition a + b; Add a and b. - Subtraction a - b; Subtract b from a. * Multiplication a * b; Multiply a by b. / Division a / b; Divide a by b. % Modulo a % b; Remainder of a/b. String
> Greater than a > b True if a exceeds b < Less than a < b True if b exceeds a >= GT or equal a >= b False if b exceeds a <= LT or equal a <= b False if a exceeds b == Equal a == b True if a equals b != Not equal a != b False if a equals b || Logical OR || True if either a or b are true && Logical AND && True if both a and b are true Bitwise
+ Concatenate a + b Append a with b - Compare a - b -1 (a<b), 0 (a==b) or 1 (a>b) > Greater than a > b True if a is lexically GT b < Less than a < b True if b is lexically GT a >= GT or equal a <= b False if b is lexically GT a <= LT or equal a >= b False if a is lexically GT b == Equal a == b True if a is the same as b != Not equal a != b False if a is the same as b Unary
>> Right shift a >> b Shift a right b bit positions << Left shift a << b Shift a left b bit positions | Bitwise OR a | b Boolean a OR b & Bitwise AND a & b Boolean a AND b ^ Exclusive OR a ^b Boolean a XOR b Compound
~ Complement ~a olean ones complement of a - Negate -a Arithmetic negation of a ! Not !a Logical complement of a & Address of &a Indirect address of a * Contents of *a Indirect contents of a ++ Increment ++a Pre-increment a -- Decrement a-- Post-decrement a (type) Cast (int)a Convert a to an integer Ternary
+= Addition a += b a equals a plus b -= Subtraction a -= b a equals b subtracted from a *= Multiplication a *= b a equals a multiplied by b /= Division a /= b equals a divided by b %= Modulo a %= b a equals the remainder of a/b >>= Right shift a >>= b a equals a shifted right b bits <<= Left shift a <<= b a equals a shifted right b bits |= Bitwise OR a |= b a equals boolean a OR b &= Bitwise AND a &= b a equals boolean a AND b ^= Exclusive OR a ^= b a equals boolean a XOR b
?: Conditional a ? b : c b if a is true, otherwise c
Unlike most languages, there is no order of precedence between the operators. Expressions are simply evaluated from left to right, with all expressions on the right side of an operator being evaluated before being applied to the left side.x = 1 + 2 * 3 - 4.0 / 5;For example, the above expression is evaluated in the order shown below. The interpreter will scan along the line recursively looking for the first complete expression. Because each expression contains another expression on the right hand side, the first complete expression is found at the end of the line;x = 1 + 2 * 3 - 4.0 / 5;The only way to enforce an order of precedence is through the use of parenthesis. An expression surrounded by parenthesis will be evaluated separately before any expressions to the right of it. The following example shows the effect of parethesis on the previous example, and the order of evaluation;
x = 1 + 2 * 3 - 0.8;
x = 1 + 2 * 2.2;
x = 1 + 4.4;
x = 5.4;x = (1 + (2 * 3)) - (4.0 / 5);
x = (1 + 6) - (4.0 / 5);
x = 7 - (4.0 / 5);
x = 7 - 0.8;
x = 6.2;
A statement may be any expression, including an operator, variable, constant or function call. The inbuilt statements may start with any one of the keywords described below. PI implements all the statements built into "C", except goto. i.e.;
- if ( expression ) statement [ else statement ]
- while ( expression ) statement
- do statement while ( expression ) ;
- switch ( expression ) compound_statement
- case constant :
- default :
- for ( [ expression ] ; [ expression ] ; [ expression ] )
- { statement; [ ... ] }
- return [ ( expression ) ] ;
- break ;
- continue ;
if statementIf the compound expression following the if keyword is true, then execute the first statement. Otherwise execute the statement following the else keyword if it is provided.
Example:
if ( y != 0 )while statement
x /= y;
else
x = 0;While the compound expression following the while keyword is true, execute the statement.
Example:
while ( i >= 0 ) {do statement
arr2[i] = arr1[i];
i--;
}Execute the statement following the do keyword until the compound expression following the while keyword is false.
Example:
do arr2[i] = arr1[i];switch/case/default statement
while ( ++i < 10 );Evaluate the compound expression following the switch keyword and start executing the statements within the compound statement following a case keyword with a constant matching the result. If the result does not match any constant following a case keyword, then start executing the statements following the default keyword if it is provided.
Example:
switch (option) {for statement
case #O_QUIT:
break;
case #O_RUN:
mode = option;
break;
default:
option = 0;
}Evaluate the first expression once, and then execute the statement for as long as the second expression is true. After each iteration, evaluate the third expression. If any expression is ommitted, it will be evaluated as true.
Example:
for ( i = 0; i < 10; i++ ) sum += arg[i];return statementStop executing the current sub-routine, and return control to the calling routine. The expression will be evaluated and returned as the result of the sub-routine.
Example:
return(answer);break statementEnd the innermost do, for, switch or while statement, and return control to the statement that follows.
Example:
while ( i > 0 ) {continue statement
if ( i < 10) break;
i--;
}Return to the start of the innermost do, for, or while statement, bypassing any remaining statements in that iteration.
Example:
for ( i = 0; i < 100; i++ ) {
if ( i == 50 ) continue;
arr2[i] = arr1[i];
}
The definition of a function is made in a glossary entry of the same name as the function, followed by opened/closed braces with arguments. The declaration of the function and it's arguments does not include any type information. The type of the arguments are determined when the function is called, and the type of the function return value is determined by the function,#max(x, y)The number of arguments passed when the function is called must match the number of arguments declared with the function, otherwise a run time error message will be generated. So the language does not support variable argument list directly. To use a variable number of arguments, pass an array;
if ( x > y ) return(x);
return(y);
))#sort(l)Similary, a function may return a variable number of arguments by returning an array. The following example will return a variable sized array depending upon the argument, which is also an array;
local x, y, size, again;size = sizeof(l);
do {
again = 0;
for ( x = 0; x < (size - 1); x++) {
if (l[x] > l[x + 1]) {
y = l[x];
l[x] = l[x + 1];
l[x + 1] = y;
again = 1;
}
}
} while (again);
))#status(stat)
ret = NULL;
switch (stat[0]) {
case #O_WAIT:
ret[0] = #O_WAIT;
ret[1] = "Waiting";
break;
case #O_RUN:
ret[0] = 0;
break;
default:
ret = stat;
}
return(ret);
))