Skip to content

Commit bc50e18

Browse files
committed
feat: improved wql parrser and minor bugs
1 parent f11ae60 commit bc50e18

26 files changed

Lines changed: 944 additions & 170 deletions

src/WebExpress.WebIndex.Test/TestWqlExpressionNodeFilterFunctionConstant.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using WebExpress.WebIndex.Wql.Function;
1+
using WebExpress.WebIndex.Wql;
2+
using WebExpress.WebIndex.Wql.Function;
23

34
namespace WebExpress.WebIndex.Test
45
{
@@ -8,6 +9,11 @@ namespace WebExpress.WebIndex.Test
89
/// <typeparam name="UnitTestIndexTestDocumentA">The type of the index item.</typeparam>
910
internal class TestWqlExpressionNodeFilterFunctionConstant<TIndexItem> : IWqlExpressionNodeFilterFunction<TIndexItem> where TIndexItem : IIndexItem
1011
{
12+
/// <summary>
13+
/// Returns the tokens associated with this syntax tree node.
14+
/// </summary>
15+
public IEnumerable<IWqlToken> Tokens { get; internal set; }
16+
1117
// <summary>
1218
// Returns the name of the function.
1319
// </summary>

src/WebExpress.WebIndex.Test/WQL/UnitTestWqlWordSearchA.cs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using WebExpress.WebIndex.Test.Fixture;
1+
using WebExpress.WebIndex.Test.Document;
2+
using WebExpress.WebIndex.Test.Fixture;
3+
using WebExpress.WebIndex.Wql;
24
using Xunit.Abstractions;
35

46
namespace WebExpress.WebIndex.Test.WQL
@@ -198,5 +200,59 @@ public void MultipleWords()
198200
Assert.Null(wql.Order);
199201
Assert.Null(wql.Partitioning);
200202
}
203+
204+
/// <summary>
205+
/// Verifies that the abstract syntax tree (AST) is generated.
206+
/// </summary>
207+
[Fact]
208+
public void AbstracrSyntaxTree()
209+
{
210+
// arrange
211+
var wql = Fixture.ExecuteWql("text~'Helena'");
212+
213+
// act
214+
var ast = wql.AbstractSyntaxTree;
215+
216+
// validation
217+
Assert.NotNull(ast);
218+
}
219+
220+
/// <summary>
221+
/// Performs the incremental lookahead analysis.
222+
/// </summary>
223+
[Theory]
224+
[InlineData("", true, 0, WqlExpressionType.None, WqlExpressionType.Attribute)]
225+
[InlineData("text", false, 1, WqlExpressionType.Attribute, WqlExpressionType.Operator)]
226+
[InlineData("text #", false, 2, WqlExpressionType.Operator, WqlExpressionType.Operator)]
227+
[InlineData("text ~", false, 2, WqlExpressionType.Operator, WqlExpressionType.Parameter)]
228+
[InlineData("text ~ (", false, 2, WqlExpressionType.Operator, WqlExpressionType.Parameter)]
229+
[InlineData("text ~ )", false, 2, WqlExpressionType.Operator, WqlExpressionType.Parameter)]
230+
[InlineData("text ~ and", false, 2, WqlExpressionType.Operator, WqlExpressionType.Parameter)]
231+
[InlineData("text ~ or", false, 2, WqlExpressionType.Operator, WqlExpressionType.Parameter)]
232+
[InlineData("text ~ &", false, 2, WqlExpressionType.Operator, WqlExpressionType.Parameter)]
233+
[InlineData("text ~ ||", false, 2, WqlExpressionType.Operator, WqlExpressionType.Parameter)]
234+
[InlineData("text ~ '", false, 3, WqlExpressionType.Parameter, WqlExpressionType.Parameter)]
235+
[InlineData("text ~ \"", false, 3, WqlExpressionType.Parameter, WqlExpressionType.Parameter)]
236+
[InlineData("text ~ Helena", true, 3, WqlExpressionType.Parameter, WqlExpressionType.LogicalOperator)]
237+
[InlineData("text ~ 'Helena'", true, 3, WqlExpressionType.Parameter, WqlExpressionType.LogicalOperator)]
238+
[InlineData("text ~ \"Helena\"", true, 3, WqlExpressionType.Parameter, WqlExpressionType.LogicalOperator)]
239+
[InlineData("text ~ day(", false, 3, WqlExpressionType.Parameter, WqlExpressionType.LogicalOperator)]
240+
[InlineData("text ~ day()", true, 3, WqlExpressionType.Parameter, WqlExpressionType.LogicalOperator)]
241+
[InlineData("text ~ Helena and", false, 4, WqlExpressionType.LogicalOperator, WqlExpressionType.Attribute)]
242+
public void Analyze(string wql, bool valid, int count, WqlExpressionType type, params WqlExpressionType[] expectedNextTokens)
243+
{
244+
// arrange
245+
var parser = new WqlParser<UnitTestIndexTestDocumentA>();
246+
247+
// act
248+
var ila = parser.Analyze(wql);
249+
250+
// validation
251+
Assert.NotNull(ila);
252+
Assert.True(ila.IsValidSoFar == valid);
253+
Assert.Equal(type, ila.LastExpressionType);
254+
Assert.Equal(count, ila.Items.Count());
255+
Assert.Contains(expectedNextTokens, t => ila.ExpectedNextTokens.Contains(t));
256+
}
201257
}
202258
}
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,16 @@
11
# Deutsch
2-
indexmanager.initialization=Der Index-Manager wurde initialisiert.
2+
indexmanager.initialization=Der Index-Manager wurde initialisiert.
3+
4+
wql.unexpected_token=Unerwartetes Token.
5+
wql.condition_unknown=Unbekannte Bedingung.
6+
wql.expected_operator=Es wird ein Operator erwartet.
7+
wql.expected_binary_or_set_condition=Es wird eine binäre oder Mengenbedingung erwartet.
8+
wql.expected_logicaloperator=Es wird ein logischer Operator erwartet.
9+
wql.attribute_unknown=Unbekanntes Attribut.
10+
wql.expected_terminated_string_token=Es wird ein String-Token mit einem abschließenden Anführungszeichen erwartet.
11+
wql.expected_similarity=Es wird eine Ähnlichkeitsbedingung erwartet.
12+
wql.expected_distance=Es wird eine Distanzbedingung erwartet.
13+
wql.function_unknown=Unbekannte Funktion.
14+
wql.expected_skip_or_take=Es wird eine Skip- oder Take-Bedingung erwartet.
15+
wql.expected_token=Es wird ein Token erwartet.
16+
wql.expected_token_matching=Das Token entspricht nicht dem erwarteten Muster oder Format.
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,16 @@
11
# Englisch
2-
indexmanager.initialization=The index manager has been initialized.
2+
indexmanager.initialization=The index manager has been initialized.
3+
4+
wql.unexpected_token=Unexpected token.
5+
wql.condition_unknown=Unknown condition.
6+
wql.expected_operator=An operator is expected.
7+
wql.expected_binary_or_set_condition=A binary or set condition is expected.
8+
wql.expected_logicaloperator=A logical operator is expected.
9+
wql.attribute_unknown=Unknown attribute.
10+
wql.expected_terminated_string_token=A string token with a terminating quotation mark is expected.
11+
wql.expected_similarity=A similarity condition is expected.
12+
wql.expected_distance=A distance condition is expected.
13+
wql.function_unknown=Unknown function.
14+
wql.expected_skip_or_take=A skip or take condition is expected.
15+
wql.expected_token=A token is expected.
16+
wql.expected_token_matching=The token does not match the expected pattern or format.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Collections.Generic;
2+
3+
namespace WebExpress.WebIndex.Wql
4+
{
5+
/// <summary>
6+
/// Defines the contract for incremental lookahead analysis (ILA) in WQL queries.
7+
/// </summary>
8+
public interface IWqlLookahead
9+
{
10+
/// <summary>
11+
/// Returns the list of lookahead items representing each token that was
12+
/// successfully processed, including metadata such as token type.
13+
/// </summary>
14+
IEnumerable<IWqlLookaheadToken> Items { get; }
15+
16+
/// <summary>
17+
/// Indicates whether the input is syntactically valid up to the current
18+
/// position.
19+
/// </summary>
20+
bool IsValidSoFar { get; }
21+
22+
/// <summary>
23+
/// Returns the type of the last token of the WQL expression.
24+
/// </summary>
25+
WqlExpressionType LastExpressionType { get; }
26+
27+
/// <summary>
28+
/// Returns the set of tokens that would be syntactically valid at the
29+
/// current position (lookahead).
30+
/// </summary>
31+
IEnumerable<WqlExpressionType> ExpectedNextTokens { get; }
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Collections.Generic;
2+
3+
namespace WebExpress.WebIndex.Wql
4+
{
5+
/// <summary>
6+
/// Defines an interface for a token that supports lookahead functionality during
7+
/// WQL parsing.
8+
/// </summary>
9+
public interface IWqlLookaheadToken
10+
{
11+
/// <summary>
12+
/// Returns the original token as produced by the tokenizer.
13+
/// </summary>
14+
IWqlToken Token { get; }
15+
16+
/// <summary>
17+
/// Returns the type of the WQL expression represented by this instance.
18+
/// </summary>
19+
WqlExpressionType ExpreesionType { get; }
20+
21+
/// <summary>
22+
/// Returns the set of tokens that would be syntactically valid at the
23+
/// current position (lookahead).
24+
/// </summary>
25+
IEnumerable<WqlExpressionType> ExpectedNextTokens { get; }
26+
27+
/// <summary>
28+
/// Indicates whether the input is syntactically valid up to the current
29+
/// position.
30+
/// </summary>
31+
bool IsValid { get; }
32+
}
33+
}

src/WebExpress.WebIndex/Wql/IWqlStatement.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.Collections.Generic;
2-
using System.Globalization;
1+
using System.Globalization;
32
using System.Linq;
43
using WebExpress.WebIndex.Queries;
54

@@ -50,7 +49,7 @@ public interface IWqlStatement<TIndexItem>
5049
/// <summary>
5150
/// Returns the syntax tree of the wql query.
5251
/// </summary>
53-
IEnumerable<IWqlExpressionNode<TIndexItem>> AbstractSyntaxTree { get; }
52+
IWqlSyntaxTree<TIndexItem> AbstractSyntaxTree { get; }
5453

5554
/// <summary>
5655
/// Applies the filter to the index.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace WebExpress.WebIndex.Wql
2+
{
3+
/// <summary>
4+
/// Represents the syntax tree for a WQL query and provides access to the root nodes.
5+
/// </summary>
6+
/// <typeparam name="TIndexItem">The index item type for the syntax tree nodes.</typeparam>
7+
public interface IWqlSyntaxTree<TIndexItem>
8+
where TIndexItem : IIndexItem
9+
{
10+
/// <summary>
11+
/// Returns the filter node of the syntax tree.
12+
/// </summary>
13+
IWqlExpressionNode<TIndexItem> Filter { get; }
14+
15+
/// <summary>
16+
/// Returns the order node of the syntax tree.
17+
/// </summary>
18+
IWqlExpressionNode<TIndexItem> Order { get; }
19+
20+
/// <summary>
21+
/// Returns the partitioning node of the syntax tree.
22+
/// </summary>
23+
IWqlExpressionNode<TIndexItem> Partitioning { get; }
24+
}
25+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace WebExpress.WebIndex.Wql
2+
{
3+
/// <summary>
4+
/// Represents a token of the wql syntax.
5+
/// </summary>
6+
public interface IWqlToken
7+
{
8+
/// <summary>
9+
/// Returns the starting position of the token in the raw statement.
10+
/// </summary>
11+
int Offset { get; }
12+
13+
/// <summary>
14+
/// Returns the length of the token (start + length = end) in the raw statement.
15+
/// </summary>
16+
int Length { get; }
17+
18+
/// <summary>
19+
/// Checks if the token is empty.
20+
/// </summary>
21+
/// <returns>True if no value is stored, false otherwise.</returns>
22+
bool IsEmpty { get; }
23+
24+
/// <summary>
25+
/// Returns the token value.
26+
/// </summary>
27+
string Value { get; }
28+
}
29+
}

src/WebExpress.WebIndex/Wql/WqlExpressionNodeAttribute.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Linq.Expressions;
34
using System.Reflection;
45

@@ -11,6 +12,11 @@ namespace WebExpress.WebIndex.Wql
1112
public class WqlExpressionNodeAttribute<TIndexItem> : IWqlExpressionNode<TIndexItem>
1213
where TIndexItem : IIndexItem
1314
{
15+
/// <summary>
16+
/// Returns the tokens associated with this syntax tree node.
17+
/// </summary>
18+
public IEnumerable<IWqlToken> Tokens { get; internal set; }
19+
1420
/// <summary>
1521
/// Returns the name of the attribute.
1622
/// </summary>
@@ -47,11 +53,11 @@ internal WqlExpressionNodeAttribute()
4753
/// <exception cref="InvalidOperationException">
4854
/// Thrown when no <see cref="PropertyInfo"/> is assigned to this attribute node.
4955
/// </exception>
50-
public Expression ToExpression(ParameterExpression param)
51-
{
52-
if (Property is null)
56+
public Expression ToExpression(ParameterExpression param)
57+
{
58+
if (Property is null)
5359
{
54-
throw new InvalidOperationException( $"Attribute '{Name}' has no PropertyInfo assigned.");
60+
throw new InvalidOperationException($"Attribute '{Name}' has no PropertyInfo assigned.");
5561
}
5662

5763
return Expression.Property(param, Property);

0 commit comments

Comments
 (0)