Mutare.Transform.QuoteStructure (mutare v0.4.1)

Copy Markdown View Source

Says which parts of a quote run, without walking any of them.

A quote expression that is itself evaluated has three kinds of part, and parts/1 names each value's state:

  • :live — an option value (bind_quoted:, unquote:, location:, …). It is ordinary code, evaluated where the quote expression is.
  • :quoted — the do: body while unquoting is enabled. It is data, except for the argument of an unquote/unquote_splicing in it, which is :live again.
  • :inert — the do: body under unquote: false or bind_quoted: (which disables unquoting unless unquote: true re-enables it). Nothing in it is ever evaluated.

Elixir rejects a quote whose arguments are not written as lists (a variable, a call), so lists are all parts/1 reads, and anything else is a crash, not a guess. It does accept a list element that is no pair (quote([{:line, 1} | []], do: …)); that element is :inert.

Inside :quoted data, quoted/1 reads one node: an escape whose argument is :live, a nested quote, or plain data to keep descending. Elixir quotes a nested quote's body with unquoting off, so neither quote(do: quote(do: unquote(f()))) nor its stacked form unquote(unquote(f())) calls f/0, and there is no quote level to count: one escape leads from :quoted to :live, and nothing leads out of :inert.

A nested quote's options depend on how it is written, because Elixir reads the argument count. Given two arguments (quote bind_quoted: [v: unquote(f())] do … end) it quotes the options as the data around them, escapes still on, and f/0 runs. Given one list holding the options and do: together (quote(bind_quoted: [v: unquote(f())], do: v)) it quotes the whole list with unquoting off, and f/0 does not run. quoted/1 returns the first as {:options, options, rebuild}, with options still :quoted, and the second as :inert.

Resolve, body analysis, self-call rewriting, super forwarding and binding analysis share this reading, not a traversal: each decides what it does with a live expression, and whether a live option is its business at all.

Summary

Functions

The values in a live quote's arguments, in source order, each with its state, and a rebuilder that puts replacement values back into the arguments' written shape.

One node met inside :quoted data: {:escape, argument, rebuild} when its argument is live; for a nested quote, {:options, options, rebuild} when its options are still :quoted, else :inert; :data for anything else.

Types

rebuild()

@type rebuild() :: ([Macro.t()] -> [Macro.t()])

state()

@type state() :: :live | :quoted | :inert

Functions

parts(args)

@spec parts([Macro.t()]) :: {[{Macro.t(), state()}], rebuild()}

The values in a live quote's arguments, in source order, each with its state, and a rebuilder that puts replacement values back into the arguments' written shape.

quoted(arg1)

@spec quoted(Macro.t()) ::
  {:escape, Macro.t(), (Macro.t() -> Macro.t())}
  | {:options, Macro.t(), (Macro.t() -> Macro.t())}
  | :inert
  | :data

One node met inside :quoted data: {:escape, argument, rebuild} when its argument is live; for a nested quote, {:options, options, rebuild} when its options are still :quoted, else :inert; :data for anything else.