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
28 changes: 14 additions & 14 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
language: cpp
compiler:
- clang
# Documentation: http://docs.travis-ci.com/user/languages/julia/
language: julia
os:
- linux
- osx
julia:
- 1.0
- 1.1
- 1.2
- nightly
matrix:
allow_failures:
- julia: nightly
fast_finish: true
notifications:
email: false
env:
matrix:
- JULIAVERSION="juliareleases"
before_install:
- sudo add-apt-repository ppa:staticfloat/julia-deps -y
- sudo add-apt-repository ppa:staticfloat/${JULIAVERSION} -y
- sudo apt-get update -qq -y
- sudo apt-get install libpcre3-dev julia -y
- if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
script:
- julia --check-bounds=yes -e 'versioninfo(); Pkg.init(); Pkg.clone("https://github.com/saltpork/Stage.jl"); Pkg.clone(pwd()); Pkg.build("LispSyntax"); Pkg.test("LispSyntax")'
16 changes: 16 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name = "LispSyntax"
uuid = "51c06dcf-91d3-5c9e-a52e-02df4e7cbcf5"
version = "0.2.0"

[deps]
ParserCombinator = "fae87a5f-d1ad-5cf0-8f61-c941e1580b46"

[compat]
ParserCombinator = "≥ 2.0.0"
julia = "≥ 1.0.0"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
3 changes: 0 additions & 3 deletions REQUIRE

This file was deleted.

101 changes: 43 additions & 58 deletions src/LispSyntax.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
__precompile__()

module LispSyntax

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

# Internal types
type s_expr
mutable struct s_expr
vector
end

Expand All @@ -17,9 +16,9 @@ function desx(s)
if typeof(s) == s_expr
return map(desx, s.vector)
elseif isa(s, Dict)
return Dict(map(x -> desx(x[1]) => desx(x[2]), s)...)
return Dict(desx(x[1])=>desx(x[2]) for x in s)
elseif isa(s, Set)
return Set(map(desx, s))
return Set(desx(v) for v in s)
else
return s
end
Expand All @@ -33,25 +32,20 @@ function lispify(s)
end
end

function construct_sexpr(items...) # convert the input tuple to an array
ret = Array(Any, length(items))
for i = 1:length(items)
ret[i] = items[i]
end
ret
end
# convert the input tuple to an array
construct_sexpr(items...) = Any[items...]

function assign_reader_dispatch(sym, fn)
reader_table[sym] = fn
end

function quasiquote(s, escape_exceptions)
function quasiquote(s)
if isa(s, Array) && length(s) == 2 && s[1] == :splice
codegen(s[2], escape_exceptions = escape_exceptions)
codegen(s[2])
elseif isa(s, Array) && length(s) == 2 && s[1] == :splice_seq
Expr(:..., codegen(s[2], escape_exceptions = escape_exceptions))
Expr(:..., codegen(s[2]))
elseif isa(s, Array)
Expr(:call, :construct_sexpr, map(s -> quasiquote(s, escape_exceptions), s)...)
Expr(:call, construct_sexpr, map(quasiquote, s)...)
elseif isa(s, Symbol)
Expr(:quote, s)
else
Expand All @@ -61,94 +55,85 @@ end

function quote_it(s)
if isa(s, Array)
Expr(:call, :construct_sexpr, map(s -> quote_it(s), s)...)
Expr(:call, construct_sexpr, map(s -> quote_it(s), s)...)
elseif isa(s, Symbol)
QuoteNode(s)
else
s
end
end

