Current section
Files
Jump to
Current section
Files
lib/parser/parser.ex
defmodule ExGherkin.Parser do
@moduledoc false
alias ExGherkin.Utils
def run(tokens) when is_list(tokens) do
{comment_tokens, main_tokens} =
tokens
|> Enum.filter(fn token ->
elem(token, 0) != :empty
end)
|> Enum.split_with(fn token ->
elem(token, 0) == :comment
end)
main_tokens
|> prepare
|> Utils.introspect(:prepare)
|> case do
error = {:error, _} ->
error
[] ->
validate_comments_not_preceding_language_token(
{normalize_comments(comment_tokens), :empty}
)
main_tokens ->
main_tokens
|> :parser.parse()
|> case do
{:ok, result} ->
validate_comments_not_preceding_language_token(
{normalize_comments(comment_tokens), result}
)
error ->
error
end
end
end
def prepare(tokens) do
tokens
|> Enum.reverse()
|> Enum.reduce_while({:none, []}, fn full_token = {token, _, _, _},
{full_prev_token, tokens} ->
token
|> case do
:comment -> raise "Unexpected Token: #{token}"
:tag -> tag_it(full_token, {full_prev_token, tokens})
_ -> {:ok, {full_token, [full_token | tokens]}}
end
|> case do
{:ok, prepared} -> {:cont, prepared}
error -> {:halt, {:error, error}}
end
end)
|> elem(1)
end
defp tag_it(current_token = {:tag, _, _, _}, {:none, _}) do
invalid_tagging_error(current_token, :tagged_eof)
end
defp tag_it(
full_tag_token = {:tag, label, location, content},
{full_prev_token = {prev_token, _, _, _}, tokens}
) do
prev_token
|> case do
:given ->
invalid_tagging_error(full_prev_token, :untaggable_token)
:when ->
invalid_tagging_error(full_prev_token, :untaggable_token)
:then ->
invalid_tagging_error(full_prev_token, :untaggable_token)
:and ->
invalid_tagging_error(full_prev_token, :untaggable_token)
:but ->
invalid_tagging_error(full_prev_token, :untaggable_token)
:content ->
invalid_tagging_error(full_prev_token, :untaggable_token)
:language ->
invalid_tagging_error(full_tag_token, :tagged_language_token)
:doc_string ->
invalid_tagging_error(full_prev_token, :untaggable_token)
:data_table ->
invalid_tagging_error(full_prev_token, :untaggable_token)
_ ->
{:ok,
{full_prev_token,
[{String.to_atom("#{prev_token}_tag"), label, location, content} | tokens]}}
end
end
defp invalid_tagging_error(full_prev_token, error_code),
do: {:error, {full_prev_token, :outside_yrl_parser, error_code}}
defp validate_comments_not_preceding_language_token(result = {_, :empty}), do: {:ok, result}
defp validate_comments_not_preceding_language_token(
result = {{:comments, _, {:constituents, []}}, _}
),
do: {:ok, result}
defp validate_comments_not_preceding_language_token(
result =
{{:comments, _,
{
:constituents,
[
first_comment =
{:comment, {:meta, {{:location, {{:line, first_comment_line}, _}}, _}, _}, _}
| _
]
}},
{:feature,
{:meta,
{_, _, {:language, {:meta, {{:location, {{:line, language_line}, _}}, _}, _}, _}},
_}, _}}
) do
if first_comment_line < language_line do
{:error, {first_comment, :outside_yrl_parser, :commented_language_token}}
else
{:ok, result}
end
end
defp validate_comments_not_preceding_language_token(result), do: {:ok, result}
defp normalize_comments(comments) do
{
:comments,
{:meta, {:none, :none}, {:type, :multiples}},
{:constituents, Enum.map(comments, &normalize_comment/1)}
}
end
defp normalize_comment({:comment, token_label, {:location, line, column}, comment_text}) do
{:comment,
{
:meta,
{
{
:location,
{
{:line, line},
{:column, column}
}
},
{
:token_label,
token_label
}
},
{
:type,
:singular
}
}, comment_text}
end
end