Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A variant of Apply() that works with any TextParser<TextSpan> #33

Merged
merged 1 commit into from
May 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 45 additions & 11 deletions src/Superpower/Combinators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ namespace Superpower
public static class Combinators
{
/// <summary>
/// Apply the character parser <paramref name="valueParser"/> to the span represented by the parsed token.
/// Apply the text parser <paramref name="valueParser"/> to the span represented by the parsed token.
/// </summary>
/// <typeparam name="TKind">The kind of the tokens being parsed.</typeparam>
/// <typeparam name="U">The type of the resulting value.</typeparam>
/// <param name="parser">The parser.</param>
/// <param name="valueParser">A function that determines which character parser to apply.</param>
/// <param name="valueParser">A function that determines which text parser to apply.</param>
/// <returns>A parser that returns the result of parsing the token value.</returns>
public static TokenListParser<TKind, U> Apply<TKind, U>(this TokenListParser<TKind, Token<TKind>> parser, Func<Token<TKind>, TextParser<U>> valueParser)
{
Expand All @@ -45,30 +45,64 @@ public static TokenListParser<TKind, U> Apply<TKind, U>(this TokenListParser<TKi

var uParser = valueParser(rt.Value);
var uResult = uParser.AtEnd()(rt.Value.Span);
if (!uResult.HasValue)
{
var message = $"invalid {Presentation.FormatExpectation(rt.Value.Kind)}, {uResult.FormatErrorMessageFragment()}";
return new TokenListParserResult<TKind, U>(input, uResult.Remainder.Position, message, null, uResult.Backtrack);
}
if (uResult.HasValue)
return TokenListParserResult.Value(uResult.Value, rt.Location, rt.Remainder);

return TokenListParserResult.Value(uResult.Value, rt.Location, rt.Remainder);
var message = $"invalid {Presentation.FormatExpectation(rt.Value.Kind)}, {uResult.FormatErrorMessageFragment()}";
return new TokenListParserResult<TKind, U>(input, uResult.Remainder.Position, message, null, uResult.Backtrack);
};
}

/// <summary>
/// Apply the character parser <paramref name="valueParser"/> to the span represented by the parsed token.
/// Apply the text parser <paramref name="valueParser"/> to the span represented by the parsed token.
/// </summary>
/// <typeparam name="TKind">The kind of the tokens being parsed.</typeparam>
/// <typeparam name="U">The type of the resulting value.</typeparam>
/// <param name="parser">The parser.</param>
/// <param name="valueParser">A character parser to apply.</param>
/// <param name="valueParser">A text parser to apply.</param>
/// <returns>A parser that returns the result of parsing the token value.</returns>
public static TokenListParser<TKind, U> Apply<TKind, U>(this TokenListParser<TKind, Token<TKind>> parser, TextParser<U> valueParser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (valueParser == null) throw new ArgumentNullException(nameof(valueParser));

return parser.Apply(rt => valueParser);
var valueParserAtEnd = valueParser.AtEnd();
return input =>
{
var rt = parser(input);
if (!rt.HasValue)
return TokenListParserResult.CastEmpty<TKind, Token<TKind>, U>(rt);

var uResult = valueParserAtEnd(rt.Value.Span);
if (uResult.HasValue)
return TokenListParserResult.Value(uResult.Value, rt.Location, rt.Remainder);

var message = $"invalid {Presentation.FormatExpectation(rt.Value.Kind)}, {uResult.FormatErrorMessageFragment()}";
return new TokenListParserResult<TKind, U>(input, uResult.Remainder.Position, message, null, uResult.Backtrack);
};
}

/// <summary>
/// Apply the text parser <paramref name="valueParser"/> to the span
/// captured by the parser.
/// </summary>
/// <typeparam name="U">The type of the resulting value.</typeparam>
/// <param name="parser">The parser.</param>
/// <param name="valueParser">A text parser to apply to the span.</param>
/// <returns>A parser that returns the result of parsing the span value.</returns>
public static TextParser<U> Apply<U>(this TextParser<TextSpan> parser, TextParser<U> valueParser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (valueParser == null) throw new ArgumentNullException(nameof(valueParser));
var valueParserAtEnd = valueParser.AtEnd();
return input =>
{
var rt = parser(input);
if (!rt.HasValue)
return Result.CastEmpty<TextSpan, U>(rt);

return valueParserAtEnd(rt.Value);
};
}

/// <summary>
Expand Down
60 changes: 60 additions & 0 deletions test/Superpower.Tests/Combinators/ApplyCombinatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Superpower.Model;
using Superpower.Parsers;
using Superpower.Tests.Support;
using Xunit;

namespace Superpower.Tests.Combinators
{
public class ApplyCombinatorTests
{
[Fact]
public void ApplyOnParsedSpanCallsAppliedParser()
{
var input = new TextSpan("1234");
var twodigits = Span.Length(2).Apply(Numerics.IntegerInt32);
var result = twodigits(input);
Assert.Equal(12, result.Value);
}

[Fact]
public void AnAppliedParserMustConsumeAllInput()
{
var input = new TextSpan("1234");
var twodigits = Span.Length(2).Apply(Character.Digit);
var result = twodigits(input);
Assert.False(result.HasValue);
Assert.Equal("Syntax error (line 1, column 2): unexpected `2`.", result.ToString());
}

[Fact]
public void AnAppliedParserIsNotCalledIfThePrecedingParseFails()
{
var input = new TextSpan("1234");
var twodigits = Span.EqualTo("aa").Apply(Character.Digit);
var result = twodigits(input);
Assert.False(result.HasValue);
Assert.Equal("Syntax error (line 1, column 1): unexpected `1`, expected `aa`.", result.ToString());
}

[Fact]
public void ApplyOnParsedTokenCallsAppliedParser()
{
var input = StringAsCharTokenList.Tokenize("abcd");
var aAs42 = Token.EqualTo('a').Apply(Character.AnyChar.Value(42));
var result = aAs42(input);
Assert.Equal(42, result.Value);
}

[Fact]
public void AnAppliedParserMustConsumeTheWholeTokenSpan()
{
var input = StringAsCharTokenList.Tokenize("abcd");
var just42 = Token.EqualTo('a').Apply(Parse.Return(42));
var result = just42(input);
Assert.False(result.HasValue);
// The "invalid a" here is the token name, since we're using characters as tokens - in normal use
// this would read more like "invalid URI: unexpected `:`".
Assert.Equal("Syntax error (line 1, column 1): invalid a, unexpected `a`.", result.ToString());
}
}
}