Skip to content

Instantly share code, notes, and snippets.

@toivoh
Created November 20, 2012 21:06
Show Gist options
  • Save toivoh/4121122 to your computer and use it in GitHub Desktop.
Save toivoh/4121122 to your computer and use it in GitHub Desktop.
lispy AST printer and reader
# ---- @sexpr: S-expression to AST conversion ----
is_expr(ex, head::Symbol) = (isa(ex, Expr) && (ex.head == head))
is_expr(ex, head::Symbol, n::Int) = is_expr(ex, head) && length(ex.args) == n
macro sexpr(ex)
esc(sexpr_to_expr(ex))
end
sexpr_to_expr(ex) = expr(:quote, ex)
sexpr_to_expr(ex::QuoteNode) = ex
function sexpr_to_expr(ex::Expr)
head, args = ex.head, ex.args
if head === :tuple
h = sexpr_to_head(args[1])
expr(:call, :expr, h, {sexpr_to_expr(arg) for arg in args[2:end]}...)
elseif head === :quote; ex
else expr(:quote, ex)
end
end
sexpr_to_head(ex::Symbol) = expr(:quote, ex)
sexpr_to_head(ex::QuoteNode) = ex
function sexpr_to_head(ex)
if is_expr(ex, :quote, 1); ex
else error("@sexpr: Cannot interpret $ex as a head")
end
end
# ---- show_sexpr: print an AST as an S-expression ----
show_sexpr(ex) = show_sexpr(OUTPUT_STREAM, ex)
show_sexpr(io::IO, ex) = show_sexpr(io, ex, 0)
show_sexpr(io::IO, ex, indent::Int) = show(io, ex)
const paren_quoted_syms = Set{Symbol}(:(:),:(::),:(:=),:(=),:(==),:(===),:(=>))
function show_sexpr(io::IO, sym::Symbol, indent::Int)
if has(paren_quoted_syms, sym); print(io, ":($sym)")
else print(io, ":$sym")
end
end
const sexpr_indent_width = 2
function show_sexpr(io::IO, ex::Expr, indent::Int)
inner = indent + sexpr_indent_width
if (ex.head === :block) inter, post = (",\n"*" "^inner, "\n"*" "^indent)
else inter, post = (", ", "")
end
print(io, '(')
show_sexpr(io, ex.head, inner)
for arg in ex.args
print(io, inter)
show_sexpr(io, arg, inner)
end
if length(ex.args) == 0; print(io, ",)")
else print(io, post, ')')
end
end
load("sexpr.jl")
ex = quote
function show_sexpr(io::IO, ex::Expr, indent::Int)
inner = indent + sexpr_indent_width
if (ex.head === :block) inter, post = (",\n"*" "^inner,"\n"*" "^indent)
else inter, post = (", ", "")
end
print(io, '(')
show_sexpr(io, ex.head, inner)
for arg in ex.args
print(io, inter)
show_sexpr(io, arg, inner)
end
if length(ex.args) == 0; print(io, ",)")
else print(io, post, ')')
end
end
end
show_sexpr(ex)
ex2 = @sexpr (:block,
(:line, 4),
(:function, (:call, :show_sexpr, (:(::), :io, :IO), (:(::), :ex, :Expr), (:(::), :indent, :Int)), (:block,
(:line, 5, $(symbol("test.jl"))),
(:(=), :inner, (:call, :+, :indent, :sexpr_indent_width)),
(:line, 6),
(:if, (:comparison, (:., :ex, (:quote, :head)), :(===), (:quote, :block)), (:block,
(:line, 6),
(:(=), (:tuple, :inter, :post), (:tuple, (:call, :*, ",\n", (:call, :^, " ", :inner)), (:call, :*, "\n", (:call, :^, " ", :indent))))
), (:block,
(:line, 7),
(:(=), (:tuple, :inter, :post), (:tuple, ", ", ""))
)),
(:line, 10),
(:call, :print, :io, '('),
(:line, 11),
(:call, :show_sexpr, :io, (:., :ex, (:quote, :head)), :inner),
(:line, 12),
(:for, (:(=), :arg, (:., :ex, (:quote, :args))), (:block,
(:line, 13),
(:call, :print, :io, :inter),
(:line, 14),
(:call, :show_sexpr, :io, :arg, :inner)
)),
(:line, 16),
(:if, (:comparison, (:call, :length, (:., :ex, (:quote, :args))), :(==), 0), (:block,
(:line, 16),
(:block,
(:call, :print, :io, ",)")
)
), (:block,
(:line, 17),
(:call, :print, :io, :post, ')')
))
))
)
# Works if you're lucky. Seems to work with include("test.jl"), but not with load("test.jl"):
# the latter puts a longer path in the filename of the (:line, 5) expr.
@assert ex == ex2
@toivoh
Copy link
Author

toivoh commented Nov 20, 2012

Simple lispy AST printer and reader.
Examples:

julia> show_sexpr(:(f(x,y::Int)))
(:call, :f, :x, (:(::), :y, :Int))
julia> @sexpr (:call, :f, :x, (:(::), :y, :Int))
:( f(x, y::Int) )

Thanks to the mysterious ways of AST interpolation, interpolation seems to work in the @sexpr macro too:

julia> fun = :foo
:foo

julia> @sexpr (:call, $fun, :x, (:(::), :y, :Int))
:( foo(x, y::Int) )

The macro is somewhat forgiving, adding quotations to unquoted elements.
So you can omit the quotes on symbols when it doesn't confuse the julia parser, e.g.

julia> @sexpr (call, foo, x, (:(::), y, Int))
:( foo(x, y::Int) )

works just as well. This also allows to use expressions instead of symbols as elements in the S-expressions:

julia> @sexpr (call, foo, x, y::Int)
:( foo(x, y::Int) )

This could be useful if there's a tricky bit that you want to do with S-expressions, but maybe not the whole AST.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment