Skip to content

Commit 00f4232

Browse files
committed
Simplified expression parsing: initial + extended expression logic is split rather than mixed, expression name assignment is now handled at the selection level.
1 parent 7c5937c commit 00f4232

2 files changed

Lines changed: 75 additions & 116 deletions

File tree

SqlServerSimulator/Parser/Expression.cs

Lines changed: 49 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -26,121 +26,84 @@ private protected Expression()
2626
/// <exception cref="NotSupportedException">A condition was encountered that may be valid but can't currently be parsed.</exception>
2727
public static Expression Parse(ParserContext context)
2828
{
29-
Expression? expression = null;
30-
bool tokenWasRead;
29+
Expression expression;
3130

32-
do
31+
switch (context.Token)
3332
{
34-
tokenWasRead = false;
33+
case Numeric number:
34+
expression = new Value(number);
35+
break;
36+
case AtPrefixedString atPrefixed:
37+
expression = new Value(atPrefixed, context);
38+
break;
39+
case DoubleAtPrefixedString doubleAtPrefixedString:
40+
expression = new Value(doubleAtPrefixedString);
41+
break;
42+
case ReservedKeyword { Keyword: Keyword.Null }:
43+
expression = new Value();
44+
break;
45+
case Name name:
46+
expression = new Reference(name);
47+
break;
48+
case Operator { Character: '+' }:
49+
context.MoveNextRequired();
50+
expression = Expression.Parse(context);
51+
break;
52+
case Operator { Character: '-' }:
53+
context.MoveNextRequired();
54+
expression = Parse(context);
55+
expression = new Subtract(new Value(0), expression);
56+
break;
57+
default:
58+
throw SimulatedSqlException.SyntaxErrorNear(context.Token);
59+
}
3560

36-
switch (context.Token)
61+
while (true)
62+
{
63+
switch (context.GetNextOptional())
3764
{
38-
case Numeric number:
39-
expression = new Value(number);
40-
break;
41-
case AtPrefixedString atPrefixed:
42-
expression = new Value(atPrefixed, context);
43-
break;
44-
case DoubleAtPrefixedString doubleAtPrefixedString:
45-
expression = new Value(doubleAtPrefixedString);
46-
break;
47-
case ReservedKeyword reservedKeyword:
48-
switch (reservedKeyword.Keyword)
49-
{
50-
case Keyword.As:
51-
if (expression is null || context.GetNextOptional() is not Name alias)
52-
throw SimulatedSqlException.SyntaxErrorNearKeyword(reservedKeyword);
53-
54-
expression = new NamedExpression(expression, alias.Value);
55-
context.MoveNextOptional();
56-
return expression;
57-
case Keyword.From:
58-
if (expression is null)
59-
throw SimulatedSqlException.SyntaxErrorNearKeyword(reservedKeyword);
60-
61-
return expression;
62-
case Keyword.Null:
63-
expression = new Value();
64-
continue;
65-
}
66-
67-
throw SimulatedSqlException.SyntaxErrorNearKeyword(reservedKeyword);
68-
case Name name:
69-
expression = new Reference(name);
70-
break;
7165
case Operator { Character: '+' }:
72-
if (expression is null)
73-
{
74-
context.MoveNextRequired();
75-
expression = Expression.Parse(context);
76-
break;
77-
}
78-
7966
context.MoveNextRequired();
80-
81-
{
82-
var parsed = Parse(context);
83-
expression = new Add(expression, parsed);
84-
if (parsed is NamedExpression named)
85-
expression = named.TransferName(expression);
86-
}
87-
88-
tokenWasRead = true;
67+
expression = new Add(expression, Parse(context));
8968
break;
9069
case Operator { Character: '-' }:
91-
if (expression is null)
92-
{
93-
context.MoveNextRequired();
94-
expression = Parse(context);
95-
expression = new Subtract(new Value(0), expression);
96-
break;
97-
}
98-
9970
context.MoveNextRequired();
100-
101-
{
102-
var parsed = Parse(context);
103-
expression = new Subtract(expression, parsed);
104-
if (parsed is NamedExpression named)
105-
expression = named.TransferName(expression);
106-
}
107-
108-
tokenWasRead = true;
71+
expression = new Subtract(expression, Parse(context));
10972
break;
11073

11174
case Operator { Character: '.' }:
112-
if (expression is null)
113-
throw new NotSupportedException("Simulated expression parser doesn't know how to handle '.' at the start of an expression.");
114-
11575
{
11676
if (expression is not Reference reference)
117-
throw new NotSupportedException("Simulated expression parser doesn't know how to handle '.' here.");
77+
throw SimulatedSqlException.SyntaxErrorNear(context.Token);
11878

11979
reference.AddMultiPartComponent(context.GetNextRequired<Name>());
12080
}
121-
break;
122-
case Operator { Character: ',' }:
81+
continue;
12382
case Operator { Character: ')' }:
124-
if (expression is null)
125-
throw SimulatedSqlException.SyntaxErrorNear(context.Token);
126-
return expression;
83+
break;
12784
case Operator { Character: '(' }:
12885
{
12986
if (expression is not Reference reference)
13087
throw SimulatedSqlException.SyntaxErrorNear(context.Token);
13188
context.MoveNextRequired(); // Move past (
13289
expression = ResolveBuiltIn(reference.Name, context);
13390
context.MoveNextOptional(); // Move past )
134-
return expression;
91+
continue;
13592
}
136-
default:
137-
throw new NotSupportedException($"Simulated expression parser doesn't know how to handle '{context.Token}'.");
13893
}
139-
} while ((tokenWasRead && context.Token is not null) || context.GetNextOptional() is not null);
14094

141-
return expression;
95+
return expression;
96+
}
14297
}
14398

