marick

marick

Where in the Elixir source does unquoting happen?

I’d like to read the code that actually implements the action of unquote. It seems like it should be somewhere in the Erlang source, in elixir_parser.yrl, elixir_compile.erl, elixir_expand.erl, or elixir_erl_pass.erl, but I can’t find it.

It probably doesn’t help that I don’t know Erlang.

Marked As Solved

dorgan

dorgan

It seems unquote is not a function itself, but rather a special call handled here:

In essence, quote takes the ast and handles calls(as in ast call) to unquote differently depending if the :unquote option is true or false

Also Liked

marick

marick

Thanks. I now think I understand. For people interested, I offer the following. Let me know if I’ve gotten things wrong.

Given this: quote do: inspect(1 + a), compilation works as follows.

Quote processing is handled by do_quote. The syntax tree for the body of the quote looks like this:

{:inspect,
 [context: MacroExamples.ExpansionViewer, import: Kernel],
 [{:unquote, [],
  [
    {:+, [context: MacroExamples.ExpansionViewer, import: Kernel],
      [1, {:a, [], MacroExamples.ExpansionViewer}]}]}]}

That matches a different do_quote, whose signature is: do_quote({Name, Meta, Args}. In Erlang, variables are uppercased. There are guards that check that Name is an atom and Args is a list. There’s a bit of rearrangement that ends with a call to do_quote_tuple, which looks like this:

do_quote_tuple(Left, Meta, Right, Q, E) ->
  TLeft = do_quote(Left, Q, E),
  TRight = do_quote(Right, Q, E),
  {'{}', [], [TLeft, meta(Meta, Q), TRight]}.

Left is :inspect and Right is:

[
  {:unquote, [],
   [
    {:+, [context: MacroExamples.ExpansionViewer, import: Kernel],
      [1, {:a, [], MacroExamples.ExpansionViewer}]}]}]

There’s a recursive descent into the list, then the do_quote on line 288 matches the single element:

do_quote({unquote, _Meta, [Expr]}, #elixir_quote{unquote=true}, _) ->
  Expr;

Here unquote is the way Erlang spells atoms. In an Elixir function, the signature would be do_quote({:unquote, _meta, [expr]}

All this version of do_quote does is strip and return the single argument:

{:+, [context: MacroExamples.ExpansionViewer, import: Kernel],
      [1, {:a, [], MacroExamples.ExpansionViewer}]}

… which may puzzle you as it did me. But let’s see how that three-tuple is used. Recall that it was calculated as part of this:

do_quote_tuple(Left, Meta, Right, Q, E) ->
  TLeft = do_quote(Left, Q, E),
  TRight = do_quote(Right, Q, E),
  {'{}', [], [TLeft, meta(Meta, Q), TRight]}.

The interesting bit that the first three arguments came from a three-tuple of the form {:inspect, meta, [{:unquote, ..., ...}]}

Because of the last line, that’s transformed into:

{:{}, [], 
 [
   :inspect, [...],
   [{:+, [...], [1, {:a, [], MacroExamples.ExpansionViewer}]}}]]}

That is:

  1. Because of the quote, the original {:inspect, ..., ...} three-tuple was not quoted verbatim, but rather converted into a different three-tuple, with the shape {:{}, ..., ...}. The compiler interprets that shape as meaning "emit code to call this particular function. (Hold that thought.)
  2. Because of the unquote, the original {:+, ..., ...} three-tuple was quoted verbatim: was left as an instruction to the compiler to create a function call.

It’s important to remember that the structure we’ve been building is destined to be compiled and then executed. The compiler is going to take that tree and turn it into a linear set of virtual machine (“BEAM”) instructions. Assuming a pretty simple virtual machine, that set of instructions will look something like this:

  1. From {:a, [], MacroExamples.ExpansionViewer}, the compiler produces instructions that say “Look up the value for a in the compilation context and push it on the stack.”
  2. The enclosing {:+, [import: Kernel], [1, {...above...}]} produces: “use the function :+ in Kernel and apply it to 1 and the top element on the stack. Put the result on top of the stack.”
  3. The enclosing {:{}, ..., [:inspect, ..metadata.., [...above...]]} says “create a three-tuple with the first element :inspect, the second ..metadata.., and the top of the stack.”

I extracted my example from this code:

defmodule MacroExamples.Inspect do
  a = 10
  IO.inspect(quote do: inspect(1 + a))

Therefore, the result of compiling and executing the quote expression is {:inspect, ..., 6}. Which, since it’s a three-tuple in “here’s a function call, dear compiler” format, could be compiled again, had it been a return value from a defmacro.

“I’ve taught you much, my little droogies.” … Maybe?

Where Next?

Popular in Questions Top

stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Other popular topics Top

Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New

We're in Beta

About us Mission Statement