Skip to content

Commit 852caf3

Browse files
committed
ParserContext now directly manages its Token property.
1 parent 81610d3 commit 852caf3

5 files changed

Lines changed: 91 additions & 54 deletions

File tree

SqlServerSimulator/Parser/Expression.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ public static Expression Parse(ParserContext context)
4848
switch (reservedKeyword.Keyword)
4949
{
5050
case Keyword.As:
51-
if (expression is null || !context.TryMoveNext(out context.Token) || context.Token is not Name alias)
51+
if (expression is null || context.GetNextOptional() is not Name alias)
5252
throw SimulatedSqlException.SyntaxErrorNearKeyword(reservedKeyword);
5353

5454
expression = new NamedExpression(expression, alias.Value);
55-
_ = context.TryMoveNext(out context.Token);
55+
context.MoveNextOptional();
5656
return expression;
5757
case Keyword.From:
5858
if (expression is null)
@@ -71,12 +71,12 @@ public static Expression Parse(ParserContext context)
7171
case Plus:
7272
if (expression is null)
7373
{
74-
context.Token = context.RequireNext();
74+
context.MoveNextRequired();
7575
expression = Expression.Parse(context);
7676
break;
7777
}
7878

79-
context.Token = context.RequireNext();
79+
context.MoveNextRequired();
8080

8181
{
8282
var parsed = Parse(context);
@@ -90,13 +90,13 @@ public static Expression Parse(ParserContext context)
9090
case Minus:
9191
if (expression is null)
9292
{
93-
context.Token = context.RequireNext();
93+
context.MoveNextRequired();
9494
expression = Parse(context);
9595
expression = new Subtract(new Value(0), expression);
9696
break;
9797
}
9898

99-
context.Token = context.RequireNext();
99+
context.MoveNextRequired();
100100

101101
{
102102
var parsed = Parse(context);
@@ -116,7 +116,7 @@ public static Expression Parse(ParserContext context)
116116
if (expression is not Reference reference)
117117
throw new NotSupportedException("Simulated expression parser doesn't know how to handle '.' here.");
118118

119-
reference.AddMultiPartComponent(context.RequireNext<Name>());
119+
reference.AddMultiPartComponent(context.GetNextRequired<Name>());
120120
}
121121
break;
122122
case Comma:
@@ -128,15 +128,15 @@ public static Expression Parse(ParserContext context)
128128
{
129129
if (expression is not Reference reference)
130130
throw SimulatedSqlException.SyntaxErrorNear(context.Token);
131-
context.Token = context.RequireNext(); // Move past (
131+
context.MoveNextRequired(); // Move past (
132132
expression = ResolveBuiltIn(reference.Name, context);
133-
_ = context.TryMoveNext(out context.Token); // Move past )
133+
context.MoveNextOptional(); // Move past )
134134
return expression;
135135
}
136136
default:
137137
throw new NotSupportedException($"Simulated expression parser doesn't know how to handle '{context.Token}'.");
138138
}
139-
} while ((tokenWasRead && context.Token is not null) || context.TryMoveNext(out context.Token));
139+
} while ((tokenWasRead && context.Token is not null) || context.GetNextOptional() is not null);
140140

141141
return expression;
142142
}

SqlServerSimulator/Parser/ParserContext.cs

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using System.Collections.Frozen;
22
using System.Data.Common;
3-
using System.Diagnostics;
4-
using System.Diagnostics.CodeAnalysis;
53

64
namespace SqlServerSimulator.Parser;
75

@@ -16,7 +14,7 @@ internal sealed class ParserContext : IDisposable
1614
#else
1715
private readonly IEnumerator<Token> tokens;
1816
#endif
19-
public Token? Token;
17+
public Token? Token { get; private set; }
2018
private readonly FrozenDictionary<string, (string Name, (DataType type, object? Value) TypeValue)> variables;
2119

2220
public ParserContext(SimulatedDbCommand command)
@@ -54,45 +52,84 @@ public ParserContext(SimulatedDbCommand command)
5452
? value.TypeValue.Value
5553
: throw new SimulatedSqlException($"Must declare the scalar variable \"@{name}\".");
5654

55+
/// <summary>
56+
/// Advances <see cref="Token"/> to the next token, if one exists.
57+
/// </summary>
58+
public void MoveNextOptional()
59+
{
60+
var enumerator = this.tokens;
61+
this.Token = enumerator.MoveNext() ? enumerator.Current : null;
62+
}
5763

