Skip to content

Commit

Permalink
handles basic type definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
ityonemo committed Jun 27, 2023
1 parent 3fe6d26 commit a36b0e1
Show file tree
Hide file tree
Showing 15 changed files with 211 additions and 51 deletions.
4 changes: 4 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
zig_doc-*.tar

# Temporary files, for example, from tests.
/tmp/
2 changes: 1 addition & 1 deletion lib/mix.tasks/zig_doc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Mix.Tasks.ZigDoc do
@shortdoc "Generate documentation for the project"
@requirements ["compile"]

@spec run([String.t], keyword) :: :ok
@spec run([String.t()], keyword) :: :ok
@moduledoc """
see `Mix.Tasks.Docs` for more information
Expand Down
3 changes: 1 addition & 2 deletions lib/zig.doc.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
defmodule Zig.Doc do

alias Zig.Doc.Generator

@doc """
Expand Down Expand Up @@ -33,7 +32,7 @@ defmodule Zig.Doc do
|> add_zig_doc_config(zig_doc_options)

docs
|> List.first
|> List.first()
|> Map.get(:docs)
|> dbg(limit: 25)

Expand Down
92 changes: 74 additions & 18 deletions lib/zig.doc/generator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,52 @@ defmodule Zig.Doc.Generator do
with {:ok, file_path} <- Keyword.fetch(options, :file),
{{:ok, file}, :read, _} <- {File.read(file_path), :read, file_path},
{{:ok, sema}, :sema, _} <- {sema_module.run_sema(file_path), :sema, file_path} do

parsed_document = Zig.Parser.parse(file)

doc_ast = if moduledoc = parsed_document.doc_comment do
DocAST.parse!(moduledoc, "text/markdown", [file: file_path, line: 1])
end
doc_ast =
if moduledoc = parsed_document.doc_comment do
DocAST.parse!(moduledoc, "text/markdown", file: file_path, line: 1)
end

# TODO: needs source_path and source_url
node = %ExDoc.ModuleNode{id: "#{id}", doc_line: 1, doc: doc_ast}

Enum.reduce(parsed_document.code, node, &obtain_content(&1, &2, file_path, sema))

else
:error -> Mix.raise("zig doc config error: configuration for module #{id} requires a `:file` option")
{{:error, reason}, :read, path} -> Mix.raise("zig doc error: file at `#{path}` doesn't exist")
{{:error, reason}, :sema, path} -> Mix.raise("zig doc error: sema failed for `#{path}`")
:error ->
Mix.raise(
"zig doc config error: configuration for module #{id} requires a `:file` option"
)

{{:error, reason}, :read, path} ->
Mix.raise("zig doc error: failure reading file at `#{path}` #{reason}")

{{:error, _reason}, :sema, path} ->
Mix.raise("zig doc error: sema failed for `#{path}`")
end
end

defp obtain_content({:fn, fun = %{pub: true}, fn_parts}, acc, file_path, sema) do
doc_ast = if fndoc = fun.doc_comment do
DocAST.parse!(fndoc, "text/markdown", [file: file_path, line: fun.position.line])
end
doc_ast =
if fndoc = fun.doc_comment do
DocAST.parse!(fndoc, "text/markdown", file: file_path, line: fun.position.line)
end

name = Keyword.fetch!(fn_parts, :name)
type = Keyword.fetch!(fn_parts, :type)
params = Keyword.fetch!(fn_parts, :params)

# find the function in the sema
specs = sema.functions
|> Enum.find(&(&1.name == name))
|> Spec.function_from_sema
|> List.wrap
specs =
sema.functions
|> Enum.find(&(&1.name == name))
|> Spec.function_from_sema()
|> List.wrap()

param_string = params
|> Enum.map(fn {var, _, type} -> "#{var}: #{type}" end)
|> Enum.join(", ")
param_string =
params
|> Enum.map(fn {var, _, type} -> "#{var}: #{type}" end)
|> Enum.join(", ")

signature = "#{name}(#{param_string}) #{type}"

Expand All @@ -60,5 +69,52 @@ defmodule Zig.Doc.Generator do
%{acc | docs: [node | acc.docs]}
end

defp obtain_content({:const, const, {name, _, _}}, acc, file_path, sema) do
doc_ast =
if doc = const.doc_comment do
DocAST.parse!(doc, "text/markdown", file: file_path, line: const.position.line)
end

# find the function in the sema
cond do
this_func = Enum.find(sema.functions, &(&1.name == name)) ->
specs =
this_func
|> Spec.function_from_sema()
|> List.wrap()

param_string = Enum.join(this_func.args, ", ")

signature = "#{name}(#{param_string}) #{this_func.return}"

# TODO: needs source_path and source_url
node = %ExDoc.FunctionNode{
id: "#{name}",
name: name,
arity: length(this_func.args),
doc: doc_ast,
signature: signature,
specs: specs
}

%{acc | docs: [node | acc.docs]}

this_type = Enum.find(sema.types, &(&1.name == name)) ->

node = %ExDoc.TypeNode{
id: "#{name}",
name: name,
signature: "#{name}",
doc: doc_ast,
spec: Spec.type_from_sema(this_type)
}

%{acc | typespecs: [node | acc.typespecs]}

true ->
acc
end
end

defp obtain_content(_, acc, _, _), do: acc
end
36 changes: 24 additions & 12 deletions lib/zig.doc/sema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,33 @@ defmodule Zig.Doc.Sema do
@type type :: atom

