Ergo.Combinators (Ergo v0.4.6)

Ergo.Combinators is the key set of parsers used for combining together other parsers.

Parsers

  • choice
  • sequence
  • many
  • optional
  • ignore
  • transform
  • lookeahead
  • not_lookahead

Link to this section Summary

Functions

The choice/1 parser takes a list of parsers. It tries each in order attempting to match one. Once a match has been made choice returns the result of the matching parser.

The ignore/1 parser matches but ignores the AST of its child parser.

The lazy/1 parser is intended for use in cases where constructing parsers creates a recursive call. By using lazy the original parser call is deferred until later, breaking the infinite recursion.

The lookahead parser accepts a parser and matches it but does not update the context when it succeeds.

Examples

This test will need to be rewritten in terms of Ergo.diganose
# iex> Logger.disable(self())
# iex> alias Ergo.Context
# iex> import Ergo.{Combinators, Terminals}
# iex> parser = many(wc(), label: "Chars")
# iex> context = Ergo.parse(parser, "Hello World", debug: true)
# iex> assert %Context{status: :ok, debug: true, ast: [?H, ?e, ?l, ?l, ?o], input: " World", index: 5, col: 6, char: ?o} = context

iex> alias Ergo.Context
iex> import Ergo.{Combinators, Terminals}
iex> parser = many(wc(), min: 6)
iex> context = Ergo.parse(parser, "Hello World")
iex> assert %Context{status: {:error, :many_less_than_min}, ast: nil, input: " World", index: 5, col: 6} = context

iex> alias Ergo.{Context, Parser}
iex> import Ergo.{Combinators, Terminals}
iex> parser = many(wc(), max: 3)
iex> context = Ergo.parse(parser, "Hello World")
iex> assert %Context{status: :ok, ast: [?H, ?e, ?l], input: "lo World", index: 3, col: 4} = context

iex> alias Ergo.{Context, Parser}
iex> import Ergo.{Combinators, Terminals}
iex> parser = many(wc(), map: &Enum.count/1)
iex> context = Ergo.parse(parser, "Hello World")
iex> assert %Context{status: :ok, ast: 5, input: " World", index: 5, col: 6} = context

The not_lookahead parser accepts a parser and attempts to match it. If the match fails the not_lookahead parser returns status: :ok but does not affect the context otherwise.

Examples

iex> alias Ergo.Context
iex> import Ergo.{Terminals, Combinators}
iex> context = Ergo.parse(optional(literal("Hello")), "Hello World")
iex> assert %Context{status: :ok, ast: "Hello", input: " World", index: 5, col: 6} = context

In this example we deliberately ensure that the Context ast is not nil
iex> alias Ergo.{Context, Parser}
iex> import Ergo.{Terminals, Combinators}
iex> context = Context.new(&Ergo.Parser.call/2, " World", ast: [])
iex> parser = optional(literal("Hello"))
iex> new_context = Parser.invoke(parser, context)
iex> assert %Context{status: :ok, ast: nil, input: " World", index: 0, col: 1} = new_context

The satisfy/3 parser takes a parser and a predicate function. If the parser is successful the AST is passed to the predicate function. If the predicate function returns true the parser returns the successful context, otherwise an error context is returned.

Examples

iex> alias Ergo.Context
iex> import Ergo.{Terminals, Combinators}
iex> parser = sequence([literal("Hello"), ws(), literal("World")])
iex> context = Ergo.parse(parser, "Hello World")
%Context{status: :ok, ast: ["Hello", ?\s, "World"], index: 11, line: 1, col: 12} = context

This test will need to be rewritten in terms of Ergo.diagnose
# iex> Logger.disable(self())
# iex> alias Ergo.Context
# iex> import Ergo.{Terminals, Combinators}
# iex> parser = sequence([literal("Hello"), ws(), literal("World")], label: "HelloWorld")
# iex> context = Ergo.parse(parser, "Hello World", debug: true)
# iex> assert %Context{status: :ok, debug: true, ast: ["Hello", ?\s, "World"], index: 11, line: 1, col: 12} = context

iex> alias Ergo.Context
iex> import Ergo.{Terminals, Combinators}
iex> parser = sequence([literal("Hello"), ws(), literal("World")], map: fn ast -> Enum.join(ast, " ") end)
iex> context = Ergo.parse(parser, "Hello World")
iex> assert %Context{status: :ok, ast: "Hello 32 World", index: 11, line: 1, col: 12} = context

This test will need to be rewritten in terms of Ergo.diagnose
# iex> Logger.disable(self())
# iex> alias Ergo.Context
# iex> import Ergo.{Terminals, Combinators}
# iex> parser = sequence([literal("Hello"), ws(), literal("World")], label: "HelloWorld", map: fn ast -> Enum.join(ast, " ") end)
# iex> context = Ergo.parse(parser, "Hello World", debug: true)
# iex> assert %Context{status: :ok, debug: true, ast: "Hello 32 World", index: 11, line: 1, col: 12} = context

iex> alias Ergo.Context
iex> import Ergo.{Combinators, Terminals}
iex> parser = sequence([literal("foo"), ws(), literal("bar")])
iex> assert %Context{status: {:error, :unexpected_char}} = Ergo.parse(parser, "Hello World")

The string/1 parser takes a parser that returns an AST which is a list of characters and converts the AST into a string.

The transform/2 parser runs a transforming function on the AST of its child parser.

Link to this section Functions

Link to this function

choice(parsers, opts \\ [])

The choice/1 parser takes a list of parsers. It tries each in order attempting to match one. Once a match has been made choice returns the result of the matching parser.

Examples

iex> alias Ergo.Context
iex> import Ergo.{Terminals, Combinators}
iex> parser = choice([literal("Foo"), literal("Bar"), literal("Hello"), literal("World")], label: "Foo|Bar|Hello|World")
iex> context = Ergo.parse(parser, "Hello World")
iex> assert %Context{status: :ok, ast: "Hello", input: " World", index: 5, col: 6} = context

iex> alias Ergo.Context
iex> import Ergo.{Terminals, Combinators}
iex> parser = choice([literal("Foo"), literal("Bar")])
iex> context = Ergo.parse(parser, "Hello World")
iex> %Context{status: {:error, :no_valid_choice}, message: "No valid choice in #", ast: nil, input: "Hello World"} = context
Link to this function

ignore(parser, opts \\ [])

The ignore/1 parser matches but ignores the AST of its child parser.

Examples

iex> alias Ergo.Context
iex> import Ergo.{Terminals, Combinators}
iex> parser = sequence([literal("Hello"), ignore(ws()), literal("World")])
iex> context = Ergo.parse(parser, "Hello World")
iex> assert %Context{status: :ok, ast: ["Hello", "World"], index: 11, col: 12} = context
Link to this macro

lazy(parser)

(macro)

The lazy/1 parser is intended for use in cases where constructing parsers creates a recursive call. By using lazy the original parser call is deferred until later, breaking the infinite recursion.

Link to this function

lookahead(parser, opts \\ [])

The lookahead parser accepts a parser and matches it but does not update the context when it succeeds.

Example

iex> alias Ergo.Context
iex> import Ergo.{Combinators, Terminals}
iex> parser = lookahead(literal("Hello"))
iex> assert %Context{status: :ok, ast: nil, input: "Hello World", index: 0} = Ergo.parse(parser, "Hello World")

iex> alias Ergo.Context
iex> import Ergo.{Combinators, Terminals}
iex> parser = lookahead(literal("Helga"))
iex> assert %Context{status: {:error, :lookahead_fail}, ast: [?l, ?e, ?H], index: 3, col: 4, input: "lo World"} = Ergo.parse(parser, "Hello World")
Link to this function

many(parser, opts \\ [])

Examples

This test will need to be rewritten in terms of Ergo.diganose
# iex> Logger.disable(self())
# iex> alias Ergo.Context
# iex> import Ergo.{Combinators, Terminals}
# iex> parser = many(wc(), label: "Chars")
# iex> context = Ergo.parse(parser, "Hello World", debug: true)
# iex> assert %Context{status: :ok, debug: true, ast: [?H, ?e, ?l, ?l, ?o], input: " World", index: 5, col: 6, char: ?o} = context

iex> alias Ergo.Context
iex> import Ergo.{Combinators, Terminals}
iex> parser = many(wc(), min: 6)
iex> context = Ergo.parse(parser, "Hello World")
iex> assert %Context{status: {:error, :many_less_than_min}, ast: nil, input: " World", index: 5, col: 6} = context

iex> alias Ergo.{Context, Parser}
iex> import Ergo.{Combinators, Terminals}
iex> parser = many(wc(), max: 3)
iex> context = Ergo.parse(parser, "Hello World")
iex> assert %Context{status: :ok, ast: [?H, ?e, ?l], input: "lo World", index: 3, col: 4} = context

iex> alias Ergo.{Context, Parser}
iex> import Ergo.{Combinators, Terminals}
iex> parser = many(wc(), map: &Enum.count/1)
iex> context = Ergo.parse(parser, "Hello World")
iex> assert %Context{status: :ok, ast: 5, input: " World", index: 5, col: 6} = context
Link to this function

not_lookahead(parser, opts \\ [])

The not_lookahead parser accepts a parser and attempts to match it. If the match fails the not_lookahead parser returns status: :ok but does not affect the context otherwise.

If the match succeeds the not_lookahead parser fails with {:error, :lookahead_fail}

Examples

iex> alias Ergo.Context iex> import Ergo.{Combinators, Terminals} iex> parser = not_lookahead(literal("Foo")) iex> assert %Context{status: :ok, input: "Hello World"} = Ergo.parse(parser, "Hello World")

iex> alias Ergo.{Context, Parser} iex> import Ergo.{Combinators, Terminals} iex> parser = not_lookahead(literal("Hello")) iex> assert %Context{status: {:error, :lookahead_fail}, input: "Hello World"} = Ergo.parse(parser, "Hello World")

Link to this function

optional(parser, opts \\ [])

Examples

iex> alias Ergo.Context
iex> import Ergo.{Terminals, Combinators}
iex> context = Ergo.parse(optional(literal("Hello")), "Hello World")
iex> assert %Context{status: :ok, ast: "Hello", input: " World", index: 5, col: 6} = context

In this example we deliberately ensure that the Context ast is not nil
iex> alias Ergo.{Context, Parser}
iex> import Ergo.{Terminals, Combinators}
iex> context = Context.new(&Ergo.Parser.call/2, " World", ast: [])
iex> parser = optional(literal("Hello"))
iex> new_context = Parser.invoke(parser, context)
iex> assert %Context{status: :ok, ast: nil, input: " World", index: 0, col: 1} = new_context
Link to this function

parse_many(parser, ctx, min, max, count)

Link to this function

satisfy(parser, pred_fn, opts \\ [])

The satisfy/3 parser takes a parser and a predicate function. If the parser is successful the AST is passed to the predicate function. If the predicate function returns true the parser returns the successful context, otherwise an error context is returned.

Example

iex> alias Ergo.Context
iex> import Ergo.{Terminals, Combinators, Numeric}
iex> parser = satisfy(any(), fn char -> char in (?0..?9) end, label: "digit char")
iex> assert %Context{status: :ok, ast: ?4} = Ergo.parse(parser, "4")
iex> assert %Context{status: {:error, :unsatisfied}} = Ergo.parse(parser, "!")
iex> parser = satisfy(number(), fn n -> Integer.mod(n, 2) == 0 end, label: "even number")
iex> assert %Context{status: :ok, ast: 42} = Ergo.parse(parser, "42")
iex> assert %Context{status: {:error, :unsatisfied}} = Ergo.parse(parser, "27")
Link to this function

sequence(parsers, opts \\ [])

Examples

iex> alias Ergo.Context
iex> import Ergo.{Terminals, Combinators}
iex> parser = sequence([literal("Hello"), ws(), literal("World")])
iex> context = Ergo.parse(parser, "Hello World")
%Context{status: :ok, ast: ["Hello", ?\s, "World"], index: 11, line: 1, col: 12} = context

This test will need to be rewritten in terms of Ergo.diagnose
# iex> Logger.disable(self())
# iex> alias Ergo.Context
# iex> import Ergo.{Terminals, Combinators}
# iex> parser = sequence([literal("Hello"), ws(), literal("World")], label: "HelloWorld")
# iex> context = Ergo.parse(parser, "Hello World", debug: true)
# iex> assert %Context{status: :ok, debug: true, ast: ["Hello", ?\s, "World"], index: 11, line: 1, col: 12} = context

iex> alias Ergo.Context
iex> import Ergo.{Terminals, Combinators}
iex> parser = sequence([literal("Hello"), ws(), literal("World")], map: fn ast -> Enum.join(ast, " ") end)
iex> context = Ergo.parse(parser, "Hello World")
iex> assert %Context{status: :ok, ast: "Hello 32 World", index: 11, line: 1, col: 12} = context

This test will need to be rewritten in terms of Ergo.diagnose
# iex> Logger.disable(self())
# iex> alias Ergo.Context
# iex> import Ergo.{Terminals, Combinators}
# iex> parser = sequence([literal("Hello"), ws(), literal("World")], label: "HelloWorld", map: fn ast -> Enum.join(ast, " ") end)
# iex> context = Ergo.parse(parser, "Hello World", debug: true)
# iex> assert %Context{status: :ok, debug: true, ast: "Hello 32 World", index: 11, line: 1, col: 12} = context

iex> alias Ergo.Context
iex> import Ergo.{Combinators, Terminals}
iex> parser = sequence([literal("foo"), ws(), literal("bar")])
iex> assert %Context{status: {:error, :unexpected_char}} = Ergo.parse(parser, "Hello World")

The string/1 parser takes a parser that returns an AST which is a list of characters and converts the AST into a string.

Examples

iex> alias Ergo
iex> alias Ergo.Context
iex> import Ergo.{Terminals, Combinators}
iex> parser = many(alpha()) |> string()
iex> assert %Context{status: :ok, ast: "FourtyTwo"} = Ergo.parse(parser, "FourtyTwo")
Link to this function

transform(parser, t_fn, opts \\ [])

The transform/2 parser runs a transforming function on the AST of its child parser.

Examples

# Sum the digits
iex> alias Ergo.Context
iex> import Ergo.{Combinators, Terminals}
iex> digit_to_int = fn d -> List.to_string([d]) |> String.to_integer() end
iex> t_fn = fn ast -> ast |> Enum.map(digit_to_int) |> Enum.sum() end
iex> parser = sequence([digit(), digit(), digit(), digit()]) |> transform(t_fn)
iex> context = Ergo.parse(parser, "1234")
iex> %Context{status: :ok, ast: 10, index: 4, line: 1, col: 5} = context