Liz: Lisp-flavored general-purpose programming language (based on Zig)
Borrowing Zig's tagline:
General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
- Written as Clojure-looking S-expressions (EDN) and translated to Zig code.
- Type-A Lisp-flavored language. I call it "Lisp-flavored" because Liz is missing many fundamental features to be called a Lisp or even a Clojure dialect (no closures, no persistent data structures).
- When you need a language closer to the metal and Clojure with GraalVM's native image is too much overhead.
- Supports many targets including x86, ARM, RISC-V, WASM and more
Why is Zig an interesting choice as a lower-level language for Clojure programmers? (compared to Rust, Go or other languages):
- Focus on simplicity
- Seamless interop with C without the need to write bindings.
Similar quality like Clojure seamlessly interoperating with Java. - Incremental compilation with the Zig self-hosted compiler.
To accomplish this Zig uses a Global Offset Table for all function calls which is similar to Clojure Vars. Therefore it will be likely possible to implement a true REPL. - Decomplecting principles
Most higher-level languages have bundled memory management, which disqualifies them from certain use cases. Zig decomplects memory management by introducing explicit Allocator interface, programmer can choose fitting memory management mechanism with regard to performance/convenience trade-offs.
Status: Experimental, but proving itself on a few projects.
Examples
Hello World:
;; hello.liz
(const print (.. (@import "std") -debug -print))
(defn ^void main []
(print "Hello, world!\n" []))
It will get translated into:
const print = @import("std").debug.print;
pub fn main() void {
print("Hello, world!\n", .{});
}
Run with:
$ liz hello.liz && zig run hello.zig
Hello, world!
FizzBuzz example:
(const print (.. (@import "std") -debug -print))
(defn ^void main []
(var ^usize i 1)
(while-step (<= i 100) (inc! i)
(cond
(zero? (mod i 15)) (print "FizzBuzz\n" [])
(zero? (mod i 3)) (print "Fizz\n" [])
(zero? (mod i 5)) (print "Buzz\n" [])
:else (print "{}\n" [i]))))
See also:
- more examples
- Advent of Code solutions
- Rendering TUI with Notcurses
Documentation
Read the work in progress language guide.
To see how a form is used you can also take a look at samples adapted from Zig docs.
Usage
Download Liz and Zig. To compile files from Liz to Zig pass them as parameters:
liz file1.liz file2.liz
# file1.zig and file2.zig will be created
Then use zig run
or zig build-exe
on the generated .zig
files.
Alternatively you can use the JAR:
java -jar liz.jar file1.liz file2.liz
Extension and Syntax highlighting
Use .liz
file extension. It works pretty well to use Clojure syntax highlighting, add -*- clojure -*-
metadata to the source file for Github and text editors to apply highlighting.
;; -*- clojure -*-
Design principles
- Create 1:1 mapping, everything expressible in Zig should be expressible in Liz, or can be considered a bug.
- If a Zig feature maps cleanly to Clojure then use the Clojure variant and name.
- If the mapping conflicts then use the Zig vocabulary.
License
MIT
Development
Get the source:
git clone https://github.com/dundalek/liz.git
cd liz
Build platform independent uberjar:
scripts/build-jar
Build the native binary (requires GraalVM):
# If you don't have native-image on PATH then you need to specify GRAALVM_HOME
export GRAALVM_HOME=/your/path/to/graal
scripts/build-native
Use Clojure CLI:
clj -M -m liz.main file1.liz file2.liz
Run tests:
clj -Mtest