This is part of a series of posts documenting Sprache:

This post covers the basic building blocks used to parse repeating elements - Many, AtLeastOnce, Until, Repeat, Once. There are some other methods used for parsing specific sorts of repeating elements, like DelimitedByand ChainOperator, however these helpers are built from these primitives.

Many

Parse a stream of 0 or more elements.

  • Parser<IEnumerable<T>> Many<T>(this Parser<T> parser)

The following example parses quoted strings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Parser<string> quotedString =
    from open in Parse.Char('"')
    from value in Parse.CharExcept('"').Many().Text()
    from close in Parse.Char('"')
    select value;

Assert.Equal("Hello, World!", quotedString.Parse("\"Hello, World!\""));

// The empty string is allowed, as many can match zero times
Assert.Equal("", quotedString.Parse("\"\""));

XMany

Parse a stream of 0 or more elements, failing if any element is only partially parsed. Siganture:

  • Parser<IEnumerable<T>> XMany<T>(this Parser<T> parser)

The difference between XMany and Many is that XMany will fail if any of the repeating elements is only partially parsed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Single record e.g. "(monday)"
Parser<string> record =
    from lparem in Parse.Char('(')
    from name in Parse.Letter.Many().Text()
    from rparem in Parse.Char(')')
    select name;

string input = "(monday)(tuesday0(wednesday)(thursday)";

Assert.Equal(new[] { "monday" }, record.Many().Parse(input));

// unexpected '('; expected end of input
Assert.Throws<ParseException>(() => record.XMany().End().Parse(input));

// unexpected '0'; expected )
Assert.Throws<ParseException>(() => record.XMany().Parse(input));

In the above example Many either returns success, or an unhelpful error message if we use End. XMany however saw that the record parser partially matched, and returned the error produced by that parser. The X* methods typically give more helpful errors and are easier to debug than their unqualified counterparts.

AtLeastOnce

Parse a stream of elements with at least one item.

  • Parser<IEnumerable<T>> AtLeastOnce<T>(this Parser<T> parser)
1
2
3
4
5
6
Parser<IEnumerable<string>> parser = Parse.String("Foo").Text().AtLeastOnce();

Assert.Equal(new[] { "Foo", "Foo" }, parser.Parse("FooFooBar"));

// unexpected 'B'; expected Foo
Assert.Throws<ParseException>(() => parser.Parse("Bar"));

XAtLeastOnce

Parse a stream elements with at least one item, however like XMany the parser fail if any element is only partially parsed.

  • Parser<IEnumerable<T>> XAtLeastOnce<T>(this Parser<T> parser)

See AtLeastOnce for examples, and XMany for examples of how the X* methods handle errors differently.

Until

Parses a sequence of items until a terminator is reached.

  • Parser<IEnumerable<T>> Until<T, U>(this Parser<T> parser, Parser<U> until)

The following example parses C-style block comments, alternatively see CommentParser for a helper for parsing both single-line and block comments.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Parser<string> parser =
    from first in Parse.String("/*")
    from comment in Parse.AnyChar.Until(Parse.String("*/")).Text()
    select comment;

Assert.Equal("this is a comment", parser.Parse("/*this is a comment*/"));

parser.Parse(
    @"/*
This comment
can span
over multiple lines*/");

Repeat

Parses a sequence of items until a specific number have been parsed. This method has 2 overloads:

  • Parser<IEnumerable<T>> Repeat<T>(this Parser<T> parser, int count)
  • Parser<IEnumerable<T>> Repeat<T>(this Parser<T> parser, int? minimumCount, int? maximumCount)

The following parser parses a sequence of digits between 3 and 6 characters long:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Parser<string> parser = Parse.Digit.Repeat(3, 6).Text();

Assert.Equal("123", parser.Parse("123"));
Assert.Equal("123456", parser.Parse("123456"));

// The parser will not consume more than the maximum
Assert.Equal("123456", parser.Parse("123456789"));

// Unexpected 'end of input'; expected 'digit' between 3 and 6 times, but found 2
Assert.Throws<ParseException>(() => parser.Parse("12"));

Once

Parses a stream of elements with exactly one item.

  • Parser<IEnumerable<T>> Once<T>(this Parser<T> parser)

This does not change what inputs the supplied parser consumes, however it does change the return type of the parser into IEnumerable<T> which could make consuming the parser easier, for example:

1
2
3
4
5
6
7
8
Parser<string> identifier = Parse.Identifier(Parse.Letter, Parse.LetterOrDigit);

Parser<IEnumerable<string>> memberAccess =
    from first in identifier.Once()
    from subs in Parse.Char('.').Then(_ => identifier).Many()
    select first.Concat(subs);

Assert.Equal(new [] { "foo", "bar", "baz"}, memberAccess.Parse("foo.bar.baz"));