关于elixir:elixir-0059-Elixir-是如何获取到-doc-内容的

34次阅读

共计 1774 个字符,预计需要花费 5 分钟才能阅读完成。

初学 elixir 时就被它不便的文档编写形式所吸引,咱们能够这样编写模块的文档和函数的文档:

defmodule M4 do
  @moduledoc """Module doc for M4."""

  @doc "function doc for f"
  def f do
  end
end

能够在 repl 里间接查看,也能够生成网页版的文档。

iex(2)> h M4

                                       M4                                       

Module doc for M4.

iex(3)> h M4.f

                                    def f()                                     

function doc for f

那么 elixir 到底是如何从代码文件中获取到 doc 内容的呢。

Code.fetch_docs

规范库的 Code 模块里自带了很多用于解决源文件的函数,其中 Code.fetch_docs 能够间接获取一个模块里全副的 doc 内容:

iex(1)> Code.fetch_docs M4
{:docs_v1, 2, :elixir, "text/markdown", %{"en" => "Module doc for M4.\n"}, %{},
 [{{:function, :f, 0}, 6, ["f()"], %{"en" => "function doc for f"}, %{}}]}

让咱们来看看它的外部实现:

首先,它调用了 :code.get_object_code/1 函数来获取模块的 beam 文件的内容和门路。

iex(5)> :code.get_object_code M4
{M4,
 <<70, 79, 82, 49, 0, 0, 4, 216, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 122,
   0, 0, 0, 14, 9, 69, 108, 105, 120, 105, 114, 46, 77, 52, 8, 95, 95, 105, 110,
   102, 111, 95, 95, 7, 99, 111, 109, 112, ...>>,
 '.../_build/dev/lib/compile/ebin/Elixir.M4.beam'}

而后应用 :beam_lib.chunks/2 从 beam 文件中获取到 chunkref 为 ‘Docs’ 的内容。

:beam_lib.chunks bin, ['Docs']
{:ok,
 {M4,
  [
    {'Docs',
     <<131, 80, 0, 0, 0, 165, 120, 156, 203, 96, 79, 97, 96, 79, 201, 79, 46,
       142, 47, 51, 76, 100, 74, 97, 96, 75, 205, 201, 172, 200, 44, 202, 101,
       96, 96, 224, 45, 73, 173, 40, 209, 207, 77, ...>>}
  ]}}

最初把获取到的内容转换成 erlang term 就行了:

iex(9)> :erlang.binary_to_term bin    
{:docs_v1, 2, :elixir, "text/markdown", %{"en" => "Module doc for M4.\n"}, %{},
 [{{:function, :f, 0}, 6, ["f()"], %{"en" => "function doc for f"}, %{}}]}

doc 内容是如何被编译到 beam 文件的

咱们晓得了 elixir 是如何从 beam 文件中获取到 doc 内容的,但 doc 内容又是如何从源代码被编译进 beam 文件的呢?最重要的是,@doc 里的内容是如何与每个函数关联起来的呢?

首先,和其它模块属性一样,咱们调用 @doc "abc" 的时候是应用 @/1 macro 来设置了模块属性 :doc 的内容。

而后,模块属性的值会被存储到一个编译时的 ets 里,咱们能够这样看到这个编译时的 ets 的内容:

  @doc "function doc for g"
  {set, bag} = :elixir_module.data_tables(__MODULE__)
  IO.inspect(:ets.tab2list(set))
  IO.inspect(:ets.tab2list(bag))

  def g do
  end

模块属性 @doc@moduledoc 都是可笼罩的,也就是前面的定义会笼罩掉之前的值。

之后,elixir 编译器会调用 Module.compile_definition_attributes/6 这个外部函数,在定义新的函数时读取以后的 @doc 的值。

最初,生成好的函数签名(signature)会被存储到 data_tables 中,形如:

{{:function, :f, 0}, 6, [], "function doc for f", %{}},

正文完
 0