5864
/// <summary>
59-
/// Moves to the next item in an enumeration (if possible) and transfers <see cref="IEnumerator{T}.Current"/> to <paramref name="current"/>.
65+
/// Returns the next token in the enumeration, or null.
66+
/// Also updates <see cref="Token"/> to the new value.
6067
/// </summary>
61-
/// <param name="current">Receives the <see cref="IEnumerator{T}.Current"/> value, or the types default if advancement isn't possible.</param>
62-
/// <returns>True if the enumerator was advanced, otherwise false.</returns>
63-
public bool TryMoveNext([NotNullWhen(true)] out Token? current)
68+
/// <returns>The next token if the enumerator was advanced, otherwise null.</returns>
69+
public Token? GetNextOptional()
6470
{
6571
var enumerator = this.tokens;
66-
bool moved;
67-
current = (moved = enumerator.MoveNext()) ? enumerator.Current : default;
68-
return moved;
72+
return this.Token = enumerator.MoveNext() ? enumerator.Current : null;
6973
}
7074

7175
/// <summary>
7276
/// Returns the next token in the enumeration, throwing an exception if the end was reached instead.
77+
/// Also updates <see cref="Token"/> to the new value.
7378
/// </summary>
7479
/// <returns>The next token.</returns>
7580
/// <exception cref="SimulatedSqlException">Incorrect syntax near '{token}'.</exception>
76-
public Token RequireNext()
81+
public Token GetNextRequired()
7782
{
7883
var enumerator = this.tokens;
79-
Debug.Assert(enumerator.Current is not null);
8084
var previous = enumerator.Current;
81-
return enumerator.MoveNext() ? enumerator.Current : throw SimulatedSqlException.SyntaxErrorNear(previous);
85+
return enumerator.MoveNext() ? this.Token = enumerator.Current : throw SimulatedSqlException.SyntaxErrorNear(previous);
8286
}
8387

8488
/// <summary>
8589
/// Returns the next token in the enumeration, throwing an exception if the end was reached instead or the token is the wrong type.
90+
/// Also updates <see cref="Token"/> to the new value.
8691
/// </summary>
92+
/// <typeparam name="T">The expected type of the new token.</typeparam>
8793
/// <returns>The next token.</returns>
8894
/// <exception cref="SimulatedSqlException">Incorrect syntax near '{token}'.</exception>
89-
public T RequireNext<T>()
95+
public T GetNextRequired<T>()
9096
where T : Token
9197
{
9298
var enumerator = this.tokens;
93-
Debug.Assert(enumerator.Current is not null);
9499
var previous = enumerator.Current;
95-
return enumerator.MoveNext() && enumerator.Current is T current ? current : throw SimulatedSqlException.SyntaxErrorNear(previous);
100+
101+
if (enumerator.MoveNext() && enumerator.Current is T current)
102+
{
103+
this.Token = current;
104+
return current;
105+
}
106+
107+
throw SimulatedSqlException.SyntaxErrorNear(previous);
108+
}
109+
110+
/// <summary>
111+
/// Advances <see cref="Token"/> to the next token in the enumeration, throwing an exception if the end was reached instead or the token is the wrong type.
112+
/// </summary>
113+
/// <typeparam name="T">The expected type of the new token.</typeparam>
114+
/// <exception cref="SimulatedSqlException">Incorrect syntax near '{token}'.</exception>
115+
public void MoveNextRequired<T>()
116+
where T : Token
117+
{
118+
var enumerator = this.tokens;
119+
var previous = enumerator.Current;
120+
121+
this.Token = enumerator.MoveNext() && enumerator.Current is T current ? (Token)current : throw SimulatedSqlException.SyntaxErrorNear(previous);
122+
}
123+
124+
/// <summary>
125+
/// Advances <see cref="Token"/> to the next token in the enumeration, throwing an exception if the end was reached instead.
126+
/// </summary>
127+
/// <exception cref="SimulatedSqlException">Incorrect syntax near '{token}'.</exception>
128+
public void MoveNextRequired()
129+
{
130+
var enumerator = this.tokens;
131+
var previous = enumerator.Current;
132+
this.Token = enumerator.MoveNext() ? enumerator.Current : throw SimulatedSqlException.SyntaxErrorNear(previous);
96133
}
97134

98135
private bool isDisposed;

