Start porting parsers to use AST

This commit is contained in:
Mitchell Hanberg 2021-03-15 00:46:16 -04:00
parent 9d6f7c176d
commit 358b5ea4cc
14 changed files with 329 additions and 17 deletions

View file

@ -1,2 +1,2 @@
elixir 1.10.2
erlang 23.0.1
erlang 23.2.6

View file

@ -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
View file

@ -0,0 +1,7 @@
defmodule Temple.Ast do
defstruct content: nil, attrs: [], children: [], meta: %{}
def new(opts \\ []) do
struct(__MODULE__, opts)
end
end

View file

@ -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}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View 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

View 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

View 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
View 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