Introduction to Moonli for Common Lispers

What

This is a syntax layer that transpiles to Common Lisp.

defun sum(args):
  if null(args):
    0
  else:
    first(args) + sum(rest(args))
  end
end

transpiles to:

(defun sum (args)
   (cond ((null args)
          0)
         (t
          (+ (first args)
             (sum (rest args))))))

See ./moonli-sample.asd and ./sample/sample.moonli to include in your project.

Table of Contents

Why

Due to tools like paredit and lispy (beyond macros and metaprogramming), s-expression (Lists) based syntax of lisps is very powerful. However, not every one has the time or patience to become comfortable with them, especially when it comes to reading code, or sharing it with your colleagues.

In the 21st century, very many more people are familiar with python, matlab and julia than they are with lisps. Given the power and flexibility of common lisp, moonli is an attempt to provide a thin syntax layer over common lisp. It is thin in the sense it can be easily transpiled to common lisp. The semantics remain the same and clean as common lisp. (Common Lisp is also good for reasons beyond macros.)

Features

For common lispers

  • Case sensitive, but invert-case reader to maintain common lisp compatibility
  • Transpile to common lisp, so lispers need not "learn a new language"
  • Extensible using moonli:define-moonli-macro and moonli:define-short-moonli-macro. See ./src/macros/ directory for examples.
  • Inability to access internal symbols of another package through "A::B" syntax; this syntax rather translates to (the B A)

For programmers in general

  • Sane variable scoping rules as given by common lisp
  • Sane namespace scoping thanks to common lisp package system
  • Sane restarts and condition system thanks to common lisp
  • Optional typing, optional dynamic scoping
  • Availability of optimizing compilers such as SBCL
  • Sensitive to newlines and semicolons but not to spaces and tabs (indentation insensitive)
  • Returning multiple values without an intermediate data structure
  • Support for rapid prototyping through CLOS and image-based development

Here's a brief comparison of features across different languages.

    FEATURES                             MOONLI       COMMON LISP       JULIA       HASKELL         RUST         PYTHON       JAVASCRIPT         C  
  ----------------------------------- ------------ ----------------- ----------- ------------- -------------- ------------ ---------------- -----------
  Syntax                                    +              +              +            +            ---             +              -             -
  Interactivity (Rapid Prototyping)       High         Very High      Moderate        Low           None        Moderate       Moderate        None
  Typing (Strong/Weak)                   Strong         Strong         Strong       Strong         Strong        Strong          Weak          Weak
  Typing (Static/Dynamic)               Flexible       Flexible       Flexible      Static         Static       Dynamic        Dynamic        Dynamic
  Typing (Expressivity)                 Flexible       Flexible       Moderate     Very High     Very High        Low            Low            Low
  Compiler Speed                        Flexible       Flexible         Slow       Moderate         Slow        Moderate       Moderate      Moderate
  Runtime Speed                         Flexible       Flexible         Fast       Moderate         Fast          Slow         Moderate        Fast
  Runtime Error Recovery                Advanced       Advanced        Limited     Moderate         None        Moderate       Moderate        None
  Binary Size                           Flexible       Flexible         Large          ?           Small          None           None          Small
  User Extensibility                      High           High         Moderate        Low           Low           None           None          None
  Compiler built-in optimizations         Low             Low         Very High        ?         Very High        Low          Moderate      Very High
  Long Term Support                       Low          Very High      Moderate         ?          Moderate      Moderate         Low         Very High
  Ecosystem (without interop)            Small           Small        Moderate       Small        Moderate       Large          Large          Large
  Memory Management                       Heap           Heap         Reference      Heap       Compile Time   Reference          ?           Manual

Plan

  • Real numbers, strings, characters, lists, infix arithmetic operators, literal hash-tables, literal hash-sets
  • Typing using "expr::type" operator
  • Support for declare and declaim
  • Literal syntax for vectors, array access
  • BODMAS rule for parsing expressions
  • Binaries
  • VS Code integration
  • Emacs mode and integration with slime
  • Infix Logical operators
  • Add more forms: progn, mvb, dsb, let+, more…
  • Add more tests
  • Reverse transpile from common lisp

Syntax

As with lisp, everything is an expression.

Simple syntax table:

  Lisp                       Moonli
  -------------------------- -------------------------
  #\a                        'a'
  "hello world"              "hello world"
  2, 2.0, 2d-3, 2.0d-3       2, 2.0, 2d-3, 2.0d-3
  'quoted-symbol             $quoted-symbol
  package:exported-symbol    package:exported-symbol
  package::internal-symbol   <WONTDO>
  (the type expr)            expr :: type
  (list form-1 form-2)       (form-1, form-2)
  (fn arg1 arg2)             fn(arg1, arg2)
  #c(re, im)                 <TODO>

Global variables

defparameter *global* = 23

Local variables

let answer-to-everything = 42 :
  answer-to-everything
end

Symbols

Most valid symbols can be written in moonli. For example, above *global* and answer-to-everything are each single symbols. This is unlike mainstream languages where * - ? ! and several other characters are not allowed in symbols.

However, this means that symbols must be separated from each other by space. This is necessary to make a distinction between whether a character stands for an infix operation or is part of a symbol. a+b is a single symbol, but a + b is translated to the lisp expression (+ a b).

Function-like calls

identity("hello world")
function(identity)

Because lisp macros and functions follow similar syntax, moonli syntax for function calls can also be used for macro calls when the macro syntax is simple. (Indeed, this can be inconvenient; see [defining your own]{.spurious-link target=“defining your own”}.)

destructuring-bind(a(b),(1,2),+(1,2))

