Data.Parser (data v0.6.0)

Higher-order functions to create and modify parsers.

Summary

Types

A parser is a function that takes any value as input and produces a Result.t.

Functions

Takes a parser p and creates a parser that will successfully parse lists of values that all satisfy p.

Takes a key parser and a value parser and creates a parser that will parse maps of keys and values.

Takes a parser and transforms it so that it works ~c"inside" Maybe.t values.

Creates a parser that behaves exactly the same as the list/1 parser, except that it will return the domain error :empty_list if applied to an empty list.

Takes a list of values, elements, and returns a parser that returns successfully if its input is present in elements.

Takes a boolean function p (the predicate), and returns a parser that parses successfully those values for which p is true.

Takes a boolean function p (the predicate) and a default value, and returns a parser that parses successfully those values for which p is true.

Takes a parser p and creates a parser that will successfully parse sets of values that all satisfy p.

Takes a list of parsers and creates a parser that returns the first successful parse result, or an error listing the parsers and the failed input.

Types

t(a, b)

@type t(a, b) :: (any() -> FE.Result.t(a, b))

A parser is a function that takes any value as input and produces a Result.t.

More specifically, a parser(a,b) is a fuction that takes any input and returns {:ok, a} on a successful parse or {:error, b} if parsing failed.

Functions

kv(fields)

See Data.Parser.KV.new/1.

list(p)

@spec list(t(a, Error.t())) :: t([a], Error.t())

Takes a parser p and creates a parser that will successfully parse lists of values that all satisfy p.

Specifically, the input:

  1. Must be a list

  2. p must parse successfully all elements in the input

If this is the case, the output will be {:ok, list_of_parsed_values}.

If not all values can be parsed with p, the result will be the original parse error, enriched with the field :failed_element in the error details.

If the input is not a list, the domain error :not_a_list will be returned.

Examples

iex> Data.Parser.list(Data.Parser.BuiltIn.integer()).([])
{:ok, []}

iex> Data.Parser.list(Data.Parser.BuiltIn.integer()).([1,2,3])
{:ok, [1, 2, 3]}

iex> {:error, e} = Data.Parser.list(Data.Parser.BuiltIn.integer()).(%{a: :b})
...> Error.reason(e)
:not_a_list

iex> {:error, e} = Data.Parser.list(Data.Parser.BuiltIn.integer()).([1, :b, 3])
...> Error.reason(e)
:not_an_integer
...> Error.details(e)
%{failed_element: :b}

map(key_parser, value_parser)

@spec map(t(a, Error.t()), t(b, Error.t())) :: t(%{required(a) => b}, Error.t())

Takes a key parser and a value parser and creates a parser that will parse maps of keys and values.

Specifically, the input:

  1. Must be a map

  2. All keys of the input map must be parsed correctly by the key parser

  3. All values of the input map must be parsed correctly by the value parser

If this is the case, the output will be {:ok, map_of_parsed_keys_and_values}.

If not all keys can be parsed with the key parser, the result will be the original parse error, enriched with the field :failed_key in the error details.

If not all values can be parsed with the value parser, the result will be the original parse error, enriched with the field :failed_value in the error details.

If the input is not a map, the domain error :not_a_map will be returned.

Examples

iex> Data.Parser.map(Data.Parser.BuiltIn.string(), Data.Parser.BuiltIn.integer()).(%{})
{:ok, %{}}

iex> Data.Parser.map(Data.Parser.BuiltIn.string(), Data.Parser.BuiltIn.integer()).(%{"a" => 1, "b" => 2})
{:ok, %{"a" => 1, "b" => 2}}

iex> {:error, e} = Data.Parser.map(Data.Parser.BuiltIn.string(), Data.Parser.BuiltIn.integer()).([])
...> Error.reason(e)
:not_a_map

iex> {:error, e} = Data.Parser.map(Data.Parser.BuiltIn.string(), Data.Parser.BuiltIn.integer()).(%{:a => 1})
...> Error.reason(e)
:failed_to_parse_key
...> Error.details(e)
%{key: :a}

iex> {:error, e} = Data.Parser.map(Data.Parser.BuiltIn.string(), Data.Parser.BuiltIn.integer()).(%{"a" => "not_int"})
...> Error.reason(e)
:failed_to_parse_value
...> Error.details(e)
%{key: "a", value: "not_int"}

maybe(parser)

@spec maybe(t(a, b)) :: t(FE.Maybe.t(a), FE.Maybe.t(b))

Takes a parser and transforms it so that it works ~c"inside" Maybe.t values.

If the original parser works on String.t(), the new one will work on Maybe.t(String.t()).

Successful parses on just() values return {:ok, {:just, result_of_parse}}. Unsuccessful parses on just() values reutrn {:error, parse_error}.

The parser will successfully return {:ok, :nothing} when applied to :nothing.

Examples

iex(2)> Data.Parser.maybe(
...> Data.Parser.predicate( &is_binary/1, :invalid)).({:just, "good"})
{:ok, {:just, "good"}}

iex> Data.Parser.maybe(
...> Data.Parser.predicate( &is_binary/1, :invalid)).({:just, ~c"bad"})
{:error, :invalid}

iex> Data.Parser.maybe(
...> Data.Parser.predicate( &is_binary/1, :invalid)).(:nothing)
{:ok, :nothing}

nonempty_list(p)

@spec nonempty_list(t(a, Error.t())) :: t([a, ...], Error.t())

