NeoLisp is a LISP-like language that I'm building to experiment and toy around with a few new language ideas.
I'll expand this readme, and document the language thoroughly
once it's a bit more mature. In the meanwhile, you can find
NeoLisp code samples/snippets in unit_tests/language_tests.cpp
.
One interesting aspect of NeoLisp is its function application rules. In addition to supporting LISP-style S-expression function application, it has a novel approach to function application that allows traditional precedence-based infix expressions.
Functions in NeoLisp can have a floating-point precedence value
associated with them. When NeoLisp evaluates a list, it finds the
function with the lowest precedence, and applies the list against
this function, passing in the items on the left as lhs
, and the
ones on the right as args
. Traditional LISP-style function invokation
in the form of (+ 1 2)
also works. The arguments on the right are
simply passed as args
to the function. As a result, both (+ 1 2)
and 1 + 2
are valid and equivalent. Differing precedence operators
means that an expression like 1 + 2 * 3 + 4
is evaluated in the
expected. This novel approach to function application means that it's
trivially easy to define new binary operators in a NeoLisp program.
NeoLisp doesn't have a progn
(Common LISP) or begin
(Scheme) special form.
Instead, it has ;
, an equivalent that is internally implemented as a
delayed-evaluation function of zero precedence. For example, to evaluate
exprA ; exprB
, the ;
function first evaluates exprA
and binds its
result to _
, then evaluates exprB
and returns its result as the result
of the ;
function. This allows exprA ; exprB ; exprC ; exprD
to
be executed in the implied order.
The script build.sh
both builds and tests this project. The exit code of
build.sh
indicates whether unit tests passed.
You need CMake, and a recent version of GCC or Clang that supports C++17.
The unit tests are written using Catch2,
and so you'll need to make sure the Catch header file catch.hpp
is accessible
from the include path, in order to be able to run unit tests.
I use Valgrind to test for memory leaks and cppcheck
for additional
static code quality checks — they're entirely optional.
I strongly recommend using rlwrap
when using the NeoLisp REPL for a better exeprience. You can configure it
to match parentheses by setting set blink-matching-paren on
in your
.inputrc
(readline configuration file).
To re-iterate, the dependencies are:
- CMake 3.5+
- GCC 9+ or Clang 8+
- LLVM Core 8+ (the development libraries)
- catch.hpp (optional, for testing)
- Cppcheck (optional, for testing)
- Valgrind (optional, for testing)
- rlwrap (optional, for a better REPL experience)
After building, you can run the REPL with rlwrap ./NeoLisp
.
I'm currently working on getting NeoLisp to a point where it compiles to machine code via LLVM.
Many thanks to Peter Norvig's Lispy guides (1, 2) and the LLVM Tutorial.
The code herein is made available under the Apache License 2.0.