Personalized Interpreters for Version 6.0 of Icon Ralph E. Griswold Introduction Despite the fact that the Icon programming language has a large repertoire of functions and operations for string and list manipulation, as well as for more conventional computations [1], users frequently need to extend that repertoire. While many extensions can be written as procedures that build on the existing repertoire, there are some kinds of extensions for which this approach is unacceptably inefficient, inconvenient, or simply impractical. Icon itself is written C and its built-in functions are written as corresponding C functions. Thus, the natural way to extend Icon's computational repertoire is to add new C functions to it. The Icon system is organized so that this is comparatively easy to do. Adding a new function does not require changes to the Icon translator, since all functions have a common syntactic form. An entry must be made in a table that is used by the linker and the run-time system in order to identify built-in functions and connect references to them to the code itself. One method of adding new functions to Icon is to add the corresponding C functions to the Icon system itself and to rebuild the entire system. This approach is impractical for many applications. If the extensions are not of general interest, it is inappropriate to include them in the public version of Icon. On the other hand, Icon is a large and complicated system, and having many private versions may create serious problems of maintenance and disk usage. Furthermore, rebuilding the Icon system is expensive. This approach therefore may be impractical in a situation such as a class in which students implement their own versions of an extension. To remedy these problems, a mechanism for building ``personalized interpreters'' is included in implementations of Icon. This mechanism allows a user to add C functions and to build a corresponding interpreter quickly, easily, and without the necessity to have a copy of the source code for the entire Icon system. To construct a personalized interpreter, the user must perform a one-time set up that copies relevant source files to a directory specified by the user and builds the nucleus of a run-time system. Once this is done, the user can add and modify C functions and include them in the personalized run-time system with little effort. Since the linker must know the names of built-in functions, a personalized linker also is constructed. In order to run Icon programs in a self-contained personalized run-time system, personalized versions of the translator, pitran, and the command processor, picont, are provided also. The modifications that can be made to Icon via a personalized interpreter essentially are limited to the run-time system: the addition of new functions, modifications to existing functions and operations, and modifications and additions to support routines. There is no provision for changing the syntax of Icon, incorporating new operators, keyword, or control structures. Building and Using a Personalized Interpreter Setting Up a Personalized Interpreter System To set up a personalized interpreter, a new directory should be created solely for the use of the interpreter; otherwise files may be accidentally destroyed by the setup process. For the purpose of example, suppose this directory is named myicon. The setup consists of mkdir myicon cd myicon icon_pi Note that icon_pi must be run from the area in which the personalized interpreter is to be built. The location of icon_pi may vary from site to site. The shell script icon_pi constructs three subdirectories: h, std, and pi. The subdirectory h contains header files that are needed in C routines. The subdirectory std contains the machine-independent portions of the Icon system that are needed to build a personalized interpreter. The subdirectory pi contains a Makefile for building a personalized interpreter and also is the place where source code for new C functions normally resides. Thus, work on the personalized interpreter is done in myicon/pi. The Makefile that is constructed by icon_pi contains two definitions to facilitate building personalized interpreters: a list of object modules that are to be added to or replaced in the run-time system. OBJS initially is empty. a list of library options that are used when the run-time system is built. LIB initially is empty, but the math library is loaded as a normal part of building the run-time system. See the listing of the generic version of this Makefile in Appendix A. Building a Personalized Interpreter Performing a make in myicon/pi creates four files in myicon: picont command processor pilink linker piconx run-time system piconx.hdr header file for linker output A link to picont also is constructed in myicon/pi so that the new personalized interpreter can be tested in the directory in which it is made. The file picont normally is built only on the first make. The file pilink is built on the first make and is rebuilt whenever the repertoire of built-in functions is changed as a result of modifications to h/fdef.h. The file piconx is rebuilt whenever the source code in the run-time system is changed. The user of the personalized interpreter uses picont in the same fashion that the standard icont is used. (Note that the accidental use of icont in place of picont may produce mysterious results.) In turn, picont translates a source program using pitran and links it using pilink. The resulting icode file uses piconx. The relocation bits and symbol tables in piconx can be removed by make Stripx in myicon/pi. This reduces the size of this file substantially but may interfere with debugging. If a make is performed in myicon/pi before any run-time files are added or modified, the resulting personalized interpreter is identical to the standard one. Such a make can be performed to verify that the personalized interpreter system is performing properly. Adding a New Function To add a new function to the personalized interpreter, it is first necessary to provide the C code, adhering to the conventions and data structures used throughout Icon. Some examples of C functions are included in Appendix B of this report. The source code for several such functions is contained in v6/pi/pil, where v6 is the root of the Icon system. The directory v6/src/iconx contains the source code for the standard functions, which also can be used as models for new ones. Suppose that getenv from v6/pi/pil is to be added to a personalized interpreter. The source code can be obtained by cp v6/pi/pil/getenv.c myicon/pi (Note that the actual paths depend on the local hierarchy.) Three things now need to be done to incorporate this function in the personalized interpreter: Add a line consisting of FncDef(getenv) to myicon/h/fdef.h in proper alphabetical order. This causes the linker and the run-time system to know about the new function. Add getenv.o to the definition of OBJS in myicon/pi/Makefile. This causes getenv.c to be compiled and the resulting object file to be loaded with the run-time system when a make is performed. Perform a make in myicon/pi. The result is new versions of pilink and piconx in myicon. The function getenv now can be used like any other built-in function. More than one function can be included in a single source file. See math.c in Appendix B. To incorporate these functions in a personalized interpreter, FncDef entries should be made for each function in math.c and math.o should be added to OBJS. Modifying the Existing Run-Time System The use of personalized interpreters is not limited to the addition of new functions. Any module in the standard run-time system can be modified as well. To modify an existing portion of the Icon run-time system, copy the source code file from v6/src/iconx to myicon/pi. (Source code for a few run-time routines is placed in myicon/std when a personalized interpreter is set up. Check this directory first and use that file, if appropriate, rather than making another copy in myicon/pi.) When a source-code file in myicon/pi has been modified, place it in the OBJS list just like a new file and perform a make. Note that an entire module must be replaced, even if a change is made to only one routine. Any module that is replaced must contain all the global variables in the original module to prevent ld(1) from also loading the original module. There is no way to delete routines from the run-time system. The directory myicon/h contains header files that are included in various source-code files. The file myicon/h/rt.h contains declarations and definitions that are used throughout the run-time system. This is where the declaration for the structure of a new type of data object would be placed. Care must be taken when modifying header files not to make changes that would produce inconsistencies between previously compiled components of the Icon run-time system and new ones. Reference Griswold, Ralph E. and Griswold, Madge T. The Icon Programming Language. Prentice-Hall, Inc., Englewood Cliffs, New Jersey. 1983.'Appendix A — Makefile for Personalized Interpreters

