Generate AST
This commit is contained in:
parent
358b5ea4cc
commit
4e9c7e95b4
16 changed files with 528 additions and 18 deletions
|
@ -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__
|
||||
]
|
||||
|
|
|
@ -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, []}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
70
test/parser/anonymous_functions_test.exs
Normal file
70
test/parser/anonymous_functions_test.exs
Normal 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
|
33
test/parser/default_test.exs
Normal file
33
test/parser/default_test.exs
Normal 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
|
52
test/parser/do_expressions_test.exs
Normal file
52
test/parser/do_expressions_test.exs
Normal 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
|
43
test/parser/match_test.exs
Normal file
43
test/parser/match_test.exs
Normal 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
|
83
test/parser/nonvoid_elements_aliases_test.exs
Normal file
83
test/parser/nonvoid_elements_aliases_test.exs
Normal 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
|
65
test/parser/right_arrow_test.exs
Normal file
65
test/parser/right_arrow_test.exs
Normal 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
|
59
test/parser/void_elements_aliases_test.exs
Normal file
59
test/parser/void_elements_aliases_test.exs
Normal 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
|
Reference in a new issue