Generate AST

This commit is contained in:
Mitchell Hanberg 2021-03-24 23:01:13 -04:00
parent 358b5ea4cc
commit 4e9c7e95b4
16 changed files with 528 additions and 18 deletions

View file

@ -3,3 +3,9 @@ use Mix.Config
# this is to make the warning go away,
# Temple does not use a json_library
config :phoenix, json_library: Temple
config :temple,
aliases: [
select: :select__,
link: :link__
]

View file

@ -94,14 +94,14 @@ defmodule Temple.Parser do
{_, false} <- {Text, Text.applicable?(ast)},
{_, false} <- {TempleNamespaceNonvoid, TempleNamespaceNonvoid.applicable?(ast)},
{_, false} <- {TempleNamespaceVoid, TempleNamespaceVoid.applicable?(ast)},
{_, false} <- {Components, Components.applicable?(ast)} do
# {_, false} <- {NonvoidElementsAliases, NonvoidElementsAliases.applicable?(ast)},
# {_, false} <- {VoidElementsAliases, VoidElementsAliases.applicable?(ast)},
# {_, false} <- {AnonymousFunctions, AnonymousFunctions.applicable?(ast)},
# {_, false} <- {RightArrow, RightArrow.applicable?(ast)},
# {_, false} <- {DoExpressions, DoExpressions.applicable?(ast)},
# {_, false} <- {Match, Match.applicable?(ast)},
# {_, false} <- {Default, Default.applicable?(ast)} do
{_, false} <- {Components, Components.applicable?(ast)},
{_, false} <- {NonvoidElementsAliases, NonvoidElementsAliases.applicable?(ast)},
{_, false} <- {VoidElementsAliases, VoidElementsAliases.applicable?(ast)},
{_, false} <- {AnonymousFunctions, AnonymousFunctions.applicable?(ast)},
{_, false} <- {RightArrow, RightArrow.applicable?(ast)},
{_, false} <- {DoExpressions, DoExpressions.applicable?(ast)},
{_, false} <- {Match, Match.applicable?(ast)},
{_, false} <- {Default, Default.applicable?(ast)} do
raise "No parsers applicable!!"
else
{parser, true} ->
@ -185,20 +185,20 @@ defmodule Temple.Parser do
{List.flatten(do_and_else), args}
end
def split_on_fn([{:fn, _, _} = func | rest], {args, _, args2}) do
split_on_fn(rest, {args, func, args2})
def split_on_fn([{:fn, _, _} = func | rest], {args_before, _, args_after}) do
split_on_fn(rest, {args_before, func, args_after})
end
def split_on_fn([arg | rest], {args, nil, args2}) do
split_on_fn(rest, {[arg | args], nil, args2})
def split_on_fn([arg | rest], {args_before, nil, args_after}) do
split_on_fn(rest, {[arg | args_before], nil, args_after})
end
def split_on_fn([arg | rest], {args, func, args2}) do
split_on_fn(rest, {args, func, [arg | args2]})
def split_on_fn([arg | rest], {args_before, func, args_after}) do
split_on_fn(rest, {args_before, func, [arg | args_after]})
end
def split_on_fn([], {args, func, args2}) do
{Enum.reverse(args), func, Enum.reverse(args2)}
def split_on_fn([], {args_before, func, args_after}) do
{Enum.reverse(args_before), func, Enum.reverse(args_after)}
end
def pop_compact?([]), do: {false, []}

View file

@ -9,11 +9,30 @@ defmodule Temple.Parser.AnonymousFunctions do
def applicable?({_, _, args}) do
import Temple.Parser.Private, only: [split_args: 1]
args |> split_args() |> elem(1) |> Enum.any?(fn x -> match?({:fn, _, _}, x) end)
args
|> split_args()
|> elem(1)
|> Enum.any?(fn x -> match?({:fn, _, _}, x) end)
end
def applicable?(_), do: false
def run({_name, _, args} = expression) do
{_do_and_else, args} = Temple.Parser.Private.split_args(args)
{_args, func_arg, _args2} = Temple.Parser.Private.split_on_fn(args, {[], nil, []})
{_func, _, [{_arrow, _, [[{_arg, _, _}], block]}]} = func_arg
children = Temple.Parser.parse(block)
Temple.Ast.new(
meta: %{type: :anonymous_functions},
content: expression,
children: children
)
end
@impl Parser
def run({name, _, args}, buffer) do
import Temple.Parser.Private

View file

@ -6,7 +6,14 @@ defmodule Temple.Parser.Default do
alias Temple.Buffer
@impl Parser
def applicable?(_), do: true
def applicable?(_ast), do: true
def run(ast) do
Temple.Ast.new(
meta: %{type: :default},
content: ast
)
end
@impl Parser
def run({_, _, args} = macro, buffer) do

View file

