This is the last in a series of posts documenting Sprache:

This post covers a number of less frequently used methods not already covered in other posts.

Ref

Refer to another parser indirectly. This allows circular compile-time dependency between parsers.

  • Parser<T> Ref<T>(Func<Parser<T>> reference)

For example, in this extremely simple expression parser the Ref is required - static fields are initialized in order, and so AdditiveExpression is still null at the point where the PrimaryExpression field is initialized.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public static readonly Parser<float> Integer =
    Parse.Number.Token().Select(float.Parse);

public static readonly Parser<float> PrimaryExpression =
    Integer.Or(Parse.Ref(() => AdditiveExpression).Contained(Parse.Char('('), Parse.Char(')')));

public static readonly Parser<float> MultiplicativeExpression =
    Parse.ChainOperator(Parse.Char('*'), PrimaryExpression, (c, left, right) => left * right);

public static readonly Parser<float> AdditiveExpression =
    Parse.ChainOperator(Parse.Char('+'), MultiplicativeExpression, (c, left, right) => left + right);

[Fact]
public void bbb()
{
    Assert.Equal(2, AdditiveExpression.End().Parse("1+1"));
}

Named

Names part of the grammar for help with error messages.

  • Parser<T> Named<T>(this Parser<T> parser, string name)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Parser<string> quotedText =
    (from open in Parse.Char('"')
        from content in Parse.CharExcept('"').Many().Text()
        from close in Parse.Char('"')
        select content).Named("quoted text");

// This throws:
//   unexpected 'f'; expected quoted text
// instead of:
//   unexpected 'f'; expected "
Assert.Throws<ParseException>(() => quotedText.Parse("foo"));

End

Parse end-of-input.

  • Parser<T> End<T>(this Parser<T> parser)

Here we use End to make sure there isn’t any left-over unparsed garbage after parsing the expression:

1
2
3
4
Assert.Equal("12", Parse.Number.End().Parse("12"));

// unexpected '_'; expected end of input
Assert.Throws<ParseException>(() => Parse.Number.End().Parse("12_"));

Not

Constructs a parser that will fail if the given parser succeeds, and will succeed if the given parser fails. In any case, it won’t consume any input. It’s like a negative look-ahead in regex.

  • Parser<object> Not<T>(this Parser<T> parser)

Example taken from ApexSharp parser:

1
2
3
4
5
6
7
8
Parser<string> Keyword(string text) =>
    Parse.IgnoreCase(text).Then(n => Parse.Not(Parse.LetterOrDigit.Or(Parse.Char('_')))).Return(text);

Parser<string> returnKeyword = Keyword("return");

Assert.Equal("return", returnKeyword.Parse("return"));
Assert.Throws<ParseException>(() => returnKeyword.Parse("return_"));
Assert.Throws<ParseException>(() => returnKeyword.Parse("returna"));

Except

Attempt parsing only if the except parser fails.

  • Parser<T> Except<T, U>(this Parser<T> parser, Parser<U> except)

Example taken from XAML binding expression parser in OmniXaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const char Quote = '\'';
const char OpenCurly = '{';
const char CloseCurly = '}';
const char Comma = ',';
const char EqualSign = '=';

Parser<char> validChars = Parse.AnyChar.Except(Parse.Chars(Quote, OpenCurly, CloseCurly, EqualSign, Comma).Or(Parse.WhiteSpace));

Assert.Equal('t', validChars.Parse("t"));
Assert.Throws<ParseException>(() => validChars.Parse(" "));

Then

Parse first, and if successful, then parse second. Returns the result of the second parser.

  • Parser<U> Then<T, U>(this Parser<T> first, Func<T, Parser<U>> second)
1
2
3
4
5
6
7
8
Parser<string> identifier = Parse.Identifier(Parse.Letter, Parse.LetterOrDigit);

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

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

Where

Succeed if the parsed value matches predicate.

  • Parser<T> Where<T>(this Parser<T> parser, Func<T, bool> predicate)
1
2
3
4
5
6
Parser<int> parser = Parse.Number.Select(int.Parse).Where(n => n >= 100 && n < 200);

Assert.Equal(151, parser.Parse("151"));

// Unexpected 201.;
Assert.Throws<ParseException>(() => parser.Parse("201"));

Can also be used as part of a linq expression:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var keywords = new[]
{
    "return",
    "var",
    "function"
};

Parser<string> identifier =
    from id in Parse.Identifier(Parse.Letter, Parse.LetterOrDigit.Or(Parse.Char('_')))
    where !keywords.Contains(id)
    select id;

// Unexpected return.;
Assert.Throws<ParseException>(() => identifier.Parse("return"));

Concat

Concatenate two streams of elements.

  • Parser<IEnumerable<T>> Concat<T>(this Parser<IEnumerable<T>> first, Parser<IEnumerable<T>> second)

The only example I could find of this was in the construction of IgnoreCase:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Parser<IEnumerable<char>> IgnoreCase(string s)
{
    return s
        .Select(Parse.IgnoreCase)
        .Aggregate(Parse.Return(Enumerable.Empty<char>()),
            (a, p) => a.Concat(p.Once()))
        .Named(s);
}

Assert.Equal("SPRACHE", IgnoreCase("sprache").Parse("SPRACHE"));

Preview

Construct a parser that indicates that the given parser is optional and non-consuming. The returned parser will succeed on any input no matter whether the given parser succeeds or not. In any case, it won’t consume any input, like a positive look-ahead in regex.

  • Parser<IOption<T>> Preview<T>(this Parser<T> parser)

The only example I was able to find was in the Commented method in Sprache itself.

Span

Constructs a parser that returns the ITextSpan of the parsed value, which includes information about the position of the parsed value in the original source.

  • Parser<ITextSpan<T>> Span<T>(this Parser<T> parser)

The following example is taken from this issue in the Sprache project on GitHub and parses conditionally based on the position of the parsed value.

1
2
3
4
5
6
7
8
Parser<string> sample =
    from a in Parse.Char('a').Many().Text().Token()
    from b in Parse.Char('b').Many().Text().Token().Span()
    where b.Start.Pos <= 10
    select a + b.Value;

Assert.Equal("aaabbb", sample.Parse(" aaa bbb "));
Assert.Throws<ParseException>(() => sample.Parse(" aaaaaaa      bbbbbb "));