Llixer - lispy syntax of the Elixir ast

Eeeeyup, it is yet another language, this time it is just a lispy syntax of the elixir ast, it was pretty quick to whip up, basically just another way of messing around with the Elixir AST, the quite tests for it:

  test "Parsing" do
    assert %{rest: "", result: {:integer, _, 1}} = parse_expression "1"
    assert %{rest: "", result: {:float, _, 6.28}} = parse_expression "6.28"
    assert %{rest: "", result: {:name, _, "test"}} = parse_expression "test"
    assert %{rest: "", result: {:name, _, "test"}} = parse_expression " test "
    assert %{rest: "fail", result: {:name, _, "test"}} = parse_expression "test fail"
    assert %{rest: "", result: {:name, _, "test pass"}} = parse_expression "test\\ pass"
    assert %{rest: "", result: {:cmd, _, [{:integer, _, 1}]}} = parse_expression "(1)"
    assert %{rest: "", result: {:cmd, _, [{:integer, _, 1}, {:integer, _, 2}]}} = parse_expression "(1 2)"
    assert %{rest: "", result: {:cmd, _, [{:name, _, "+"}, {:integer, _, 1}, {:integer, _, 2}]}} = parse_expression "(+ 1 2)"
    assert %{rest: "", result: {:name, _, ":add"}} = parse_expression ":add"
    assert %{rest: "", result: {:name, _, ":+"}} = parse_expression ":+"
    assert %{rest: "", result: {:cmd, _, [{:name, _, ":+"}, {:integer, _, 1}, {:integer, _, 2}]}} = parse_expression "(:+ 1 2)"
    assert %{rest: "", result: {:name, _, "add"}} = parse_expression "add"
    assert %{rest: "", result: {:name, _, "A string"}} = parse_expression "A\\ string"
    assert 2 = 1 + 1
  end


  def testcall(), do: 42
  def testcall(a), do: 42 + a
  test "Sigil" do
    # Direct values
    assert 1 = ~L{1}u
    assert 6.28 = ~L{6.28}u

    # Atoms
    assert :test         = ~L{atom test}
    assert :"split test" = ~L{atom split\ test}

    # Strings
    assert "test"       = ~L{string test}
    assert "split test" = ~L{string split\ test}

    # Lists
    assert []     = ~L{list}
    assert [1]    = ~L{list 1}
    assert [1, 2] = ~L{list 1 2}

    # Tuples
    assert {}     = ~L{tuple}
    assert {1}    = ~L{tuple 1}
    assert {1, 2} = ~L{tuple 1 2}

    # Maps
    assert %{}               = ~L{map}
    assert %{1 => 2}         = ~L{map (1 2)}
    assert %{1 => 2, 3 => 4} = ~L{map (1 2) (3 4)}

    # Mixed map
    assert %{{1, 2} => [3, 4]} = ~L{map ((tuple 1 2) (list 3 4))}

    # Local call
    assert 42 = ~L{testcall}
    assert 43 = ~L{testcall 1}
    assert 3 = ~L{+ 1 2}

    # Remote call
    assert "42" = ~L{Elixir.Kernel.inspect 42}

    # Anonymous function 0-arg
    assert 42 = ~L{fn (() 42)}.()
    assert 42 = ~L{fn (() () 42)}.()

    # Anonymous function 1-arg
    assert 42 = ~L{fn ((x) x)}.(42)
    assert 42 = ~L{fn ((x) (* x 2))}.(21)

    # Anonymous function 1-arg pattern matching
    assert 42 = ~L{fn ((0) 42) ((x) x)}.(0)

    # Anonymous function 1-arg guarded
    assert 42 = ~L{fn ((x) () x)}.(42)
    assert 42 = ~L{fn ((x) ((> x 0)) x)}.(42)
    assert 42 = ~L{fn ((x) ((> x 0)) x) ((x) ((< x 0)) (- x))}.(-42)

    # Quote
    assert {:name, _, "x"} = ~L{quote x}
    assert {:cmd, _, [{:name, _, "blah"}, {:integer, _, 1}]} = ~L{quote (blah 1)}
  end

  ~L{Elixir.Kernel.defmodule (atom TestGenModule0) (list (do))}
  ~L{Elixir.Kernel.defmodule (atom TestGenModule1) (list (do
    (def get (list (do 42)))
    (def (id x) (list (do x)))
    (def (idq _x) (list (do (quote _x))))
    ))}
  test "Generated module tests" do
    assert :TestGenModule0 = :TestGenModule0.module_info()[:module]
    assert :TestGenModule1 = :TestGenModule1.module_info()[:module]
    assert 42 = :TestGenModule1.get()
    assert 42 = :TestGenModule1.id(42)
    assert {:name, _, "_x"} = :TestGenModule1.idq(42)
  end

