cblage/elixir-json

View on GitHub
lib/json/parser/string.ex

Summary

Maintainability
Test Coverage
A
100%
defmodule JSON.Parser.String do
  @moduledoc """
  Implements a JSON String Parser for Bitstring values
  """

  alias JSON.Parser.Unicode, as: UnicodeParser

  use Bitwise

  @doc """
  parses a valid JSON string, returns its elixir representation

  ## Examples

      iex> JSON.Parser.String.parse ""
      {:error, :unexpected_end_of_buffer}

      iex> JSON.Parser.String.parse "face0ff"
      {:error, {:unexpected_token, "face0ff"}}

      iex> JSON.Parser.String.parse "-hello"
      {:error, {:unexpected_token, "-hello"}}

      iex> JSON.Parser.String.parse "129245"
      {:error, {:unexpected_token, "129245"}}

      iex> JSON.Parser.String.parse "\\\"7.something\\\""
      {:ok, "7.something", ""}

      iex> JSON.Parser.String.parse "\\\"-88.22suffix\\\" foo bar"
      {:ok, "-88.22suffix", " foo bar"}

      iex> JSON.Parser.String.parse "\\\"star -> \\\\u272d <- star\\\""
      {:ok, "star -> ✭ <- star", ""}

      iex> JSON.Parser.String.parse "\\\"\\\\u00df ist wunderbar\\\""
      {:ok, "ß ist wunderbar", ""}

      iex> JSON.Parser.String.parse "\\\"Rafaëlla\\\" foo bar"
      {:ok, "Rafaëlla", " foo bar"}

      iex> JSON.Parser.String.parse "\\\"Éloise woot\\\" Éloise"
      {:ok, "Éloise woot", " Éloise"}
  """
  def parse(<<?"::utf8, json::binary>>), do: parse_string_contents(json, [])
  def parse(<<>>), do: {:error, :unexpected_end_of_buffer}
  def parse(json), do: {:error, {:unexpected_token, json}}

  # stop conditions
  defp parse_string_contents(<<>>, _), do: {:error, :unexpected_end_of_buffer}

  # found the closing ", lets reverse the acc and encode it as a string!
  defp parse_string_contents(<<?"::utf8, json::binary>>, acc) do
    encoded = acc |> Enum.reverse() |> List.to_string()
    {:ok, encoded, json}
  end

  # parsing
  defp parse_string_contents(<<?\\, ?f, json::binary>>, acc),
    do: parse_string_contents(json, [?\f | acc])

  defp parse_string_contents(<<?\\, ?n, json::binary>>, acc),
    do: parse_string_contents(json, [?\n | acc])

  defp parse_string_contents(<<?\\, ?r, json::binary>>, acc),
    do: parse_string_contents(json, [?\r | acc])

  defp parse_string_contents(<<?\\, ?t, json::binary>>, acc),
    do: parse_string_contents(json, [?\t | acc])

  defp parse_string_contents(<<?\\, ?", json::binary>>, acc),
    do: parse_string_contents(json, [?" | acc])

  defp parse_string_contents(<<?\\, ?\\, json::binary>>, acc),
    do: parse_string_contents(json, [?\\ | acc])

  defp parse_string_contents(<<?\\, ?/, json::binary>>, acc),
    do: parse_string_contents(json, [?/ | acc])

  defp parse_string_contents(<<?\\, ?u, _::binary>> = bin, acc) do
    case UnicodeParser.parse(bin) do
      {:error, error_info} ->
        {:error, error_info}

      {:ok, decoded_unicode_codepoint, after_codepoint} ->
        case decoded_unicode_codepoint do
          <<_::utf8>> ->
            parse_string_contents(after_codepoint, [decoded_unicode_codepoint | acc])

          _ ->
            {:error, {:unexpected_token, bin}}
        end
    end
  end

  defp parse_string_contents(<<char::utf8, json::binary>>, acc) do
    parse_string_contents(json, [char | acc])
  end
end