A simple scripting language for Hidey-Chess and similar projects.
Duck-lisp is your typical hobby lisp with one or two twists. I started this project before I knew what lisp was, choosing the syntax solely for parsability, so variable declaration has a bit of a JavaScript-like feel. Macros are also a little weird since the language is split into separate runtime and compile-time environments. Parentheses are optional.
- Optional free-form parenthesis inference that is mostly backwards compatible with S-expressions
- Optional compile-time arity checks (part of parenthesis inference)
- First class functions and lexical scope
- Common Lisp-like macros
- UTF-8 compatible
- C FFI
- Split compiler and VM
- Compilation with the C standard library is optional
- Tested on x64 (Linux) and ARM (Linux)
- A simplified VM has been used on an ATmega328P, and the full VM runs on an LPC1769.
- C99 minimum
- Independent of OS
- Functions created by
defun
anddefmacro
are lexically scoped. - Built-in keywords can be overridden using
var
,defun
anddefmacro
. - Variables are declared as they are in C-like languages. There is no
let
. - Parenthesis inference does not work well with lisp auto-formatters.
- Not quite a lisp-2.
- Recursion is performed using they keyword
self
.
- Error reporting is horrible. It will likely stay this way.
- There are no debug features other than a disassembler.
- Macros are unhygienic due to the inability of closures to be passed from the compilation VM to the runtime VM. Thus the lisp-2.
git clone https://github.com/oitzujoey/duck-lisp
cd duck-lisp/scratchwork
mkdir build && cd build
cmake ..
cmake --build .
You can generally ignore compile flags, but they are here in case you want them:
To enable parenthesis inference and compile-time arity checks, configure the project with cmake .. -DUSE_PARENTHESIS_INFERENCE=ON
instead of cmake ..
.
To build with shared libraries, set -DBUILD_SHARED_LIBS=ON
as with the option above.
To use DuckLib's memory allocator instead of the system's, set -DUSE_DUCKLIB_MALLOC=ON
. DuckLib's allocator is sluggish.
Duck-lisp may be used without the standard library if necessary. Use the option USE_STDLIB=OFF
. This will result in decreased performance.
Advanced options: The settings NO_OPTIMIZE_JUMPS=ON
and NO_OPTIMIZE_PUSHPOPS=ON
disable peephole optimizations. I suggest ignoring these variables.
If you need maximum performance out of the compiler, then USE_DATALOGGING=ON
might be helpful. duckLisp-dev
is setup to print the data collected when this flag is enabled.
For maximum performance, I suggest using -DUSE_DUCKLIB_MALLOC=OFF -DUSE_STDLIB=ON -DNO_OPTIMIZE_JUMPS=OFF -DNO_OPTIMIZE_PUSHPOPS=OFF
. This is the default.
For maximum portability, I suggest using -DUSE_DUCKLIB_MALLOC=ON -DUSE_STDLIB=OFF
.
Examples and other junk can be found in the scratchwork directory.
# Run duck-lisp language tests.
./duckLisp-test ../../tests
# Run the duck-lisp program "factorial.dl".
./duckLisp-dev ../scripts/factorial.dl
# Run a script with arguments.
./duckLisp-dev "(include \"../scripts/underout.dl\") (main 52)"
# Start the REPL.
./duckLisp-dev
"docs/language.md" contains a brief description of the language.
"docs/language-reference.md" contains a description of all duck-lisp keywords.
"docs/api.md" contains the C API documentation. (incomplete)
"duckLisp.h" contains everything needed for normal usage of the compiler.
"parser.h" contains declarations for the reader.
"emitters.h" contains declarations for emitters for all special forms.
"generators.h" contains declarations for generators for all special forms.
"duckVM.h" contains everything needed usage of the VM.
"duckLisp.c" contains the main compiler functions.
"parser.c" contains the reader.
"parenthesis-inferrer.c" contains the parenthesis inferrer and arity checker.
"emitters.c" contains the emitters (functions that produce "high-level assembly").
"generators.c" contains the generators (functions that convert AST forms to "high-level assembly" by calling emitters).
"assembler.c" contains the assembler for the "high-level assembly" generated by the compiler.
"compiler-debug.c" contains a single 7500 line function that disassembles bytecode.
"duckVM.c" contains the VM.
Typical usage of the language only requires including "duckLisp.h" and "duckVM.h". Adding new user-defined generators that generate bytecode requires "emitters.h". Some user-defined generators may want to call existing generators, which are declared in "generators.h". Function declarations for the reader can be found in "parser.h".
Examples on how to extend the language can be found in scratchwork/duckLisp-dev.c
.