The ``generic'' Makefile for personalized interpreters follows. A copy, with the value of Dir filled in and appropriate definitions for the flags, is placed in myicon/pi when icon_pi is run. Dir= RHDRS= ../h/rt.h ../h/config.h ../h/cpuconf.h ../h/memsize.h # # To add or replace object files, add their names to the OBJS list below. # For example, to add nfncs.o and iolib.o, use: # # OBJS=nfncs.o iolib.o (this is a sample line) # # For each object file added to OBJS, add a dependency line to reflect files # that are depended on. In general, new functions depend on $(RHDRS). # For example, if nfncs.c contains new functions, use # # nfncs.o: $(RHDRS) # OBJS= LIB= RTOBJS=../std/rconv.o ../std/idata.o $(OBJS) Pi: ../picont ../piconx ../pilink ../piconx.hdr ../picont: ../std/icont.c ../h/config.h rm -f ../picont picont $(CC) $(CFLAGS) -o ../picont -DItran="\"$(Dir)/pitran\""\ -DIconx="\"$(Dir)/piconx\"" \ -DIlink="\"$(Dir)/pilink\"" ../std/icont.c strip ../picont ln ../picont picont ../pilink: ../std/linklib ../std/builtin.o ../std/ilink.o $(CC) $(LDFLAGS) -o ../pilink ../std/builtin.o ../std/ilink.o\ ../std/linklib strip ../pilink ../std/ixhdr.o: ../h/config.h cd ../std; $(CC) -c $(XCFLAGS) -DIconx="\"$(Dir)/piconx\"" ixhdr.c ../piconx.hdr: ../std/ixhdr.o $(CC) $(XLDFLAGS) ../std/ixhdr.o -o ../piconx.hdr strip ../piconx.hdr ../piconx: ../std/rtlib $(RTOBJS) $(CC) $(LDFLAGS) -o ../piconx $(RTOBJS) ../std/rtlib $(LIB) -lm