99+
/// <summary>
100+
/// Wraps the provided <see cref="Expression"/> in a <see cref="NamedExpression"/> with the provided <paramref name="name"/>.
101+
/// </summary>
102+
/// <param name="expression">The expression to wrap.</param>
103+
/// <param name="name">The name to assign.</param>
104+
/// <returns>The named expression.</returns>
105+
public static Expression AssignName(Expression expression, string name) => new NamedExpression(expression, name);
106+
144107
/// <summary>
145108
/// Runs the expression, returning its result.
146109
/// </summary>
@@ -180,30 +143,13 @@ private sealed class NamedExpression(Expression expression, string name) : Expre
180143
{
181144
private readonly Expression expression = expression;
182145
private readonly string name = name;
183-
#if DEBUG
184-
private bool transferred;
185-
#endif
186146

187147
public override string Name => this.name;
188148

189149
public override object? Run(Func<List<string>, object?> getColumnValue) => this.expression.Run(getColumnValue);
190150

191-
/// <summary>
192-
/// Transfers the name to an outer expression.
193-
/// </summary>
194-
/// <param name="destination">The expression wrapping this<see cref="NamedExpression"/>.</param>
195-
/// <returns>A new <see cref="NamedExpression"/> wrapping <paramref name="destination"/> using <see cref="Name"/>.</returns>
196-
public NamedExpression TransferName(Expression destination)
197-
{
198-
#if DEBUG
199-
transferred = true;
200-
#endif
201-
202-
return new(destination, this.name);
203-
}
204-
205151
#if DEBUG
206-
public override string ToString() => transferred ? expression.ToString() : $"{expression} {name}";
152+
public override string ToString() => $"{expression} {name}";
207153
#endif
208154
}
209155

SqlServerSimulator/Parser/Selection.cs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,29 +54,38 @@ public static Selection Parse(ParserContext context, uint depth)
5454

5555
do
5656
{
57-
expressions.Add(Expression.Parse(context));
58-
5957
switch (context.Token)
6058
{
59+
case ReservedKeyword { Keyword: Keyword.From }:
60+
break;
61+
6162
case Operator { Character: ',' }:
63+
if (expressions.Count == 0)
64+
throw SimulatedSqlException.SyntaxErrorNear(context.Token);
6265
continue;
63-
6466
case Operator { Character: ')' }:
6567
if (depth == 0)
6668
throw SimulatedSqlException.SyntaxErrorNear(context.Token);
69+
goto ExitWhileTokenLoop;
6770

68-
goto case null;
71+
default:
72+
expressions.Add(Expression.Parse(context));
73+
break;
74+
}
6975

70-
case null: // "Select" with no "From".
71-
return new(new(
72-
expressions,
73-
ApplyClauses([[.. expressions.Select(x => x.Run(column => throw SimulatedSqlException.InvalidColumnName(column)))]])
74-
));
76+
switch (context.Token)
77+
{
78+
case null:
79+
goto ExitWhileTokenLoop;
7580

76-
case ReservedKeyword expectFrom:
77-
if (expectFrom.Keyword != Keyword.From)
78-
throw new NotSupportedException("Simulated selection processor expected a `from`.");
81+
case Operator { Character: ',' }:
82+
continue;
83+
84+
case ReservedKeyword { Keyword: Keyword.As }:
85+
expressions[^1] = Expression.AssignName(expressions.Last(), context.GetNextRequired<StringToken>().Value);
86+
continue;
7987

88+
case ReservedKeyword { Keyword: Keyword.From }:
8089
switch (context.GetNextRequired())
8190
{
8291
case StringToken tableName:
@@ -138,7 +147,11 @@ public static Selection Parse(ParserContext context, uint depth)
138147

139148
throw new NotSupportedException($"Simulated selection processor doesn't know what to do with {context.Token}.");
140149
} while (context.GetNextOptional() is not null);
150+
ExitWhileTokenLoop:
141151

142-
throw new NotSupportedException($"Simulated selection reached the end of the command before expected.");
152+
return new(new(
153+
expressions,
154+
ApplyClauses([[.. expressions.Select(x => x.Run(column => throw SimulatedSqlException.InvalidColumnName(column)))]])
155+
));
143156
}
144157
}

0 commit comments

Comments
 (0)