function codegen(s; escape_exceptions = Set{Symbol}())
function codegen(s)
if isa(s, Symbol)
if s in escape_exceptions
s
else
esc(s)
end
elseif isa(s, Dict)
coded_s = map(x -> Expr(symbol("=>"),
codegen(x[1], escape_exceptions = escape_exceptions),
codegen(x[2], escape_exceptions = escape_exceptions)), s)
Expr(:call, :Dict, coded_s...)
coded_s = [:($(codegen(x[1])) => $(codegen(x[2]))) for x in s]
Expr(:call, Dict, coded_s...)
elseif isa(s, Set)
coded_s = map(x -> codegen(x, escape_exceptions = escape_exceptions), s)
Expr(:call, :Set, Expr(:vect, coded_s...))
coded_s = [codegen(x) for x in s]
Expr(:call, Set, Expr(:vect, coded_s...))
elseif !isa(s, Array) # constant
s
elseif length(s) == 0 # empty array
s
elseif s[1] == :if
if length(s) == 3
:($(codegen(s[2], escape_exceptions = escape_exceptions)) && $(codegen(s[3], escape_exceptions = escape_exceptions)))
:($(codegen(s[2])) && $(codegen(s[3])))
elseif length(s) == 4
:($(codegen(s[2], escape_exceptions = escape_exceptions)) ? $(codegen(s[3], escape_exceptions = escape_exceptions)) : $(codegen(s[4], escape_exceptions = escape_exceptions)))
:($(codegen(s[2])) ? $(codegen(s[3])) : $(codegen(s[4])))
else
throw("illegal if statement $s")
end
elseif s[1] == :def
assert(length(s) == 3)
:($(esc(s[2])) = $(codegen(s[3], escape_exceptions = escape_exceptions)))
length(s) == 3 || error("Malformed def: Length of list must be == 3")
:($(s[2]) = $(codegen(s[3])))
elseif s[1] == :let
syms = Set([ s[2][i] for i = 1:2:length(s[2]) ])
bindings = [ :($(s[2][i]) = $(codegen(s[2][i+1], escape_exceptions = escape_exceptions ∪ syms))) for i = 1:2:length(s[2]) ]
coded_s = map(x -> codegen(x, escape_exceptions = escape_exceptions ∪ syms), s[3:end])
Expr(:let, Expr(:block, coded_s...), bindings...)
bindings = [ :($(s[2][i]) = $(codegen(s[2][i+1]))) for i = 1:2:length(s[2]) ]
coded_s = map(codegen, s[3:end])
Expr(:let, Expr(:block, bindings...), Expr(:block, coded_s...))
elseif s[1] == :while
coded_s = map(x -> codegen(x, escape_exceptions = escape_exceptions), s[2:end])
coded_s = map(codegen, s[2:end])
Expr(:while, coded_s[1], Expr(:block, coded_s[2:end]...))
elseif s[1] == :for
syms = Set([ s[2][i] for i = 1:2:length(s[2]) ])
bindings = [ :($(s[2][i]) = $(codegen(s[2][i+1], escape_exceptions = escape_exceptions ∪ syms))) for i = 1:2:length(s[2]) ]
coded_s = map(x -> codegen(x, escape_exceptions = escape_exceptions ∪ syms), s[3:end])
bindings = [ :($(s[2][i]) = $(codegen(s[2][i+1]))) for i = 1:2:length(s[2]) ]
coded_s = map(codegen, s[3:end])
Expr(:for, Expr(:block, bindings...), Expr(:block, coded_s...))
elseif s[1] == :do
Expr(:block, map(x -> codegen(x, escape_exceptions = escape_exceptions), s[2:end])...)
Expr(:block, map(codegen, s[2:end])...)
elseif s[1] == :global
Expr(:global, map(x -> esc(x), s[2:end])...)
Expr(:global, s[2:end]...)
elseif s[1] == :quote
quote_it(s[2])
elseif s[1] == :import
Expr(:using, map(x -> esc(x), s[2:end])...)
Expr(:using, [Expr(:., x) for x in s[2:end]]...)
elseif s[1] == :splice
throw("missplaced ~ (splice)")
elseif s[1] == :splice_seq
throw("missplaced ~@ (splice_seq)")
elseif s[1] == :quasi
quasiquote(s[2], escape_exceptions)
quasiquote(s[2])
elseif s[1] == :lambda || s[1] == :fn
assert(length(s) >= 3)
coded_s = map(x -> codegen(x, escape_exceptions = escape_exceptions ∪ Set(s[2])), s[3:end])
length(s) >= 3 || error("Malformed lambda/fn: list length must be >= 3")
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.
coded_s = map(x -> codegen(x, escape_exceptions = escape_exceptions ∪ Set(s[3])), s[4:end])
Expr(:function, Expr(:call, esc(s[2]), s[3]...), Expr(:block, coded_s...))
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, esc(s[2]), s[3]...),
Expr(:macro, Expr(:call, s[2], s[3]...),
begin
coded_s = map(x -> codegen(x, escape_exceptions = escape_exceptions ∪ Set(s[3])), s[4:end])
sexpr = Expr(:block, coded_s...) #codegen(s[4], escape_exceptions = escape_exceptions ∪ Set(s[3]))
:(codegen($sexpr, escape_exceptions = $escape_exceptions ∪ Set($(s[3]))))
sexpr = Expr(:block, map(codegen, s[4:end])...)
Expr(:block, Expr(:call, codegen, sexpr))
end)
elseif s[1] == :defmethod
# TODO
else
coded_s = map(x -> codegen(x, escape_exceptions = escape_exceptions), s)
if (typeof(coded_s[1]) == Symbol && ismatch(r"^@.*$", string(coded_s[1]))) ||
(typeof(coded_s[1]) == Expr && ismatch(r"^@.*$", string(coded_s[1].args[1])))
Expr(:macrocall, coded_s[1], coded_s[2:end]...)
coded_s = map(codegen, s)
if (typeof(coded_s[1]) == Symbol && occursin(r"^@.*$", string(coded_s[1]))) ||
(typeof(coded_s[1]) == Expr && occursin(r"^@.*$", string(coded_s[1].args[1])))
Expr(:macrocall, coded_s[1], nothing, coded_s[2:end]...)
else
Expr(:call, coded_s[1], coded_s[2:end]...)
end
Expand All @@ -162,11 +147,11 @@ function lisp_eval_helper(str :: AbstractString)
end

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

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