../std/idata.o: $(RHDRS) ../h/fdef.h cd ../std; $(CC) -c $(CFLAGS) idata.c ../std/rconv.o: $(RHDRS) ../h/fdef.h cd ../std; $(CC) -c $(CFLAGS) rconv.c ../std/builtin.o: ../std/ilink.h ../h/config.h ../h/fdef.h cd ../std; $(CC) -c $(CFLAGS) builtin.c ../std/ilink.o: ../std/ilink.h ../h/config.h ../h/header.h ../h/paths.h cd ../std; $(CC) -c $(CFLAGS) -DHeader="\"$(Dir)/piconx.hdr\"" ilink.c Stripx: ../piconx strip ../piconx'Appendix B — Sample C Functions

getenv.c: /* * GETENV * * Get values of environment variables. * * Stephen B. Wampler * * Last modified 5/2/86 by Ralph E. Griswold * */ #include "../h/rt.h" /* * getenv(s) - return contents of environment variable s */ FncDcl(getenv,1) { register char *p; register int len; char sbuf[256]; extern char *getenv(); extern char *alcstr(); if (!Qual(arg1)) /* check legality of argument */ runerr(103, &arg1); if (StrLen(arg1) 0 || StrLen(arg1) MaxCvtLen) runerr(401, &arg1); qtos(&arg1, sbuf); /* convert argument to C-style string */ if ((p = getenv(sbuf)) != NULL) { /* get environment variable */ len = strlen(p); strreq(len); StrLen(arg0) = len; StrLoc(arg0) = alcstr(p, len); Return; } else /* fail if variable not in environment */ Fail; }
math.c: /* * MATH * * Miscellaneous math functions. * * Ralph E. Griswold * * Last modified 5/2/86 * */ #include "../h/rt.h" #include <errno.h> int errno; /* * exp(x) */ FncDcl(exp,1) { int t; double y; union numeric r; double exp();

if ((t = cvreal(&arg1, &r)) == NULL) runerr(102, &arg1); y = exp(r.real); if (errno == ERANGE) runerr(252, NULL); mkreal(y,&arg0); Return; } /* * log(x) */ FncDcl(log,1) { int t; double y; union numeric r; double log();

if ((t = cvreal(&arg1, &r)) == NULL) runerr(102, &arg1); y = log(r.real); if (errno == EDOM) runerr(251, NULL); mkreal(y,&arg0); Return; } /* * log10(x) */ FncDcl(log10,1) { int t; double y; union numeric r; double log10();

if ((t = cvreal(&arg1, &r)) == NULL) runerr(102, &arg1); y = log10(r.real); if (errno == EDOM) runerr(251, NULL); mkreal(y,&arg0); Return; } /* * sqrt(x) */ FncDcl(sqrt,1) { int t; double y; union numeric r; double sqrt();

if ((t = cvreal(&arg1, &r)) == NULL) runerr(102, &arg1); y = sqrt(r.real); if (errno == EDOM) runerr(251, NULL); mkreal(y,&arg0); Return; }