@ -12,6 +12,20 @@ defmodule Temple.Parser.DoExpressions do
def applicable?(_), do: false
def run({name, meta, args}) do
{do_and_else, args} = Temple.Parser.Private.split_args(args)
do_body = Temple.Parser.parse(do_and_else[:do])
else_body = Temple.Parser.parse(do_and_else[:else])
Temple.Ast.new(
meta: %{type: :do_expression},
children: [do_body, else_body],
content: {name, meta, args}
)
end
@impl Parser
def run({name, meta, args}, buffer) do
import Temple.Parser.Private

View file

@ -12,6 +12,13 @@ defmodule Temple.Parser.Match do
def applicable?(_), do: false
def run(macro) do
Temple.Ast.new(
meta: %{type: :match},
content: macro
)
end
@impl Parser
def run({_, _, args} = macro, buffer) do
import Temple.Parser.Private

View file

@ -12,6 +12,36 @@ defmodule Temple.Parser.NonvoidElementsAliases do
def applicable?(_), do: false
def run({name, _, args}) do
name = Parser.nonvoid_elements_lookup()[name]
{do_and_else, args} =
args
|> Temple.Parser.Private.split_args()
{do_and_else, args} =
case args do
[args] when is_list(args) ->
{do_value, args} = Keyword.pop(args, :do)
do_and_else = Keyword.put_new(do_and_else, :do, do_value)
{do_and_else, args}
_ ->
{do_and_else, args}
end
children = Temple.Parser.parse(do_and_else[:do])
Temple.Ast.new(
content: to_string(name),
meta: %{type: :nonvoid_alias},
attrs: args,
children: children
)
end
@impl Parser
def run({name, _, args}, buffer) do
import Temple.Parser.Private

View file

@ -9,6 +9,16 @@ defmodule Temple.Parser.RightArrow do
def applicable?({:->, _, _}), do: true
def applicable?(_), do: false
def run({_, _, [[pattern], args]}) do
children = Temple.Parser.parse(args)
Temple.Ast.new(
meta: %{type: :right_arrow},
content: pattern,
children: children
)
end
@impl Parser
def run({_, _, [[pattern], args]}, buffer) do
import Temple.Parser.Private

View file

@ -12,6 +12,18 @@ defmodule Temple.Parser.VoidElementsAliases do
def applicable?(_), do: false
def run({name, _, args}) do
{_do_and_else, [args]} = Temple.Parser.Private.split_args(args)
name = Parser.void_elements_lookup()[name]
Temple.Ast.new(
content: name,
meta: %{type: :void_alias},
attrs: args
)
end
@impl Parser
def run({name, _, args}, buffer) do
import Temple.Parser.Private

View file

@ -0,0 +1,70 @@
defmodule Temple.Parser.AnonymousFunctionsTest do
use ExUnit.Case, async: true
alias Temple.Parser.AnonymousFunctions
describe "applicable?/1" do
test "returns true when the node contains an anonymous function as an argument to a function" do
raw_asts = [
quote do
form_for changeset, Routes.foo_path(conn, :create), fn form ->
Does.something!(form)
end
end
]
for raw_ast <- raw_asts do
assert AnonymousFunctions.applicable?(raw_ast)
end
end
test "returns false when the node is anything other than an anonymous function as an argument to a function" do
raw_asts = [
quote do
Temple.div do
"foo"
end
end,
quote do
link to: "/the/route" do
"Label"
end
end
]
for raw_ast <- raw_asts do
refute AnonymousFunctions.applicable?(raw_ast)
end
end
end
describe "run/2" do
test "adds a node to the buffer" do
expected_child =
quote do
Does.something!(form)
end
raw_ast =
quote do
form_for changeset, Routes.foo_path(conn, :create), fn form ->
unquote(expected_child)
end
end
ast = AnonymousFunctions.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :anonymous_functions},
content: _,
children: [
%Temple.Ast{
meta: %{type: :default},
content: ^expected_child,
children: []
}
]
} = ast
end
end
end

View file

@ -0,0 +1,33 @@
defmodule Temple.Parser.DefaultTest do
use ExUnit.Case, async: true
alias Temple.Parser.Default
describe "applicable?/1" do
test "returns true when the node is an elixir expression" do
ast =
quote do
Foo.bar!(baz)
end
assert Default.applicable?(ast)
end
end
describe "run/2" do
test "adds a elixir expression node to the buffer" do
expression =
quote do
Foo.bar!(baz)
end
ast = Default.run(expression)
assert %Temple.Ast{
meta: %{type: :default},
content: expression,
children: []
} == ast
end
end
end

View file