Creates a parser that behaves exactly the same as the list/1 parser, except that it will return the domain error :empty_list if applied to an empty list.

Examples

iex> Data.Parser.nonempty_list(Data.Parser.BuiltIn.integer()).([1, 2, 3])
{:ok, [1, 2, 3]}

iex> {:error, e} = Data.Parser.nonempty_list(Data.Parser.BuiltIn.integer()).([1, :b, 3])
...> Error.reason(e)
:not_an_integer
...> Error.details(e)
 %{failed_element: :b}


iex> {:error, e} = Data.Parser.nonempty_list(Data.Parser.BuiltIn.integer()).([])
...> Error.reason(e)
:empty_list

one_of(elements, default)

@spec one_of([a], b | (a -> b)) :: t(a, b) when a: var, b: var

Takes a list of values, elements, and returns a parser that returns successfully if its input is present in elements.

If the input is not a member of elements and default is a value, the parser fails with {:error, default}. If default is a unary function, the parser fails with {:error, default.(input)}.

Examples

iex> Data.Parser.one_of([:he, :ne, :ar, :kr, :xe, :rn], "not a noble gas").(:he)
{:ok, :he}

iex> Data.Parser.one_of([:he, :ne, :ar, :kr, :xe, :rn], "not a noble gas").(:n)
{:error, "not a noble gas"}

iex> Data.Parser.one_of([:he, :ne, :ar, :kr, :xe, :rn],
...> fn x -> "not a noble gas: #{inspect x}" end).(:o)
{:error, "not a noble gas: :o"}

predicate(p)

@spec predicate((a -> boolean())) :: t(a, Error.t()) when a: var

Takes a boolean function p (the predicate), and returns a parser that parses successfully those values for which p is true.

If the predicate returns false the parser will return a domain Error with the input value and the predicate functions listed in the error details.

Examples

iex> {:error, e} = Data.Parser.predicate(&is_binary/1).(~c"charlists are not ok")
...> e.reason
:predicate_not_satisfied
...> e.details
%{input: ~c"charlists are not ok", predicate: &is_binary/1}

iex> Data.Parser.predicate(&is_binary/1).("this is fine")
{:ok, "this is fine"}

iex> Data.Parser.predicate(&(&1<10)).(5)
{:ok, 5}

iex> {:error, e} = Data.Parser.predicate(&(&1<10)).(55)
...> e.details.input
55

predicate(p, default)

@spec predicate((a -> boolean()), b | (a -> b)) :: t(a, b) when a: var, b: var

Takes a boolean function p (the predicate) and a default value, and returns a parser that parses successfully those values for which p is true.

If the predicate function applied to the input returns true, the parser wraps the input in an {:ok, input} tuple.

If the predicate function returns false, and default is a value, the parser returns {:error, default}

If the predicate returns false and default is a unary function, the parser returns {:error, default.(the_failed_input)}.

Examples

iex> Data.Parser.predicate(&is_binary/1, "invalid string").(~c"charlists are not ok")
{:error, "invalid string"}

iex> Data.Parser.predicate(&is_binary/1, "invalid string").(<<0::1, 1::2>>)
{:error, "invalid string"}

iex> Data.Parser.predicate(&is_binary/1, "invalid string").("this is fine")
{:ok, "this is fine"}

iex> Data.Parser.predicate(&is_binary/1, fn x -> "the bad value is: #{inspect x}" end).(12345)
{:error, "the bad value is: 12345"}

set(p)

@spec set(t(a, Error.t())) :: t(MapSet.t(a), Error.t())

Takes a parser p and creates a parser that will successfully parse sets of values that all satisfy p.

Specifically, the input:

  1. must be a MapSet

  2. all elements of the input set must be parsed correctly by p

If this is the case, the output will be {:ok, set_of_parsed_values}.

If not all values can be parsed with p, the result will be the original parse error, enriched with the field :failed_element in the error details.

If the input is not a MapSet, the domain error :not_a_set will be returned.

Examples

iex> {:ok, s} = Data.Parser.set(Data.Parser.BuiltIn.integer()).(MapSet.new())
...> s
MapSet.new([])

iex> {:ok, s} = Data.Parser.set(Data.Parser.BuiltIn.integer()).(MapSet.new([1,2,3]))
...> s
MapSet.new([1, 2, 3])

iex> {:error, e} = Data.Parser.set(Data.Parser.BuiltIn.integer()).(%{a: :b})
...> Error.reason(e)
:not_a_set

iex> {:error, e} = Data.Parser.set(Data.Parser.BuiltIn.integer()).(MapSet.new([1, :b, 3]))
...> Error.reason(e)
:not_an_integer
...> Error.details(e)
%{failed_element: :b}

union(parsers)

@spec union([t(any(), any())]) :: t(any(), any())

Takes a list of parsers and creates a parser that returns the first successful parse result, or an error listing the parsers and the failed input.

Examples

iex> Data.Parser.union(
...> [Data.Parser.BuiltIn.integer(),
...>  Data.Parser.BuiltIn.boolean()]).(true)
{:ok, true}

iex> Data.Parser.union(
...> [Data.Parser.BuiltIn.integer(),
...>  Data.Parser.BuiltIn.boolean()]).(1)
{:ok, 1}

iex> {:error, e} = Data.Parser.union(
...>   [Data.Parser.BuiltIn.integer(),
...>    Data.Parser.BuiltIn.boolean()]).(:atom)
...> Error.reason(e)
:no_parser_applies
...> Error.details(e).input
:atom