budgie
Defining nested macros for a DSL? How can you share state?
I’m trying to build a macro for edge definitions in a graph. How can I access parameters from the outer macro inside the inner macro?
- The direction macro must define a module with the block passed into schema.
- The schema macro must implicitly be able to access the “accesses” string passed into the top-level edge macro.
How can I define complex multi-level macros like this for building DSLs but have the state be shared like I described?
This is the edge module:
defmodule App.Edge.Accesses do
import App.Edge.EdgeType
alias App.Users.User
alias App.Invitations.Invitation
alias App.Tenants.Tenant
edge "accesses" do
direction from: User, to: Invitation
direction from: Invitation, to: Tenant
schema do
end
end
end
This is the EdgeType module with all of the macros:
defmodule Quota.Edge.EdgeType do
defmacro edge(collection_name, do: do_block) do
quote do
end
end
defmacro direction(from: from, to: to) do
quote do
end
end
defmacro schema(do: do_block) do
quote do
end
end
end
Most Liked
zachdaniel
In spark, this DSL would look something like this:
defmodule App.Edge.EdgeType.Dsl do
@schema ... (left out for brevity)
@direction %Spark.Dsl.Entity{
name: :direction,
schema: [
from: [type: :module, required: true],
to: [type: :module, required: true]
]
}
defmodule Edge do
defstruct [:name, :directions]
end
@edge %Spark.Dsl.Entity{
name: :edge,
target: Edge,
schema: [
name: [
type: :atom,
required: true
],
entities: [
directions: [@direction],
schema: [@schema]
],
singleton_entity_keys: [:schema]
]
}
@edges %Spark.Dsl.Section{
name: :edges,
top_level?: true,
entities: [
@edge
]
}
use Spark.Dsl.Extension, sections: [@edge]
end
defmodule App.Edge.EdgeType do
use Spark.Dsl,
default_extensions: [extensions: [App.Edge.EdgeType,Dsl]]
end
defmodule EdgeType.Info do
use Spark.InfoGenerator, extension: App.Edge.EdgeType.Dsl, sections: [:edge]
end
With the above (I left some stuff out, and probably got a few things wrong since I typed this all in a forum window and didn’t test it), the syntax you’ve laid out should work as it is.
defmodule App.Edge.Accesses do
use App.Edge.EdgeType # but you'd need to `use` instead of `import`
alias App.Users.User
alias App.Invitations.Invitation
alias App.Tenants.Tenant
edge "accesses" do
direction from: User, to: Invitation
direction from: Invitation, to: Tenant
schema do
end
end
end
zachallaun
A somewhat common option often used for “contexts” like this is to use module attributes, where each macro would push their context into the attribute prior to injecting the body block and then pop it out after. Each inner macro can access the current context through that attribute.
I’m on my phone so can’t easily provide a more complete example, but basically the idea is to inject a call to Module.put_attribute at the beginning of the quote block, and then
Here’s an example (untested):
@edge_context :"#{__MODULE__}.edge"
defmacro edge(collection, do: quoted) do
quote do
prev = Module.get_attribute(__MODULE__, unquote(@edge_context), [])
Module.put_attribute(__MODULE__, unquote(@edge_context), [unquote(collection) | prev])
result = unquote(quoted)
Module.put_attribute(__MODULE__, unquote(@edge_context), prev)
result
end
end
defmacro schema(do: quoted) do
context = Module.get_attribute(__CALLER__.module, @edge_context)
...
end
zimt28
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