transpiles to

(destructuring-bind (a b) (list 1 2)
  (+ 1 2))

Functions

Like lisp, return is implicit.

defun fib(n):
  if n < 0:
     error("Don't know how to compute fib for n=~d < 0", n)
  elif n == 0 or n == 1:
     1
  else:
    fib(n - 1) + fib(n - 2)
  end
end

Dictionaries or Hash-tables

{
  :a : 2,
  "b": $cl:progn
}

transpiles to

(fill-hash-table (:a 2) ("b" 'progn))

which expands to

(let ((#:hash-table413 (make-hash-table :test #'equal :size 2)))
  (setf (gethash :a #:hash-table413) 2
        (gethash "b" #:hash-table413) 'progn)
  #:hash-table413)

Sets or Hash-sets

{:a, "b" , $cl:progn}

transpiles to

(fill-hash-set :a "b" 'progn)

which expands to

(let ((#:hash-set417 (make-hash-table :test #'equal :size 3)))
  (setf (gethash :a #:hash-set417) t
        (gethash "b" #:hash-set417) t
        (gethash 'progn #:hash-set417) t)
  #:hash-set417)

Infix operators

The following infix operators are recognized:

  • + - * / ^
  • or and not
  • < <= == != >= >

lm

lm (): nil

transpiles to

(lambda () nil)
lm (x): x

transpiles to

(lambda (x) x)
lm (x, y): x + y

transpiles to

(lambda (x y) (+ x y))

declaim

declaim inline(foo)

transpiles to

(declaim (inline foo))
declaim type(hash-table, *map*)

transpiles to

(declaim (type hash-table *map*))

declare

declare type(single-float, x, y)

transpiles to

(declare (type single-float x y))
declare type(single-float, x, y), optimize(debug(3))

transpiles to

(declare (type single-float x y)
         (optimize (debug 3)))

ifelse

ifelse a 5

transpiles to

(if a
    5
    nil)
ifelse a :hello :bye

transpiles to

(if a
    hello
    bye)

lambda

lambda (): nil end

transpiles to

(lambda () nil)
lambda (x):
  x
end

transpiles to

(lambda (x) x)
lambda (x, y):
  let sum = x + y:
    sum ^ 2
  end
end

transpiles to

(lambda (x y)
  (let ((sum (+ x y)))
    (expt sum 2)))

let-plus:let+

let-plus:let+ x = 42: x
end

transpiles to

(let+ ((x 42))
  x)
let-plus:let+ (a,b) = list(1,2):
  a + b
end

transpiles to

(let+ (((a b) (list 1 2)))
  (+ a b))
let-plus:let+ let-plus:&values(a,b) = list(1,2):
  a + b
end

transpiles to

(let+ (((&values a b) (list 1 2)))
  (+ a b))
let-plus:let+
  let-plus:&values(a,b) = list(1,2),
  (c,d,e) = list(1,2,3):
  {a,b,c,d,e}
end

transpiles to

(let+ (((&values a b) (list 1 2)) ((c d e) (list 1 2 3)))
  (fill-hash-set a b c d e))

loop

loop end loop

transpiles to

(loop)
loop :repeat n :do
  print("hello")
end

transpiles to

(loop repeat n
      do (print hello))
loop :for i :below n :do
  print(i + 1)
end

transpiles to

(loop for i below n
      do (print (+ i 1)))

defun

defun our-identity(x): x end

transpiles to

(defun our-identity (x) x)
defun add (&rest, args):
 args
end defun

transpiles to

(defun add (&rest args) args)
defun add(args):
  if null(args):
    0
  else:
    first(args) + add(rest(args))
  end if
end

transpiles to

(defun add (args) (cond ((null args) 0) (t (+ (first args) (add (rest args))))))
defun foo(&optional, a = 5): a end

transpiles to

(defun foo (&optional (a 5)) a)

if

if a: b end if

transpiles to

(cond (a b) (t))
if a:
  b; c
end

transpiles to

(cond (a b c) (t))
if a: b
else: c
end if

transpiles to

(cond (a b) (t c))
if a:
   b; d
else:
   c; e
end if

transpiles to

(cond (a b d) (t c e))
if a: b
elif c: d; e
else: f
end if

transpiles to

(cond (a b) (c d e) (t f))
(if a: b else: c; end)::boolean

transpiles to

(the boolean (cond (a b) (t c)))
if null(args): 0; else: 1 end

transpiles to

(cond ((null args) 0) (t 1))
if null(args):
    0
else:
    first(args)
end if

transpiles to

(cond ((null args) 0) (t (first args)))
if null(args):
  0
else:
  2 + 3
end if

transpiles to

(cond ((null args) 0) (t (+ 2 3)))
if null(args):
  0
else:
  first(args) + add(rest(args))
end if

transpiles to

(cond ((null args) 0) (t (+ (first args) (add (rest args)))))

let

let a = 2, b = 3:
   a + b
end

transpiles to

(let ((a 2) (b 3))
  (+ a b))
let a = 2, b = 3:
   a + b
end let

transpiles to

(let ((a 2) (b 3))
  (+ a b))

for:for

for:for (i,j) in ((1,2),(3,4)):
  print(i + j)
end

transpiles to

(for (((i j) in (list (list 1 2) (list 3 4))))
  (print (+ i j)))
for:for i in (1,2,3), j in (2,3,4):
  print(i + j)
end

transpiles to

(for ((i in (list 1 2 3)) (j in (list 2 3 4)))
  (print (+ i j)))

Last modified October 22, 2025: Minor fixes (b92ce90)