ExLisp

A Lisp implementation running on Elixir / BEAM.

Installation

Install from Hex.pm

You can install it globally from Hex.pm.

mix escript.install hex exlisp

Note: After installation, add ~/.mix/escripts to your PATH to run the exlisp command directly.

exlisp

Build from Source

mix escript.build
./exlisp

REPL

When started, it enters the REPL interface and evaluates expressions as soon as top-level parentheses are closed (supports multi-line input).

exlisp(1)> (+ 1 2)
3
exlisp(2)> (+ (* 2 3)
(- 10 4))
12
exlisp(3)> (:math:sqrt 16)
4.0
exlisp(4)> (:math:pow 2 8)
256.0
exlisp(5)> (cons 1 [2 3])
[1, 2, 3]
exlisp(6)> (:lists:reverse [1 2 3 4])
[4, 3, 2, 1]
exlisp(7)> (:lists:sum [10 20 30])
60
exlisp(8)> (:math:sqrt (+ (:math:pow 3 2) (:math:pow 4 2)))
5.0
exlisp(9)> :io:read ">> "
>> 42.
{:ok, 42}
exlisp(10)> IO.puts "hello"
hello
:ok
exlisp(11)> (String.upcase "hello")
"HELLO"
exlisp(12)> (Enum.count [1 2 3 4 5])
5

Re-running Expressions and Referencing Results in REPL

You can specify the prompt number n to re-execute past expressions or reference evaluation results.

1. Re-executing Expressions

exlisp(1)> (+ 10 20)
30
exlisp(2)> !1
30
exlisp(3)> (r 1)
30

2. Referencing Evaluation Results and Input Expressions

exlisp(1)> (+ 1 2)
3
exlisp(2)> (+ * 10)
13
exlisp(3)> (v 1)
3
exlisp(4)> [*** ** *]
[3, 13, 3]

Examples

Basic Arithmetic and Built-in Functions

Built-in functions such as arithmetic operations and cons are available.

;; Arithmetic operations
(+ 1 2) ; => 3
(* 2 3 4) ; => 24
;; Constructing lists (cons)
(cons 1 [2 3]) ; => [1, 2, 3]
(cons 1 []) ; => [1]
(cons (+ 1 2) [(* 2 2)]) ; => [3, 4]

Calling Erlang Functions

You can call Erlang standard module functions in the format :module:function or module:function.

;; Math functions
(:math:sqrt 16) ; => 4.0
(:math:pow 2 8) ; => 256.0
;; List operations
(:lists:reverse [1 2 3 4]) ; => [4, 3, 2, 1]
(:lists:sum [10 20 30]) ; => 60
(:lists:nth 2 [10 20 30]) ; => 20
;; System and date information
(:erlang:date) ; => {2026, 9, 4}
(:os:type) ; => {:unix, :linux}

Nested Execution and Combining Return Values

Return values of functions can be directly combined into other calculations or function calls.

;; Passing function results to arithmetic operations
(+ 10 (:math:sqrt 25)) ; => 15.0
;; Combining multiple function results (Pythagorean theorem: 3^2 + 4^2 = 5^2)
(:math:sqrt (+ (:math:pow 3 2) (:math:pow 4 2))) ; => 5.0
;; Fetching elements from list and calculating
(* (:lists:nth 2 [10 20 30]) 2) ; => 40

Calling Elixir Functions

You can also call Elixir module functions using the ModuleName.function_name format.

(String.upcase "hello") ; => "HELLO"
(Enum.count [1 2 3 4 5]) ; => 5
(IO.puts "hello") ; => :ok

At the top level, you can also execute calls without outer parentheses:

String.upcase "hello" ; => "HELLO"
:math:sqrt 16 ; => 4.0

Elixir Interoperability (API)

You can evaluate Lisp expressions, get/set global variables, and invoke Lisp functions directly from Elixir code.

# Evaluate Lisp expressions
ExLisp.eval("(defparameter count 100)")
# => :count
# Get variable
ExLisp.get_var(:count)
# => 100
# Set variable
ExLisp.set_var(:count, 200)
ExLisp.eval("count")
# => 200
# Define Lisp function and call it from Elixir
ExLisp.eval("(defun multiply (a b) (* a b))")
ExLisp.call(:multiply, [6, 7])
# => 42
# Get as an Elixir anonymous function and execute
mult = ExLisp.get_fun(:multiply)
mult.(3, 5)
# => 15

