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

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
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
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
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New

Other popular topics Top

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
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
sergio
I couldn’t find any guides that worked well with Phoenix 1.6.0 and esbuild. I hope this helps people test the waters and eases you into t...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
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
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement