defmodule Parser.Core do @moduledoc """ This module provides the monad instance definition for a parser. A parser essentially is a function that consumes (part of) a string and produces a value and the unconsumed part of the string. """ use ContinuationMonad defmacro __using__(_options) do quote do import unquote(__MODULE__) use ContinuationMonad end end @success :success @failure :failure # Monad instance defp _bind(ma, a2mb) do fn input -> case ma.(input) do {@success, a, remainder} -> a2mb.(a).(remainder) @failure -> @failure end end end defp _return(a) do fn input -> {@success, a, input} end end @doc """ injects the passed value into function that accepts a string and returns a pair of the injected value and the unchanged string """ def return(a) do ic(_return(a)) end # Alternative instance defp _empty() do const(@failure) end @doc """ function that accepts a string and always fails. """ def empty() do ic(_empty()) end defp _choice(c1, c2) do fn input -> case {run(c1).(input), run(empty()).(input)} do {x, x} -> run(c2).(input) {c1x, _} -> c1x end end end @doc """ accepts 2 parsers a and b and first applies parser a. If that that parser fails parser b applies. """ def c1 <|> c2 do ic(_choice(c1, c2)) end # Primitives defp _elem() do fn <> -> {@success, char, remainder} _ -> @failure end end @doc """ single element parser. """ def elem() do ic(_elem()) end # Wrapping / Unwrapping defp ic(parser) do make_ic(&_bind/2).(parser) end defp run(m) do make_run(&_return/1).(m) end # Helper functions @doc """ applies the parser to the input string, returns value and remaining string """ def parse(parser, input) do run(parser).(input) end @doc""" applies the parser and returns {:ok, result} on successful parse when there is no more input left. Returns {:error, reason} if either the parser failed or the input string was not consumed entirely. """ def run_parser(parser, input) do case parse(parser, input) do {@success, result, ""} -> {:ok, result} {@success, _, remainder} -> {:error, "incomplete parse, stopped at: #{remainder}"} @failure -> {:error, "parsing failed"} end end @doc """ applies the parser and returns an un-wrapped value. Raises an error if the parser failed, or the parser succeeded but the input was not consumed entirely. """ def run_parser!(parser, input) do case run_parser(parser, input) do {:ok, result} -> result {:error, reason} -> raise(reason) end end end