diff --git a/README.md b/README.md index c931267..370ec87 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. @@ -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) diff --git a/src/LispSyntax.jl b/src/LispSyntax.jl index 4aa8a1b..780ab7f 100644 --- a/src/LispSyntax.jl +++ b/src/LispSyntax.jl @@ -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 @@ -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 @@ -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]) @@ -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 @@ -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)) diff --git a/test/runtests.jl b/test/runtests.jl index 3fc2972..284eecd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,8 @@ using LispSyntax using Test -# ---------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------- # Setup -# ---------------------------------------------------------------------------------------------------------------------- macro incr(x) esc(quote $x = $x + 1 @@ -11,262 +10,265 @@ macro incr(x) end) end -macro incr_global(x) - esc(quote - global $x - $x = $x + 1 - $x - end) -end +#------------------------------------------------------------------------------- +@testset "Reader" begin + @test LispSyntax.read("1.1f") == 1.1f0 + @test LispSyntax.read("1.2f") == 1.2f0 + @test LispSyntax.read("2f") == 2f0 + + @test LispSyntax.read("3.0d") == 3.0 + + @test LispSyntax.read("4") == 4 + + @test LispSyntax.read("\\u2312") == '\u2312' + + @test LispSyntax.read("\\040") == ' ' + + @test LispSyntax.read("\\c") == 'c' + + @test LispSyntax.read("\"test\"") == "test" + + @test LispSyntax.read("true") == true + @test LispSyntax.read("false") == false + + @test LispSyntax.read("test") == :test + + @test LispSyntax.read("()") == sx() + @test LispSyntax.read("(1.1f)") == sx(1.1f0) + @test LispSyntax.read("(1.1f 2.2f)") == sx(1.1f0, 2.2f0) + @test LispSyntax.read("(+ 1.1f 2)") == sx(:+, 1.1f0, 2) + @test LispSyntax.read("(this (+ 1.1f 2))") == sx(:this, sx(:+, 1.1f0, 2)) + @test LispSyntax.read("(this (+ 1.1f 2) )") == sx(:this, sx(:+, 1.1f0, 2)) + + @test LispSyntax.read("#{1 2 3 4}") == Set([1, 2, 3, 4]) + + @test LispSyntax.read("""#{ + 1 2 + 3 4 + }""") == Set([1, 2, 3, 4]) + + @test LispSyntax.read("{a 2 b 3}") == Dict(:a => 2, :b => 3) -# ---------------------------------------------------------------------------------------------------------------------- -# Reader -# ---------------------------------------------------------------------------------------------------------------------- -@test LispSyntax.read("1.1f") == 1.1f0 -@test LispSyntax.read("1.2f") == 1.2f0 -@test LispSyntax.read("2f") == 2f0 + @test LispSyntax.read("""{ + a 2 + b 3 + }""") == Dict(:a => 2, :b => 3) -@test LispSyntax.read("3.0d") == 3.0 + @test LispSyntax.read("[1 2 3 4]") == sx(1, 2, 3, 4) -@test LispSyntax.read("4") == 4 + @test LispSyntax.read("""[ + 1 2 + 3 4 + ]""") == sx(1, 2, 3, 4) -@test LispSyntax.read("\\u2312") == '\u2312' + @test LispSyntax.read("[]") == sx() + @test LispSyntax.read("[1]") == sx(1) -@test LispSyntax.read("\\040") == ' ' + @test LispSyntax.read("'test") == sx(:quote, :test) -@test LispSyntax.read("\\c") == 'c' + @test LispSyntax.read("`test") == sx(:quasi, :test) -@test LispSyntax.read("\"test\"") == "test" + @test LispSyntax.read("~test") == sx(:splice, :test) + @test LispSyntax.read("~@(1 2 3)") == sx(:splice_seq, sx(1, 2, 3)) -@test LispSyntax.read("true") == true -@test LispSyntax.read("false") == false + @test LispSyntax.read("`~test") == sx(:quasi, sx(:splice, :test)) -@test LispSyntax.read("test") == :test + @test desx(sx(:splice_seq, sx(1, 2, 3))) == Any[ :splice_seq, [1, 2, 3] ] + @test desx(sx(:splice_seq, sx(1, 2, sx(3)))) == Any[ :splice_seq, Any[ 1, 2, [3] ] ] -@test LispSyntax.read("()") == sx() -@test LispSyntax.read("(1.1f)") == sx(1.1f0) -@test LispSyntax.read("(1.1f 2.2f)") == sx(1.1f0, 2.2f0) -@test LispSyntax.read("(+ 1.1f 2)") == sx(:+, 1.1f0, 2) -@test LispSyntax.read("(this (+ 1.1f 2))") == sx(:this, sx(:+, 1.1f0, 2)) -@test LispSyntax.read("(this (+ 1.1f 2) )") == sx(:this, sx(:+, 1.1f0, 2)) - -@test LispSyntax.read("#{1 2 3 4}") == Set([1, 2, 3, 4]) - -@test LispSyntax.read("""#{ - 1 2 - 3 4 - }""") == Set([1, 2, 3, 4]) + @test LispSyntax.read("""(defn multiline + [x] + (+ x 1))""") == sx(:defn, :multiline, sx(:x), sx(:+, :x, 1)) -@test LispSyntax.read("{a 2 b 3}") == Dict(:a => 2, :b => 3) + @test LispSyntax.read(""" + (defn f1 [n] + (if (< n 2) + 1 + (+ (f1 (- n 1)) + (f1 (- n 2))))) + """) == sx(:defn, :f1, sx(:n), + sx(:if, sx(:<, :n, 2), + 1, + sx(:+, sx(:f1, sx(:-, :n, 1)), sx(:f1, sx(:-, :n, 2))))) -@test LispSyntax.read("""{ - a 2 - b 3 - }""") == Dict(:a => 2, :b => 3) + assign_reader_dispatch(:sx, x -> sx(x.vector...)) + assign_reader_dispatch(:hash, x -> Dict(x.vector[i] => x.vector[i+1] for i = 1:2:length(x.vector))) + @test LispSyntax.read("#sx[a b c]") == sx(:a, :b, :c) + @test LispSyntax.read("#sx [ 1 2 3 ]") == sx(1, 2, 3) +end -@test LispSyntax.read("[1 2 3 4]") == sx(1, 2, 3, 4) - -@test LispSyntax.read("""[ - 1 2 - 3 4 - ]""") == sx(1, 2, 3, 4) - -@test LispSyntax.read("[]") == sx() -@test LispSyntax.read("[1]") == sx(1) - -@test LispSyntax.read("'test") == sx(:quote, :test) - -@test LispSyntax.read("`test") == sx(:quasi, :test) - -@test LispSyntax.read("~test") == sx(:splice, :test) -@test LispSyntax.read("~@(1 2 3)") == sx(:splice_seq, sx(1, 2, 3)) - -@test LispSyntax.read("`~test") == sx(:quasi, sx(:splice, :test)) - -@test desx(sx(:splice_seq, sx(1, 2, 3))) == Any[ :splice_seq, [1, 2, 3] ] -@test desx(sx(:splice_seq, sx(1, 2, sx(3)))) == Any[ :splice_seq, Any[ 1, 2, [3] ] ] - -@test LispSyntax.read("""(defn multiline - [x] - (+ x 1))""") == sx(:defn, :multiline, sx(:x), sx(:+, :x, 1)) - -@test LispSyntax.read(""" -(defn f1 [n] - (if (< n 2) - 1 - (+ (f1 (- n 1)) - (f1 (- n 2))))) -""") == sx(:defn, :f1, sx(:n), sx(:if, sx(:<, :n, 2), 1, sx(:+, sx(:f1, sx(:-, :n, 1)), sx(:f1, sx(:-, :n, 2))))) - -assign_reader_dispatch(:sx, x -> sx(x.vector...)) -assign_reader_dispatch(:hash, x -> Dict(x.vector[i] => x.vector[i+1] for i = 1:2:length(x.vector))) -@test LispSyntax.read("#sx[a b c]") == sx(:a, :b, :c) -@test LispSyntax.read("#sx [ 1 2 3 ]") == sx(1, 2, 3) - -# ---------------------------------------------------------------------------------------------------------------------- -# Code generation -# ---------------------------------------------------------------------------------------------------------------------- -@test codegen(desx(LispSyntax.read("(if true a)"))) == :(true && a) -@test codegen(desx(LispSyntax.read("(if true a b)"))) == :(true ? a : b) - -@test codegen(desx(LispSyntax.read("(call)"))) == :(call()) -@test codegen(desx(LispSyntax.read("(call a)"))) == :(call(a)) -@test codegen(desx(LispSyntax.read("(call a b)"))) == :(call(a,b)) -@test codegen(desx(LispSyntax.read("(call a b c)"))) == :(call(a,b,c)) - -@test codegen(desx(LispSyntax.read("(lambda (x) (call x))"))) == Base.remove_linenums!(:(function (x) call(x) end)) -@test codegen(desx(LispSyntax.read("(def x 3)"))) == :(x = 3) -@test codegen(desx(LispSyntax.read("(def x (+ 3 1))"))) == :(x = 3 + 1) - -construct_sexpr = LispSyntax.construct_sexpr -@test codegen(desx(LispSyntax.read("test"))) == :test -@test codegen(desx(LispSyntax.read("'test"))) == QuoteNode(:test) -@test codegen(desx(LispSyntax.read("'(1 2)"))) == :($construct_sexpr(1, 2)) -@test codegen(desx(LispSyntax.read("'(1 x)"))) == :($construct_sexpr(1, :x)) -@test codegen(desx(LispSyntax.read("'(1 (1 2))"))) == :($construct_sexpr(1, $construct_sexpr(1, 2))) -@test codegen(desx(LispSyntax.read("'(1 (test x))"))) == :($construct_sexpr(1, $construct_sexpr(:test, :x))) -@test codegen(desx(LispSyntax.read("(call 1 '2)"))) == :(call(1,2)) - -# ---------------------------------------------------------------------------------------------------------------------- -# Scope and variables -# ---------------------------------------------------------------------------------------------------------------------- -global x = 10 -@test @lisp("x") == 10 -@test lisp"x" == 10 - -lisp"(def w (+ 3 1))" -@test w == 4 - -# ---------------------------------------------------------------------------------------------------------------------- -# Quoting and splicing -# ---------------------------------------------------------------------------------------------------------------------- -@test @lisp("`~x") == 10 -@test lisp"`~x" == 10 -@test lisp"'test" == :test -@test lisp"'(1 2)" == Any[1, 2] -@test lisp"'(1 x)" == Any[1, :x] -@test lisp"'(1 (1 2))" == Any[1, Any[1, 2]] -@test lisp"'(1 (test x))" == Any[1, Any[:test, :x]] -@test @lisp("`(test ~x)") == Any[ :test, 10 ] -@test lisp"`(test ~x)" == Any[ :test, 10 ] -@test @lisp("`(~x ~x)") == Any[ 10, 10 ] -global y = Any[ 1, 2 ] -@test @lisp("`(~x ~@y)") == Any[ 10, 1, 2 ] -@test @lisp("`(~x ~y)") == Any[ 10, Any[1, 2] ] - -@test @lisp("`(10 ~(+ 10 x))") == Any[10, 20] - -@test lisp"(quote (+ 1 2))" == Any[:+, 1, 2] - -# ---------------------------------------------------------------------------------------------------------------------- -# Functions -# ---------------------------------------------------------------------------------------------------------------------- -@lisp("(defn xxx [a b] (+ a b))") -@test @lisp("(xxx 1 2)") == 3 - -global z = 10 -@lisp("(defn yyy [a] (+ a z))") -@test @lisp("(yyy 1)") == 11 -@test @lisp("(yyy z)") == 20 - -# recursion -lisp"(defn fib [a] (if (< a 2) a (+ (fib (- a 1)) (fib (- a 2)))))" -@test lisp"(fib 2)" == 1 -@test lisp"(fib 4)" == 3 -@test lisp"(fib 30)" == 832040 -@test lisp"(fib 40)" == 102334155 - -# Note this version is very slow due to the anonymous function -lisp"(def fib2 (lambda [a] (if (< a 2) a (+ (fib2 (- a 1)) (fib2 (- a 2))))))" -@test lisp"(fib2 2)" == 1 -@test lisp"(fib2 4)" == 3 -@test lisp"(fib2 30)" == 832040 - -lisp"(defn dostuff [a] (@incr a) (@incr a) (@incr a))" -@test lisp"(dostuff 3)" == 6 -@test lisp"(dostuff 6)" == 9 - -lisp"(def dostuff2 (lambda [a] (@incr a) (@incr a) (@incr a)))" -@test lisp"(dostuff2 3)" == 6 -@test lisp"(dostuff2 6)" == 9 - -lisp"(def dostuff3 (fn [a] (@incr a) (@incr a) (@incr a)))" -@test lisp"(dostuff3 3)" == 6 -@test lisp"(dostuff3 6)" == 9 -@test lisp"((lambda [x] (+ x 1)) 5)" == 6 -@test lisp"#{1 2 z}" == Set([1, 2, 10]) -@test lisp"{1 2 2 z}" == Dict(1 => 2, 2 => 10) -@test lisp"#sx[+ 1 2]" == 3 -@test lisp"#hash['+ 1 '- z]" == Dict(:+ => 1, :- => 10) - -# ---------------------------------------------------------------------------------------------------------------------- -# Macros -# ---------------------------------------------------------------------------------------------------------------------- -lisp"(defn fact [a] (if (< a 1) 1 (* a (fact (- a 1)))))" -lisp"(defmacro fapply [f a] `(~f ~a))" -@test @fapply(fib2, 2) == 1 -@test @fapply(fact, 3 + 1) == 24 -@test lisp"(@fapply fib2 2)" == 1 -@test lisp"(@fapply fact (+ 3 1))" == 24 +#------------------------------------------------------------------------------- +@testset "Code generation" begin + @test codegen(desx(LispSyntax.read("(if true a)"))) == :(true && a) + @test codegen(desx(LispSyntax.read("(if true a b)"))) == :(true ? a : b) + + @test codegen(desx(LispSyntax.read("(call)"))) == :(call()) + @test codegen(desx(LispSyntax.read("(call a)"))) == :(call(a)) + @test codegen(desx(LispSyntax.read("(call a b)"))) == :(call(a,b)) + @test codegen(desx(LispSyntax.read("(call a b c)"))) == :(call(a,b,c)) + + @test codegen(desx(LispSyntax.read("(lambda (x) (call x))"))) == Base.remove_linenums!(:(function (x) call(x) end)) + @test codegen(desx(LispSyntax.read("(def x 3)"))) == :(global x = 3) + @test codegen(desx(LispSyntax.read("(def x (+ 3 1))"))) == :(global x = 3 + 1) + + construct_sexpr = LispSyntax.construct_sexpr + @test codegen(desx(LispSyntax.read("test"))) == :test + @test codegen(desx(LispSyntax.read("'test"))) == QuoteNode(:test) + @test codegen(desx(LispSyntax.read("'(1 2)"))) == :($construct_sexpr(1, 2)) + @test codegen(desx(LispSyntax.read("'(1 x)"))) == :($construct_sexpr(1, :x)) + @test codegen(desx(LispSyntax.read("'(1 (1 2))"))) == :($construct_sexpr(1, $construct_sexpr(1, 2))) + @test codegen(desx(LispSyntax.read("'(1 (test x))"))) == :($construct_sexpr(1, $construct_sexpr(:test, :x))) + @test codegen(desx(LispSyntax.read("(call 1 '2)"))) == :(call(1,2)) +end +#------------------------------------------------------------------------------- +@testset "Scope and variables" begin + x = 10 + @test lisp"x" == 10 + + let + # In clojure, def affects global bindings + lisp"(def w (+ 3 1))" + end + let + @test w == 4 + end +end + +#------------------------------------------------------------------------------- +@testset "Quoting and splicing" begin + x = 10 + @test lisp"`~x" == 10 + @test lisp"'test" == :test + @test lisp"'(1 2)" == Any[1, 2] + @test lisp"'(1 x)" == Any[1, :x] + @test lisp"'(1 (1 2))" == Any[1, Any[1, 2]] + @test lisp"'(1 (test x))" == Any[1, Any[:test, :x]] + @test lisp"`(test ~x)" == Any[ :test, 10 ] + @test lisp"`(~x ~x)" == Any[ 10, 10 ] + global y = Any[ 1, 2 ] + @test lisp"`(~x ~@y)" == Any[ 10, 1, 2 ] + @test lisp"`(~x ~y)" == Any[ 10, Any[1, 2] ] + + @test lisp"`(10 ~(+ 10 x))" == Any[10, 20] + + @test lisp"(quote (+ 1 2))" == Any[:+, 1, 2] +end + +#------------------------------------------------------------------------------- +@testset "Functions" begin + lisp"(defn xxx [a b] (+ a b))" + @test lisp"(xxx 1 2)" == 3 + + global z = 10 + lisp"(defn yyy [a] (+ a z))" + @test lisp"(yyy 1)" == 11 + @test lisp"(yyy z)" == 20 + + # recursion + lisp"(defn fib [a] (if (< a 2) a (+ (fib (- a 1)) (fib (- a 2)))))" + @test lisp"(fib 2)" == 1 + @test lisp"(fib 4)" == 3 + @test lisp"(fib 30)" == 832040 + @test lisp"(fib 40)" == 102334155 + + # Note this version is slow due to the non-const global binding fib2 + lisp"(def fib2 (lambda [a] (if (< a 2) a (+ (fib2 (- a 1)) (fib2 (- a 2))))))" + @test lisp"(fib2 2)" == 1 + @test lisp"(fib2 4)" == 3 + @test lisp"(fib2 30)" == 832040 + + lisp"(defn dostuff [a] (@incr a) (@incr a) (@incr a))" + @test lisp"(dostuff 3)" == 6 + @test lisp"(dostuff 6)" == 9 + + lisp"(def dostuff2 (lambda [a] (@incr a) (@incr a) (@incr a)))" + @test lisp"(dostuff2 3)" == 6 + @test lisp"(dostuff2 6)" == 9 + + lisp"(def dostuff3 (fn [a] (@incr a) (@incr a) (@incr a)))" + @test lisp"(dostuff3 3)" == 6 + @test lisp"(dostuff3 6)" == 9 + @test lisp"((lambda [x] (+ x 1)) 5)" == 6 + @test lisp"#{1 2 z}" == Set([1, 2, 10]) + @test lisp"{1 2 2 z}" == Dict(1 => 2, 2 => 10) + @test lisp"#sx[+ 1 2]" == 3 + @test lisp"#hash['+ 1 '- z]" == Dict(:+ => 1, :- => 10) +end + +#------------------------------------------------------------------------------- +# Macros. Note that LispSyntax currently uses the Julia macro system, which +# includes the hygenic-by-default hygiene rules. This is very different from +# Clojure style explicit hygiene. +lisp"(defmacro fapply [f a] (esc `(~f ~a)))" fcount = 0 -lisp"(defmacro fapply_trace [f a] (global fcount) (@incr fcount) `(~f ~a))" -@test @fapply_trace(fib2, 2) == 1 -@test fcount == 1 -@test @fapply_trace(fact, 3 + 1) == 24 -@test fcount == 2 - -# ---------------------------------------------------------------------------------------------------------------------- -# Loops -# ---------------------------------------------------------------------------------------------------------------------- -number = 0 -output = 0 - -lisp"(while (< number 2) (@incr_global number) (@incr_global output))" -@test number == 2 -@test output == 2 -r = output -lisp"(for [i (: 1 10)] (@incr_global r))" -@test r == 12 - -r = 0 -lisp"(for [i (: 1 10) j (: 1 10)] (@incr_global r))" -@test r == 100 - -# ---------------------------------------------------------------------------------------------------------------------- -# Let and do -# ---------------------------------------------------------------------------------------------------------------------- -number = 2 -r = 100 -@test lisp"(let [x 10] x)" == 10 -@test lisp"(let [x 10 y 20] (+ x y))" == 30 -@test lisp"(let [x 10 y 20 z 20] (+ x y z))" == 50 -@test lisp"(let [x 10 y 20 z 20] (+ x y z number))" == 52 -@test lisp"(let [x 10 y 20 z 20 number 10] (+ x y z number))" == 60 -@test lisp"(let [x 10 y 20 z 20] (- (+ x y z number) output))" == 50 - -lisp"(do (@incr_global r) (@incr_global number))" -@test number == 3 -@test r == 101 - -# ---------------------------------------------------------------------------------------------------------------------- -# Import -# ---------------------------------------------------------------------------------------------------------------------- +lisp"(defmacro fapply_trace [f a] (esc `(do (global fcount) (@incr fcount) (~f ~a))))" +@testset "Macros" begin + lisp"(defn fact [a] (if (< a 1) 1 (* a (fact (- a 1)))))" + @test @fapply(fib2, 2) == 1 + @test @fapply(fact, 3 + 1) == 24 + @test lisp"(@fapply fib2 2)" == 1 + @test lisp"(@fapply fact (+ 3 1))" == 24 + + @test @fapply_trace(fib2, 2) == 1 + @test fcount == 1 + @test @fapply_trace(fact, 3 + 1) == 24 + @test fcount == 2 +end + +#------------------------------------------------------------------------------- +@testset "Loops" begin + number = 0 + output = 0 + + lisp"(while (< number 2) (@incr number) (@incr output))" + @test number == 2 + @test output == 2 + r = output + lisp"(for [i (: 1 10)] (@incr r))" + @test r == 12 + + r = 0 + lisp"(for [i (: 1 10) j (: 1 10)] (@incr r))" + @test r == 100 +end + +#------------------------------------------------------------------------------- +@testset "Let and do" begin + number = 2 + r = 100 + output = 2 + @test lisp"(let [x 10] x)" == 10 + @test lisp"(let [x 10 y 20] (+ x y))" == 30 + @test lisp"(let [x 10 y 20 z 20] (+ x y z))" == 50 + @test lisp"(let [x 10 y 20 z 20] (+ x y z number))" == 52 + @test lisp"(let [x 10 y 20 z 20 number 10] (+ x y z number))" == 60 + @test lisp"(let [x 10 y 20 z 20] (- (+ x y z number) output))" == 50 + + lisp"(do (@incr r) (@incr number))" + @test number == 3 + @test r == 101 +end + +#------------------------------------------------------------------------------- lisp"(import ParserCombinator)" -@test lisp"(@E_str \"S\")" == E"S" - -# ---------------------------------------------------------------------------------------------------------------------- -# Bug reports -# ---------------------------------------------------------------------------------------------------------------------- -@test lisp"""(def game_map (Dict - (=> 'living_room - '((you are in the living room - of a wizards house - there is a wizard - snoring loudly on the couch -) - (west door garden) - (upstairs stairway attic)))))""" == Dict(:living_room => - Any[ Any[ :you, :are, :in, :the, :living, :room, :of, :a, :wizards, :house, :-, - :there, :is, :a, :wizard, :snoring, :loudly, :on, :the, :couch, :- ], - Any[ :west, :door, :garden ], - Any[ :upstairs, :stairway, :attic ] ]) +@testset "Import" begin + @test lisp"(@E_str \"S\")" == E"S" +end + +#------------------------------------------------------------------------------- +@testset "Bug reports" begin + @test lisp"""(def game_map (Dict + (=> 'living_room + '((you are in the living room + of a wizards house - there is a wizard + snoring loudly on the couch -) + (west door garden) + (upstairs stairway attic)))))""" == + Dict(:living_room => + Any[ Any[ :you, :are, :in, :the, :living, :room, :of, :a, :wizards, :house, :-, + :there, :is, :a, :wizard, :snoring, :loudly, :on, :the, :couch, :- ], + Any[ :west, :door, :garden ], + Any[ :upstairs, :stairway, :attic ] ]) +end