Part 2 covered some of the grammar of icmake source files. This part completes the task. The final part of this article will appear next month and will show examples of the use of icmake.
In addition to the operators of the C programming language, icmake recognizes some `special' operators. These are:
The `younger' operator is used to compare two strings which represent filenames. An expression using younger is evaluated to non-zero or zero and may be used in a condition. The operator `newer' is an alias for younger.
The expression using the younger operator yields non-zero if a file with the name represented by the left operand is more recent than the file represented by the right operand.
E.g., the following code prints a message if file main.c is more recent than main:
if ("main.c" newer "main") printf ("main.c is more recent than main\n");
The `older' operand compares two files and yields non-zero if the file represented by the left operand is older than the file represented by the right operand. When the date of a file is compared using older or younger and when no file with such a name is present, then the age of the file is assumed to be infinite. A consequence of this implementation is that as in the following code example, a message is displayed if “try” does not exist:
if ("try.c" younger "try") printf ("try.c should be compiled!!\n");
Though icmake does not allow the use of operators on different types, it is possible to convert one type into another. The conversion of a type into another type is referred to as a `type cast'.
Type casts are denoted by a type name in parentheses before the operand which should be converted, e.g., (int)x converts the operand x to integer representation. Type casts are not allowed on all types. For example, a list variable cannot be converted to int.
The following type casts are permitted:
An integer may be cast to string. For example:
string stringvar; stringvar = (string) 14; // now, stringvar is "14"
A string may be cast to int. This is the reverse action of the type cast shown in the listing above.
A string may be cast to a list. This may be particularly useful when filenames should be added to or removed from a list, e.g., in the listing below, the filename “main.c” (a string) is removed from the list cfiles:
list cfiles; // cfiles is set to hold a list of cfiles = makelist ("*.c"); // all filenames with extension .c cfiles -= (list) "main.c"; // filename main.c is removed from // the list
Note that the string “main.c” must be converted to a list type to allow the subtraction from the list.
Other typecasts, specifically from a string to an ascii-representation, can be realized through specialized functions (see, e.g., the function ascii() in the next section).
Built into icmake is a number of functions which may be used to perform special operations, such as scanning a directory for files, displaying information, etc.. Here, all built-in functions are described.
arghead: int arghead(string): This function sets the argument head to string. The `argument head' is used with the functions exec() and execute(), described below.
argtail: int argtail(string): This function sets the argument tail to string. The `argument tail' is used with the functions exec() and execute(), described below.
ascii: int ascii(string): This function returns the ascii-number of the first character in the string, supplied as argument, e.g., ascii(“A”) returns 65.
ascii: string ascii(int): The overloaded function, which expects an int argument, returns a string representation of a numeric ascii number. e.g., ascii(65) returns “A”.
change_base: string change_base (string, string): This function changes the basename in the string which is supplied as its first argument to the basename which is supplied as second argument. The string with the changed basename is returned.
Example:
string name; name = change_base ("main.c", "test"); // name now is "test.c"
change_ext: string change_ext(string, string): This function changes the extension in the string which is supplied as its first argument to the extension which is supplied as second argument. The modified string is returned.
The extension (the second argument) may be specified as an empty string (“”); in this case change_ext() removes the extension. Also, the extension may be specified as one dot (”.“); in this case change_ext() removes the extension but leaves the dot.
Example:
char name; name = change_ext ("main.c", "o"); // name now is "main.o" name = change_ext (name, ""); // name now is "main" name = change_ext (name, "."); // name now is "main."
change_path: string change_path(string, string): This function changes the path in the string which is supplied as its first argument to the path which is supplied as second argument.
Example:
string name; // name is now "/bin/prog" name = change_path ("c:/usr/local/bin/prog", "/bin"); name = change_path (name, ""); // name is now "prog"
chdir: string chdir(int, string): This function changes the current working directory to the supplied name. The first int argument may be P_CHECK or P_NOCHECK. This argument is optional. When absent, P_CHECK is assumed. Failure to change the working directory with the presence of P_CHECK leads to the termination of icmake process.
However, on completion of the icmake process the original directory is always restored.
A string containing the new working directory, always ending in a directory separator, is returned. The string argument may terminate in a final slash. The returned string can be used to inspect whether the requested directory is reached, given that the modifier P_NOCHECK is supplied as first argument.
Two special string arguments are recognized by chdir():
a. A directory argument which consists of one dot (i.e., the string ”.“) realizes a `change' to the current directory. The return value is then a string holding the current working directory.
b. A directory argument which is an empty string (i.e., the string ”“) will not produce a directory change. Instead, the directory from which icmake was started originally is returned.
Example:
// print the current working directory printf("Current dir: ", chdir ("."), "\n"); chdir ("/usr/bin"); // change to directory /usr/bin // print startup directory printf ("Startup dir: ", chdir (""), "\n");
cmdhead: int cmdhead(string): This function sets the command head to string. The `command head' is used with the functions exec() and execute(), described below.
cmdtail: int cmdtail(string): This function sets the command tail to string. The `command tail' is used with the functions exec() and execute(), described below.
echo: int echo(int): This function determines whether, before the execution of a command, the command will be displayed. The argument of the function determines the displaying mode: when zero, displaying is suppressed; else, commands are displayed before execution. Two predefined constants are available for use as an argument to echo(): the constants ON and OFF. The values of these constants are, respectively, 1 and 0. Initially, echoing is on.
Example:
echo (ON); // commands will be displayed echo (OFF); // commands will not be displayed
element: string element(int, list): This function retrieves a string from a list. The order number of the name in the list is given by the first argument. Note that this index is zero-based, i.e., the first element in the list has index 0. The last element in the list has index sizeoflist(list) - 1.
Example:
list l; string n; int i; l = makelist ("*.c"); for (i = 0; i < sizeoflist (l); i++) if (element (i, l) newer "main") printf ("Source file ", element (i, l), " is more recent than main\n");
element: string element(int, string): This function retrieves a substring of one character from the string given as its second argument.
The character which is returned is found in the second (string) argument at the offset position specified in the first (int) argument. This index is zero-based: the first character of the string has index 0.
Example:
string s; int count; i; count = 0; s = "Hello world"; for (i = 0; element(i, s); i++) count++; printf("String '", s, "' contains ", count, " characters.\n");
exec: int exec(int, string, ...): This function executes a command by spawning a child process. The arguments are:
a. The first argument is an optional mode (an int). It may be P_CHECK (0) or P_NOCHECK (2). These predefined constants determine whether the exit status of the command should be checked or not. If the exit status should be checked, and a non-zero value is returned by the called program, the processing of the icmake file is aborted. If the first argument is omitted (i.e., if the first argument is not an int), P_CHECK is assumed.
b. The second argument is the command to run (a string). This is the name of the program to be activated.
c. The following arguments are the arguments which should be passed to the called program. These arguments may be ints, lists or strings.
Each command is composed of the program name (the second argument), followed by the current setting of the command head (see cmdhead(), followed by all arguments and terminated with the command tail (see cmdtail()). Each argument to the command is prefixed with the argument head (see arghead()) and postfixed by the argument tail (see argtail()).
execute: int execute(int, string, string, string, ..., string, string): This function executes a command by spawning a child process. The arguments are:
a. The first argument is an optional mode (an int). It may be P_CHECK (0) or P_NOCHECK (2). These predefined constants determine whether the exit status of the command should be checked or not. If the exit status should be checked, and a non-zero value is returned by the called program, the processing of the icmake file is aborted. If the first argument is omitted (i.e., if the first argument is not an int), P_CHECK is assumed.
b. The second argument is the command to run (a string). This is the name of the program to be activated.
c. The third argument is the command head (a string). This string is used as first argument to the program name. The string may be empty (i.e., ”“), in which case no command head is used.
d. The fourth argument is the argument head (a string). This string is prefixed to all following arguments. The string may be empty, in which case no argument head is used.
e. The following arguments are the arguments which should be passed to the called program. These arguments may be ints, lists or strings.
f. The next to the last argument is the argument tail (a string). This string is postfixed to each argument passed to the called program. The string may be empty, in which case no argument tail is used.
g. The last argument is the command tail (a string). The command run by the execute() function is postfixed with this string. The string may be empty, in which case no command tail is used.
After execution, execute() resets the command head, command tail, argument head and argument tail to empty strings. Both the exec() and execute() functions terminate the making process if error checking is turned on (mode flag P_CHECK) and if the run command exits with a non-zero exit value. If error checking is off, the exit status of the child process is returned.
exists: int exists(string): This function tests if a file exists. The file name is supplied as argument. A non-zero value is returned when the file exists; else, zero is returned.
if (exists ("main.c")) printf ("file main.c found\n"); else printf ("file main.c not found\n");
fgets: list fgets(string, int): This function reads a line of text from the file whose name is given as its first (string) argument. Reading starts at the offsetposition specified in the second (int) argument. A list is returned, containing as its first element the string which was read, including the final newline character (as it is returned by the C++ function fgets()). The second element of the returned list contains the string representation of the offset of the file after the line was read. This string can be cast to an int.
Example:
// showing the file info.doc on the screen: int offset; list l; for ( offset = 0; l = fgets("info.doc", offset); offset = (int)element(1, l) ) printf(element(0, l));
fprintf: int fprintf(string, ...): This function appends information to the file whose name is given as its first string argument. The remaining arguments define the information which is written to the file. The information is always appended to an existing file, which is opened in textmode.
fprintf() acts analogously to printf() (see below), but the information is written to file rather than to screen.
The arguments beyond the first argument of fprintf() define the information to print and may be ints, lists or strings. Example: fprintf ("file.txt", 1, " line written to file.txt\n");
get_base: string get_base(string): This function returns the basename of the filename stored in the string argument. The empty string is returned if the argument contains no basename. This happens when a disk or a root directory is specified in string. It may also happen if the syntax rules for a filename specification are violated. Example:
// prints 'main' printf(get_base ("/path/main.c")); // prints 'No basename: ' printf("No basename: ", get_base ("/"));
getch: string getch(): This function returns one character as a ministring. The character is read from the standard input stream (usually the keyboard).
Under Unix, this function waits until a key and the enter key are pressed. Example:
printf(getch()); // prints a character // (or an empty string)
get_ext: string get_ext(string): This function returns the extension in the string argument of the function. The empty string is returned if the argument does not contain an extension. Example:
printf(get_ext ("/path/main.c")); // prints 'c' printf(get_ext ("main")); // returns empty string
get_path: string get_path(string): This function returns the path stored in the string argument of the function. An empty string is returned if string does not contain a disk specifier. The function returns the longest possible pathname which can be derived from the string argument. Example:
printf(get_path ("/path/main.c")); // prints '/path/' get_path ("main.c"); // returns an empty string
getpid: int getpid(): This function returns the process number of the currently executing icmake program. Example:
// this function kills the current process.. // analogous to exit() void harakiri() { exec ("kill", "-9", getpid ()); } // this function returns a name for a temporary file // based on the process ID number file names are, // e.g., "/tmp/_TMPFILE.1256" string tempfilename () { return ("/tmp/" + "_TMPFILE." + (string)getpid()); }
gets: string gets(): This function returns the return value of the C function gets() as a string. The function accepts character, including backspaces allowing corrections, until the enter-key is pressed. The entered characters are returned in a string. Example:
printf(gets()); // prints a string // (or an empty string)
makelist: list makelist(int, string): This function makes a list of strings, representing filenames which match the expanded form of its argument. The arguments are an optional int, specifying the type of directory entries to search for, and a string specifying the file mask. The returned list may hold zero or more names.
The first int argument specifies the type of entries to search for. It may be O_FILE (when searching for files), O_DIR (when searching for directories) or O_SUBDIR (when searching for subdirectories). The difference between the searching for directories and the searching for subdirectories lies in the fact that the current directory, denoted by ”.“, and the parent directory, denoted by ”..“, are not considered subdirectories but are considered directories. This argument may be absent, in which case O_FILE is assumed.
A fourth type is O_ALL. When this type is given, makelist() searches for all directory entries irrespective of their type; e.g., under DOS, hidden and system files are matched as well as normal files or (sub)directories.
The behavior of makelist() is dependent on the used platform, e.g., to search for all files or (sub)directories under DOS, the file mask ”*.*“ must be given. The file mask ”*“ will fail to find files or (sub)directories with an extension. Furthermore, makelist() behaves under DOS similar to the C run-time functions _dos_findfirst() and _dos_findnext(); e.g., makelist(O_DIR, ".") returns a list containing the name of the current directory.
In a similar vein, the filemask ”*“ will, under Unix, fail to find files or (sub)directories starting with a dot. Example:
list l; l = makelist ("*.c"); printf ("All found *.c files are: ", l, "\n"); l = makelist (O_SUBDIR, "*"); printf ("All found subdirectories are: ", l, "\n");
makelist: The function makelist(), furthermore, has overloaded versions. These, apart from a first optional int indicating the type of entries to search for, expect three arguments. The arguments of these versions are a (string) mask; a comparison operator which must be younger, newer or older; and a (string) referencefile. The function returns a list of files matching the mask which are older or newer than the referencefile.
An optional first int argument, which specifies the type of directory entry (O_FILE, O_DIR or O_SUBDIR) may be present. Example:
list l; l = makelist ("*.c", newer, "libprog.a"); printf ("All .c files newer than libprog.a are: ", l, "\n");
printf: int printf(int, ...): This function displays information. The arguments define the information to print and may be ints, lists or strings. A list is printed as a series of all its elements with spaces in between.
putenv: void putenv(string): This function may be used to set environment variables during the execution of icmake programs. The environment variables remain active during the complete icmake run. Example:
main() { putenv("term=vt320"); // set variable system("set"); // show settings }
sizeof: int sizeof(list): This function performs the same action as sizeoflist().
sizeoflist: int sizeoflist(list): This function determines the number of names held in a list. Example:
list l; int i; list = makelist ("*.c"); i = sizeoflist (l); printf ("There are ", i, " names in the list.\n");
stat: list stat(int, string): This function attempts to retrieve file attributes of the file specified by the second string argument. The first int argument may be P_CHECK or P_NOCHECK. When absent, P_CHECK is assumed. The making process is aborted when stat() fails to retrieve file attributes and when P_CHECK is given for the first argument. The returned list holds the following information:
a. The first element is a string representation of the mode of the file or directory. This string can be converted to an int where the following bits represent the modes:
The bit S_IFDIR is set when the entry is a directory.
The bit S_IFCHR is set when the entry is a character-special.
The bit S_IFREG is set when the entry is a regular file.
The bit S_IREAD is set when the entry is readable.
The bit S_IWRITE is set when the entry is writeable.
The bit S_IEXEC is set when the entry is executable.
The second element is the file size, also represented as a string.
strlen: int strlen(string): This function returns the number of characters in the string which is supplied as its argument. Its working is analogous to the function strlen() in C.
strlwr: string strlwr(string): This function returns a copy of the string which is supplied as argument in lower case.
strupr: string strupr(string): This function returns a copy of the string which is supplied as argument in upper case.
strtok: list strtok (string, string): This function parses the first string in substrings, separated from each other by the separators specified in the second string. Each substring is an element of the returned list. If no separators are specified (i.e., the second string is empty), the individual characters of the first string become the elements of the returned list. Example:
list l; int i; l = strtok("Hello-world\n", "-"); printf("Two elements: ", l, "\n"); l = strtok("Hello-world\n", ""); printf("A string of ", sizeof(l), " characters\n");
substr: int substr(string, string) This function searches for the string which is given as the second argument in the string given as first argument. The position where the second string occurs in the first string is returned. The value -1 is returned when the second string does not occur in the first string.
system: int system(int, string): This function activates the operating system's command interpreter to run the command defined by the argument. The string holds the command to execute and, if needed, its arguments.
The first argument specifies whether a failure of the system() function should terminate the making process. The value of this int may be P_CHECK or P_NOCHECK. This argument may be absent, in which case P_CHECK is assumed. The return value of system() is zero when the command could successfully be executed. A return value which is not zero can be received by the makefile only when P_NOCHECK is given as first argument.
system() succeeds if the command could be executed and if the return value of the command itself is zero.