defmodule Diesel do @moduledoc """ Declarative programming in Elixir Diesel is a toolkit that helps you build your own DSLs. Usage: ```elixir defmodule MyApp.Fsm do use Diesel, otp_app: :my_app, dsl: MyApp.Fsm.Dsl, parsers: [ ... ], generators: [ ... ] end ``` For more information on how to use this library, please check: * the `Diesel.Dsl` and `Diesel.Tag` modules, * the guides and tutorials provided in the documentation * the examples used in tests """ @type tag() :: atom() @type element() :: {tag(), keyword(), [element()]} @doc "Returns the raw definition for the dsl, before compilation" @callback definition() :: element() import Diesel.Naming defmacro __using__(opts) do otp_app = Keyword.fetch!(opts, :otp_app) mod = __CALLER__.module default_dsl = Module.concat(mod, Dsl) default_parser = Module.concat(mod, Parser) dsl = opts |> Keyword.get(:dsl, default_dsl) |> module_name() overrides = Keyword.get(opts, :overrides, []) generators = opts |> Keyword.get(:generators, []) |> Enum.map(&module_name/1) parsers = opts |> Keyword.get(:parsers, [default_parser]) |> Enum.map(&module_name/1) quote do @otp_app unquote(otp_app) @dsl unquote(dsl) @overrides unquote(overrides) @mod unquote(mod) @parsers unquote(parsers) @generators unquote(generators) def parsers, do: @parsers def dsl, do: @dsl defmacro __using__(opts) do mod = __CALLER__.module opts = Keyword.put(opts, :caller_module, mod) quote do @behaviour Diesel @otp_app unquote(@otp_app) @dsl unquote(@dsl) @parsers unquote(@parsers) @generators unquote(@generators) @root @dsl.root() import Kernel, except: unquote(@overrides) import unquote(@dsl), only: :macros @before_compile unquote(@mod) @opts unquote(opts) end end defmacro __before_compile__(_env) do mod = __CALLER__.module opts = Module.get_attribute(mod, :opts) dsl = Module.get_attribute(mod, :dsl) definition = Module.get_attribute(mod, :definition) code = if definition do parsers = Module.get_attribute(mod, :parsers) generators = Module.get_attribute(mod, :generators) Diesel.Dsl.validate!(dsl, definition) definition = Enum.reduce(parsers, definition, & &1.parse(&2, opts)) generated_code = generators |> Enum.flat_map(&[&1.generate(definition, opts)]) |> Enum.reject(&is_nil/1) [definition_ast() | generated_code] else [] end if opts[:debug] do code |> Macro.to_string() |> Code.format_string!() |> IO.puts() end code end defp definition_ast do quote do @impl Diesel def definition, do: @definition end end end end end