Introduction to Moonli for Common Lispers
Moonli is a syntax layer that transpiles to Common Lisp.
For example,
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
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.)
Contrasts with Common Lisp
- Case sensitive, but invert-case reader to maintain common lisp compatibility
- Extensible using
moonli:define-moonli-macroandmoonli: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)
Syntax
As with lisp, everything is an expression.
Moonli’s syntax can be understood in terms of a (i) Core Syntax, and (ii) Macros. The core syntax makes space for macros, and macros provide extensibility. A third part concerns the with-special macros.
Core Syntax
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>
Macros
One defines a Moonli Macro or a Moonli Short Macro that expands to a Common Lisp macro or special form. These can be defined by moonli:define-moonli-macro and moonli:define-moonli-short-macro respectively. The difference between a Moonli Macro and a Moonli Short Macro is that the former end with end and can stretch over multiple lines, while the latter are expected to either span a single line or have their components be separated by non-newline whitespaces. See src/macros for examples.
Several Moonli macros are predefined as part of Moonli system, and you can add more Moonli macros as part of your own library or application.
Example transpilations for these predefined Moonli macros are given below:
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)))
defclass
defclass point():
end
transpiles to
(defclass point nil nil)
defclass point():
options:
metaclass: standard-class;
end
end
transpiles to
(defclass point nil nil (:metaclass standard-class))
defclass point():
options:
metaclass: standard-class;
documentation: "A class for Points!";
end
end
transpiles to
(defclass point nil nil (:metaclass standard-class)
(:documentation "A class for Points!"))
defclass point():
slots:
end
end
transpiles to
(defclass point nil nil)
defclass point():
slots:
x;
y;
end
end
transpiles to
(defclass point nil ((x) (y)))
defclass point():
slots:
x:
initform: 2.0,
type: single-float,
accessor: point-x;
end
end
transpiles to
(defclass point nil ((x :initform 2.0 :type single-float :accessor point-x)))
defclass point():
slots:
x:
initform: 2.0,
type: single-float,
accessor: point-x;
y:
initform: 2.0,
type: single-float,
accessor: point-y;
end
end
transpiles to
(defclass point nil
((x :initform 2.0 :type single-float :accessor point-x)
(y :initform 2.0 :type single-float :accessor point-y)))
defclass point():
slots:
x:
initform: 2.0,
type: single-float,
accessor: point-x;
y:
initform: 2.0,
type: single-float,
accessor: point-y;
end
options:
metaclass: standard-class;
documentation: "Two dimensional points.";
end
end
transpiles to
(defclass point nil
((x :initform 2.0 :type single-float :accessor point-x)
(y :initform 2.0 :type single-float :accessor point-y))
(:metaclass standard-class)
(:documentation "Two dimensional points."))
defgeneric
defgeneric area(shape)
transpiles to
(defgeneric area
(shape))
defmethod
defmethod our-identity(x): x end
transpiles to
(defmethod our-identity (x) x)
defmethod :before our-identity(x):
format(t, "Returning identity~%")
end
transpiles to
(defmethod :before our-identity (x) (format t "Returning identity~%"))
defmethod :after our-identity(x):
format(t, "Returned identity~%")
end
transpiles to
(defmethod :after our-identity (x) (format t "Returned identity~%"))
defmethod add (x :: number, y :: number):
x + y
end
transpiles to
(defmethod add ((x number) (y number)) (+ x y))
defmethod add (x :: number, y :: number, &rest, others):
x + if null(others):
y
else:
apply(function(add), y, others)
end
end
transpiles to
(defmethod add ((x number) (y number) &rest others)
(+ x (cond ((null others) y) (t (apply #'add y others)))))
defmethod add (x :: number, y :: number, &rest, others):
x + (if null(others):
y
else:
apply(function(add), y, others)
end)
end
transpiles to
(defmethod add ((x number) (y number) &rest others)
(+ x (cond ((null others) y) (t (apply #'add y others)))))
defmethod add (x :: string, y):
uiop:strcat(x, y)
end
transpiles to
(defmethod add ((x string) y) (uiop/utility:strcat x y))
defpackage
defpackage foo
:use cl;
end
transpiles to
(defpackage foo
(:use cl))
defparameter
defparameter a = 5
transpiles to
(defparameter a 5)
defstruct
defstruct foo:
a;
b;
end
transpiles to
(defstruct foo a b)
defstruct foo:
(a = 4) :: number;
b;
end
transpiles to
(defstruct foo (a 4 :type number) b)
defstruct foo:
(a = 4), :read-only = t;
b;
end
transpiles to
(defstruct foo (a 4 :read-only t) b)
defstruct foo:
(a = 4), :read-only = t;
(b = 2.0) :: single-float, :read-only = t;
end
transpiles to
(defstruct foo (a 4 :read-only t) (b 2.0 :type single-float :read-only t))
defstruct foo:
a = 4;
b = 2.0;
end
transpiles to
(defstruct foo (a 4) (b 2.0))
deftype
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)
defvar
defvar a = 5
transpiles to
(defvar a 5)
for
for:for (i,j) in ((1,2),(3,4)):
print(i + j)
end
transpiles to
(for-minimal: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-minimal:for ((i in (list 1 2 3)) (j in (list 2 3 4)))
(print (+ i j)))
if
if a: b end if
transpiles to
(cond (a b) (t nil))
if a:
b; c
end
transpiles to
(cond (a b c) (t nil))
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)))))
ifelse
ifelse a 5
transpiles to
(if a
5
nil)
ifelse a :hello :bye
transpiles to
(if a
:hello
:bye)
in-package
labels
labels foo(x):
bar(x - 1)
end,
bar(x):
if (x < 0): nil else: foo(x - 1) end
end:
foo(42)
end
transpiles to
(labels ((foo (x)
(bar (- x 1)))
(bar (x)
(cond ((< x 0) nil) (t (foo (- x 1))))))
(foo 42))
labels foo(x):
if (x < 0): nil else: foo(x - 1) end
end:
foo(42)
end
transpiles to
(labels ((foo (x)
(cond ((< x 0) nil) (t (foo (- x 1))))))
(foo 42))
labels :
nil
end
transpiles to
(labels ()
nil)
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
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))
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))
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))
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)))
time
time length("hello world")
transpiles to
(time (length "hello world"))
with
Standard Common Lisp code uses a number of with- macros. These can all be generated using the with- special macro of Moonli. This is “special” because
Unlike standard macros which are identified by symbols, the
with-block is identified using the keyword “with”, regardless of the package under consideration.with pkg:symbolexpands intopkg:with-symbolwithout the interning ofpkg:symbol.
Example transpilations:
with open-file(f, "/tmp/a.txt"):
f
end
transpiles to
(with-open-file (f "/tmp/a.txt") f)
with-open-file(f, "/tmp/a.txt"):
f
end
transpiles to
(with-open-file (f "/tmp/a.txt") f)
with output-to-string(*standard-output*),
open-file(f, "/tmp/a.txt"):
write-line(read-line(f))
end
transpiles to
(with-output-to-string (*standard-output*)
(with-open-file (f "/tmp/a.txt")
(write-line (read-line f))))
with alexandria:gensyms(a,b,c):
list(a,b,c)
end
transpiles to
(alexandria:with-gensyms (a b c)
(list a b c))
with alexandria:gensyms(a,b,c),
open-file(f, "/tmp/a.txt", :direction, :output):
write(list(a,b,c), f)
end
transpiles to
(alexandria:with-gensyms (a b c)
(with-open-file (f "/tmp/a.txt" :direction :output)
(write (list a b c) f)))
defstruct pair:
x;
y;
end
with access:dot():
let pair = make-pair(:x, 2, :y, 3):
format(t, "~&x + y = ~a~%", pair.x + pair.y)
end
end
transpiles to
(defstruct pair x y)
(access:with-dot
(let ((pair (make-pair :x 2 :y 3)))
(format t "~&x + y = ~a~%" (+ pair.x pair.y))))
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.