SqlServerSimulator/Parser/Selection.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,24 @@ internal sealed class Selection
2121
/// <exception cref="NotSupportedException">A condition was encountered that may be valid but can't currently be parsed.</exception>
2222
public static Selection Parse(ParserContext context, uint depth)
2323
{
24-
context.Token = context.RequireNext();
24+
context.MoveNextRequired();
2525

2626
int? topCount = null;
2727

2828
if (context.Token is ReservedKeyword { Keyword: Keyword.Top })
2929
{
3030
// SQL Server doesn't require outer parentheses.
3131
// When Expression.Parse supports them, the checks for them here should be removed.
32-
context.Token = context.RequireNext<OpenParentheses>();
33-
context.Token = context.RequireNext();
32+
context.MoveNextRequired<OpenParentheses>();
33+
context.MoveNextRequired();
3434

3535
var resolvedExpression = Expression.Parse(context).Run(name => throw SimulatedSqlException.ColumnReferenceNotAllowed(name));
3636
topCount = resolvedExpression is int unboxed ? unboxed : throw SimulatedSqlException.TopFetchRequiresInteger();
3737

3838
if (context.Token is not null and not CloseParentheses)
3939
throw SimulatedSqlException.SyntaxErrorNear(context.Token);
4040

41-
context.Token = context.RequireNext();
41+
context.MoveNextRequired();
4242
}
4343

4444
List<Expression> expressions = [];
@@ -76,13 +76,13 @@ public static Selection Parse(ParserContext context, uint depth)
7676
if (expectFrom.Keyword != Keyword.From)
7777
throw new NotSupportedException("Simulated selection processor expected a `from`.");
7878

79-
switch (context.Token = context.RequireNext())
79+
switch (context.GetNextRequired())
8080
{
8181
case StringToken tableName:
8282
if (!context.Simulation.Tables.TryGetValue(tableName.Value, out var table) && !context.Simulation.SystemTables.Value.TryGetValue(tableName.Value, out table))
8383
throw SimulatedSqlException.InvalidObjectName(tableName);
8484

85-
if (context.TryMoveNext(out context.Token))
85+
if (context.GetNextOptional() is not null)
8686
{
8787
if (context.Token is ReservedKeyword { Keyword: Keyword.As })
8888
{
@@ -105,13 +105,13 @@ public static Selection Parse(ParserContext context, uint depth)
105105
));
106106

107107
case OpenParentheses:
108-
if ((context.Token = context.RequireNext()) is not ReservedKeyword { Keyword: Keyword.Select })
108+
if (context.GetNextRequired() is not ReservedKeyword { Keyword: Keyword.Select })
109109
throw SimulatedSqlException.SyntaxErrorNear(context.Token);
110110

111111
{
112112
var derived = Selection.Parse(context, depth + 1).Results;
113113

114-
if ((context.Token = context.RequireNext()) is ReservedKeyword { Keyword: Keyword.As })
114+
if (context.GetNextRequired() is ReservedKeyword { Keyword: Keyword.As })
115115
{
116116
if (context.Token is not ReservedKeyword)
117117
break;
@@ -136,7 +136,7 @@ public static Selection Parse(ParserContext context, uint depth)
136136
}
137137

138138
throw new NotSupportedException($"Simulated selection processor doesn't know what to do with {context.Token}.");
139-
} while (context.TryMoveNext(out context.Token));
139+
} while (context.GetNextOptional() is not null);
140140

141141
throw new NotSupportedException($"Simulated selection reached the end of the command before expected.");
142142
}

SqlServerSimulator/SimulatedSqlException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ internal static SimulatedSqlException ColumnReferenceNotAllowed(IEnumerable<stri
108108

109109
internal static SimulatedSqlException SyntaxErrorNearKeyword(ReservedKeyword token) => new($"Incorrect syntax near the keyword '{token}'.", 156, 15, 1);
110110

111-
internal static SimulatedSqlException SyntaxErrorNear(Token token) => new($"Incorrect syntax near '{token}'.", 102, 15, 1);
111+
internal static SimulatedSqlException SyntaxErrorNear(Token? token) => new($"Incorrect syntax near '{token}'.", 102, 15, 1);
112112

113113
internal static SimulatedSqlException ThereIsAlreadyAnObject(string name) => new($"There is already an object named '{name}' in the database.", 2714, 16, 6);
114114

0 commit comments

Comments
 (0)