A clean, tested, maintainable Push interpreter written in Clojure.
This project is maintained by Vaguery
This is the core documentation site for the Klapaucius interpreter, written in Clojure.
Push is a stack-based language invented by Lee Spector, Maarten Keijzer and their colleagues. It’s a very peculiar language, with a lot of features that will seem unfamiliar and even off-putting to modern programmers, because it’s not intended to be used by human programmers.
Rather, Push (and the many variations that have cropped up through the years) is intended as a representation framework for automatically generated code, especially (but not exclusively) that the sort produced by genetic programming.
Nonetheless, Push is a simple, extensible programming paradigm. It’s easily extended for domain-specific problems, and beyond a few deep quirks that make it more robust under the sort of syntax-destroying transformations that happen in the course of automated code generation, it tends to be much more readable than many counterparts you’ll see in tree-based, Cartesian, grammatical and other genetic programming paradigms.
In building this particular Push interpreter for my own genetic programming work, I’ve designed it to be more readily extended than most. As a result, there are features of both the underlying Clojure codebase and the way the library has been and is intended to be extended that will affect the user experience. I’ll go into much more detail in the individual documentation below, but the core features that set Klapaucius apart from other Push interpreter implementations are:
PushDSL for writing instructions: Push is fundamentally a collection of imperative instructions, executed serially, which affect the global state of a large collection of type-based stacks. In Klapaucius, these instructions are written in a specially-designed domain-specific language, using a small vocabulary of about two dozen Clojure functions. These DSL functions, or “steps”, make the composition of new instructions easier and also more formal, but they have the added benefit of permitting the Interpreter itself to collapse arbitrary collections of Push instructions and literals into higher-order functions. As a result, evolved Push code build in Klapaucius has the ability to discover abstracted and reusable code more easily than its peers.:code blocks, :vector ordered collections of various types, unordered :set collections, ordered :strings composed of characters, the indexed key-value :tagspace collection type, :binding stacks, and the central stacks on which code is manipulated during execution. Code that wants to read or manipulate individual components of these collections is often dependent on fiddly integer-based ordinal indexing, and can become a weak spot for automated code generation. Here, we can use arbitrary scalar values to index the contents of collections. Not just positive integers but any floating-point, rational, and even negative or infinite values can be used to reference individual items in finite-sized collections, using a consistent approximate modulo indexing approach that all collections share.0 result when some random code being executed attempts to divide by zero. Here, I’ve taken a more instructive approach with these low-level risky functions. Instead of emitting a “fake” result and barreling on, Klapaucius programs that attempt to divide by zero or take the arcsin(88) will produce an :error result instead of the “expected” numeric result. As :error messages accumulate in the read-only :error stack, evolved code has the opportunity to interrogate that stack, and potentially to “learn” from earlier missteps.:scalar BigDecimal and BigInteger values. All in one big pile.:complex real and imaginary parts are both :scalar values.:interval min and max value (both :scalar values), either of which may be included as part of the range.:boolean true and false.:char :set :set items.:string :char elements. You probably already know what they look like and how they work.:vector :code items.:vectorized types :booleans:chars:complexes:scalars:intervals:refs:strings:tagspace :scalar-indexed key-value collection, with an approximate lookup function. Items (of any type) are stored at arbitrary sorted :scalar indices. When another :scalar is used to retrieve an item stored in a :tagspace, any item stored in that exact value is returned, or if that specific index is empty, then the item at the next-larger index existing is returned.:code :code. A :vector is a collection of :code items.:exec :code items in a special place.:ref :ref is always a Clojure keyword, and can be used to refer to a :binding.:snapshot :generator iterator in some other languages.:quoted :code. When the interpreter encounters a :quoted item, it is stored on the :code stack instead of being interpreted and acted upon.:log :error :return :code values.:print :code values.