pears/combinators

Functions that build parsers and combine them into bigger ones. They work on any kind of token; pears/chars has parsers made for strings. For an introduction, see the pears module.

Values

pub fn any() -> pears.Parser(i, i)

Consumes any single item from the input except for the end of file.

import pears

assert pears.parse_string_all("x", any()) == Ok("x")
pub fn at_least(
  parser: pears.Parser(i, a),
  n: Int,
) -> pears.Parser(i, List(a))

Applies the given parser n or more times.

import pears

assert pears.parse_string_all("aaa", at_least(token("a"), 2)) == Ok(["a", "a", "a"])
let assert Error(_) = pears.parse_string_all("a", at_least(token("a"), 2))
pub fn at_most(
  parser: pears.Parser(i, a),
  n: Int,
) -> pears.Parser(i, List(a))

Applies the given parser up to n times.

import pears

let assert Ok(parsed) = pears.parse_string("aaa", at_most(token("a"), 2))
assert parsed.value == ["a", "a"]
pub fn between(
  parser: pears.Parser(i, a),
  open: pears.Parser(i, b),
  close: pears.Parser(i, c),
) -> pears.Parser(i, a)

Applies the given parser between the open and close parsers, returning the result of the inner parser.

import pears

assert pears.parse_string_all("(a)", between(token("a"), token("("), token(")"))) == Ok("a")
pub fn chain_left(
  parser: pears.Parser(i, a),
  operator: pears.Parser(i, fn(a, a) -> a),
) -> pears.Parser(i, a)

Parses one or more occurrences of the given parser separated by operator, combining the values from left to right with the functions operator returns. Use it for left-associative operators like -:

import gleam/int
import pears
import pears/chars

let minus = replace(token("-"), int.subtract)
assert pears.parse_string_all("10-2-3", chain_left(chars.natural(), minus)) == Ok(5)
pub fn chain_right(
  parser: pears.Parser(i, a),
  operator: pears.Parser(i, fn(a, a) -> a),
) -> pears.Parser(i, a)

Like chain_left, but combines the values from right to left, so 10-2-3 becomes 10-(2-3):

import gleam/int
import pears
import pears/chars

let minus = replace(token("-"), int.subtract)
assert pears.parse_string_all("10-2-3", chain_right(chars.natural(), minus)) == Ok(11)
pub fn choice(
  parsers: List(pears.Parser(i, a)),
) -> pears.Parser(i, a)

Tries to apply the given parsers in order and returns the result of the first one that succeeds. If all fail, returns the error that got furthest.

import pears

let operator = choice([token("+"), token("-"), token("*")])
assert pears.parse_string_all("*", operator) == Ok("*")
pub fn eof() -> pears.Parser(i, Nil)

Consumes the end of the input and returns a Nil value. Fails if there is any input left.

import pears

let assert Ok(_) = pears.parse_string("a", then_skip(token("a"), eof()))
let assert Error(_) = pears.parse_string("ab", then_skip(token("a"), eof()))
pub fn exactly(
  parser: pears.Parser(i, a),
  n: Int,
) -> pears.Parser(i, List(a))

Applies the given parser exactly n times.

import pears

assert pears.parse_string_all("aaa", exactly(token("a"), 3)) == Ok(["a", "a", "a"])
let assert Error(_) = pears.parse_string_all("aa", exactly(token("a"), 3))
pub fn fail(message: String) -> pears.Parser(i, a)

Fails at the current position with message.

import pears.{Custom}

assert pears.parse_string("a", fail("not supported")) == Error(Custom(0, "not supported"))
pub fn hidden(parser: pears.Parser(i, a)) -> pears.Parser(i, a)

Leaves the parser out of error messages, e.g. for optional whitespace.

import pears.{UnexpectedToken}

let parser = skip_then(hidden(many0(token(" "))), token("a"))
assert pears.parse_string("x", parser) == Error(UnexpectedToken(0, ["\"a\""], "x"))
pub fn label(
  parser: pears.Parser(i, a),
  name: String,
) -> pears.Parser(i, a)

Reports name as expected when the parser fails without consuming input.

import pears.{UnexpectedToken}
import pears/chars

let number = label(many1(chars.digit()), "number")
assert pears.parse_string("x", number) == Error(UnexpectedToken(0, ["number"], "x"))
pub fn lazy(f: fn() -> pears.Parser(i, a)) -> pears.Parser(i, a)

Lazily applies the given parser allowing for recursive parsers.

f is called every time the parser runs, so keep it cheap. Every level of nesting in the input also adds to the call stack: on JavaScript, input nested more than a few hundred levels deep can overflow it.

Examples

Parsing a recursive list of chars:

pub type Tree(a) {
  Leaf(a)
  Node(List(Tree(a)))
}

fn tree_parser() -> Parser(Char, Tree(Int)) {
  let tree = lazy(tree_parser)
  let leaf = map(number(), Leaf)
  let node =
    tree
    |> sep_by0(token(","))
    |> between(token("["), token("]"))
    |> map(Node)
  or(leaf, node)
}
pub fn many0(
  parser: pears.Parser(i, a),
) -> pears.Parser(i, List(a))

Applies the given parser zero or more times.

Stops as soon as the parser fails or succeeds without consuming any input.

import pears

assert pears.parse_string_all("aaa", many0(token("a"))) == Ok(["a", "a", "a"])
assert pears.parse_string_all("", many0(token("a"))) == Ok([])
pub fn many1(
  parser: pears.Parser(i, a),
) -> pears.Parser(i, List(a))

Applies the given parser one or more times.

import pears

assert pears.parse_string_all("aaa", many1(token("a"))) == Ok(["a", "a", "a"])
let assert Error(_) = pears.parse_string_all("", many1(token("a")))
pub fn many_till(
  parser: pears.Parser(i, a),
  end: pears.Parser(i, b),
) -> pears.Parser(i, List(a))

Applies the given parser until end succeeds, returning the results of the parser. end is consumed but its result is dropped.

import pears
import pears/chars

let comment = skip_then(chars.string("/*"), many_till(any(), chars.string("*/")))
assert pears.parse_string_all("/*hi*/", comment) == Ok(["h", "i"])
pub fn map(
  parser: pears.Parser(i, a),
  fun: fn(a) -> b,
) -> pears.Parser(i, b)

Maps the result of a parser to a new value using a mapper function.

import pears
import pears/chars

assert pears.parse_string_all("21", map(chars.natural(), fn(n) { n * 2 })) == Ok(42)
pub fn map_error(
  parser: pears.Parser(i, a),
  fun: fn(pears.ParseError(i)) -> pears.ParseError(i),
) -> pears.Parser(i, a)

Maps the error of a parser to a new error using a mapper function.

import pears.{Custom}

let parser = map_error(token("a"), fn(error) { Custom(error.position, "expected an a") })
assert pears.parse_string("b", parser) == Error(Custom(0, "expected an a"))
pub fn none_of(items: List(i)) -> pears.Parser(i, i)

Consumes an item that is not included in the given list.

import pears

assert pears.parse_string_all("x", none_of(["+", "-"])) == Ok("x")
pub fn not_followed_by(
  parser: pears.Parser(i, a),
) -> pears.Parser(i, Nil)

Succeeds without consuming any input if the parser fails, and fails if it succeeds.

import pears
import pears/chars

let letter = satisfying(fn(c) { string.contains("abcdefghijklmnopqrstuvwxyz", c) })
let keyword = then_skip(chars.string("let"), not_followed_by(letter))
let assert Ok(_) = pears.parse_string("let x", keyword)
let assert Error(_) = pears.parse_string("letter", keyword)
pub fn one_of(items: List(i)) -> pears.Parser(i, i)

Consumes an item that is included in the given list.

import pears

assert pears.parse_string_all("-", one_of(["+", "-"])) == Ok("-")
pub fn optional(
  parser: pears.Parser(i, a),
) -> pears.Parser(i, option.Option(a))

Applies the given parser and wraps the result in an Option, returning None if the parser fails instead of an error.

import pears

assert pears.parse_string_all("-", optional(token("-"))) == Ok(Some("-"))
assert pears.parse_string_all("", optional(token("-"))) == Ok(None)
pub fn or(
  first: pears.Parser(i, a),
  second: pears.Parser(i, a),
) -> pears.Parser(i, a)

Tries the first parser, and if it fails, the second one. The second parser starts where the first one did, even if the first consumed input before failing.

import pears
import pears/chars

