Skip to content
/ liz Public

Lisp-flavored general-purpose programming language (based on Zig)

License

Notifications You must be signed in to change notification settings

dundalek/liz

Repository files navigation

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:

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