@type fun :: %{
name: atom,
return: type,
args: [type],
}
name: atom,
return: type,
args: [type]
}

@type const :: %{
name: atom,
type: type
}
@type decls :: %{
name: atom,
type: type
}

@type collection :: %{
vars: [decls],
consts: [decls],
functions: [fun]
}

@type typedef :: %{
name: atom,
def: atom | collection
}

@type file :: %{
functions: [function],
consts: [const],
types: [type]
}
functions: [function],
consts: [decls],
vars: [decls],
types: [typedef]
}

def new(addin \\ []) do
Enum.into(addin, %{functions: [], consts: [], types: []})
Expand Down
6 changes: 5 additions & 1 deletion lib/zig.doc/spec.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Zig.Doc.Spec do
alias Zig.Doc.Sema

@spec function_from_sema(Sema.fun) :: Macro.t
@spec function_from_sema(Sema.fun()) :: Macro.t()

def function_from_sema(fun) do
name = fun.name
Expand All @@ -10,4 +10,8 @@ defmodule Zig.Doc.Spec do

{:"::", [], [{name, [], args}, return_type]}
end

def type_from_sema(type) do
{:"::", [], [{type.name, [], Elixir}, {type.def, [], Elixir}]}
end
end
6 changes: 6 additions & 0 deletions test/_sources/const_function.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn foo(value: i32) i32 {
return value + 1;
}

/// this is the function bar
pub const bar = foo;
2 changes: 2 additions & 0 deletions test/_sources/type_basic.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// this is the foo type.
const foo = i32;
4 changes: 2 additions & 2 deletions test/_support/sema.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
defmodule Zig.SemaAPI do
@type json :: nil | boolean | number | String.t | [json] | %{optional(String.t) => json}
@callback run_sema(Path.t) :: {:ok, json} | {:error, String.t}
@type json :: nil | boolean | number | String.t() | [json] | %{optional(String.t()) => json}
@callback run_sema(Path.t()) :: {:ok, json} | {:error, String.t()}
end

Mox.defmock(Zig.SemaMock, for: Zig.SemaAPI)
21 changes: 13 additions & 8 deletions test/_support/zig_doc_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ defmodule Zig.Doc.Case do
end

def get_module(file) do
[module] =
Zig.Doc.add_zig_doc_config([], [module: [file: file]], Zig.SemaMock)
[module] = Zig.Doc.add_zig_doc_config([], [module: [file: file]], Zig.SemaMock)
module
end

Expand All @@ -23,13 +22,19 @@ defmodule Zig.Doc.Case do

defmacro assert_code(string, data) do
quote do
tgt = unquote(string)
|> Code.format_string!
|> IO.iodata_to_binary
tgt =
unquote(string)
|> Code.format_string!()
|> IO.iodata_to_binary()

assert tgt == unquote(data)
|> List.first
|> Macro.to_string()
# we'll never have multiple function heads, but here
# we need to be able to handle arrays and singular macros
# which is what types will give us.
assert tgt ==
unquote(data)
|> List.wrap
|> List.first()
|> Macro.to_string()
end
end
end
25 changes: 25 additions & 0 deletions test/documentation/const_function_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule ZigDocTest.Documentation.ConstFunctionTest do
use Zig.Doc.Case, async: true

alias Zig.Doc.Sema

test "documentation is generated for consts that are functions" do
expect_sema({:ok, Sema.new(functions: [%{name: :bar, return: :i32, args: [:i32]}])})

assert %{docs: [function]} = get_module("test/_sources/const_function.zig")

assert [{:p, [], [" this is the function bar"], %{}}] = function.doc

# note that we lose the signature because we won't be digging too hard to find
# the rest.

assert "bar(i32) i32" = function.signature

assert_code(
"""
bar(i32) :: i32
""",
function.specs
)
end
end
13 changes: 7 additions & 6 deletions test/documentation/function_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ defmodule ZigDocTest.Documentation.FunctionTest do
test "function-level documentation is generated" do
expect_sema({:ok, Sema.new(functions: [%{name: :foo, return: :i32, args: [:i32]}])})

assert %{docs: [function]} =
get_module("test/_sources/function.zig")
assert %{docs: [function]} = get_module("test/_sources/function.zig")

assert [{:p, [], [" this is the function foo"], %{}}] = function.doc
assert "foo(value: i32) i32" = function.signature

assert_code("""
foo(i32) :: i32
""",
function.specs)
assert_code(
"""
foo(i32) :: i32
""",
function.specs
)
end
end
1 change: 0 additions & 1 deletion test/documentation/module_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ defmodule ZigDocTest.Documentation.ModuleTest do
alias Zig.Doc.Sema

test "module-level documentation is generated" do

expect_sema({:ok, Sema.new()})

assert %{doc: [{:p, [], [" tests module-level comment content"], %{}}]} =
Expand Down
21 changes: 21 additions & 0 deletions test/documentation/type_basic_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule ZigDocTest.Documentation.TypeBasicTest do
use Zig.Doc.Case, async: true

alias Zig.Doc.Sema

test "type-level documentation is generated" do
expect_sema({:ok, Sema.new(types: [%{name: :foo, def: :i32}])})

assert %{typespecs: [type]} = get_module("test/_sources/type_basic.zig")

assert [{:p, [], [" this is the foo type."], %{}}] = type.doc
assert "foo" = type.signature

assert_code(
"""
foo :: i32
""",
type.spec
)
end
end

0 comments on commit a36b0e1

Please sign in to comment.