However, I can make macro’s inside the lispy thing that operate over the lispy stuff in a not quite lispy way (they are lisp, but they are tagged with some metadata like column and line numbers and such), so this is an easy way to make little embedded DSEL’s. ^.^

And not only macro’s, but read macro’s too! Only ones so far is `, which just parses the next expression and wraps it up in a (quote <expression>) and a ,, which will unquote within a quote (technically it just parses the next expression and wraps it in a (unquote <expression>) that can be detected by the main quote, not entirely done yet, but the read macro’s exist). And have some starter ones that grab out { and [ (the implementations are not done yet) that parse lists of expressions until the termination } and ] (recursive is fine as it just re-enters the parser as normal) and puts it in a (tuple <stuff>) or (list <stuff>) as appropriate (those of which are ‘special forms’, but even special forms are user-definable and overridable if you are wanting to do some crazy stuff that even macros and read-macros cannot do).

Barely started, I had some time today but it was only about an hour so this is about all I got done so far, but it was easy to do. :slight_smile:

10 Likes

I really like this lisp with pattern matching. But I can’t help but think that Elixir is already a pretty good lisp, as long as you write it in pure AST (which is verbose but fun!).

Someone should really write and (self-?) publish a an elixir book written only using the AST. It would be quite interesting… And people might even buy it!

For example, defining your first module:smile::

{:defmodule, [context: Elixir, import: Kernel],
 [{:__aliases__, [alias: false], [:MyModule]},
  [do:
    {:def, [context: Elixir, import: Kernel],
      [{:f, [context: Elixir],
        [{:x, [], Elixir}]},
      [do:
        {:+, [context: Elixir, import: Kernel],
           [{:x, [], Elixir},
            {:x, [], Elixir}]}]]}]]}
3 Likes

I rather like using the Elixir AST, but managing the metadata and so forth is a bit painful, plus lack of read-macro’s really sucks, so that is why I made this. ^.^

~L{Elixir.Kernel.defmodule (atom Elixir.TestGenModule1) (list (do
  (def (f x) (list (do (+ x x)))
))}

/me coughs ^.^

Oh, I’m sure it’s much better than raw Elixir AST.

Can’t you write a compiler that can be invoked from mix instead of a sigil? This way we could mix Lixir and Elixir files with proper syntax highlighting. Although I still think your ML project might be cooler. Or maybe a lisp with HM type inference… I suppose it would be possible.

Yes, and in fact this can compile files as it is, the entrance function could handle it no problem, just feed the output to the elixir compiler then, so yes, a compile could be made very easily.

And yes, typed HM I think is significantly better, but this was a really quick whip-up. ^.^;

Speaking of, had a few minutes after work, added unquote'ing and unquote_splice'ing (along with read-macro’s for them of , and ,@ respectively), see the added parts of the tests (near the bottom is where they are actually used in a more complex quoting/unquoting/unquote_splicing):

  test "Parsing" do
    assert %{rest: "", result: {:integer, _, 1}} = parse_expression "1"
    assert %{rest: "", result: {:float, _, 6.28}} = parse_expression "6.28"
    assert %{rest: "", result: {:name, _, "test"}} = parse_expression "test"
    assert %{rest: "", result: {:name, _, "test"}} = parse_expression " test "
    assert %{rest: "fail", result: {:name, _, "test"}} = parse_expression "test fail"
    assert %{rest: "", result: {:name, _, "test pass"}} = parse_expression "test\\ pass"
    assert %{rest: "", result: {:cmd, _, [{:integer, _, 1}]}} = parse_expression "(1)"
    assert %{rest: "", result: {:cmd, _, [{:integer, _, 1}, {:integer, _, 2}]}} = parse_expression "(1 2)"
    assert %{rest: "", result: {:cmd, _, [{:name, _, "+"}, {:integer, _, 1}, {:integer, _, 2}]}} = parse_expression "(+ 1 2)"
    assert %{rest: "", result: {:name, _, ":add"}} = parse_expression ":add"
    assert %{rest: "", result: {:name, _, ":+"}} = parse_expression ":+"
    assert %{rest: "", result: {:cmd, _, [{:name, _, ":+"}, {:integer, _, 1}, {:integer, _, 2}]}} = parse_expression "(:+ 1 2)"
    assert %{rest: "", result: {:name, _, "add"}} = parse_expression "add"
    assert %{rest: "", result: {:name, _, "A string"}} = parse_expression "A\\ string"
    assert %{rest: "", result: {:cmd, _, [{:name, _, "fn"}, {:cmd, _, [{:cmd, _, [{:name, _, "id"}]}, {:name, _, "id"}]}]}} = parse_expression "(fn ((id) id))"
  end

  test "Parsing - Read Macro's" do
    assert %{rest: "", result: {:cmd, _, [{:name, _, "quote"}, {:name, _, "x"}]}} = parse_expression "`x"
    assert %{rest: "", result: {:cmd, _, [{:name, _, "unquote"}, {:name, _, "x"}]}} = parse_expression ",x"
    assert %{rest: "", result: {:cmd, _, [{:name, _, "unquote-splicing"}, {:name, _, "x"}]}} = parse_expression ",@x"
  end


  def testcall(), do: 42
  def testcall(a), do: 42 + a
  test "Sigil" do
    test = 42

    # Direct values
    assert 1 = ~L{1}u
    assert 6.28 = ~L{6.28}u

    # Atoms
    assert :test         = ~L{atom test}
    assert :"split test" = ~L{atom split\ test}

    # Strings
    assert "test"       = ~L{string test}
    assert "split test" = ~L{string split\ test}

    # Lists
    assert []     = ~L{list}
    assert [1]    = ~L{list 1}
    assert [1, 2] = ~L{list 1 2}

    # Tuples
    assert {}     = ~L{tuple}
    assert {1}    = ~L{tuple 1}
    assert {1, 2} = ~L{tuple 1 2}

    # Maps
    assert %{}               = ~L{map}
    assert %{1 => 2}         = ~L{map (1 2)}
    assert %{1 => 2, 3 => 4} = ~L{map (1 2) (3 4)}

    # Mixed map
    assert %{{1, 2} => [3, 4]} = ~L{map ((tuple 1 2) (list 3 4))}

    # Local call
    assert 42 = ~L{testcall}
    assert 43 = ~L{testcall 1}
    assert 3 = ~L{+ 1 2}

    # Remote call
    assert "42" = ~L{Elixir.Kernel.inspect 42}

    # Anonymous function 0-arg
    assert 42 = ~L{fn (() 42)}.()
    assert 42 = ~L{fn (() () 42)}.()

    # Anonymous function 1-arg
    assert 42 = ~L{fn ((x) x)}.(42)
    assert 42 = ~L{fn ((x) (* x 2))}.(21)

    # Anonymous function 1-arg pattern matching
    assert 42 = ~L{fn ((0) 42) ((x) x)}.(0)

    # Anonymous function 1-arg guarded
    assert 42 = ~L{fn ((x) () x)}.(42)
    assert 42 = ~L{fn ((x) ((> x 0)) x)}.(42)
    assert 42 = ~L{fn ((x) ((> x 0)) x) ((x) ((< x 0)) (- x))}.(-42)

    # Quote
    assert {:name, _, "x"} = ~L{quote x}
    assert {:cmd, _, [{:name, _, "blah"}, {:integer, _, 1}]} = ~L{quote (blah 1)}
    assert 42 = ~L{quote (unquote test)}
    assert {:cmd, _, [{:name, _,"list"}, {:integer, _, 1}, {:name, _, "test"}, 42]} = ~L{quote (list 1 (unquote-splicing (list (quote test) test)))}

    # Read macro's
    assert {:name, _, "x"} = ~L{`x}u
    assert {:cmd, _, [{:name, _, "blah"}, {:integer, _, 1}]} = ~L{`(blah 1)}u
    assert 42 = ~L{`,test}u
    assert {:cmd, _, [{:name, _,"list"}, {:integer, _, 1}, {:name, _, "test"}, 42]} = ~L{`(list 1 ,@(list `test test))}u
  end

  ~L{Elixir.Kernel.defmodule (atom TestGenModule0) (list (do))}
  ~L{Elixir.Kernel.defmodule (atom TestGenModule1) (list (do
    (def get (list (do 42)))
    (def (id x) (list (do x)))
    (def (idq _x) (list (do (quote _x))))
    ))}
  test "Generated module tests" do
    assert :TestGenModule0 = :TestGenModule0.module_info()[:module]
    assert :TestGenModule1 = :TestGenModule1.module_info()[:module]
    assert 42 = :TestGenModule1.get()
    assert 42 = :TestGenModule1.id(42)
    assert {:name, _, "_x"} = :TestGenModule1.idq(42)
  end

Have you seen this: https://github.com/tpoulsen/terp?

It seems to be a lisp interpreter written in Elixir with dead-simple Elixir interop that typechecks the code using HM (and supports type signatures in Elixir ffi calls)

2 Likes

That project made me think about a lispy Elixir again. The main difficulty regarding “syntax” is to add type annotations in a sensible way. The following seems simple to what the project above does:

(defmodule Fibonacci
  (@moduledoc "Docs for the 'Test' module")
  (alias Deeply.Nested.Geometry.Module.Rect)

  (@doc "Extract first element of the tuple")
  (type area (-> {'a, 'b} 'a))
  (def (fst {x, _}) x)

  (@doc "Extract first element of the tuple")
  (type area (-> {'a, 'b} 'b))
  (def (snd {_, y}) y)

  (@doc "Area of a rectangle")
  (type area (-> Rect integer))
  (def (area (= (%Rect{} :width w :height h) rect))
    (type w integer)
    (type y integer)
    (* w y))

  (@doc "Docs for the 'fib' function")
  (type fib (-> integer integer))
  (def (when (fib n) (< n 1) 0))
  (def (fib 1) 1)
  (def (fib 2) 2)
  (def (fib n)
    (+ (fib (- n 1)) (fib (- n 2)))))

Except for the type declarations, this maps almost node-for node into the Elixir AST. Actually, I’ve cheated a little, because if we’re going to be faithful to the Elixir AST we should write:

(@ doc "Docs for the function")

instead of

(@doc "Docs for the function")

But I prefer the latter for completely arbitrary aesthetic reasons.

Synctatically, one big complication (if one wants a very literal mapping) is the when operator. The lispy version of this operator is weird:

(def (when (fib n) (< n 1) 0))

Maybe the lispy version should switch the arguments, so that it reads more naturally:

(def (when (< n 1) (fib n) 0))

This reads a little better, but it’s still a little weird, and the condition refers to a variable that hasn’t been bound in the function head.

Regarding the type annotations, there are many possibilities.

The more verbose adopted by the project above:

(type fib (-> integer integer))
(def fib ...)

Or more compact ones, like the one used by Racket (inspired by ML languages):

(: fib (-> integer integer))
2 Likes

I was just going to expose the base elixir calls to compile and such then just build macro’s to make building modules and functions and such easy, thus the syntax could be whatever. ^.^;

Hah, I had not, cool looking. ^.^

1 Like

Could you elaborate on this? Which base calls would o like to expose? the main difference between Elixir and lisp is that Lisp doesnt need do blocks. If you want maximum AST compatibility you could have (def (f x) (do x)) instead of (def (f x) x) where (do arg1 arg2 arg3) is a macro that expands into {:do, {:__block__, [], [arg1, arg2, arg3]}} (just like the clojure do macro or the Common Lisp progn special operator.

But this gets us into the question on how to support keyword lists of arguments. Should we have special syntax (i.e. read macros, there is no such thing as “special syntax” in the Holy Land of Lisp) to support a: a, :b b shenanigans? Or should we force the user to write the list explicitly?

Let’s look at the example of ExActor, a library that uses macros pretty heavily to simplify the definition of GenServers:

defmodule Calculator do
  use ExActor.GenServer

  defstart start_link, do: initial_state(0)

  defcast inc(x), state: state, do: new_state(state + x)
  defcast dec(x), state: state, do: new_state(state - x)

  defcall get, state: state, do: reply(state)

  defcast stop, do: stop_server(:normal)
end

Note that we need to use both the :state and :do keywords. Thus, if do is a macro that expands into the pair above, we’d have to write:

(defstart (inc x) [
  (:state . state)
  (do
    (new_state (+ state x)))
])

One way is to emulate Clojure.
In clojure, you can have keyword arguments with the following syntax:

(f req1 req2 :key1 val1 :key2 val2) ; step #1

This could expand to:

(f req1 req2 (list (cons 'key1 val1) (cons 'key2 val2))) ; step #2

Which could expand to:

(f req1 req2 (list (cons (atom key1) val1) (cons (atom key2) val2))) ; step #3

Which compiles to: f req1, req2, [key1: val1, key2: val2]

The first expansion (step #1) is the most problematic one.
It can be done by having : as a read macro,
but it would have to be a pretty complex one.

But this probably doesn’t play well with do blocks:

We want this to work:

(def (f x)
  (do
    expr1
    expr2
    expr3))

Possibly with this:

(def (f x) expr1)

Expanding into this:

(def (f x) (do expr1))

Which expands into this:

(def (f x)
  (list
    (cons 'do
          (__block__ expr1))))

This is not a problem because it can probably be tackled in the definition of def.

But then, let’s say we have the defstart macro from ExActor.

(defstart (inc x) :state state
  (do
    (new_state (+ state x))))

How can we expand this in a way that makes sense?

We need to find a way for the fragment

:state state (do ...)

To expand into:

(list (cons 'state state) (cons 'do (block ...)))

We could force users to write:

(defstart (inc x) :state state :do
  (...))

but then we would have to have a way for

:do (...)

to expand into

(list
  (cons 'do
        (__block__ ...)))

which would be easy if we lifted __block__ into an actual concept, instead of hiding it like Elixir does.
Maybe the problem here is that we’re treating do as special.
The really special thing about elixir is not do, it’s __block__
(this should be publicized more somehow).

So, if we allow block to be a thing, we have:

(defstart (inc x) :state state :do
  (block
    expr1
    expr2
    expr3))

Which is pretty much idiomatic lisp and quite faithful to Elixir.
But it’s a bit ugly.

A way to make (do x1 x2 x3) work in an idiomatic way is to do this crazy thing:

(do x1 x2 x3)

would expand into something like:

(unquote-splicing (:do (block x1 x2 x3)))

and have the : read macro handle this somehow.
It’s very ugly, though, and I don’t think it’s even possible to do so while still calling it a lisp.

I don’t know how common macros like this are in the wild.
Taking both options and a do block (like defstart) isn’t the most common thing ever…

Other kinds of blocks can give us problems:

try do
  Enum.each -50..50, fn(x) ->
    if rem(x, 13) == 0, do: throw(x)
  end
  "Got nothing"
catch
  x -> "Got #{x}"
end

Would have to be written as:

(try
  :do (block
        (Enum.each (.. -50 50)
          (lambda (x)
            (if (== (rem x 13) 0)
              (throw x)))))
  :catch (block
            (-> x (<> "Got " (inspect x)))))

With suitable macros it would be cleaner, but we’d have to make sure they’d expand into the right thing:

(try
  (do
    (Enum.each (.. -50 50)
      (lambda x (
        (if (== (rem x 13) 0)
          (throw x))))))
  (catch
    (-> x (<> "Got " (inspect x)))))

As I said above, another possibility is to use another read macro.
Let’s repurpose ., for example.

What if we could write:

(f req1 req2 . :key1 arg1 :key2 arg2)

which would expand to

(f req1 req2
  (list (cons :key1 arg1)
        (cons :key2 arg2)))

This is lighter than something like:

(f req1 req2 [key1: arg1 key2: arg2])

which is also possible…

That way, we could have a (do ...) macro that expands into a sliced list fo two elements,
which would be parsed by the “keyword list” read macro.

Let’s look at the examples from the previous post:

(defstart (inc x) . :state state
  (do
    (new_state (+ x 1))))

(try .
  (do
    (Enum.each (.. -50 50)
      (lambda x (
        (if (== (rem x 13) 0)
          (throw x))))))
  (catch
    (-> x (<> "Got " (inspect x)))))

(def (f x) . (do x))

The last example is stupid, because for something as important as def
we can define our own Lisp macro that allows us to write this:

(def (f x) x)

Which generalizes naturally into something like:

(def (f x)
  (block
    expr1
    expr2
    expr3))

Or even, if you really want this to look like Elixir:

(def (f x)
  (do
    expr1
    expr2
    expr2))

In any case, I think I like lifting block into a first class thing.
It maps naturally into Elixir’s semantics, although unfortunately not into its syntax.

For completeness, the above examples using [] as a read macro:

(defstart (inc x) [:state state
  (do
    (new_state (+ x 1)))])

(try [
  (do
    (Enum.each (.. -50 50)
      (lambda x (
        (if (== (rem x 13) 0)
          (throw x))))))
  (catch
    (-> x (<> "Got " (inspect x))))])

(def (f x) [(do x)])

Again, sorry for highkaking your thread, but I’m having fun with this :slight_smile:

How should we represent Elixir datatypes?

Tuples

Being faithful to the elixir AST

({} elem1)
({} elem1 elem2)
({} elem1 elem3 elem3)

Or with more sugar:

{elem1 elem2 elem3}

I don’t thing the syntax sugar brings much here…

Maps

Being faithful to the elixir AST

(%{} ({} key1 val1)
     ({} key2 val2)
     ({} key3 val3))

Ok, this is getting a bit out of hand.
Maybe it should be (no need for =>)

%{key1 val1 key2 val2 key3 val3}

Or like this:

(%{} key1 val1 key2 val2 key3 val3)

Or with the read macro:

(%{} key1 val1 key2 val2 key3 val3)

Structs

I don’t think structs should have a read macro.
It works pretty well like this:

(% Struct :feild1 field1 :field2 field2)

List

Lists could be just:

(list a b c d)

And could supporting pattern matching with the | operator:

(| x xs)
(| x1 x2 xs)

etc.

This is a very interesting thought experiment xD

And basing the whole thing on ExSpirit means read macro support must be awesome.

Literally was just going to make a macro for a module that parsed the format inside it entirely into something useful to pass straight to :elixir_module.compile that the defmodule itself uses now (or one of the higher level calls). Can make a much better Lisp’y syntax if parsing everything straight than trying to wrap things. ^.^

But yes, inside that macro there would be lots of constructs, like the do you propose among many others, or could feed it Elixir AST straight if you want (even calling normal Elixir macro’s too, which I’ve already tested with and it works fine). ^.^

I would make it normal Lisp’y, so yes KWLists would be a normal list (and since there is already a [ read-macro that is pretty simple then).

Yeah there are a lot of options to go there and we could easily make a customized scoped read-macro inside a defmodule implementation for customized syntax as well (could even default to the normal elixir one if we wanted, but that is kind of point defeating ^.^). But could use any syntax you could think of there in any case, from the Elixir AST itself to anything that compiles down to it.

Nah that is still entirely a LISP. LISP is a language to make domain specific embedded languages, so it all makes sense as long as it all ‘does’ make sense for its context. ^.^

Also you keep assuming that the do has to be represented in the user code, it doesn’t need to be at all, wrapped can be made for all sorts of things, that is what you really should do in LISP, make things readable. :slight_smile:

Your example is assuming basically writing the Elixir AST in Lisp, which though doable I would consider it only to be a last resort. Higher level should be used.

Not really much lighter actually, they are the same length and the dot seems odd to read to me in that context? ^.^;

Lol, likewise! Keep it going. ^.^

Currently I have most BEAM datatypes have their own constructors, from lists to maps to etc…:

(list blah bloop bleep)
(tuple vwoop vweep vwaap)
(map (tuple key0 value0) (tuple key1 value1) (tuple key2 value2))

However I do already have read-macros built for the common types too, things like [blah bloop] expands to (list blah bloop) and {vwoop vweep} expands to (tuple vwoop vweep) and %{{key0 value0} {key1 value1}} expands to (map (tuple key0 value0) (tuple key1 value1)) among others like strings "blah bloop" expands to (string blah\nbloop) and charlists and floats and integers and so forth. The read macro’s could of course be disabled for a scope or changed for a scope or so but it is all common stuff, I built it to work on the BEAM after all. ^.^

Support for read-macro’s was practically trivial, I just pass the context to the read macro like any other parser, you can take a look at that code in the Llixer.Simple namespace somewhere (Parser I think). ^.^

I was aiming at the possibility of code reuse… Say you want to write a phoenix application in Llixer.

The router by default does things like these:

defmodule HelloWeb.Router do
  use HelloWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", HelloWeb do
    pipe_through :browser

    get "/", PageController, :index
  end
end

If you had a (do ...) macro that expanded into:

[{:do, [], [{:__block__, [], [...]}]}]

You’d get this for free with no need for wrapping anything:

(defmodule HelloWeb.Router
  (use HelloWeb :router)
  (pipeline :browser
    (do
      (plug :accepts '("html"'))
      (plug :fetch_session)
      (plug :fetch_flash)
      (plug :protect_from_forgery)
      (plug :put_secure_browser_headers)))

  (pipeline :api
    (do
      (plug :accepts '("json"'))))

  (scope "/" HelloWeb
    (do
      (pipe_through :browser)
      (get "/" PageController :index))))

This is a carbon-copy of the Phoenix router, which has some of the highest uses of DSLs I’ve seen in elixir code. You might say that if I want to use Phoenix, I should use Elixir and using a lisp-like syntax doesn’t bring anything new, but that’s beside the point. No reason to let perfectly good macros go to waste just because they were written in Elixir xD

What I was trying to say is that if you choose carefully, you can get a a high level of compatibility with Elixir.

Just defining this do macro like I said gives you free interop with 90% of elixir macros that use do ... end. You’re only missing on those that have more keyword arguments besides :do.

1 Like

You’re right, I was trying to hard to fake elixir keyword lists into the lisp AST :stuck_out_tongue:

1 Like

Indeed, that is why you’d have a lot of other helper macro’s anyway. ^.^

Heh, if in doubt, add more parenthesis. ^.^;

Now you should read the source of the project I’ve linked above and steal the implementation of the HM type system from it xD

1 Like

Lol, already can do an HM system, that is not really an issue, and I was thinking of maybe adding it to Llixer straight, but Llixer.Simple will remain simple. ^.^

Cool :slight_smile: Please say something when you add it! I have plans for it :slight_smile:

Where is your repo? I can’t find it.

EDIT: foun it: https://github.com/OvermindDL1/llixer

Lol, plans eh? It’s mostly just a sandbox currently, me just testing various things and styles (one of the ways I get my little creativity out ^.^). :slight_smile:

/me has a weird obsession with creating languages and interpreters and compilers and type systems and etc…