From pmoran@ncsa.uiuc.edu Mon Jul 22 14:04:47 1991 Received: from bardeen.ncsa.uiuc.edu by newton.ncsa.uiuc.edu with SMTP id AA19983 (5.65a/IDA-1.4.2 for rbrady); Mon, 22 Jul 91 14:04:45 -0500 Received: from kepler.ncsa.uiuc.edu by bardeen.ncsa.uiuc.edu with SMTP id AA05641 (5.65a/IDA-1.4.2 for cpotter@newton.ncsa.uiuc.edu); Mon, 22 Jul 91 14:04:43 -0500 Return-Path: Received: by kepler.ncsa.uiuc.edu (4.1/NCSA-4.1) id AA05608; Mon, 22 Jul 91 14:04:42 CDT Date: Mon, 22 Jul 91 14:04:42 CDT From: pmoran@ncsa.uiuc.edu (Patrick Moran) Message-Id: <9107221904.AA05608@kepler.ncsa.uiuc.edu> To: cpotter@ncsa.uiuc.edu, lawrance@ncsa.uiuc.edu, liang@ncsa.uiuc.edu, rbrady@ncsa.uiuc.edu Subject: viewit hacker style guide Status: RO OK gang, here's a style guide I've written up on how to add functions to viewit. I'd appreciate any feedback. Pat ---------- GUIDELINES FOR WRITING NEW VIEWIT FUNCTIONS Adding new functions into viewit is not difficult, by following the guidelines below, you should be able to painlessly integrate your routines. By taking advantage of the support routines that have been added as part of the parser improvements, your routines can have the added virtue of checking for some common user errors with little additional code. BASIC DESIGN CONSIDERATIONS: The viewit interpreter works with a large table, located close to the beginning of viewit.c, with all the information needed to execute user functions such as "-dim". Adding a new viewit function basically requires two things: writing your own routine and placing an entry in the master table. There are tips for both of these steps in the following sections. An issue that should be resolved early in the design process is specifying the command line arguments that your function will require. If the number and type of arguments are fixed, then your function can be much simpler because argument type checking can be done automatically for you by the interpreter. Variable numbers of arguments are possible too, but your routine must then take the responsibility of doing argument checking. There's a discussion about this in a later section. The model used for representing user arguments to internal routines is essentially the same as that used for C programming: argc and argv. Argc is an integer specifying the number of arguments, argv is an array of strings. As in C, argv[0] contains the name of the function, e.g. "-dim". Also as in C, argc returns the number of strings in argv. Argc is always at least one since there will always at least be a value in argv[0]. As an example, if the user types "-dim 2 100 200" on one line, then the internal function reduce will be called where argc is four, argv[0] is "-dim", argv[1] is "2", argv[2] is "100" and argv[3] is "200". A second design issue to resolve early on is to determine what arguments your routine should be called with. The interpreter has a large but fixed set of options to choose from, for example (ss1, ss2) or (ss1, argv). The choices can be found in viewit.h as the constants with the FMT_ prefix. Typically, your routine should use as arguments the registers that it will access and argv if any command line arguments are needed. The formats with argc as well as argv are special because they are only to be used with routines with variable numbers of arguments. The reasoning is that if a function has a fixed arguments, then the interpreter will check for their presence and the routine can assume that they are available, thus argc is unnecessary. Using formats with argc is also special because if the interpreter calls your function using one of these argument lists, then it expects your routine to return the number of command line arguments used. The interpreter needs to know this in order to advance the appropriate number of tokens after executing your routine. USEFUL SUPPORT ROUTINES, STRUCTURES AND CONSTANTS: This section lists support routines that can be very handy for writing your own functions. Some of them have been in viewit for a long time, but they are listed for completeness and for neophyte viewit hackers. To learn more about these routines, structures and constants, look in viewit.h viewit.c and support_subs.c. gprintf(): Use in place of printf(). Do not use printf because in some viewit modes (e.g. DTM or ALICE) writing to stdout will not work. error_exit(): Call when a non-recoverable error is encountered. Unlike error_exit in older versions of viewit, this can handle the same arguments as gprintf(). malloc1(): The same as malloc, but checks for NULL result and does error_exit if there is a failure. calloc1(): Same as calloc() but error_exits on failure realloc1(): Same as realloc() but error_exits on failure nelements(): Returns the number of elements given the number of dimensions (ndim) and an array of dimension sizes (nn) atoi(): System function, probably faster than sscanf(s, "%d", ...) atof(): System function, probably faster than sscanf(s, "%f", ...) ic_strcmp(): Just like strcmp, but ignores character case keyword_value_t: A typedef for a structure, see viewit.h of the actual definition. A constant array keyword-value pairs is a good way to represent a set of valid keyword arguments for a function. See plot() and scan() in viewit.c for examples. Note that an array of keyword-value pairs needs a null pair at the end for the functions described below to work properly. member(): Given an array of keyword_value_t and a string, returns the value corresponding to the matching keyword or -1 if no match. ic_member(): Just like member, but ignores character case. show_keywords(): Takes an array of keyword_value_t's and displays the keywords, useful when the user hasn't made a valid choice. Plot() and scan() have example usages check_arguments(): Useful for argument checking by functions that have a variable number of arguments. There's more discussion about using this function below. is_uint(): Takes a string argument, returns TRUE if the string represents an unsigned integer is_int(): Takes a string argument, returns TRUE if the string represents an integer is_float(): Takes a string argument, returns TRUE if the string represents a float MAX_DIM: defined in viewit.h, the maximum number of dimensions allowed for a viewit register_struct, useful for specifying temporary arrays in functions without using [mc]alloc[1]() and free(). register_struct_new(): create a new register_struct given the number of dimensions, an array of dimension sizes and a buffer with floating point values. The array of dimension sizes is copied, so it can be free()'d by the routine calling this function after the call. The buffer of floating point values is not copied, so it is the calling routine's responsibility to make a copy if necessary. register_struct_free(): Deallocate memory for the given register_struct. register_struct_copy(): Create a new register_struct, making copies of all the fields except for ostruct. push_register(): Takes a register_struct pointer argument, pushes all the register values and puts the register given as an argument on top of the stack. This function makes it easy to achieve behavior like an H-P calculator. See examples in dyadic_subs.c. pop_register(): Returns the register_struct from the top of stack, moves the other values down, and duplicates the value in ss4. command_error: A global boolean (bool_t) variable, set to TRUE to indicate to the interpreter that an error has occured so that it doesn't continue merrily along. GUIDELINES FOR WRITING FUNCTIONS: Here are few suggestions for writing new functions, most are pretty obvious. There are tips for writing functions with variable numbers of arguments in the next section. 1. If you allocate memory for temporary arrays, be sure to clean up after yourself. 2. For small temporary arrays where you know maximum size, consider using an automatic variable (e.g. int nn[MAX_DIM]) instead of explicitly allocating and freeing an array. Your routine will probably be faster and you don't have to worry about forgetting to free something. 3. Do not modify any of the strings in the argv array. For example, if you need to have a string where you are concatenating something to one of the argv strings, copy the argv string first. 4. If you open files, close them before returning. 5. If you encounter an error, set command_error to TRUE and then return. 6. Use error_exit() if a non-recoverable error occurs. 7. Try using the utility functions described in the previous section to make your routine shorter and more robust. 8. The built-in argument checking of the interpreter type checks the arguments (e.g. ensures that an argument is an unsigned integer) but does not check that the arguments are in the correct range. Your routine should do this. At least, it should try to anticipate some of the more likely errors. 9. If your routine fits into a category such as a monadic operator or I/O, put it into the appropriate file (e.g. io_subs.c) rather than viewit.c. HANDLING VARIABLE ARGUMENTS: If your routine does not have a fixed set of arguments, then it needs to do checking itself. A function that is very helpful in this regard is check_arguments(), which can be found in support_subs.c. Check arguments takes your function name, an argument format like that used in the fourth column of the built_in table, argc, and argv and returns the number of arguments if they are OK or -1 if there's a problem. Good examples of using check_arguments can be found in plot(), translate(), dim() and fscale(). Note the use of the arrays uivector_args and fvector_args can be very useful as format arguments when multiple unsigned integers or floats are expected. ADDING YOUR FUNCTIONS TO THE INTERPRETER: After completing your own new function and adding it to one of the source files, the next step is to add it the interpreter table, called built_ins, close to the beginning of viewit.c. There are four fields to fill in: #1 the string representing the user function name #2 the name of the routine that the interpreter will call #3 the format of the arguments with which the function will be called #4 the type of the user arguments Before making an entry in the table, make an addition to the function declarations preceding the table as a routine returning an int. If the function is not in viewit.c, then it should be added as an extern. Next, add a new line to the table. Note that the rows are ordered alphabetically by the user function name. It is essential to keep the table ordered because the interpreter uses binary search when it does lookup. If your routine has special requirements such as FORTRAN support or certain numerical libraries, then is surround the line with #if and #endif statements so that is compiles conditionally. There are already lots of examples of this in the table. In the second field place the routine name to call, this is the same name that you declared previously. The third field gets the calling format that the interpreter will use. The fourth field uses the macros and constants from viewit.h to specify the command line arguments. A_NOCHECKING means than the routine will do argument checking itself. A_NONE means that no arguments are expected. For a single argument, the choices are A_UI (unsigned integer), A_F (float), A_NAME (most any sequence of characters), A_I (an integer), and A_REG (an integer between one and four specifying a register). Multiple arguments can be described using the A1() through A10() macros. A1() is for the first argument, i.e. the argument immediately following the command name. Arguments can be combined by adding or by bitwise and. There are plenty of examples in the table to use as a guide. Note that if a routine has more than ten arguments then the it must do the checking itself because of limitations of the encoding scheme. See bkproj_3d2() in viewit.c as an example.