Skip to content

Instantly share code, notes, and snippets.

@lud
Created July 29, 2020 07:12
Show Gist options
  • Save lud/2eedbea6e10232c94398918fc1a79c84 to your computer and use it in GitHub Desktop.
Save lud/2eedbea6e10232c94398918fc1a79c84 to your computer and use it in GitHub Desktop.
Elixir uppercase constants with macros
defmodule Constants.Compiler do
@moduledoc false
defmacro __using__(_) do
quote do
Module.register_attribute(__MODULE__, :const_def, accumulate: true)
import unquote(__MODULE__), only: [const: 2]
@before_compile unquote(__MODULE__)
end
end
defmacro const(key, value) when is_binary(key) do
quote do
@const_def {unquote(key), unquote(value)}
end
end
defmacro const(key, value) do
raise ArgumentError, "Constants keys must be binaries, got: #{inspect(key)}"
end
defmacro __before_compile__(env) do
quote location: :keep, unquote: false do
for {key, value} <- Module.get_attribute(__MODULE__, :const_def) do
# escaping so the value can be unquoted in the macro block
value = Macro.escape(value)
defmacro sigil_x({:<<>>, _, [unquote(key)]}, []) do
# escaping so we return a quoted expression
Macro.escape(unquote(value))
end
end
defmacro sigil_x({:<<>>, _, [key]}, []) do
raise ArgumentError, "Undefined constant #{key}"
end
defmacro sigil_x(key, []) do
raise ArgumentError, "Invalid constant key #{key}"
end
defmacro sigil_x(_, [some | _]) do
raise ArgumentError, "Constant sigil does not accept modifiers"
end
end
end
end
defmodule Constants do
use Constants.Compiler
defmacro __using__(_) do
quote do
import unquote(__MODULE__), only: [sigil_x: 2]
end
end
const("A_VALUE", 123)
const("A_CALL", System.cwd())
end
defmodule Constants.Test do
use Constants
def test do
{~x(A_CALL), ~x(A_VALUE)}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment