lib/kuddle/utils.ex

defmodule Kuddle.Utils do
  @non_identifier_characters [?\\, ?<, ?>, ?{, ?}, ?;, ?[, ?], ?(, ?), ?=, ?,, ?"]

  @doc """
  Check if a string is a valid identifier (that is a plain unbroken character sequence)
  """
  @spec valid_identifier?(String.t()) :: boolean()
  def valid_identifier?(str, state \\ :start)

  def valid_identifier?(<<c::utf8, _rest::binary>>, _) when c in @non_identifier_characters do
    false
  end

  def valid_identifier?(<<c::utf8, _rest::binary>>, _) when c < 0x20 or c > 0x10FFFF do
    false
  end

  def valid_identifier?(<<c::utf8, _rest::binary>>, :start) when c in ?0..?9 do
    false
  end

  def valid_identifier?(<<_c::utf8, rest::binary>>, _) do
    valid_identifier?(rest, :body)
  end

  def valid_identifier?(<<>>, :start) do
    true
  end

  def valid_identifier?(<<>>, :body) do
    true
  end

  def need_quote?(str, state \\ :start)

  def need_quote?(<<c::utf8, _rest::binary>>, _) when c in @non_identifier_characters do
    true
  end

  def need_quote?(<<c::utf8, _rest::binary>>, _) when c < 0x20 or c > 0x10FFFF do
    true
  end

  def need_quote?(<<c::utf8, _rest::binary>>, :start) when c in ?0..?9 do
    true
  end

  def need_quote?(<<_c::utf8, rest::binary>>, _) do
    need_quote?(rest, :body)
  end

  def need_quote?(<<>>, :start) do
    true
  end

  def need_quote?(<<>>, :body) do
    false
  end

  def parse_float_string(bin, state \\ :start, acc \\ [])

  def parse_float_string(<<>>, :start, _acc) do
    {:error, :invalid_float_format}
  end

  def parse_float_string(<<>>, state, acc) when state in [:fraction, :exponent] do
    {:ok, IO.iodata_to_binary(Enum.reverse(acc))}
  end

  def parse_float_string(<<c::utf8, rest::binary>>, :start, acc) when c == ?- or
                                                                      c == ?+  do
    parse_float_string(rest, :start_number, [<<c::utf8>> | acc])
  end

  def parse_float_string(<<c::utf8, rest::binary>>, state, acc) when c in ?0..?9 and state in [:start, :start_number, :body] do
    parse_float_string(rest, :body, [<<c::utf8>> | acc])
  end

  def parse_float_string(<<".", rest::binary>>, :body, acc) do
    parse_float_string(rest, :start_fraction, [<<".">> | acc])
  end

  def parse_float_string(<<"_", rest::binary>>, :body, acc) do
    parse_float_string(rest, :body, acc)
  end

  def parse_float_string(<<"E", rest::binary>>, state, acc) when state in [:body, :fraction] do
    parse_float_string(rest, :start_exponent, [<<"E">> | acc])
  end

  def parse_float_string(<<"e", rest::binary>>, state, acc) when state in [:body, :fraction] do
    parse_float_string(rest, :start_exponent, [<<"E">> | acc])
  end

  def parse_float_string(<<c::utf8, rest::binary>>, state, acc) when c in ?0..?9 and state in [:fraction, :start_fraction] do
    parse_float_string(rest, :fraction, [<<c::utf8>> | acc])
  end

  def parse_float_string(<<c::utf8, rest::binary>>, :start_exponent, acc) when c == ?- or
                                                                               c == ?+  do
    parse_float_string(rest, :exponent, [<<c::utf8>> | acc])
  end

  def parse_float_string(<<c::utf8, rest::binary>>, state, acc) when c in ?0..?9 and state in [:start_exponent, :exponent] do
    parse_float_string(rest, :exponent, [<<c::utf8>> | acc])
  end

  def parse_float_string(<<"_", rest::binary>>, :exponent, acc) do
    parse_float_string(rest, :exponent, acc)
  end

  def parse_float_string(_, _state, _acc) do
    {:error, :unexpected_characters}
  end
end