Skip to content

Commit af56230

Browse files
committed
Added support for the ?? (null-coalescing) operator
1 parent d082d35 commit af56230

File tree

6 files changed

+61
-9
lines changed

6 files changed

+61
-9
lines changed

MathConverter/Parser.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private IEnumerable<AbstractSyntaxTree> ConverterParameter()
7373
}
7474
private AbstractSyntaxTree Conditional()
7575
{
76-
return Conditional(ConditionalOr());
76+
return Conditional(NullCoalescing());
7777
}
7878
private AbstractSyntaxTree Conditional(AbstractSyntaxTree e)
7979
{
@@ -85,12 +85,12 @@ private AbstractSyntaxTree Conditional(AbstractSyntaxTree e)
8585
if (scanner.Peek().TokenType == TokenType.QuestionMark)
8686
throw new ParsingException(scanner.Position, "The ?? operator is not supported.");
8787

88-
var then = ConditionalOr();
88+
var then = NullCoalescing();
8989
t = scanner.GetToken();
9090
switch (t.TokenType)
9191
{
9292
case TokenType.Colon:
93-
return Conditional(new TernaryNode(e, then, ConditionalOr()));
93+
return Conditional(new TernaryNode(e, then, NullCoalescing()));
9494
default:
9595
throw new ParsingException(scanner.Position, "Could not find the ':' to terminate the ternary ('?:') statement");
9696
}
@@ -99,6 +99,23 @@ private AbstractSyntaxTree Conditional(AbstractSyntaxTree e)
9999
return e;
100100
}
101101
}
102+
private AbstractSyntaxTree NullCoalescing()
103+
{
104+
return NullCoalescing(ConditionalOr());
105+
}
106+
private AbstractSyntaxTree NullCoalescing(AbstractSyntaxTree e)
107+
{
108+
var t = scanner.GetToken();
109+
110+
switch (t.TokenType)
111+
{
112+
case TokenType.DoubleQuestionMark:
113+
return NullCoalescing(new NullCoalescingNode(e, ConditionalOr()));
114+
default:
115+
scanner.PutBackToken();
116+
return e;
117+
}
118+
}
102119
private AbstractSyntaxTree ConditionalOr()
103120
{
104121
return ConditionalOr(ConditionalAnd());

MathConverter/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@
5151
// You can specify all the values or you can default the Build and Revision Numbers
5252
// by using the '*' as shown below:
5353
// [assembly: AssemblyVersion("1.0.*")]
54-
[assembly: AssemblyVersion("1.2.0.0")]
55-
[assembly: AssemblyFileVersion("1.2.0.0")]
54+
[assembly: AssemblyVersion("1.2.1.0")]
55+
[assembly: AssemblyFileVersion("1.2.1.0")]
5656

5757
[assembly: XmlnsPrefix("http://hexinnovation.com/math", "math")]
5858
[assembly: XmlnsDefinition("http://hexinnovation.com/math", "HexInnovation")]

MathConverter/Scanner.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,15 @@ private Token NextToken()
102102
case 'z':
103103
return new Token(TokenType.Z);
104104
case '?':
105-
return new Token(TokenType.QuestionMark);
105+
switch (_reader.Peek())
106+
{
107+
case '?':
108+
Position++;
109+
_reader.Read();
110+
return new Token(TokenType.DoubleQuestionMark);
111+
default:
112+
return new Token(TokenType.QuestionMark);
113+
}
106114
case ':':
107115
return new Token(TokenType.Colon);
108116
case '.':

MathConverter/SyntaxTree.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,23 @@ public override string ToString()
134134
return "(" + left + " && " + right + ")";
135135
}
136136
}
137+
138+
class NullCoalescingNode : BinaryNode
139+
{
140+
public NullCoalescingNode(AbstractSyntaxTree left, AbstractSyntaxTree right)
141+
: base(left, right)
142+
{
143+
144+
}
145+
public override object Evaluate(object[] Parameters)
146+
{
147+
return (dynamic)left.Evaluate(Parameters) ?? (dynamic)right.Evaluate(Parameters);
148+
}
149+
public override string ToString()
150+
{
151+
return "(" + left + " ?? " + right + ")";
152+
}
153+
}
137154
class OrNode : BinaryNode
138155
{
139156
public OrNode(AbstractSyntaxTree left, AbstractSyntaxTree right)

MathConverter/Token.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ enum TokenType
6868
LessThanEqual,
6969
GreaterThanEqual,
7070
QuestionMark,
71+
DoubleQuestionMark,
7172
Colon,
7273
String,
7374
Or,

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ A WPF Converter class that does it all
77

88

99

10+
Installation:
11+
-------------
12+
13+
`MathConverter` is available on [NuGet](https://www.nuget.org/packages/MathConverter/)
14+
15+
To install MathConverter, run the following command in the [Package Manager Console](https://docs.microsoft.com/en-us/nuget/tools/package-manager-console):
16+
17+
```
18+
PM> Install-Package MathConverter
19+
```
1020

1121

1222
What is MathConverter?
@@ -352,7 +362,7 @@ Just like in C#, you can embed strings within in an interpolated string. So `Mat
352362
Operators
353363
---------
354364

355-
The ternary conditional operator is just one of several operators we can use. In general, the operators used in MathConverter will follow [the standard C# rules regarding operator ordering](https://msdn.microsoft.com/en-us/library/aa691323(v=vs.71).aspx), meaning you can usually expect it to behave just like C#. But there are a few notable exceptions:
365+
The ternary conditional operator is just one of several operators we can use. In general, the operators used in MathConverter will follow [the standard C# rules regarding operator ordering](https://docs.microsoft.com/en-us/dotnet/articles/csharp/language-reference/operators/), meaning you can usually expect it to behave just like C#. But there are a few notable exceptions:
356366
357367
* Since `MathConverter` is specifically designed to perform math calculations, the caret (`^`) operator does not perform the `XOR` operation. Rather, it is an exponent symbol. It uses [`System.Math.Pow`](https://msdn.microsoft.com/en-us/library/system.math.pow(v=vs.110).aspx) to evaluate expressions, and its precedence is just above multiplicative operations (`*`, `/`, and `%`).
358368
* The multiplication operator can often be safely ommitted. A `ConverterParameter` value of `xyz` will evaluate to `x*y*z`. The parameter `x2y` will evaluate to `x^2*y` (or equivalently, `xxy` or `x*x*y`). Similarly, `2x3` is equivalent to `2*x^3` or `2*x*x*x`. Note that `x(2)` is equivalent to `x*(2)`, in the same way that `x(y+z)` is equivalent to `x*(y+z)`.
@@ -364,7 +374,6 @@ The ternary conditional operator is just one of several operators we can use. In
364374
* Binary operations (`<<`, `>>`, `~`) are not supported.
365375
* The unary operators `++` and `--` are not supported, since they change the values of the inputs.
366376
* Primary operators (`x.y`, `f(x)`, `a[x]`, `new`, `typeof`, `checked`, `unchecked`) are not supported.
367-
* The `??` operator is not listed in [the standard C# rules for operator ordering](https://msdn.microsoft.com/en-us/library/aa691323(v=vs.71).aspx), so `MathConverter` does not support it. See the `isnull` function in the next section.
368377

369378

370379

@@ -373,7 +382,7 @@ The ternary conditional operator is just one of several operators we can use. In
373382
null
374383
----
375384

376-
`MathConverter` fully supportes `null` values. You can include `null` in the `ConverterParameter`, and it will evaluate to `null`. Also, any bindings will still work if the binding returns `null`. As previously mentioned, MathConverter does not support the `??` operator. It does, however, include the 2-value function `isnull`/`ifnull`. `MathConverter` evaluates the expression `isnull(x,y)` in the same way that it would evaluate the expression `x == null ? y : x`.
385+
`MathConverter` fully supportes `null` values. You can include `null` in the `ConverterParameter`, and it will evaluate to `null`. Also, any bindings will still work if the binding returns `null`. In addition to supporting the `??` null-coalescing operator, it also includes the 2-value function `isnull`/`ifnull`. `MathConverter` evaluates the expression `x ?? y` in the same way that it would evaluate the expressions `isnull(x,y)` or `x == null ? y : x`.
377386

378387
`MathConverter` evaluates most of its values using the `dynamic` type. So `x+y` will yield `null` if `x = 3` and `y = null`. However, if `x = "Hello World"` and `y = null`, `x+y` will yield `"Hello World"`.
379388

0 commit comments

Comments
 (0)