Node: Overview, Next: , Previous: Introduction, Up: Introduction


Lpp (Lisp Plus Plus), is a library of Lisp like functions and macros usable in C++ programs. The philosophy behind Lpp is to provide as close as possible the semantics and style of Lisp rather than try to force it to fit a static style of programming. Lpp tries to emulate Common Lisp as much as possible in this regard. By doing things this way part of the true power and flexibility of Lisp can coexist and mix with the static typing features of C++ even within functions and objects. The hope is that Lpp will be useful for the following

One of Lisp's advertised benefits is that of dynamically typed objects. Standard C++ does not offer this capability. Instead, the programmer is expected to created virtual functions whose objects dynamically dispatch through the use of indirect pointers to v-tables and then a v-table has a pointer to the virtual function for that object. While the basic idea of virtual functions is good, relying only on it for real world complex problems presents difficulties. One such problem is that it is impossible to write true generic code using virtual functions since all possible types must be accounted for. The default virtual function of a base class can only serve the declared sub-classes that do not define their own virtual functions. Contrast this with Lisp functions that usually do not have to account for the types of the objects that it operates on. As a very simple example, Lisp can compute the length of a list irrespective of the types of the objects in the list. Furthermore a provider could supply an object dynamically to a consumer of such a list that is not required to be seen by the compiler of the program operating on the list.

Lpp strives to provide the full power of Lisp in terms of dynamic typing. The way that Lpp achieves this is that all Lpp objects contain a type header that is a pointer to a type meta-object. In some sense this is similar to the v-table concept mentioned above for virtual function tables in that type meta-objects can and do contain type dispatcher functions that are similar to virtual functions. But the type meta-objects are automatic in Lpp as well as much of their standard behavior. And type meta-objects are first class Lpp objects so that they can be manipulated dynamically as ordinary objects can. For example a dispatcher function can be dynamically added to a type meta-object and then operate on subsequent ordinary objects of that type. Furthermore the type meta-objects maintain a run time type lattice. For example at run time a program can query whether an object is a number or an integer (integers being a subset of all numbers).

All Lpp objects as in Lisp have the same base type. In Lisp the base type is type t. In Lpp the base type is intentionally left unknown and abstracted with a type definition called let. This was done so that the base type can be experimented with while preserving Lpp syntax. Type let serves as the declaration type for all such Lpp objects when referring to the Lpp object as a generic object. As in Lisp Lpp objects are passed around as pointers with pointer abstraction in Lpp function arguments and Lpp slots. These objects can be mixed freely with other C++ classes and class members and can be used with other C++ libraries that do not use the same class names that Lpp uses.

Lpp provides a the macro for descending into specific Lpp objects. This is similar to the the special form in Common Lisp. Lpp automatically does a dynamic type check on the object wrapped in a the macro which assures that specific object member references will always be correct. In a debugged program compilation unit this type checking can be turned off for maximum compilation efficiency.

Lpp also provides other useful Lisp objects such as Cons cells, lists, symbols, strings, characters and numbers. As much as possible Lpp tries to use the Common Lisp function names and semantics for the operations on Lisp like objects.

Lpp chose address 0 as nil. This is so that it is easy to check for the end of lists or nil returned in predicate arguments in C++ functions, operators and statements like if, while, for. So nil and non-nil is consistent with C++ predicate semantics on 0 and non-0. But even though nil is 0, it is exactly equivalent to the symbol nil. It is as if the symbol object nil was an object at address 0.

Symbols are introduced into C++ programs easily with the S() macro. For example, S(foo), S(Foo), S(my:foo), S(foo-bar), S(+), S(!\@#$%^&*) are all legitimate symbols in Lpp. Symbols after being interned are just as efficient but far more powerful than enums. The default for symbols in Lpp is case sensitive, so that foo and Foo are two distinct symbols.

Primitive C objects can easily be converted to their analog Lpp objects using the L constructor. For example L("abc") will produce an Lpp String object and L(cdr) will produce an Lpp first class function object.

Conversions back are also easy for example 22 == iL(L(22)) where iL conversion is back to C++ integers. The programmer can use dynamic Lpp objects when needed and use simple C++ objects when absolute efficiency is needed. Efficiency is an elusive property though. For example Lpp strings seem less efficient on the surface, but since they have a length they provide for faster string comparisons than ordinary 0 terminated C++ strings. The names of such things as L (the conversion macro), True and S (the symbol macro) can be redefined per compilation unit by the programmer without affecting the operation of Lpp.

Lpp provides the basic Common Lisp I/O functions like read, print, prin1, princ, pprint ... etc, but also provides that any Lpp object can appear in stream operators. So if obj is the Lpp symbol object car then cout << "obj = " << obj would print as obj = car. All Lpp objects have C++ stream print methods that inherit from the basic princ method of an object. All new Lpp objects defined automatically get default dispatcher functions defined for princ and prin1. New princ or prin1 methods can be set dynamically by the programmer for Lpp type meta-objects.

Lpp provides mathematically correct rational numbers, ie. ratios whose numerator and denominator and integers are in the range minus to plus infinity. Lpp numbers automatically expand or shrink to any size when operated on by Lpp math functions. Overflow or underflow are impossible.

Lpp provides two debugger functions pdb and pdc for examining Lpp objects in a debugger. pdb uses prin1 and pdc uses princ. So while in a debugger the user can type, for example, p pdc(list1) where list1 is some variable in a program that contains an Lpp list, the list and all of it's subcomponents will be printed exactly as you would expect in a lisp interpreter. In addition the function ppdb is provided for pretty printing such objects in a debugger.