Current section
Files
Jump to
Current section
Files
lib/parselix.ex
defmodule Parselix do
@moduledoc """
Provides the macro for creating parser and some helper functions.
"""
defmacro __using__(_opts) do
quote do
import Parselix
alias Parselix.Position
alias Parselix.Meta
end
end
defmodule Position, do: defstruct index: 0, vertical: 0, horizontal: 0
defmodule Meta, do: defstruct label: nil, value: nil, position: %Position{}
@typedoc """
A successful result of parser.
`{:ok, RESULT, REMAINDER, NEW_POSITION}`
"""
@type ok :: {:ok, any, String.t, %Position{}}
@typedoc """
A failed result of parser.
`{:error, ERROR_MESSAGE, POSITION}`
"""
@type error :: {:error, String.t, %Position{}}
@typedoc """
A successful result of source parser.
`{:ok, RESULT, LENGTH_OF_CONSUMED_STRING}`
"""
@type ok_1 :: {:ok, any, integer}
@typedoc """
A successful result of source parser.
`{:ok, RESULT, REMAINDER}`
"""
@type ok_2 :: {:ok, any, String.t}
@typedoc """
A failed result of source parser.
`{:error, ERROR_MESSAGE}`
"""
@type error_1 :: {:error, String.t}
@typedoc "A parser generated by parser/2."
@type produced_parser :: (String.t, %Position{} -> ok | error)
@typedoc """
A parser passed to parser/2.
## Parameters
1. Its own parser.
2. The option which is passed to your parser.
3. Target string.
4. Current position.
"""
@type source_parser :: (produced_parser, any, String.t, %Position{} -> ok | ok_1 | ok_2 | error | error_1)
def position(index \\ 0, vertical \\ 0, horizontal \\ 0), do: %Position{index: index, vertical: vertical, horizontal: horizontal}
def parse(parser, target, position \\ position) do
parser.(target, position)
end
def get_position(current, target, consumed) when is_integer(consumed) do
used = String.slice target, 0, consumed
used_list = String.codepoints used
a = fn x ->
case x do
nil -> []
x -> x
end
end
vertical = (used_list |> Enum.count(fn x -> x == "\n" or x == "\r" end)) - length(a.(Regex.run ~r/\r\n/, used))
get_horizontal = fn
[head | tail], count, get_horizontal -> case head do
x when x == "\r" or x == "\n" -> get_horizontal.(tail, 0, get_horizontal)
_ -> get_horizontal.(tail, count + 1, get_horizontal)
end
[], count, _ -> count
end
horizontal = get_horizontal.(used_list, current.horizontal, get_horizontal)
%Position{
index: current.index + (String.length used),
vertical: current.vertical + vertical,
horizontal: horizontal
}
end
def get_position(current, target, remainder) when is_binary(remainder) do
get_position current, target, String.length(target) - String.length(remainder)
end
defp parser_body(name, parser_name, block) do
quote do
fn target, current_position ->
own = fn x -> apply(__MODULE__, unquote(parser_name), [x]) end
case (unquote(block)).(own, option, target, current_position) do
{:ok, children, remainder, position} = x -> x
{:ok, children, remainder} when is_binary(remainder) ->
{:ok, children, remainder, get_position(current_position, target, remainder)}
{:ok, children, consumed} when is_integer(consumed) ->
{:ok, children, String.slice(target, Range.new(consumed, -1)), get_position(current_position, target, consumed)}
{:error, message, position} = x -> x
{:error, message} -> {:error, message, current_position}
x -> {:error, "\"" <> unquote(name) <> "\" returns a misformed result.\n#{inspect x}", current_position}
end
end
end
end
@doc """
Defines a parser.
## Example
parser "any" do
fn
_parser, _option, "", position ->
{:error, "EOF appeared.", position}
_parser, _option, x, _position ->
{:ok, String.first(x), 1}
end
end
"""
defmacro parser(name, do: block) do
parser_name = String.to_atom(name)
body = parser_body(name, parser_name, block)
quote do
def unquote(parser_name)(option \\ nil) do
unquote(body)
end
end
end
@doc """
Defines a private parser.
"""
defmacro parserp(name, do: block) do
parser_name = String.to_atom(name)
body = parser_body(name, parser_name, block)
quote do
defp unquote(parser_name)(option \\ nil) do
unquote(body)
end
end
end
end