Embedded XPL

Embedded XPL

Top  Previous  Next

Embedded XPL (EXPL) is a version of XPL0 that is designed to be embedded in other programs to provide high-level macro capabilities. For example, EXPL could be embedded in a text editor to help the user manipulate the text. This way, the user has the ability to write high-level language macros that operate on the text in the editor. To accomplish this with EXPL, all that is needed is a special set of intrinsics that interface to and access the editor's text buffer.

 

EXPL is modular, consisting of four major pieces: 1) compiler, 2) loader, 3) interpreter and 4) intrinsics.

 

Plug-Together Modularity. Each of these modules is designed to be connected together in different ways depending on the needs of a project. Here are the details of the modular aspects of EXPL:

 

1. Compiler Error Handling. The compiler can be configured to adapt it to a variety of environments. In the stand-alone mode, the compiler outputs error messages to a pop-up dialog box. Alternatively, it can deliver a list of errors to the calling program. The error message list includes the line number, column number and other details about each error. This is useful for integrating the compiler with an IDE. Using this information, an IDE can go to the line and column of the error and show the programmer exactly where the error occurred.

 

The programmer controls error handling by the kind of arguments that are passed to the compiler. If the programmer passes the address of a message handling routine to the compiler, error messages are directed to the handler. If the programmer passes a null pointer (nil) to the routine, the compiler handles error messages on its own by directing them to a pop-up dialog box.

 

       DoXPLCompile(Source, I2LCode, MsgHandler);

 

2. Compiler Error Text. The compiler now delivers more useful, detailed and verbose error information. For example, in the case of the "Unused Variable" warning, the compiler now identifies the location where the variable was declared, not the location where the compiler discovered that the variable was unused, which is usually the end of the program or the end of a procedure. As another example, the compiler also differentiates between variables that are never used and variables that are assigned a value and the value is never used.

 

3. Flexible Compiler I/O. The compiler, loader and interpreter can now read or write data from either a file, stream or a string. This has a number of advantages:

 

a) The ability to work with strings enables the compiler, loader and interpreter to be integrated with an editor and other tools to make a stand-alone development environment (IDE) that can be embedded in other applications or even in a web page. In this scenario the code can be taken directly from an editor and passed to the compiler without the need to save it to a file. Likewise, text-string versions of the I2L code can be passed from the compiler to the loader. Finally, the binary for the program itself can be passed to the interpreter.

 

b) Intrinsic declarations can be injected into the source stream by the development system software, circumventing the need to have intrinsic-declaration files floating around on the disk. This frees the user from the task of knowing and declaring the path to the intrinsic file, which is especially important for embedded applications where the user shouldn't be expected to understand installation details. (To support this function, current versions of the interpreter can generate an intrinsic declaration string directly from the intrinsic tables. See the description below.)

 

An example of how this flexibility is accomplished, the compiler has three entry points, each compiling programs in a different way. One handles the input and output as strings, one handles it as streams and one handles it by file names:

 

DoXPLCompile - Input and output are strings.

DoXPLCompileStream - Input and output are streams.

DoXPLCompileFile - Input and output are specified by filenames (like the original version of XPL0).

 

4. Internal Intrinsic Generation. The intrinsic module can generate a string containing a complete set of intrinsic declarations on the fly. This is valuable for several reasons:

 

a. First, since the intrinsic declarations are generated directly from the actual intrinsic tables, the declarations are guaranteed to be up-to-date and correct, even when changes have been made to the intrinsics.

 

b. Second, having  a string version of the intrinsics available on-demand enables you to create an embedded version of XPL that is not dependent on external files and directories. The intrinsic declarations can be injected, on the fly, into the source code so the user never needs to declare or even know about the location of the intrinsics.

 

5. Debugging Info. The compiler can also deliver debugging information. This includes maps that translate program counter positions into source code line numbers, and variable names and scope into the interpreter's variable storage. This enables the IDE to do things like single step through a program, display the values of variables during runtime and display stack dumps of subroutine calls. Here is a description of the main subroutines:

 

GetSymbolTableDump

Returns the symbol table information from the last compiled program, including each procedure, variable and constant. This information can be used to find the value of a variable during program execution.

 

PCToSourceData

This subroutine converts the I2L program counter (PC) to the corresponding filename, line and column number.

 

LineNumberToPC

This subroutine converts a source line number to the corresponding I2L program counter value. This is useful for setting breakpoints in the source code. It is also useful for finding which variables are within scope to display their values.

 

GetSymbolByName

This subroutine retrieves information about a symbol based on the symbol name. The subroutine also requires the I2L program counter to differentiate between symbols with the same name. The subroutine returns information about the most local procedure, the scope of the symbol, along with the symbol's name, type, level, offset, line number, constant value and whether the symbol has been used. This is useful for displaying variable values at runtime and setting watches.

 

AddressToProcName

This subroutine converts an I2L code address to the name of the most local enclosing procedure. This information is useful for displaying a stack trace.