-
Notifications
You must be signed in to change notification settings - Fork 103
/
Copy pathErrorMessageScenarioTests.cs
174 lines (147 loc) · 7.6 KB
/
ErrorMessageScenarioTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
using System.Collections.Generic;
using Superpower.Model;
using Superpower.Parsers;
using Superpower.Tests.ArithmeticExpressionScenario;
using Superpower.Tests.SExpressionScenario;
using Superpower.Tests.ComplexTokenScenario;
using Superpower.Tests.Support;
using Superpower.Tokenizers;
using Xunit;
namespace Superpower.Tests
{
public class ErrorMessageScenarioTests
{
[Fact]
public void ErrorMessagesFromAppliedCharacterParsersPropagate()
{
var number = Token.EqualTo(SExpressionToken.Number)
.Apply(t => Character.EqualTo('1').Then(_ => Character.EqualTo('x')));
var numbers = number.AtEnd();
AssertParser.FailsWithMessage(numbers, "123", new SExpressionTokenizer(),
"Syntax error (line 1, column 2): invalid number, unexpected `2`, expected `x`.");
}
[Fact]
public void ErrorMessageFromMatchedTokenProducesMeaningfulError()
{
var number = Token.Matching<SExpressionXToken>(x => x.Number < 100,"number less than `100`");
var numbers = number.AtEnd();
AssertParser.FailsWithMessage(numbers, "123", new SExpressionXTokenizer(),
"Syntax error (line 1, column 1): unexpected S-Expression Token `123`, expected number less than `100`.");
}
[Fact]
public void ErrorMessageFromPartialItemsPropagate()
{
var atom = Token.EqualTo(SExpressionToken.Atom);
var number = Token.EqualTo(SExpressionToken.Number);
var alternating = number.Then(n => atom).AtEnd();
AssertParser.FailsWithMessage(alternating, "123 123", new SExpressionTokenizer(),
"Syntax error (line 1, column 5): unexpected number `123`, expected atom.");
}
[Fact]
public void ErrorMessageFromLastPartialItemPropagates()
{
var atom = Token.EqualTo(SExpressionToken.Atom);
var number = Token.EqualTo(SExpressionToken.Number);
var alternating = number.Then(n => atom).Many().AtEnd();
AssertParser.FailsWithMessage(alternating, "123 abc 123 123", new SExpressionTokenizer(),
"Syntax error (line 1, column 13): unexpected number `123`, expected atom.");
}
[Fact]
public void ErrorMessageFromIncompleteItemPropagates()
{
var atom = Token.EqualTo(SExpressionToken.Atom);
var number = Token.EqualTo(SExpressionToken.Number);
var alternating = number.Then(n => atom).AtEnd();
AssertParser.FailsWithMessage(alternating, "123", new SExpressionTokenizer(),
"Syntax error: unexpected end of input, expected atom.");
}
// ReSharper disable once MemberCanBePrivate.Global
public static IEnumerable<object[]> ArithmeticExpressionTokenizers()
{
yield return new object[] { new ArithmeticExpressionTokenizer() };
yield return new object[] {
new TokenizerBuilder<ArithmeticExpressionToken>()
.Ignore(Span.WhiteSpace)
.Match(Character.EqualTo('+'), ArithmeticExpressionToken.Plus)
.Match(Character.EqualTo('-'), ArithmeticExpressionToken.Minus)
.Match(Character.EqualTo('*'), ArithmeticExpressionToken.Times)
.Match(Character.EqualTo('/'), ArithmeticExpressionToken.Divide)
.Match(Character.EqualTo('('), ArithmeticExpressionToken.LParen)
.Match(Character.EqualTo(')'), ArithmeticExpressionToken.RParen)
.Match(Numerics.Natural, ArithmeticExpressionToken.Number, requireDelimiters: true)
.Build()
};
}
[Theory, MemberData(nameof(ArithmeticExpressionTokenizers))]
public void DroppedClosingParenthesisProducesMeaningfulError(Tokenizer<ArithmeticExpressionToken> tokenizer)
{
AssertParser.FailsWithMessage(ArithmeticExpressionParser.Lambda, "1 + (2 * 3", tokenizer,
"Syntax error: unexpected end of input, expected `)`.");
}
[Theory, MemberData(nameof(ArithmeticExpressionTokenizers))]
public void MissingOperandProducesMeaningfulError(Tokenizer<ArithmeticExpressionToken> tokenizer)
{
AssertParser.FailsWithMessage(ArithmeticExpressionParser.Lambda, "1 + * 3", tokenizer,
"Syntax error (line 1, column 5): unexpected operator `*`, expected expression.");
}
[Theory, MemberData(nameof(ArithmeticExpressionTokenizers))]
public void MissingOperatorProducesMeaningfulError(Tokenizer<ArithmeticExpressionToken> tokenizer)
{
AssertParser.FailsWithMessage(ArithmeticExpressionParser.Lambda, "1 3", tokenizer,
"Syntax error (line 1, column 3): unexpected number `3`.");
}
[Fact]
public void AmbiguousMatchesFailWithoutTry()
{
var abc = Span.EqualTo("ab").Or(Span.EqualTo("ac"));
AssertParser.FailsWithMessage(abc, "ac",
"Syntax error (line 1, column 2): unexpected `c`, expected `b`.");
}
[Fact]
public void AmbiguousMatchesProducePreciseErrors()
{
var abc = Span.EqualTo("ab").Try().Or(Span.EqualTo("ac"));
AssertParser.FailsWithMessage(abc, "bb",
"Syntax error (line 1, column 1): unexpected `b`, expected `ab` or `ac`.");
}
[Fact]
public void AmbiguousPrefixMatchesProducePreciseErrors()
{
var abc = Span.EqualTo("ab").Try().Or(Span.EqualTo("ac"));
AssertParser.FailsWithMessage(abc, "ad",
"Syntax error (line 1, column 2): unexpected `d`, expected `b` or `c`.");
}
[Fact]
public void EmptySpanEqualToCharProducesCorrectExpectations()
{
var equalToA = Span.EqualTo('a');
AssertParser.FailsWithMessage(equalToA, "",
"Syntax error: unexpected end of input, expected `a`.");
}
[Fact]
public void EmptySpanEqualToCharProducesCorrectExpectationsIgnoreCase()
{
var equalToA = Span.EqualToIgnoreCase('a');
AssertParser.FailsWithMessage(equalToA, "",
"Syntax error: unexpected end of input, expected `a`.");
}
[Fact]
public void MessageWithExpectedTokensUsesTokenPresentation()
{
// Composing a complex parser which does not fit a LALR(1) grammar, one might need
// to have multiple look-ahead tokens. While it is possible to compose parsers with back-tracking,
// manual generated parsers are some times easier to construct. These parsers would like
// to report expectations using tokens, but still benefit from the annotations put on
// the tokens, to generated nicely formatted error messages. The following construct
// shows how to generate an empty result, which indicates which tokens are expected.
var emptyParseResult = TokenListParserResult.Empty<ArithmeticExpressionToken, string>(
new TokenList<ArithmeticExpressionToken>(),
new []{ ArithmeticExpressionToken.Times, ArithmeticExpressionToken.Zero});
// Empty result represent expectations using nice string representation taken from
// annotations of enum values of tokens
Assert.Equal(2, emptyParseResult.Expectations!.Length);
Assert.Equal( "`*`", emptyParseResult.Expectations![0]);
Assert.Equal("`zero`", emptyParseResult.Expectations![1]);
}
}
}