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-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)
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)))
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.