let parser = or(chars.string("ab"), chars.string("ac"))
assert pears.parse_string_all("ac", parser) == Ok("ac")
pub fn pair(
  first: pears.Parser(i, a),
  second: pears.Parser(i, b),
) -> pears.Parser(i, #(a, b))

Applies both parsers and returns a tuple of the results.

import pears

assert pears.parse_string_all("ab", pair(token("a"), token("b"))) == Ok(#("a", "b"))
pub fn peek(parser: pears.Parser(i, a)) -> pears.Parser(i, a)

Runs the parser without consuming any input.

import pears

assert pears.parse_string_all("a", pair(peek(token("a")), token("a"))) == Ok(#("a", "a"))
pub fn recognize(
  parser: pears.Parser(i, a),
) -> pears.Parser(i, List(i))

Creates a parser that returns the consumed tokens instead of its parsed value.

import pears
import pears/chars

let decimal = recognize(pair(chars.natural(), pair(token("."), chars.natural())))
assert pears.parse_string_all("1.05", decimal) == Ok(["1", ".", "0", "5"])
pub fn replace(
  parser: pears.Parser(i, a),
  value: b,
) -> pears.Parser(i, b)

Maps the result of a parser to a constant value.

import pears
import pears/chars

assert pears.parse_string_all("yes", replace(chars.string("yes"), True)) == Ok(True)
pub fn satisfying(predicate: fn(i) -> Bool) -> pears.Parser(i, i)

Consumes a single item from the input that satisfies the given predicate function.

import pears

let vowel = satisfying(fn(c) { string.contains("aeiou", c) })
assert pears.parse_string_all("e", vowel) == Ok("e")
pub fn sep_by0(
  parser: pears.Parser(i, a),
  separator: pears.Parser(i, b),
) -> pears.Parser(i, List(a))

Parses zero or more occurrences of the given parser separated by the given separator.

import pears
import pears/chars

assert pears.parse_string_all("1,2,3", sep_by0(chars.natural(), token(","))) == Ok([1, 2, 3])
assert pears.parse_string_all("", sep_by0(chars.natural(), token(","))) == Ok([])
pub fn sep_by1(
  parser: pears.Parser(i, a),
  separator: pears.Parser(i, b),
) -> pears.Parser(i, List(a))

Parses one or more occurrences of the given parser separated by the given separator.

import pears
import pears/chars

assert pears.parse_string_all("1,2,3", sep_by1(chars.natural(), token(","))) == Ok([1, 2, 3])
let assert Error(_) = pears.parse_string_all("", sep_by1(chars.natural(), token(",")))
pub fn sep_end_by0(
  parser: pears.Parser(i, a),
  separator: pears.Parser(i, b),
) -> pears.Parser(i, List(a))

Like sep_by0, but allows a trailing separator.

import pears

assert pears.parse_string_all("a,a,", sep_end_by0(token("a"), token(","))) == Ok(["a", "a"])
assert pears.parse_string_all("", sep_end_by0(token("a"), token(","))) == Ok([])
pub fn sep_end_by1(
  parser: pears.Parser(i, a),
  separator: pears.Parser(i, b),
) -> pears.Parser(i, List(a))

Like sep_by1, but allows a trailing separator.

import pears

assert pears.parse_string_all("a,a,", sep_end_by1(token("a"), token(","))) == Ok(["a", "a"])
let assert Error(_) = pears.parse_string_all(",", sep_end_by1(token("a"), token(",")))
pub fn seq(
  parsers: List(pears.Parser(i, a)),
) -> pears.Parser(i, List(a))

Applies the given parsers in sequence and returns a list of the results

import pears

assert pears.parse_string_all("abc", seq([token("a"), token("b"), token("c")])) == Ok(["a", "b", "c"])
pub fn skip_then(
  first: pears.Parser(i, a),
  second: pears.Parser(i, b),
) -> pears.Parser(i, b)

Applies the first parser and then the second parser, returning the result of the second parser.

import pears

assert pears.parse_string_all("ab", skip_then(token("a"), token("b"))) == Ok("b")
pub fn succeed(value: a) -> pears.Parser(i, a)

Returns value without consuming any input.

import pears

assert pears.parse_string_all("", succeed(1)) == Ok(1)
pub fn then(
  parser: pears.Parser(i, a),
  next: fn(a) -> pears.Parser(i, b),
) -> pears.Parser(i, b)

Runs the parser and passes its result to next, which returns the parser to run on the rest of the input. Works well with use:

import pears
import pears/chars

let integer = {
  use sign <- then(optional(token("-")))
  use number <- then(chars.natural())
  case sign {
    Some(_) -> succeed(-number)
    None -> succeed(number)
  }
}

assert pears.parse_string_all("-42", integer) == Ok(-42)
pub fn then_skip(
  first: pears.Parser(i, a),
  second: pears.Parser(i, b),
) -> pears.Parser(i, a)

Applies the first parser and then the second parser, returning the result of the first parser.

import pears

assert pears.parse_string_all("ab", then_skip(token("a"), token("b"))) == Ok("a")
pub fn token(item: i) -> pears.Parser(i, i)

Consumes a single item from the input that is equal to the given item.

import pears

assert pears.parse_string_all("a", token("a")) == Ok("a")
assert pears.parse_all([1, 2], pair(token(1), token(2))) == Ok(#(1, 2))
pub fn try_map(
  parser: pears.Parser(i, a),
  fun: fn(a) -> Result(b, String),
) -> pears.Parser(i, b)

Maps the result of a parser with a function that can fail. If it returns Error(message), parsing fails with that message at the position where the parser started.

import pears.{Custom}
import pears/chars

let byte =
  try_map(chars.natural(), fn(n) {
    case n < 256 {
      True -> Ok(n)
      False -> Error("number must be below 256")
    }
  })
assert pears.parse_string_all("255", byte) == Ok(255)
assert pears.parse_string_all("256", byte) == Error(Custom(0, "number must be below 256"))
pub fn unwrap(
  parser: pears.Parser(i, option.Option(a)),
  default: a,
) -> pears.Parser(i, a)

Maps a parser that returns an Option to a parser that returns a value or a default value.

import pears

assert pears.parse_string_all("", unwrap(optional(token("-")), "+")) == Ok("+")
pub fn with_span(
  parser: pears.Parser(i, a),
) -> pears.Parser(i, #(a, pears.Span))

Returns the parsed value together with the span of tokens the parser consumed.

import pears.{Span}
import pears/chars

let parser = skip_then(chars.string("let "), with_span(chars.string("x")))
assert pears.parse_string_all("let x", parser) == Ok(#("x", Span(4, 5)))
✨ Search Document