Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ LispSyntax.jl: A clojure-like lisp syntax for julia
[![Join the chat at https://gitter.im/swadey/LispSyntax.jl](https://badges.gitter.im/swadey/LispSyntax.jl.svg)](https://gitter.im/swadey/LispSyntax.jl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Build Status](https://travis-ci.org/swadey/LispSyntax.jl.svg?branch=master)

This package provides a julia-to-lisp syntax translator with
convenience macros that let you do this:
This package provides a lisp-to-julia syntax translator with
convenience macros that let you do this:

```julia
lisp"(defn fib [a] (if (< a 2) a (+ (fib (- a 1)) (fib (- a 2)))))"
@test lisp"(fib 30)" == 832040
@test fib(30) == 832040
```julia
lisp"(defn fib [a] (if (< a 2) a (+ (fib (- a 1)) (fib (- a 2)))))"
@test lisp"(fib 30)" == 832040
@test fib(30) == 832040
```

LispSyntax.jl is implemented as an expression translator between
Expand Down Expand Up @@ -57,6 +57,9 @@ Notable Differences
this may be needed (but a macro-implementation of tail call rewriting may be
more appropriate for julia).
- *Optional typing* - Currently not implemented.
- *Named functions are julia methods* - For efficiency, functions defined with
`defn` are translated to normal julia `function` expressions. This means the
act as named lambdas in local scope.
- *Method definition* - Also not currently implemented. If
implemented it will probably not be a full implementation of
Clojure's sophisticated dispatch system.
Expand All @@ -65,28 +68,31 @@ Notable Differences
julia, S-expressions returned from macros require a special
translation step to generate julia expression trees. The result is
that `LispSyntax.jl` macros are directly translated into Julia macros and
must be called via special syntax (e.g. `(@macro expr)`).
must be called via special syntax (e.g. `(@macro expr)`). Macro hygiene
follows the Julia approach of hygenic-by-default with explicit escaping
using `esc`. This is the opposite of Clojure's macros which use explicit
hygiene with specially named variables.
- *Julia's string macro dispatch not supported (yet)* - for macros
like `@r_str` which in Julia can be called via `r""`, it is
currently necessary to call these via standard macro syntax:
`(@r_str "string")`

REPL Mode
---------
In order to avoid having to type out `lisp"( ... )"` for each top level expression,
one can use [ReplMaker.jl](https://github.com/MasonProtter/ReplMaker.jl) to make a
one can use [ReplMaker.jl](https://github.com/MasonProtter/ReplMaker.jl) to make a
REPL mode for LispSyntax.jl
```julia
julia> using LispSyntax, ReplMaker

julia> initrepl(LispSyntax.lisp_eval_helper,
prompt_text="λ> ",
prompt_color=:red,
start_key=")",
julia> initrepl(LispSyntax.lisp_eval_helper,
prompt_text="λ> ",
prompt_color=:red,
start_key=")",
mode_name="Lisp Mode")
REPL mode Lisp Mode initialized. Press ) to enter and backspace to exit.
```
As instructed, if we now press `)` at an empty `julia>` prompt, we enter `Lisp Mode`.
As instructed, if we now press `)` at an empty `julia>` prompt, we enter `Lisp Mode`.
```julia
λ> (defn fib [a] (if (< a 2) a (+ (fib (- a 1)) (fib (- a 2)))))
fib (generic function with 1 method)
Expand Down
30 changes: 17 additions & 13 deletions src/LispSyntax.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module LispSyntax

include("parser.jl")
export sx, desx, codegen, @lisp, @lisp_str, assign_reader_dispatch
export sx, desx, codegen, @lisp_str, assign_reader_dispatch

# Internal types
mutable struct s_expr
Expand Down Expand Up @@ -72,6 +72,9 @@ function codegen(s)
elseif isa(s, Set)
coded_s = [codegen(x) for x in s]
Expr(:call, Set, Expr(:vect, coded_s...))
elseif s isa Expr && s.head == :escape
# Special case to allow use of `esc` in lisp syntax macros
esc(codegen(s.args[1]))
elseif !isa(s, Array) # constant
s
elseif length(s) == 0 # empty array
Expand All @@ -86,7 +89,7 @@ function codegen(s)
end
elseif s[1] == :def
length(s) == 3 || error("Malformed def: Length of list must be == 3")
:($(s[2]) = $(codegen(s[3])))
:(global $(s[2]) = $(codegen(s[3])))
elseif s[1] == :let
bindings = [ :($(s[2][i]) = $(codegen(s[2][i+1]))) for i = 1:2:length(s[2]) ]
coded_s = map(codegen, s[3:end])
Expand Down Expand Up @@ -117,16 +120,21 @@ function codegen(s)
coded_s = map(codegen, s[3:end])
Expr(:function, Expr(:tuple, s[2]...), Expr(:block, coded_s...))
elseif s[1] == :defn
# Note: julia's lambdas are not optimized yet, so we don't define defn as a macro.
# this should be revisited later.
# NB: This lowering of `defn` makes a julia function which may be a closure
# if used in local scope. This is a semantic mismatch with clojure where
# `defn` binds a lambda to a mutable global name. We could do it the
# closure way, but it would generate much worse code.
coded_s = map(codegen, s[4:end])
Expr(:function, Expr(:call, s[2], s[3]...), Expr(:block, coded_s...))
elseif s[1] == :defmacro
Expr(:macro, Expr(:call, s[2], s[3]...),
begin
sexpr = Expr(:block, map(codegen, s[4:end])...)
Expr(:block, Expr(:call, codegen, sexpr))
end)
# NB: Clojure macros are unhygenic by default, the opposite of Julia.
# Choose Julia semantics and allow the use of `esc` intermixed with lists
# (see `Expr(:escape)` handling above).
Expr(:macro, Expr(:call, s[2], s[3]...),
begin
sexpr = Expr(:block, map(codegen, s[4:end])...)
Expr(:block, Expr(:call, codegen, sexpr))
end)
elseif s[1] == :defmethod
# TODO
else
Expand All @@ -145,10 +153,6 @@ function lisp_eval_helper(str :: AbstractString)
s = desx(LispSyntax.read(str))
return codegen(s)
end

macro lisp(str)
return esc(lisp_eval_helper(str))
end

macro lisp_str(str)
return esc(lisp_eval_helper(str))
Expand Down
Loading