defmodule Given.Parser do
@moduledoc """
Parse a textual scenario into a keyword list of clauses.
See `Given.Case` for a description on how to structure your features and
scenarios, what follows are the rules inside a scenario.
A scenario is a list of clauses. Each clause must start with one of
GIVEN, WHEN, or THEN in UPPER or Title case. The remainder of a clause is
split into terms and passed to your code as a tuple.
There are some restrictions on the terms supported - more flexibility and
more terms will be added.
The following terms are supported:
* atoms - only simple roman alphabet with colon prefix e.g. `:abc`
* dates - ISO-8601 in the format `YYYY-MM-DD` becomes `Date`
* floats
* hexadecimal - with `0x` prefix e.g. `0xFE` is 254
* integer - positive and negative base 10 without separators
* range - a dashed pair of positive integers e.g. `1-6` becomes `1..6`
* string - any unicode chars within double quotes (no escaping of quotes)
* time - ISO-8601 in the format `HH:MM:SS` becomes `Time`
* words - only using the Roman alphabet (temporary restriction!)
Not currently supported but planned:
* atoms with extended alphabet
* date times
* lists
* time locale
* words using any alphabet
Any consecutive words will be collapsed into a single atom. For example
the words "the cat sat on the mat" becomes `:the_cat_sat_on_the_mat`
The clause:
"Elîxir 1.0" was released 2014-09-18
becomes the tuple:
{"Elîxir 1.0", :was_released, ~D[2014-09-18]}
"""
alias Given.SyntaxError
@type input :: binary | list
@doc "Parse a textual scenario into a keyword list of clauses"
@spec parse(input) :: {:ok, Keyword.t()} | {:error, term} | {:error, term, pos_integer}
def parse(b) when is_binary(b), do: b |> to_charlist() |> parse()
def parse(s) do
with {:ok, tokens, _} <- :given_lexer.string(s) do
:given_parser.parse(tokens)
end
end
@doc """
Parse a textual scenario into a keyword list of clauses.
Intended for internal use - expects the `__CALLER__` struct to be supplied
and returns error constructor in case of syntax error.
"""
@spec parse!(input, Macro.Env.t()) :: {:ok, Keyword.t()} | {:error, atom, Keyword.t()}
def parse!(prose, %{file: file, line: line}) do
case parse(prose) do
{:ok, result} ->
{:ok, result}
{:error, {sub_line, :given_lexer, error}, _} ->
{:error, SyntaxError, [error: error, file: file, line: line + sub_line - 1]}
{:error, {sub_line, :given_parser, error}} ->
{:error, SyntaxError, [error: error, file: file, line: line + sub_line - 1]}
end
end
end