Using Quicklisp Libraries (ql:quickload)

ExLisp supports Quicklisp and ASDF, allowing you to automatically download and load libraries written in Pure Common Lisp (without C extensions or FFI/CFFI).

;; Load and use Alexandria
exlisp(1)> (ql:quickload :alexandria)
(ALEXANDRIA)
exlisp(2)> (clamp 15 0 10)
10
exlisp(3)> (clamp -5 0 10)
0
;; Load and use split-sequence
exlisp(4)> (ql:quickload :split-sequence)
(SPLIT-SEQUENCE)
exlisp(5)> (split-sequence #\Space "hello world foo bar")
("hello" "world" "foo" "bar")

Using Hex.pm Libraries

Supports single packages (with or without version requirements) or lists of multiple packages.

;; Install and use Jason
exlisp(1)> (hex:install :jason "~> 1.4")
(Jason)
exlisp(2)> (Jason.encode! [1 2 3])
"[1,2,3]"
;; Install latest version (omitting version requirement)
exlisp(3)> (hex:install :floki)
(Floki)
exlisp(4)> (Floki.parse_document! "<h1>Hello</h1>")
({"h1", NIL, ("Hello")})
;; Batch install multiple packages
exlisp(5)> (hex:install '((:decimal "~> 2.0") :timex))
(Decimal Timex)
exlisp(6)> (Decimal.new "123.45")
#Decimal<123.45>

Running Lisp Files and BEAM Compilation

Supports script execution (passing a file argument or --script), interactive REPL startup with preloaded files (--load), and compiling to BEAM bytecode (-c / --compile).

1. Script Execution (File Argument or --script, -s)

Evaluates and executes the file sequentially as a script from top to bottom, then exits the process upon completion (no banner display, does not enter REPL).

# Pass file argument directly
exlisp script.lisp
# Or use --script option
exlisp --script script.lisp
exlisp -s script.lisp

2. Loading Files and Starting REPL (--load, -l)

Loads and runs a file, then starts the interactive REPL with the loaded environment preserved.

# --load option
exlisp --load file.lisp
exlisp -l file.lisp

3. Compiling to BEAM Bytecode and Persistence (-c, --compile)

By compiling Lisp source files (.lisp) to BEAM bytecode (.beam files), compiled variables and functions can be directly accessed from other processes such as iex -S mix even after the exlisp command exits.

Creating a Lisp File

;; sample.lisp
(defparameter base 100)
(defvar multiplier 2)
(defun add (a b)
(+ a b))
(defun calc (x)
(* (+ x base) multiplier))

Compiling to BEAM with exlisp -c

Specifying the -c (or --compile) option compiles the Lisp file into BEAM bytecode.

If -m or -o is not specified, the defaults are:

# When -m and -o are omitted (default)
exlisp -c sample.lisp
# => Compiled sample.lisp to _build/dev/lib/exlisp/ebin/Elixir.ExLisp.Global.beam (module: ExLisp.Global)

Options can be specified to customize output:

Calling from Elixir (When -m, -o are not specified)

If -m is omitted, it is defined as the ExLisp.Global module, callable via ExLisp.Global.<function_name> (function names are lowercased).

1. Calling Inside a Mix Project (iex -S mix)

Under a Mix environment, the compile path is automatically set, so you can call it directly:

$ iex -S mix
# Call compiled module functions directly
iex> ExLisp.Global.add(10, 20)
30
iex> ExLisp.Global.calc(5)
210
# Access via ExLisp API
iex> ExLisp.get_var(:base)
100
iex> ExLisp.call(:add, [1, 2])
3
# Use compiled functions/variables inside Lisp expressions
iex> ExLisp.eval("(calc 10)")
220
2. Loading beam files output to ebin/

Outside a Mix project where .beam files are generated in ebin/ of the current directory, add the load path before calling:

# Pass path with -pa when starting iex / elixir
$ iex -pa ebin
iex> ExLisp.Global.add(1, 2)
3

Alternatively, add the path from within Elixir code:

Code.append_path("ebin")
ExLisp.Global.add(1, 2)
# => 3