lclvrs is run as part of qef's initialization to find and interpret the lclvrs files for the current directory, and build a temporary database of variable/value pairs that other tools will access directly. lclvrs files are used to specify process controls, build options, the names of special files and directories, search paths, tool names and flags, special library mappings and search mechanisms, and other such values. lclvrs provides a variety of options to select the form of the output, list the selected files, report where specified variables are set, or to output special values.
In most situations, lclvrs will find and interpret: root.vrs at the top of the current tree -- provided by developer (using the program rootvrs) to indicate the root of a tree and specify links to other trees; config.vrs at the top of the current tree -- copied from annotated prototype and modified by developer as required -- used to contain all configuration settings and user options; tree.vrs at the top of the master tree -- used to specify project parameters and controls; hostsys.vrs a host provided file containing host-specific controls such as library mappings and search paths; and qeffile, in the corresponding source directory -- the directory specific settings and local construction controls. The qeffile also usually contains the input to the script generator or the back-end.
In addition to the above, a user can create files called leaf.vrs and branch.vrs in the current tree to specify temporary lclvrs settings without changing source files. lclvrs settings can also be specified via the qef -L flag. This feature is often used to specify the type of binaries to be produced as in:
qef -LDEBUGGING echo # produce "debuggable" program
qef -LPROFILING echo # produce "profiling" program
The hostsys.vrs file will set the C flags and library search mechanisms as required to create the requested type of binary.
The lclvrs language provides simple keywords to set, manipulate and test variables, paths and associative array elements, to do flow control ("if" and "switch" -- no loops), and output fatal or non-fatal diagnostics. lclvrs also offers a set of functions that can be used in keyword argument lists to test and manipulate strings and/or variables, search or test for files, and to retrieve user or environment information.
One of the most important lclvrs features is that it is a separate program whose sole purpose is to process the configuration files and prepare data for other processes use. Any tool that requires lclvrs settings either executes lclvrs or reads a previously prepared lclvrs output. Configuration information need not be passed via command line arguments or environment variables thereby ensuring consistent application of the tool no matter through whatever invocation mechanism was used.
qef's ability to invoke an arbitrary shell command coupled with the preprocessor to prepare the input to an arbitrary back-end is an extremely powerful and important feature that has been a crucial component of the qef system since its birth (see [Tilbrook 86]). In qef's early years there were a large number of special purpose script generators but all but one (qefdirs) have been replaced or superceded by qsg, which is used about 80% of the time.
qsg is a fairly simple programming language, specifically designed to create input for other processors from simple shell-like commands. Because qsg is used to generate data to be read by arbitrary languages it has a number of lexical characteristics such as minimal quoting -- consider as a contrary example the difficulty of running awk from tcl. For performance reasons it is compiled to a very efficient intermediate representation either at run-time or to prepare object files for possible inclusion in a library of qsg scripts. The library mechanism facilitates provision of a rich set of standard procedures. These standard library functions can, for most constructions, reduce the entire build specifications as provided by the user to simple host-independent single-line commands.
The set of qsg library scripts deliver the type of functionality that make inference rules are intended to supply. The actual construction details are hidden in the library allowing the end-user to trivially create portable construction specifications.
qsg commands consist of a keyword or the name of a procedure, file or qsg library member, and an argument list. The keywords provide flow and I/O control, variable manipulation, procedure and flag definition, and debugging controls. Argument lists are simply strings that may incorporate the values of variables and/or functions. The value of a variable is used by `@' followed by the variable's name as in:
Functions are provided to read files or pipes, evaluate lclvrs expressions, find or check files, etc. Function calls look like "@(function flags args ...)" as in:
Various manipulations of a variable or function value can be performed using tilde postfix operators. For example, "@argv*l" is replaced by the number of elements in the variable argv. Tilde operators are provided to select matched elements, selected parts of individual elements (e.g., the directory name, tail, suffix), or to perform substring replacements. However, the normal user uses a very small subset of qsg's facilities when creating a qeffile. Most qeffiles will consist of a few inv ocations of the qsg library members as in:
# install *.dat source files in directory _DestDir_/lib
install -d _DestDir_/lib @argv*x/dat/
# compile and link example.c and parser.y program example.c parser.y
# create and install dtree library with a version module library -v -n dtree @argv~x/c.y.l/
In the above "install", "program", and "library" are qsg scripts that have been compiled into the qsg pseudo-code and installed in an archive. A typical qsg library script will produce output in a form suitable for the chosen back-end. The qsg scripts are built to deal with host-dependent conventions, although in many cases they output qefpp macros to deal with host dependent names and mappings.
An example of qsg output would be of limited value. "program file.c" will generate 30 to 60 lines, depending on the host. The output would contain recipes and dependencies to produce the program, the installed program, the object modules, the assembler versions, the purify, quantify, and pixie versions (if supported), to lint file.c, and to remove any intermediate and/or installed files.
One of the significant advantages of this approach, when compared with make's inference/suffix rules or imake's macros, is that qsg creates scripts that provide complete and comprehensive facilities from succinct portable specifications. Many qeffiles consist of one or two qsg commands, yet the equivalent make scripts are huge. In one of the author's source directories (containing 70 c, lex, yacc, and shell programs) a three line qsg script produces the equivalent of a 3,500 line make script.
qefpp()'s primary role is much the same as the C preprocessor -- to define macros and replace embedded instances of those macros by their values and to provide primitive flow control.
However, qefpp() offers a number of important features far beyond cpp's capabilities as illustrated by part of qsg's output for "program eg.c":
The above uses the following qefpp built-in macros:
_Touch_(cc) replaced by list of files called cc in the @TouchPath directories. Thus the target file "eg" is dependent on any existing "Touch cc" file. To force the recompilation of all C sources, one touches a file called "cc" in a @TouchPath directories. The script generators output these Touch dependencies for every significant tool thereby providing a simple and consistent way to force the reexecution of processes.
_FndSrc_(eg.c) qefpp searches the @SrcPath directories for the first instance of a file called "eg.c" and replaces the macro by its path.
_Libs_(eg.c) Replaced by the libraries for file eg.c A variety of configuration parameters are used to create this list. The "_CcLibs_()" is almost the same except that the list of libraries might be modified to be suitable for a cc command.
_T_cc, _F_cc, _F_cc_o[eg] Symbols beginning with "_T_" and "_F_" are for tool names and flags respectively. Such symbols are treated specially in that they are automatically imported from the lclvrs database as predefined symbols and also have special default values (the symbol name minus the "_T_" prefix for _T_ symbols, the empty string for _F_). Also note that associated array elements are support to allow the specification of tool flags for specific files.
In the initial implementation of qef (circa 1983) the preprocessor played a much more important role. Its importance has been greatly reduced as the script generators and lclvrs now provide better mechanisms for managing variables and special constructions. However, it still serves to perform simple macro processing and variable substitution which greatly reduces the complexity of the script generation process.