Skip to content

Commit 6d5ffb0

Browse files
committed
Improve documentation, tests, and assembly signing
Added README.md
1 parent 5191a32 commit 6d5ffb0

File tree

9 files changed

+139
-29
lines changed

9 files changed

+139
-29
lines changed

dotnet/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[![Maven Central](https://img.shields.io/maven-central/v/io.cucumber/tag-expressions.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.cucumber%22%20AND%20a:%22tag-expressions%22)
2+
3+
# Cucumber Tag Expressions for .NET
4+
5+
[The docs are here](https://cucumber.io/docs/cucumber/api/#tag-expressions).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using System.Runtime.CompilerServices;
2+
3+
[assembly: InternalsVisibleTo("TagExpressionsTest, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bd9b4db256712217f4906b5eb0113e633e319ef574450f83cb837097cac352cfac3a3d3e652c74e2f19e4af0464877e171602bb254fba62c9023894123393ca270ac5c1c01e99e233d0ccdb75f1ebf9b18b66f1618c1d9904658bc7e0a836c5488fb6e54845d9daabbf37594f54bc58f9136fd66cdad9b2761e8710956b2f1ce")]

dotnet/TagExpressions/TagExpression.cs

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,68 @@
44

55
namespace TagExpressions;
66

7-
// Abstract base class implementing the interface
7+
/// <summary>
8+
/// Provides an abstract base for tag expressions that can be evaluated against a set of input tags.
9+
/// </summary>
810
public abstract class TagExpression : ITagExpression
911
{
10-
// Public API
12+
/// <summary>
13+
/// Evaluates the tag expression against the provided input tags.
14+
/// </summary>
15+
/// <param name="inputs">A collection of input tags to evaluate against.</param>
16+
/// <returns><c>true</c> if the expression matches the inputs; otherwise, <c>false</c>.</returns>
1117
public bool Evaluate(IEnumerable<string> inputs)
1218
{
1319
var inputSet = new HashSet<string>(inputs);
1420
return EvaluateInternal(inputSet);
1521
}
1622

23+
/// <summary>
24+
/// Returns the string representation of the tag expression.
25+
/// </summary>
26+
/// <returns>A string that represents the tag expression.</returns>
1727
public abstract override string ToString();
1828

19-
// Internal recursive evaluation method
29+
/// <summary>
30+
/// Recursively evaluates the tag expression against the provided set of input tags.
31+
/// </summary>
32+
/// <param name="inputs">A set of input tags.</param>
33+
/// <returns><c>true</c> if the expression matches the inputs; otherwise, <c>false</c>.</returns>
2034
internal abstract bool EvaluateInternal(HashSet<string> inputs);
2135
}
2236

2337
/// <summary>
2438
/// Represents an expression that always evaluates to <see langword="true"/>.
2539
/// </summary>
26-
/// <remarks>This expression is used as a fallback condition when the expression input is empty.
27-
/// By convention, an empty tag expression is considered equivalent to <see langword="true"/> regardless of input.</remarks>
40+
/// <remarks>
41+
/// This expression is used as a fallback condition when the expression input is empty.
42+
/// By convention, an empty tag expression is considered equivalent to <see langword="true"/> regardless of input.
43+
/// </remarks>
2844
public class NullExpression : TagExpression
2945
{
46+
/// <inheritdoc/>
3047
public override string ToString() => "true";
48+
/// <inheritdoc/>
3149
internal override bool EvaluateInternal(HashSet<string> inputs) => true;
3250
}
3351

34-
// Leaf node representing literals (variables)
52+
/// <summary>
53+
/// Represents a leaf node for a tag expression, corresponding to a literal tag name.
54+
/// </summary>
3555
public class LiteralNode : TagExpression
3656
{
57+
/// <summary>
58+
/// Gets the name of the literal tag.
59+
/// </summary>
3760
public string Name { get; }
61+
62+
/// <summary>
63+
/// Initializes a new instance of the <see cref="LiteralNode"/> class.
64+
/// </summary>
65+
/// <param name="name">The name of the tag.</param>
3866
public LiteralNode(string name) => Name = name;
3967

68+
/// <inheritdoc/>
4069
public override string ToString()
4170
{
4271
var sb = new StringBuilder();
@@ -49,44 +78,75 @@ public override string ToString()
4978
return sb.ToString();
5079
}
5180

81+
/// <inheritdoc/>
5282
internal override bool EvaluateInternal(HashSet<string> inputs) => inputs.Contains(Name);
5383
}
5484

55-
// Unary NOT node
85+
/// <summary>
86+
/// Represents a unary NOT operation in a tag expression.
87+
/// </summary>
5688
public class NotNode : TagExpression
5789
{
90+
/// <summary>
91+
/// Gets the operand of the NOT operation.
92+
/// </summary>
5893
public ITagExpression Operand { get; }
94+
95+
/// <summary>
96+
/// Initializes a new instance of the <see cref="NotNode"/> class.
97+
/// </summary>
98+
/// <param name="operand">The operand expression to negate.</param>
5999
public NotNode(ITagExpression operand) => Operand = operand;
60100

101+
/// <inheritdoc/>
61102
public override string ToString()
62103
{
63104
string operandStr = Operand is not BinaryOpNode ? $"( {Operand} )" : Operand.ToString();
64105
return $"not {operandStr}";
65106
}
66107

67-
internal override bool EvaluateInternal(HashSet<string> inputs) => !( ((TagExpression) Operand).EvaluateInternal(inputs) );
108+
/// <inheritdoc/>
109+
internal override bool EvaluateInternal(HashSet<string> inputs) => !(((TagExpression)Operand).EvaluateInternal(inputs));
68110
}
69111

70-
// Binary operator node for AND / OR
112+
/// <summary>
113+
/// Represents a binary operator node for AND and OR operations in a tag expression.
114+
/// </summary>
71115
public class BinaryOpNode : TagExpression
72116
{
117+
/// <summary>
118+
/// Gets the left operand of the binary operation.
119+
/// </summary>
73120
public ITagExpression Left { get; }
121+
/// <summary>
122+
/// Gets the right operand of the binary operation.
123+
/// </summary>
74124
public ITagExpression Right { get; }
75-
public string Op { get; } // "AND" or "OR"
76-
125+
/// <summary>
126+
/// Gets the operator for the binary operation ("AND" or "OR").
127+
/// </summary>
128+
public string Op { get; }
129+
130+
/// <summary>
131+
/// Initializes a new instance of the <see cref="BinaryOpNode"/> class.
132+
/// </summary>
133+
/// <param name="op">The operator ("AND" or "OR").</param>
134+
/// <param name="left">The left operand.</param>
135+
/// <param name="right">The right operand.</param>
77136
public BinaryOpNode(string op, ITagExpression left, ITagExpression right)
78137
{
79138
Op = op;
80139
Left = left;
81140
Right = right;
82141
}
83142

143+
/// <inheritdoc/>
84144
public override string ToString()
85145
{
86146
string leftStr = Left is BinaryOpNode leftBin && Precedence(leftBin.Op) < Precedence(Op)
87-
? $"( {Left} )" : Left.ToString();
147+
? $"( {Left} )" : Left.ToString();
88148
string rightStr = Right is BinaryOpNode rightBin && Precedence(rightBin.Op) < Precedence(Op)
89-
? $"( {Right} )" : Right.ToString();
149+
? $"( {Right} )" : Right.ToString();
90150
return $"( {leftStr} {Op} {rightStr} )";
91151
}
92152

@@ -95,6 +155,7 @@ private int Precedence(string op) =>
95155
op == "and" ? 2 :
96156
op == "or" ? 1 : 0;
97157

158+
/// <inheritdoc/>
98159
internal override bool EvaluateInternal(HashSet<string> inputs)
99160
{
100161
bool leftVal = ((TagExpression)Left).EvaluateInternal(inputs);

dotnet/TagExpressions/TagExpressionParser.cs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,22 @@
22

33
namespace TagExpressions;
44

5-
// Recursive descent parser for logical expressions
5+
/// <summary>
6+
/// Provides a recursive descent parser for logical tag expressions.
7+
/// </summary>
68
public class TagExpressionParser : ITagExpressionParser
79
{
810
private string _text;
911
private TagLexer _lexer;
1012
private TagToken _current;
1113
private int _openParens;
1214

15+
/// <summary>
16+
/// Parses the specified tag expression string into an <see cref="ITagExpression"/>.
17+
/// </summary>
18+
/// <param name="text">The tag expression string to parse.</param>
19+
/// <returns>An <see cref="ITagExpression"/> representing the parsed expression.</returns>
20+
/// <exception cref="Exception">Thrown when a syntax error is encountered in the tag expression.</exception>
1321
public ITagExpression Parse(string text)
1422
{
1523
_text = text;
@@ -30,6 +38,9 @@ public ITagExpression Parse(string text)
3038
return expr;
3139
}
3240

41+
/// <summary>
42+
/// Advances to the next token in the input stream.
43+
/// </summary>
3344
private void Next()
3445
{
3546
_current = _lexer.NextToken();
@@ -43,12 +54,20 @@ private void Next()
4354
}
4455
}
4556

57+
/// <summary>
58+
/// Throws a syntax error exception with the specified message.
59+
/// </summary>
60+
/// <param name="message">The error message.</param>
61+
/// <exception cref="Exception">Always thrown to indicate a syntax error.</exception>
4662
private void ThrowSyntaxError(string message)
4763
{
4864
throw new Exception($"Tag expression \"{_text}\" could not be parsed because of syntax error: {message}.");
4965
}
5066

51-
// Expression := Term { OR Term }
67+
/// <summary>
68+
/// Parses an expression consisting of terms separated by the OR operator.
69+
/// </summary>
70+
/// <returns>The parsed <see cref="ITagExpression"/>.</returns>
5271
private ITagExpression ParseExpression()
5372
{
5473
var left = ParseTerm();
@@ -69,8 +88,10 @@ private ITagExpression ParseExpression()
6988
return left;
7089
}
7190

72-
73-
// Term := Factor { AND Factor }
91+
/// <summary>
92+
/// Parses a term consisting of factors separated by the AND operator.
93+
/// </summary>
94+
/// <returns>The parsed <see cref="ITagExpression"/>.</returns>
7495
private ITagExpression ParseTerm()
7596
{
7697
var left = ParseFactor();
@@ -92,7 +113,10 @@ private ITagExpression ParseTerm()
92113
return left;
93114
}
94115

95-
// Factor := NOT Factor | (Expression) | Identifier
116+
/// <summary>
117+
/// Parses a factor, which can be a NOT operation, a parenthesized expression, or an identifier.
118+
/// </summary>
119+
/// <returns>The parsed <see cref="ITagExpression"/>.</returns>
96120
private ITagExpression ParseFactor()
97121
{
98122
switch (_current.Type)

dotnet/TagExpressions/TagLexer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Collections.Generic;
22
using System.Text;
33

4-
public class TagLexer
4+
internal class TagLexer
55
{
66
private readonly string _text;
77
private int _pos;

dotnet/TagExpressions/TagToken.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
public enum TagTokenType
1+
internal enum TagTokenType
22
{
33
Identifier,
44
And,
@@ -9,7 +9,7 @@ public enum TagTokenType
99
End
1010
}
1111

12-
public class TagToken
12+
internal class TagToken
1313
{
1414
public TagTokenType Type { get; }
1515
public string Value { get; }

dotnet/TagExpressionsTest/TagExpressionsTest.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
For more information, visit https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-integration-dotnet-test#show-failure-per-test
1414
-->
1515
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
16+
<SignAssembly>True</SignAssembly>
17+
<AssemblyOriginatorKeyFile>TagExpressionsTest.snk</AssemblyOriginatorKeyFile>
1618
</PropertyGroup>
1719

1820
<ItemGroup>
596 Bytes
Binary file not shown.

dotnet/TagExpressionsTest/TagLexerTests.cs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,30 @@ namespace TagExpressionsTest
77
[TestClass]
88
public class TagLexerTests
99
{
10-
[DataTestMethod]
11-
[DataRow("AND", TagTokenType.And)]
12-
[DataRow("OR", TagTokenType.Or)]
13-
[DataRow("NOT", TagTokenType.Not)]
14-
public void TokenizesOperators(string input, TagTokenType expectedType)
10+
[TestMethod]
11+
public void TokenizesOperators_And()
12+
{
13+
var lexer = new TagLexer("AND");
14+
var token = lexer.NextToken();
15+
Assert.AreEqual(TagTokenType.And, token.Type);
16+
Assert.AreEqual(TagTokenType.End, lexer.NextToken().Type);
17+
}
18+
19+
[TestMethod]
20+
public void TokenizesOperators_Or()
21+
{
22+
var lexer = new TagLexer("OR");
23+
var token = lexer.NextToken();
24+
Assert.AreEqual(TagTokenType.Or, token.Type);
25+
Assert.AreEqual(TagTokenType.End, lexer.NextToken().Type);
26+
}
27+
28+
[TestMethod]
29+
public void TokenizesOperators_Not()
1530
{
16-
var lexer = new TagLexer(input);
31+
var lexer = new TagLexer("NOT");
1732
var token = lexer.NextToken();
18-
Assert.AreEqual(expectedType, token.Type);
33+
Assert.AreEqual(TagTokenType.Not, token.Type);
1934
Assert.AreEqual(TagTokenType.End, lexer.NextToken().Type);
2035
}
2136

@@ -81,4 +96,4 @@ public void PeekTokenDoesNotAdvancePosition()
8196
Assert.AreEqual(peeked.Value, next.Value);
8297
}
8398
}
84-
}
99+
}

0 commit comments

Comments
 (0)