@ -0,0 +1,52 @@
defmodule Temple.Parser.DoExpressionsTest do
use ExUnit.Case, async: true
alias Temple.Parser.DoExpressions
describe "applicable?/1" do
test "returns true when the node contains a do expression" do
raw_ast =
quote do
for big <- boys do
"bob"
end
end
assert DoExpressions.applicable?(raw_ast)
end
end
describe "run/2" do
test "adds a node to the buffer" do
raw_ast =
quote do
for big <- boys do
"bob"
end
end
ast = DoExpressions.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :do_expression},
content: _,
children: [
[
%Temple.Ast{
meta: %{type: :text},
content: "bob",
children: []
}
],
[
%Temple.Ast{
meta: %{type: :empty},
content: nil,
children: []
}
]
]
} = ast
end
end
end

View file

@ -0,0 +1,43 @@
defmodule Temple.Parser.MatchTest do
use ExUnit.Case, async: true
alias Temple.Parser.Match
describe "applicable?/1" do
test "returns true when the node is an elixir match expression" do
ast =
quote do
bingo = Foo.bar!(baz)
end
assert Match.applicable?(ast)
end
test "returns false when the node is a anything other than an elixir match expression" do
for node <- [
:atom,
%{key: :value},
[]
] do
refute Match.applicable?(node)
end
end
end
describe "run/2" do
test "adds a elixir expression node to the buffer" do
expression =
quote do
bingo = Foo.bar!(baz)
end
ast = Match.run(expression)
assert %Temple.Ast{
meta: %{type: :match},
content: expression,
children: []
} == ast
end
end
end

View file

@ -0,0 +1,83 @@
defmodule Temple.Parser.NonvoidElementsAliasesTest do
use ExUnit.Case, async: true
alias Temple.Parser.NonvoidElementsAliases
describe "applicable?/1" do
test "returns true when the node is a nonvoid element or alias" do
raw_asts = [
quote do
div do
"foo"
end
end,
quote do
select__ do
option do
"Label"
end
end
end
]
for raw_ast <- raw_asts do
assert NonvoidElementsAliases.applicable?(raw_ast)
end
end
test "returns false when the node is anything other than a nonvoid element or alias" do
raw_asts = [
quote do
Temple.div do
"foo"
end
end,
quote do
link to: "/the/route" do
"Label"
end
end
]
for raw_ast <- raw_asts do
refute NonvoidElementsAliases.applicable?(raw_ast)
end
end
end
describe "run/2" do
test "adds a node to the buffer" do
raw_ast =
quote do
div class: "foo", id: var do
select__ do
option do
"foo"
end
end
end
end
ast = NonvoidElementsAliases.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :nonvoid_alias},
content: "div",
attrs: [class: "foo", id: {:var, [], _}],
children: [
%Temple.Ast{
content: "select",
children: [
%Temple.Ast{
content: "option",
children: [
%Temple.Ast{content: "foo"}
]
}
]
}
]
} = ast
end
end
end

View file

@ -0,0 +1,65 @@
defmodule Temple.Parser.RightArrowTest do
use ExUnit.Case, async: true
alias Temple.Parser.RightArrow
describe "applicable?/1" do
test "returns true when the node contains a right arrow" do
[raw_ast] =
quote do
:bar ->
:bang
end
assert RightArrow.applicable?(raw_ast)
end
test "returns false when the node is anything other than an anonymous function as an argument to a function" do
raw_asts = [
quote do
Temple.div do
"foo"
end
end,
quote do
link to: "/the/route" do
"Label"
end
end
]
for raw_ast <- raw_asts do
refute RightArrow.applicable?(raw_ast)
end
end
end
describe "run/2" do
test "adds a node to the buffer" do
[raw_ast] =
quote do
:bing ->
:bong
end
bong =
quote do
:bong
end
ast = RightArrow.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :right_arrow},
content: :bing,
children: [
%Temple.Ast{
meta: %{type: :default},
content: ^bong,
children: []
}
]
} = ast
end
end
end

View file

@ -0,0 +1,59 @@
defmodule Temple.Parser.VoidElementsAliasesTest do
use ExUnit.Case, async: true
alias Temple.Parser.VoidElementsAliases
describe "applicable?/1" do
test "returns true when the node is a nonvoid element or alias" do
raw_asts = [
quote do
link__(src: "example.com/foo")
end,
quote do
meta content: "foo"
end
]
for raw_ast <- raw_asts do
assert VoidElementsAliases.applicable?(raw_ast)
end
end
test "returns false when the node is anything other than a nonvoid element or alias" do
raw_asts = [
quote do
Temple.div do
"foo"
end
end,
quote do
link to: "/the/route" do
"Label"
end
end
]
for raw_ast <- raw_asts do
refute VoidElementsAliases.applicable?(raw_ast)
end
end
end
describe "run/2" do
test "adds a node to the buffer" do
raw_ast =
quote do
meta content: "foo"
end
ast = VoidElementsAliases.run(raw_ast)
assert %Temple.Ast{
meta: %{type: :void_alias},
content: :meta,
attrs: [content: "foo"],
children: []
} = ast
end
end
end