Skip to content

Commit 1b2fe20

Browse files
authored
Fix Separated parser (sebastienros#55)
Fixes sebastienros#48
1 parent b306212 commit 1b2fe20

File tree

5 files changed

+64
-21
lines changed

5 files changed

+64
-21
lines changed

Parlot.sln

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 16
4-
VisualStudioVersion = 16.0.30709.64
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.1.31903.286
55
MinimumVisualStudioVersion = 15.0.26124.0
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0D1E6480-3C81-4951-8F44-BF74398BA8D4}"
77
EndProject
@@ -16,6 +16,8 @@ EndProject
1616
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0EA81138-0646-4AFD-96C6-83A94D568C83}"
1717
ProjectSection(SolutionItems) = preProject
1818
.editorconfig = .editorconfig
19+
.github\workflows\build.yml = .github\workflows\build.yml
20+
.github\workflows\publish.yml = .github\workflows\publish.yml
1921
README.md = README.md
2022
EndProjectSection
2123
EndProject

docs/parsers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ null // success
438438

439439
### Separated
440440

441-
Matches all occurrences of a parser that are separated by another one.
441+
Matches all occurrences of a parser that are separated by another one. If a separator is not followed by a value, it is not consumed.
442442

443443
```
444444
Parser<List<T>> Separated<U, T>(Parser<U> separator, Parser<T> parser)

src/Parlot/Fluent/Separated.cs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,42 +23,50 @@ public override bool Parse(ParseContext context, ref ParseResult<List<T>> result
2323
List<T> results = null;
2424

2525
var start = 0;
26-
var end = 0;
26+
var end = context.Scanner.Cursor.Position;
2727

2828
var first = true;
2929
var parsed = new ParseResult<T>();
3030
var separatorResult = new ParseResult<U>();
3131

3232
while (true)
3333
{
34+
if (!first)
35+
{
36+
if (!_separator.Parse(context, ref separatorResult))
37+
{
38+
break;
39+
}
40+
}
41+
3442
if (!_parser.Parse(context, ref parsed))
3543
{
3644
if (!first)
3745
{
46+
// A separator was found, but not followed by another value.
47+
// It's still succesful if there was one value parsed, but we reset the cursor to before the separator
48+
context.Scanner.Cursor.ResetPosition(end);
3849
break;
3950
}
4051

41-
// A parser that returns false is reponsible for resetting the position.
42-
// Nothing to do here since the inner parser is already failing and resetting it.
4352
return false;
4453
}
45-
54+
else
55+
{
56+
end = context.Scanner.Cursor.Position;
57+
}
58+
4659
if (first)
4760
{
61+
results = new List<T>();
4862
start = parsed.Start;
63+
first = false;
4964
}
50-
51-
end = parsed.End;
52-
results ??= new List<T>();
65+
5366
results.Add(parsed.Value);
54-
55-
if (!_separator.Parse(context, ref separatorResult))
56-
{
57-
break;
58-
}
5967
}
6068

61-
result = new ParseResult<List<T>>(start, end, results);
69+
result = new ParseResult<List<T>>(start, end.Offset, results);
6270
return true;
6371
}
6472

@@ -68,6 +76,8 @@ public CompilationResult Compile(CompilationContext context)
6876

6977
var success = context.DeclareSuccessVariable(result, false);
7078
var value = context.DeclareValueVariable(result, Expression.New(typeof(List<T>)));
79+
80+
var end = context.DeclarePositionVariable(result);
7181

7282
// value = new List<T>();
7383
//
@@ -79,6 +89,7 @@ public CompilationResult Compile(CompilationContext context)
7989
// {
8090
// success = true;
8191
// value.Add(parse1.Value);
92+
// end = currenPosition;
8293
// }
8394
// else
8495
// {
@@ -93,6 +104,8 @@ public CompilationResult Compile(CompilationContext context)
93104
// }
94105
// }
95106
//
107+
// resetPosition(end);
108+
//
96109

97110
var parserCompileResult = _parser.Build(context);
98111
var breakLabel = Expression.Label("break");
@@ -110,7 +123,8 @@ public CompilationResult Compile(CompilationContext context)
110123
context.DiscardResult
111124
? Expression.Empty()
112125
: Expression.Call(value, typeof(List<T>).GetMethod("Add"), parserCompileResult.Value),
113-
Expression.Assign(success, Expression.Constant(true))
126+
Expression.Assign(success, Expression.Constant(true)),
127+
Expression.Assign(end, context.Position())
114128
),
115129
Expression.Break(breakLabel)
116130
),
@@ -127,7 +141,8 @@ public CompilationResult Compile(CompilationContext context)
127141
Expression.Break(breakLabel)
128142
)
129143
),
130-
breakLabel)
144+
breakLabel),
145+
context.ResetPosition(end)
131146
);
132147

133148
result.Body.Add(block);

test/Parlot.Tests/CompileTests.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,14 @@ public void ShouldCompileBetweens()
210210
}
211211

212212
[Fact]
213-
public void ShouldCompileSeparatedChar()
213+
public void ShouldcompiledSeparated()
214214
{
215215
var parser = Separated(Terms.Char(','), Terms.Decimal()).Compile();
216216

217-
Assert.Null(parser.Parse(""));
218217
Assert.Single(parser.Parse("1"));
218+
Assert.Equal(2, parser.Parse("1,2").Count);
219219
Assert.Null(parser.Parse(",1,"));
220+
Assert.Null(parser.Parse(""));
220221

221222
var result = parser.Parse("1, 2,3");
222223

@@ -225,6 +226,18 @@ public void ShouldCompileSeparatedChar()
225226
Assert.Equal(3, result[2]);
226227
}
227228

229+
[Fact]
230+
public void SeparatedShouldNotBeConsumedIfNotFollowedByValueCompiled()
231+
{
232+
// This test ensures that the separator is not consumed if there is no valid net value.
233+
234+
var parser = Separated(Terms.Char(','), Terms.Decimal()).AndSkip(Terms.Char(',')).And(Terms.Identifier()).Then(x => true).Compile();
235+
236+
Assert.False(parser.Parse("1"));
237+
Assert.False(parser.Parse("1,"));
238+
Assert.True(parser.Parse("1,x"));
239+
}
240+
228241
[Fact]
229242
public void ShouldCompileExpressionParser()
230243
{

test/Parlot.Tests/FluentTests.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,9 +582,10 @@ public void SeparatedShouldSplit()
582582
{
583583
var parser = Separated(Terms.Char(','), Terms.Decimal());
584584

585-
Assert.Null(parser.Parse(""));
586585
Assert.Single(parser.Parse("1"));
586+
Assert.Equal(2, parser.Parse("1,2").Count);
587587
Assert.Null(parser.Parse(",1,"));
588+
Assert.Null(parser.Parse(""));
588589

589590
var result = parser.Parse("1, 2,3");
590591

@@ -593,6 +594,18 @@ public void SeparatedShouldSplit()
593594
Assert.Equal(3, result[2]);
594595
}
595596

597+
[Fact]
598+
public void SeparatedShouldNotBeConsumedIfNotFollowedByValue()
599+
{
600+
// This test ensures that the separator is not consumed if there is no valid net value.
601+
602+
var parser = Separated(Terms.Char(','), Terms.Decimal()).AndSkip(Terms.Char(',')).And(Terms.Identifier()).Then(x => true);
603+
604+
Assert.False(parser.Parse("1"));
605+
Assert.False(parser.Parse("1,"));
606+
Assert.True(parser.Parse("1,x"));
607+
}
608+
596609
[Fact]
597610
public void ShouldSkipWhiteSpace()
598611
{

0 commit comments

Comments
 (0)