Current section

Files

Jump to
minipeg lib minipeg input.ex
Raw

lib/minipeg/input.ex

defmodule Minipeg.Input do
use Minipeg.Types
@moduledoc ~S"""
An input wrapper
"""
defstruct col: 1, context: %{}, input: "", lnb: 1
@type t :: %__MODULE__{col: pos_integer(), context: map(), input: binary(), lnb: pos_integer()}
@type take_and_rest_t :: {binary(), t()}
@doc ~S"""
`new` just generates an Input struct with default values
iex(1)> new("hello")
%Input{col: 1, lnb: 1, context: %{}, input: "hello"} # Input is aliased in these doctests
But the defaults can be overwritten
iex(2)> new("hello", 1, 2, [])
%Input{col: 1, lnb: 2, context: [], input: "hello"}
The keyword syntax is preferred to overwrite default values, for backward compatibility reasons
we still support the old syntax, and implement `make/2` as shown below
"""
@spec new(input_t(), pos_integer(), pos_integer()) :: t()
def new(source, col \\ 1, lnb \\ 1, context \\ %{})
def new(%__MODULE__{} = src, col, lnb, context), do: %{src | col: col, lnb: lnb, context: context}
def new(source, col, lnb, context) when is_binary(source) do
%__MODULE__{input: source, col: col, lnb: lnb, context: context}
end
@type input_arg_t :: {:col, pos_integer()} | {:lnb, pos_integer()} | {:context, any()}
@type input_args_t :: list(input_arg_t())
@doc ~S"""
The preferred syntax to create Input structs
iex(3)> make("world", col: 2)
%Input{col: 2, lnb: 1, context: %{}, input: "world"}
Of course we can overwrite more defaults
iex(4)> make("world", lnb: 42, col: 2)
%Input{col: 2, lnb: 42, context: %{}, input: "world"}
Or none at all
iex(5)> make("world")
%Input{col: 1, lnb: 1, context: %{}, input: "world"}
"""
@spec make(String.t(), input_args_t()) :: t()
def make(source, opts \\ []) do
col = Keyword.get(opts, :col, 1)
lnb = Keyword.get(opts, :lnb, 1)
context = Keyword.get(opts, :context, %{})
%__MODULE__{input: source, col: col, lnb: lnb, context: context}
end
@spec drop(t(), str_or_count_t()) :: t()
def drop(inp, str_or_count \\ 1)
def drop(%__MODULE__{} = inp, str) when is_binary(str) do
_drop(inp, String.length(str))
end
def drop(%__MODULE__{} = inp, count) do
_drop(inp, count)
end
@spec empty?(t()) :: boolean()
def empty?(input)
def empty?(%__MODULE__{input: ""}), do: true
def empty?(_), do: false
@spec position(t()) :: position_t()
def position(%__MODULE__{col: col, lnb: lnb}), do: {col, lnb}
@spec report_position(t(), binary?()) :: binary()
def report_position(input, name \\ nil)
def report_position(%__MODULE__{col: col, lnb: lnb}, nil) do
"#{lnb},#{col}"
end
def report_position(%__MODULE__{col: col, lnb: lnb}, name) do
"#{lnb},#{col} in #{name}"
end
@spec _drop(t(), non_neg_integer()) :: t()
defp _drop(inp, count)
defp _drop(%__MODULE__{} = inp, 0), do: inp
defp _drop(%__MODULE__{input: ""} = inp, _), do: inp
defp _drop(%__MODULE__{input: input, col: col, lnb: lnb} = inp, 1) do
<<first::utf8, rest::binary>> = input
graph = List.to_string([first])
{col1, lnb1} = if graph == "\n", do: {1, lnb + 1}, else: {col + 1, lnb}
%{inp | input: rest, col: col1, lnb: lnb1}
end
defp _drop(%__MODULE__{input: input, col: col, lnb: lnb} = inp, count) do
<<first::utf8, rest::binary>> = input
graph = List.to_string([first])
{col1, lnb1} = if graph == "\n", do: {1, lnb + 1}, else: {col + 1, lnb}
_drop(%{inp | input: rest, col: col1, lnb: lnb1}, count - 1)
end
@doc ~S"""
Just `take` without returning `drop` too
iex(6)> peek(new("alpha"))
"a"
iex(7)> peek(new(""))
""
iex(8)> peek(new("alpha"), 2)
"al"
iex(9)> peek(new("alpha"), 9)
"alpha"
"""
@spec peek(t(), pos_integer()) :: String.t()
def peek(%__MODULE__{input: input}, count \\ 1), do: input |> String.slice(0, count)
@spec take(t(), str_or_count_t()) :: take_and_rest_t()
def take(input, str_or_count \\ 1)
def take(%__MODULE__{} = inp, str) when is_binary(str) do
take(inp, String.length(str))
end
def take(%__MODULE__{input: input} = inp, count) do
{
input |> String.slice(0, count),
drop(inp, count)
}
end
end
# SPDX-License-Identifier: Apache-2.0