@@ -7,57 +7,36 @@ namespace SqlServerSimulator.Parser;
77/// <summary>
88/// Organizes relevant information for parsing of SQL commands.
99/// </summary>
10- internal sealed class ParserContext : IDisposable
10+ internal sealed class ParserContext ( SimulatedDbCommand command )
1111{
12- public readonly SimulatedDbCommand Command ;
13- #if DEBUG
14- private readonly TokenArrayEnumerator tokens ;
15- #else
16- private readonly IEnumerator < Token > tokens ;
17- #endif
18- public Token ? Token { get ; private set ; }
19- private readonly FrozenDictionary < string , ( string Name , ( DataType type , object ? Value ) TypeValue ) > variables ;
12+ #pragma warning disable CA2213 // Disposable fields should be disposed
13+ public readonly SimulatedDbCommand Command = command ;
14+ #pragma warning restore CA2213 // Suppressed because ParserContext doesn't own the command object.
2015
21- public ParserContext ( SimulatedDbCommand command )
22- {
23- this . Command = command ;
24-
25- this . variables = command
26- . Parameters
27- . Cast < DbParameter > ( )
28- . Select ( parameter =>
29- {
30- var name = parameter . ParameterName ;
31- var type = DataType . GetByDbType ( parameter . DbType ) ;
32- return ( Name : name . StartsWith ( '@' ) ? name [ 1 ..] : name , TypeValue : ( DataType : type , Value : parameter . Value is null ? null : type . ConvertFrom ( parameter . Value ) ) ) ;
33- } )
34- . ToFrozenDictionary ( tuple => tuple . Name , StringComparer . InvariantCultureIgnoreCase ) ;
16+ private readonly string commandText = string . IsNullOrEmpty ( command . CommandText ) ?
17+ throw new InvalidOperationException ( "ExecuteReader: CommandText property has not been initialized" ) :
18+ command . CommandText ;
3519
36- #if DEBUG
37- this . tokens = new TokenArrayEnumerator ( command . CommandText ) ;
38- #else
39- this . tokens = TokenizerIterator ( command . CommandText ) . GetEnumerator ( ) ;
40- #endif
41- }
20+ /// <summary>
21+ /// The tokenizer position within <see cref="commandText"/>.
22+ /// </summary>
23+ private int index = - 1 ;
4224
4325 /// <summary>
44- /// Converts the redesigned tokenizer to a traditional enumerable for compatibility with the existing logic.
45- /// This should be phased out eventually.
26+ /// The most recently identified token in the command string.
4627 /// </summary>
47- static IEnumerable < Token > TokenizerIterator ( string ? commandText )
48- {
49- if ( string . IsNullOrEmpty ( commandText ) )
50- throw new InvalidOperationException ( "ExecuteReader: CommandText property has not been initialized" ) ;
28+ public Token ? Token { get ; private set ; }
5129
52- var index = - 1 ;
53- while ( Tokenizer . NextToken ( commandText , ref index ) is Token token )
30+ private readonly FrozenDictionary < string , ( string Name , ( DataType type , object ? Value ) TypeValue ) > variables = command
31+ . Parameters
32+ . Cast < DbParameter > ( )
33+ . Select ( parameter =>
5434 {
55- if ( token is Whitespace )
56- continue ;
57-
58- yield return token ;
59- }
60- }
35+ var name = parameter . ParameterName ;
36+ var type = DataType . GetByDbType ( parameter . DbType ) ;
37+ return ( Name : name . StartsWith ( '@' ) ? name [ 1 ..] : name , TypeValue : ( DataType : type , Value : parameter . Value is null ? null : type . ConvertFrom ( parameter . Value ) ) ) ;
38+ } )
39+ . ToFrozenDictionary ( tuple => tuple . Name , StringComparer . InvariantCultureIgnoreCase ) ;
6140
6241 public Simulation Simulation => Command . simulation ;
6342
@@ -77,8 +56,7 @@ static IEnumerable<Token> TokenizerIterator(string? commandText)
7756 /// </summary>
7857 public void MoveNextOptional ( )
7958 {
80- var enumerator = this . tokens ;
81- this . Token = enumerator . MoveNext ( ) ? enumerator . Current : null ;
59+ _ = MoveNext ( ) ;
8260 }
8361
8462 /// <summary>
@@ -88,8 +66,7 @@ public void MoveNextOptional()
8866 /// <returns>The next token if the enumerator was advanced, otherwise null.</returns>
8967 public Token ? GetNextOptional ( )
9068 {
91- var enumerator = this . tokens ;
92- return this . Token = enumerator . MoveNext ( ) ? enumerator . Current : null ;
69+ return MoveNext ( ) ? this . Token : null ;
9370 }
9471
9572 /// <summary>
@@ -100,9 +77,8 @@ public void MoveNextOptional()
10077 /// <exception cref="SimulatedSqlException">Incorrect syntax near '{token}'.</exception>
10178 public Token GetNextRequired ( )
10279 {
103- var enumerator = this . tokens ;
104- var previous = enumerator . Current ;
105- return enumerator . MoveNext ( ) ? this . Token = enumerator . Current : throw SimulatedSqlException . SyntaxErrorNear ( previous ) ;
80+ var previous = this . Token ;
81+ return MoveNext ( ) ? this . Token ! : throw SimulatedSqlException . SyntaxErrorNear ( previous ) ;
10682 }
10783
10884 /// <summary>
@@ -115,16 +91,9 @@ public Token GetNextRequired()
11591 public T GetNextRequired < T > ( )
11692 where T : Token
11793 {
118- var enumerator = this . tokens ;
119- var previous = enumerator . Current ;
94+ var previous = this . Token ;
12095
121- if ( enumerator . MoveNext ( ) && enumerator . Current is T current )
122- {
123- this . Token = current ;
124- return current ;
125- }
126-
127- throw SimulatedSqlException . SyntaxErrorNear ( previous ) ;
96+ return MoveNext ( ) && this . Token is T current ? current : throw SimulatedSqlException . SyntaxErrorNear ( previous ) ;
12897 }
12998
13099 /// <summary>
@@ -135,10 +104,10 @@ public T GetNextRequired<T>()
135104 public void MoveNextRequired < T > ( )
136105 where T : Token
137106 {
138- var enumerator = this . tokens ;
139- var previous = enumerator . Current ;
107+ var previous = this . Token ;
140108
141- this . Token = enumerator . MoveNext ( ) && enumerator . Current is T current ? ( Token ) current : throw SimulatedSqlException . SyntaxErrorNear ( previous ) ;
109+ if ( ! MoveNext ( ) || this . Token is not T )
110+ throw SimulatedSqlException . SyntaxErrorNear ( previous ) ;
142111 }
143112
144113 /// <summary>
@@ -147,98 +116,55 @@ public void MoveNextRequired<T>()
147116 /// <exception cref="SimulatedSqlException">Incorrect syntax near '{token}'.</exception>
148117 public void MoveNextRequired ( )
149118 {
150- var enumerator = this . tokens ;
151- var previous = enumerator . Current ;
152- this . Token = enumerator . MoveNext ( ) ? enumerator . Current : throw SimulatedSqlException . SyntaxErrorNear ( previous ) ;
119+ var previous = this . Token ;
120+ if ( ! MoveNext ( ) )
121+ throw SimulatedSqlException . SyntaxErrorNear ( previous ) ;
153122 }
154123
155- private bool isDisposed ;
156-
157- private void Dispose ( bool disposing )
124+ private bool MoveNext ( )
158125 {
159- if ( isDisposed )
160- return ;
161-
162- if ( disposing )
126+ while ( Tokenizer . NextToken ( commandText , ref index ) is Token token )
163127 {
164- this . tokens . Dispose ( ) ;
165- this . Token = null ;
128+ if ( token is Whitespace )
129+ continue ;
130+
131+ this . Token = token ;
132+ return true ;
166133 }
167134
168- isDisposed = true ;
169- }
170-
171- public void Dispose ( )
172- {
173- // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
174- Dispose ( true ) ;
175- GC . SuppressFinalize ( this ) ;
135+ this . Token = null ;
136+ return false ;
176137 }
177138
178139#if DEBUG
179- public override string ? ToString ( ) => this . tokens . ToString ( ) ;
180-
181140 /// <summary>
182- /// Simplifies parser debugging by providing a useful string representation of the token enumeration .
183- /// Specifically, when calling <see cref="ToString"/> tokens are separated by '·' and the <see cref="Current "/> token is wrapped by '»' and '«'.
141+ /// Returns a string representation of the tokenized command .
142+ /// The <see cref="Token "/> token is wrapped by '»' and '«'.
184143 /// </summary>
185- /// <param name="command">The SQL command to process.</param>
186- /// <remarks>This should only be included in debug builds because it reduces parsing efficiency.</remarks>
187- private sealed class TokenArrayEnumerator ( string ? command ) : IEnumerator < Token >
144+ /// <returns>The string representation.</returns>
145+ public override string ToString ( )
188146 {
189- /// <summary>
190- /// Retains the full results of tokenization.
191- /// This is less efficient than streaming the results, but enables this class's debugging-friendly <see cref="ToString"/>.
192- /// </summary>
193- private readonly Token [ ] source = [ .. TokenizerIterator ( command ) ] ;
194-
195- public int Index { get ; private set ; } = - 1 ;
196-
197- public Token Current => source [ Index ] ;
198-
199- object System . Collections . IEnumerator . Current => Current ;
200-
201- public void Dispose ( )
147+ var command = this . commandText ;
148+ Span < char > result = stackalloc char [ command . Length + 2 ] ;
149+ if ( index < 0 )
202150 {
151+ result [ 0 ] = '»' ;
152+ result [ 1 ] = '«' ;
153+ command . CopyTo ( result [ 2 ..] ) ;
203154 }
204-
205- public bool MoveNext ( )
155+ else if ( index >= command . Length )
206156 {
207- var newIndex = checked ( Index + 1 ) ;
208- if ( newIndex >= source . Length )
209- return false ;
210-
211- Index = newIndex ;
212- return true ;
157+ command . CopyTo ( result ) ;
158+ result [ ^ 2 ] = '»' ;
159+ result [ ^ 1 ] = '«' ;
213160 }
214-
215- public void Reset ( ) => Index = - 1 ;
216-
217- /// <summary>
218- /// Returns a string representation of the tokenized command.
219- /// Tokens are separated by '·' and the <see cref="Current"/> token is wrapped by '»' and '«'.
220- /// </summary>
221- /// <returns>The string representation.</returns>
222- public override string ToString ( )
161+ else
223162 {
224- var result = new System . Text . StringBuilder ( ) ;
225- var source = this . source ;
226-
227- for ( var i = 0 ; i < source . Length ; i ++ )
228- {
229- var token = source [ i ] ;
230- if ( i != Index )
231- {
232- _ = result . Append ( token ) . Append ( '·' ) ;
233- continue ;
234- }
235-
236- _ = result . Append ( '»' ) . Append ( token ) . Append ( '«' ) . Append ( '·' ) ;
237- }
238-
239- return result . ToString ( 0 , result . Length - 1 ) ;
240-
163+ System . Diagnostics . Debug . Assert ( this . Token is not null ) ;
164+ this . Token . Highlight ( result ) ;
241165 }
166+
167+ return new string ( result ) ;
242168 }
243169#endif
244170}
0 commit comments