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.