lib/sparql/language/parse_helper.ex

defmodule SPARQL.Language.ParseHelper do
  alias RDF.Literal

  def variable('?' ++ name), do: List.to_string(name)
  def variable('$' ++ name), do: List.to_string(name)

  # TODO: Literal construction should not happen in the lexer, but during parsing;
  #       grammars and RDF.Serialization.ParseHelper should be rewritten accordingly
  def extract_literal({_, _, literal}), do: literal


  def strip_sign(%Literal{} = literal) do
    {_sign, number} = literal |> Literal.lexical() |> String.split_at(1)
    Literal.update(literal, fn _ -> number end)
  end


  def arithmetic_expr({:rpn, rpn}), do: rpn_to_ast(rpn)
  def arithmetic_expr(expr),        do: expr

  def arithmetic_expr(left, [operator, {:rpn, rpn}]) do
    arithmetic_expr(left, [operator, rpn_to_ast(rpn)])
  end

  def arithmetic_expr({:rpn, rpn}, [operator, right]) do
    arithmetic_expr(rpn_to_ast(rpn), [operator, right])
  end

  def arithmetic_expr([{left_operator, _}, left], {:rpn, rpn})
      when left_operator in ~w[* /]a do
    {:rpn, [left, left_operator | rpn]}
  end

  def arithmetic_expr([{left_operator, _}, left], [{right_operator, _}, right])
      when left_operator in ~w[* /]a and right_operator in ~w[* /]a do
    {:rpn, [left, left_operator, right, right_operator]}
  end

  def arithmetic_expr(left, [operator, right]) do
    {:builtin_function_call, arithmetic_operator(operator), [left, right]}
  end

  def arithmetic_expr(left, {:rpn, rpn}) do
    rpn_to_ast([left | rpn])
  end

  def arithmetic_expr(left_operator, left, [right_operator, {:rpn, rpn}]) do
    arithmetic_expr(left_operator, left, [right_operator, rpn_to_ast(rpn)])
  end

  def arithmetic_expr(left_operator, left, [right_operator, right]) do
    {:rpn, [left, arithmetic_operator(left_operator), right, arithmetic_operator(right_operator)]}
  end

  def arithmetic_expr(left_operator, left, {:rpn, rpn}) do
    {:rpn, [left, arithmetic_operator(left_operator) | rpn]}
  end

  def arithmetic_quirk_expr(sign, left, {:rpn, rpn}, [additive_operator, right]) do
    {:rpn,
      [left | rpn] ++ [
        sign,
        right,
        arithmetic_operator(additive_operator)
      ]
    }
  end

  def arithmetic_quirk_expr(sign, left,
                      [{multiplicative_operator, _}, middle],
                      [additive_operator, right]) do
    {:rpn,
      [
        arithmetic_expr(left, [multiplicative_operator, middle]),
        sign,
        right,
        arithmetic_operator(additive_operator)
      ]
    }
  end

  def multiplicative_expr({left_operator, _}, left, {:rpn, [right | rpn]}) do
    {:rpn, [left, right, left_operator | rpn]}
  end

  def multiplicative_expr({left_operator, _}, left, right) do
    {:rpn, [left, right, left_operator]}
  end

  def multiplicative_quirk_expr(sign, left, {:rpn, rpn}) do
    {:rpn, [left | rpn] ++ [sign]}
  end

  def multiplicative_quirk_expr(sign, left, [{right_operator, _}, right]) do
    {:rpn, [left, right, right_operator, sign]}
  end

  defp arithmetic_operator({operator, _}),                   do: operator
  defp arithmetic_operator(operator) when is_atom(operator), do: operator

  defp rpn_to_ast(rpn) do
    {function_call, []} = rpn |> Enum.reverse() |> do_rpn_to_ast()
    function_call
  end

  defp do_rpn_to_ast([operator | rest]) when operator in ~w[+ - * /]a do
    {right, rest} = do_rpn_to_ast(rest)
    {left,  rest} = do_rpn_to_ast(rest)
    {{:builtin_function_call, operator, [left, right]}, rest}
  end

  defp do_rpn_to_ast([arg | rest]), do: {arg, rest}

end