lib/json/parser/number.ex
defmodule JSON.Parser.Number do
@moduledoc """
Implements a JSON Numeber Parser for Bitstring values
"""
@doc """
parses a valid JSON numerical value, returns its elixir numerical representation
## Examples
iex> JSON.Parser.Number.parse ""
{:error, :unexpected_end_of_buffer}
iex> JSON.Parser.Number.parse "face0ff"
{:error, {:unexpected_token, "face0ff"}}
iex> JSON.Parser.Number.parse "-hello"
{:error, {:unexpected_token, "hello"}}
iex> JSON.Parser.Number.parse "129245"
{:ok, 129245, ""}
iex> JSON.Parser.Number.parse "7.something"
{:ok, 7, ".something"}
iex> JSON.Parser.Number.parse "7.4566something"
{:ok, 7.4566, "something"}
iex> JSON.Parser.Number.parse "-88.22suffix"
{:ok, -88.22, "suffix"}
iex> JSON.Parser.Number.parse "-12e4and then some"
{:ok, -1.2e+5, "and then some"}
iex> JSON.Parser.Number.parse "7842490016E-12-and more"
{:ok, 7.842490016e-3, "-and more"}
"""
def parse(<<?-, rest::binary>>) do
case parse(rest) do
{:ok, number, json} -> {:ok, -1 * number, json}
{:error, error_info} -> {:error, error_info}
end
end
def parse(binary) do
case binary do
<<number::utf8, _::binary>> when number in ?0..?9 ->
binary |> to_integer |> add_fractional |> apply_exponent
<<>> ->
{:error, :unexpected_end_of_buffer}
_ ->
{:error, {:unexpected_token, binary}}
end
end
# error condition
defp add_fractional({:error, error_info}), do: {:error, error_info}
# stop condition
defp add_fractional({:ok, acc, bin}) do
case bin do
<<?., after_dot::binary>> ->
case after_dot do
<<c::utf8, _::binary>> when c in ?0..?9 ->
{fractional, rest} = parse_fractional(after_dot, 0, 10.0)
{:ok, acc + fractional, rest}
_ ->
{:ok, acc, bin}
end
_ ->
{:ok, acc, bin}
end
end
defp parse_fractional(<<number::utf8, rest::binary>>, acc, power) when number in ?0..?9 do
parse_fractional(rest, acc + (number - ?0) / power, power * 10)
end
defp parse_fractional(json, acc, _) when is_binary(json), do: {acc, json}
# error condition
defp apply_exponent({:error, error_info}), do: {:error, error_info}
# stop condition
defp apply_exponent({:ok, acc, <<exponent::utf8, rest::binary>>}) when exponent in 'eE' do
case to_integer(rest) do
{:ok, power, rest} -> {:ok, acc * :math.pow(10, power), rest}
{:error, error_info} -> {:error, error_info}
end
end
defp apply_exponent({:ok, acc, json}), do: {:ok, acc, json}
defp to_integer(<<>>), do: {:error, :unexpected_end_of_buffer}
defp to_integer(binary) do
case Integer.parse(binary) do
{result, rest} when is_integer(result) and is_binary(rest) -> {:ok, result, rest}
:error -> {:error, {:unexpected_token, binary}}
end
end
end