Start porting parsers to use AST
This commit is contained in:
parent
9d6f7c176d
commit
358b5ea4cc
14 changed files with 329 additions and 17 deletions
|
@ -1,2 +1,2 @@
|
|||
elixir 1.10.2
|
||||
erlang 23.0.1
|
||||
erlang 23.2.6
|
||||
|
|
|
@ -142,7 +142,7 @@ defmodule Temple do
|
|||
```
|
||||
"""
|
||||
defmacro temple([do: block] = _block) do
|
||||
markup = Parser.parse(block)
|
||||
markup = Parser.old_parse(block)
|
||||
|
||||
quote location: :keep do
|
||||
unquote(markup)
|
||||
|
@ -151,7 +151,7 @@ defmodule Temple do
|
|||
|
||||
defmacro temple(block) do
|
||||
quote location: :keep do
|
||||
Parser.parse(unquote(block))
|
||||
Parser.old_parse(unquote(block))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -173,7 +173,7 @@ defmodule Temple do
|
|||
```
|
||||
"""
|
||||
defmacro compile(engine, [do: block] = _block) do
|
||||
markup = Parser.parse(block)
|
||||
markup = Parser.old_parse(block)
|
||||
|
||||
EEx.compile_string(markup, engine: engine, line: __CALLER__.line, file: __CALLER__.file)
|
||||
end
|
||||
|
|
7
lib/temple/ast.ex
Normal file
7
lib/temple/ast.ex
Normal file
|
@ -0,0 +1,7 @@
|
|||
defmodule Temple.Ast do
|
||||
defstruct content: nil, attrs: [], children: [], meta: %{}
|
||||
|
||||
def new(opts \\ []) do
|
||||
struct(__MODULE__, opts)
|
||||
end
|
||||
end
|
|
@ -1,6 +1,19 @@
|
|||
defmodule Temple.Parser do
|
||||
@moduledoc false
|
||||
|
||||
alias Temple.Parser.Empty
|
||||
alias Temple.Parser.Text
|
||||
alias Temple.Parser.TempleNamespaceNonvoid
|
||||
alias Temple.Parser.TempleNamespaceVoid
|
||||
alias Temple.Parser.Components
|
||||
alias Temple.Parser.NonvoidElementsAliases
|
||||
alias Temple.Parser.VoidElementsAliases
|
||||
alias Temple.Parser.AnonymousFunctions
|
||||
alias Temple.Parser.RightArrow
|
||||
alias Temple.Parser.DoExpressions
|
||||
alias Temple.Parser.Match
|
||||
alias Temple.Parser.Default
|
||||
|
||||
@doc """
|
||||
Should return true if the parser should apply for the given AST.
|
||||
"""
|
||||
|
@ -77,6 +90,28 @@ defmodule Temple.Parser do
|
|||
]
|
||||
|
||||
def parse(ast) do
|
||||
with {_, false} <- {Empty, Empty.applicable?(ast)},
|
||||
{_, 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
|
||||
raise "No parsers applicable!!"
|
||||
else
|
||||
{parser, true} ->
|
||||
ast
|
||||
|> parser.run()
|
||||
|> List.wrap()
|
||||
end
|
||||
end
|
||||
|
||||
def old_parse(ast) do
|
||||
{:ok, buffer} = Buffer.start_link()
|
||||
|
||||
Temple.Parser.Private.traverse(buffer, ast)
|
||||
|
@ -137,12 +172,14 @@ defmodule Temple.Parser do
|
|||
def split_args(args) do
|
||||
{do_and_else, args} =
|
||||
args
|
||||
|> Enum.split_with(fn
|
||||
arg when is_list(arg) ->
|
||||
Keyword.keyword?(arg) && (Keyword.keys(arg) -- [:do, :else]) |> Enum.count() == 0
|
||||
|
||||
_ ->
|
||||
|> Enum.split_with(fn arg ->
|
||||
if Keyword.keyword?(arg) do
|
||||
arg
|
||||
|> Keyword.drop([:do, :else])
|
||||
|> Enum.empty?()
|
||||
else
|
||||
false
|
||||
end
|
||||
end)
|
||||
|
||||
{List.flatten(do_and_else), args}
|
||||
|
|
|
@ -10,6 +10,19 @@ defmodule Temple.Parser.Components do
|
|||
|
||||
def applicable?(_), do: false
|
||||
|
||||
def run({:c, meta, [component_module, [do: _] = block]}) do
|
||||
run({:c, meta, [component_module, [], block]})
|
||||
end
|
||||
|
||||
def run({:c, _meta, [component_module, args, [do: block]]}) do
|
||||
Temple.Ast.new(
|
||||
meta: %{type: :component},
|
||||
content: Macro.expand_once(component_module, __ENV__),
|
||||
attrs: args,
|
||||
children: block
|
||||
)
|
||||
end
|
||||
|
||||
def run({:c, _meta, [component_module | args]}, buffer) do
|
||||
import Temple.Parser.Private
|
||||
|
||||
|
|
|
@ -3,11 +3,16 @@ defmodule Temple.Parser.Empty do
|
|||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Parser
|
||||
alias Temple.Ast
|
||||
|
||||
@impl Parser
|
||||
def applicable?(ast) when ast in [nil, []], do: true
|
||||
def applicable?(_), do: false
|
||||
|
||||
def run(_ast) do
|
||||
Ast.new(meta: %{type: :empty})
|
||||
end
|
||||
|
||||
@impl Parser
|
||||
def run(_ast, _buffer) do
|
||||
:ok
|
||||
|
|
|
@ -12,6 +12,37 @@ defmodule Temple.Parser.TempleNamespaceNonvoid do
|
|||
|
||||
def applicable?(_), do: false
|
||||
|
||||
def run({name, _, args}) do
|
||||
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name
|
||||
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: :temple_nonvoid},
|
||||
attrs: args,
|
||||
children: children
|
||||
)
|
||||
end
|
||||
|
||||
@impl Parser
|
||||
def run({name, _, args}, buffer) do
|
||||
import Temple.Parser.Private
|
||||
|
|
|
@ -12,6 +12,18 @@ defmodule Temple.Parser.TempleNamespaceVoid do
|
|||
|
||||
def applicable?(_), do: false
|
||||
|
||||
def run({name, _, [args]}) do
|
||||
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name
|
||||
|
||||
name = Parser.void_elements_lookup()[name]
|
||||
|
||||
Temple.Ast.new(
|
||||
content: to_string(name),
|
||||
meta: %{type: :temple_void},
|
||||
attrs: args
|
||||
)
|
||||
end
|
||||
|
||||
@impl Parser
|
||||
def run({name, _, args}, buffer) do
|
||||
import Temple.Parser.Private
|
||||
|
|
|
@ -4,11 +4,16 @@ defmodule Temple.Parser.Text do
|
|||
|
||||
alias Temple.Buffer
|
||||
alias Temple.Parser
|
||||
alias Temple.Ast
|
||||
|
||||
@impl Parser
|
||||
def applicable?(text) when is_binary(text), do: true
|
||||
def applicable?(_), do: false
|
||||
|
||||
def run(text) do
|
||||
Ast.new(content: text, meta: %{type: :text})
|
||||
end
|
||||
|
||||
@impl Parser
|
||||
def run(text, buffer) do
|
||||
Buffer.put(buffer, text)
|
||||
|
|
|
@ -28,10 +28,28 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
end
|
||||
|
||||
describe "run/2" do
|
||||
test "is correct" do
|
||||
buf = start_supervised!(Temple.Buffer)
|
||||
test "adds a node to the buffer" do
|
||||
raw_ast =
|
||||
quote do
|
||||
c SomeModule do
|
||||
aside class: "foobar" do
|
||||
"I'm a component!"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ast =
|
||||
ast = Components.run(raw_ast)
|
||||
|
||||
assert %Temple.Ast{
|
||||
meta: %{type: :component},
|
||||
content: SomeModule,
|
||||
attrs: [],
|
||||
children: _
|
||||
} = ast
|
||||
end
|
||||
|
||||
test "adds a node to the buffer that takes args" do
|
||||
raw_ast =
|
||||
quote do
|
||||
c SomeModule, foo: :bar do
|
||||
aside class: "foobar" do
|
||||
|
@ -40,12 +58,14 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
end
|
||||
end
|
||||
|
||||
Temple.Parser.Components.run(ast, buf)
|
||||
ast = Components.run(raw_ast)
|
||||
|
||||
result = Temple.Buffer.get(buf)
|
||||
|
||||
assert result ==
|
||||
~s{<%= Phoenix.View.render_layout SomeModule, :self, [foo: :bar] do %><aside class="foobar">I'm a component!</aside><% end %>}
|
||||
assert %Temple.Ast{
|
||||
meta: %{type: :component},
|
||||
content: SomeModule,
|
||||
attrs: [foo: :bar],
|
||||
children: _
|
||||
} = ast
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
37
test/parser/empty_test.exs
Normal file
37
test/parser/empty_test.exs
Normal file
|
@ -0,0 +1,37 @@
|
|||
defmodule Temple.Parser.EmptyTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.Empty
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is non-content" do
|
||||
assert Empty.applicable?(nil)
|
||||
assert Empty.applicable?([])
|
||||
end
|
||||
|
||||
test "returns fals when the node is a anything other than a string literal" do
|
||||
for node <- [
|
||||
"string",
|
||||
:atom,
|
||||
%{key: :value},
|
||||
{:ast?, [], []}
|
||||
] do
|
||||
refute Empty.applicable?(node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "run/2" do
|
||||
test "adds an empty node to the buffer" do
|
||||
for _ <- [nil, []] do
|
||||
ast = Empty.run(nil)
|
||||
|
||||
assert %Temple.Ast{
|
||||
meta: %{type: :empty},
|
||||
content: nil,
|
||||
children: []
|
||||
} == ast
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
57
test/parser/temple_namespace_nonvoid_test.exs
Normal file
57
test/parser/temple_namespace_nonvoid_test.exs
Normal file
|
@ -0,0 +1,57 @@
|
|||
defmodule Temple.Parser.TempleNamespaceNonvoidTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.TempleNamespaceNonvoid
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is a Temple aliased nonvoid element" do
|
||||
raw_ast =
|
||||
quote do
|
||||
Temple.div do
|
||||
"foo"
|
||||
end
|
||||
end
|
||||
|
||||
assert TempleNamespaceNonvoid.applicable?(raw_ast)
|
||||
end
|
||||
|
||||
test "returns false when the node is anything other than a Temple aliased nonvoid element" do
|
||||
raw_asts = [
|
||||
quote do
|
||||
div do
|
||||
"foo"
|
||||
end
|
||||
end,
|
||||
quote do
|
||||
link to: "/the/route" do
|
||||
"Label"
|
||||
end
|
||||
end
|
||||
]
|
||||
|
||||
for raw_ast <- raw_asts do
|
||||
refute TempleNamespaceNonvoid.applicable?(raw_ast)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "run/2" do
|
||||
test "adds a node to the buffer" do
|
||||
raw_ast =
|
||||
quote do
|
||||
Temple.div class: "foo", id: var do
|
||||
"foo"
|
||||
end
|
||||
end
|
||||
|
||||
ast = TempleNamespaceNonvoid.run(raw_ast)
|
||||
|
||||
assert %Temple.Ast{
|
||||
meta: %{type: :temple_nonvoid},
|
||||
content: "div",
|
||||
attrs: [class: "foo", id: {:var, [], _}],
|
||||
children: [%Temple.Ast{content: "foo"}]
|
||||
} = ast
|
||||
end
|
||||
end
|
||||
end
|
53
test/parser/temple_namespace_void_test.exs
Normal file
53
test/parser/temple_namespace_void_test.exs
Normal file
|
@ -0,0 +1,53 @@
|
|||
defmodule Temple.Parser.TempleNamespaceVoidTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.TempleNamespaceVoid
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is a Temple aliased nonvoid element" do
|
||||
raw_ast =
|
||||
quote do
|
||||
Temple.input(name: "bob")
|
||||
end
|
||||
|
||||
assert TempleNamespaceVoid.applicable?(raw_ast)
|
||||
end
|
||||
|
||||
test "returns false when the node is anything other than a Temple aliased nonvoid element" 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 TempleNamespaceVoid.applicable?(raw_ast)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "run/2" do
|
||||
test "adds a node to the buffer" do
|
||||
raw_ast =
|
||||
quote do
|
||||
Temple.meta(class: "foo", id: var)
|
||||
end
|
||||
|
||||
ast = TempleNamespaceVoid.run(raw_ast)
|
||||
|
||||
assert %Temple.Ast{
|
||||
meta: %{type: :temple_void},
|
||||
content: "meta",
|
||||
attrs: [class: "foo", id: {:var, [], _}],
|
||||
children: []
|
||||
} = ast
|
||||
end
|
||||
end
|
||||
end
|
35
test/parser/text_test.exs
Normal file
35
test/parser/text_test.exs
Normal file
|
@ -0,0 +1,35 @@
|
|||
defmodule Temple.Parser.TextTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.Text
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is a string literal" do
|
||||
assert Text.applicable?("string literal")
|
||||
end
|
||||
|
||||
test "returns fals when the node is a anything other than a string literal" do
|
||||
for node <- [
|
||||
:atom,
|
||||
%{key: :value},
|
||||
{:ast?, [], []},
|
||||
[]
|
||||
] do
|
||||
refute Text.applicable?(node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "run/2" do
|
||||
test "adds a text node to the buffer" do
|
||||
text = "string literal"
|
||||
ast = Text.run(text)
|
||||
|
||||
assert %Temple.Ast{
|
||||
meta: %{type: :text},
|
||||
content: text,
|
||||
children: []
|
||||
} == ast
|
||||
end
|
||||
end
|
||||
end
|
Reference in a new issue