end # module
15 changes: 8 additions & 7 deletions src/parser.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using ParserCombinator, Compat
using ParserCombinator
import Base.==

reader_table = Dict{Symbol, Function}()
Expand All @@ -14,27 +14,28 @@ doubley = p"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?[dD]" > (x -> parse(Float

inty = p"[-+]?\d+" > (x -> parse(Int, x))

uchary = p"\\(u[\da-fA-F]{4})" > (x -> begin y = unescape_string(x); y[chr2ind(y, 1)] end)
uchary = p"\\(u[\da-fA-F]{4})" > (x -> first(unescape_string(x)))
achary = p"\\[0-7]{3}" > (x -> unescape_string(x)[1])
chary = p"\\." > (x -> x[2])

stringy = p"(?<!\\)\".*?(?<!\\)\"" > (x -> x[2:end-1]) #_0[2:end-1] } #r"(?<!\\)\".*?(?<!\\)"
booly = p"(true|false)" > (x -> x == "true" ? true : false)
symboly = p"[^\d(){}#'`,@~;~\[\]^\s][^\s()#'`,@~;^{}~\[\]]*" > symbol
macrosymy = p"@[^\d(){}#'`,@~;~\[\]^\s][^\s()#'`,@~;^{}~\[\]]*" > symbol
symboly = p"[^\d(){}#'`,@~;~\[\]^\s][^\s()#'`,@~;^{}~\[\]]*" > Symbol
macrosymy = p"@[^\d(){}#'`,@~;~\[\]^\s][^\s()#'`,@~;^{}~\[\]]*" > Symbol

sexpr = E"(" + ~opt_ws + Repeat(expr + ~opt_ws) + E")" |> (x -> s_expr(x))
hashy = E"#{" + ~opt_ws + Repeat(expr + ~opt_ws) + E"}" |> (x -> Set(x))
curly = E"{" + ~opt_ws + Repeat(expr + ~opt_ws) + E"}" |> (x -> [ x[i] => x[i+1] for i = 1:2:length(x) ])
curly = E"{" + ~opt_ws + Repeat(expr + ~opt_ws) + E"}" |> (x -> Dict(x[i] => x[i+1] for i = 1:2:length(x)))
dispatchy = E"#" + symboly + ~opt_ws + expr |> (x -> reader_table[x[1]](x[2]))
bracket = E"[" + ~opt_ws + Repeat(expr + ~opt_ws) + E"]" |> (x -> s_expr(x)) # TODO: not quite right
quot = E"'" + expr > (x -> sx(:quote, x))
quasi = E"`" + expr > (x -> sx(:quasi, x))
tildeseq = E"~@" + expr > (x -> sx(:splice_seq, x))
tilde = E"~" + expr > (x -> sx(:splice, x))

expr.matcher = Nullable{ParserCombinator.Matcher}(doubley | floaty | inty | uchary | achary | chary | stringy | booly | symboly | macrosymy | dispatchy |
sexpr | hashy | curly | bracket | quot | quasi | tildeseq | tilde)
expr.matcher = doubley | floaty | inty | uchary | achary | chary | stringy | booly | symboly |
macrosymy | dispatchy | sexpr | hashy | curly | bracket |
quot | quasi | tildeseq | tilde

function read(str)
x = parse_one(str, expr)
Expand Down
2 changes: 2 additions & 0 deletions src/pegparser.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# PEGParser is a dead package. See parser.jl instead.

using PEGParser

@grammar lisp_grammar begin
Expand Down
Loading