Skip to content

Commit

Permalink
Added support for the ?? (null-coalescing) operator
Browse files Browse the repository at this point in the history
  • Loading branch information
timothylcooke committed May 11, 2017
1 parent d082d35 commit af56230
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 9 deletions.
23 changes: 20 additions & 3 deletions MathConverter/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private IEnumerable<AbstractSyntaxTree> ConverterParameter()
}
private AbstractSyntaxTree Conditional()
{
return Conditional(ConditionalOr());
return Conditional(NullCoalescing());
}
private AbstractSyntaxTree Conditional(AbstractSyntaxTree e)
{
Expand All @@ -85,12 +85,12 @@ private AbstractSyntaxTree Conditional(AbstractSyntaxTree e)
if (scanner.Peek().TokenType == TokenType.QuestionMark)
throw new ParsingException(scanner.Position, "The ?? operator is not supported.");

var then = ConditionalOr();
var then = NullCoalescing();
t = scanner.GetToken();
switch (t.TokenType)
{
case TokenType.Colon:
return Conditional(new TernaryNode(e, then, ConditionalOr()));
return Conditional(new TernaryNode(e, then, NullCoalescing()));
default:
throw new ParsingException(scanner.Position, "Could not find the ':' to terminate the ternary ('?:') statement");
}
Expand All @@ -99,6 +99,23 @@ private AbstractSyntaxTree Conditional(AbstractSyntaxTree e)
return e;
}
}
private AbstractSyntaxTree NullCoalescing()
{
return NullCoalescing(ConditionalOr());
}
private AbstractSyntaxTree NullCoalescing(AbstractSyntaxTree e)
{
var t = scanner.GetToken();

switch (t.TokenType)
{
case TokenType.DoubleQuestionMark:
return NullCoalescing(new NullCoalescingNode(e, ConditionalOr()));
default:
scanner.PutBackToken();
return e;
}
}
private AbstractSyntaxTree ConditionalOr()
{
return ConditionalOr(ConditionalAnd());
Expand Down
4 changes: 2 additions & 2 deletions MathConverter/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.2.0.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyVersion("1.2.1.0")]
[assembly: AssemblyFileVersion("1.2.1.0")]

[assembly: XmlnsPrefix("http://hexinnovation.com/math", "math")]
[assembly: XmlnsDefinition("http://hexinnovation.com/math", "HexInnovation")]
10 changes: 9 additions & 1 deletion MathConverter/Scanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,15 @@ private Token NextToken()
case 'z':
return new Token(TokenType.Z);
case '?':
return new Token(TokenType.QuestionMark);
switch (_reader.Peek())
{
case '?':
Position++;
_reader.Read();
return new Token(TokenType.DoubleQuestionMark);
default:
return new Token(TokenType.QuestionMark);
}
case ':':
return new Token(TokenType.Colon);
case '.':
Expand Down
17 changes: 17 additions & 0 deletions MathConverter/SyntaxTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,23 @@ public override string ToString()
return "(" + left + " && " + right + ")";
}
}

class NullCoalescingNode : BinaryNode
{
public NullCoalescingNode(AbstractSyntaxTree left, AbstractSyntaxTree right)
: base(left, right)
{

}
public override object Evaluate(object[] Parameters)
{
return (dynamic)left.Evaluate(Parameters) ?? (dynamic)right.Evaluate(Parameters);
}
public override string ToString()
{
return "(" + left + " ?? " + right + ")";
}
}
class OrNode : BinaryNode
{
public OrNode(AbstractSyntaxTree left, AbstractSyntaxTree right)
Expand Down
1 change: 1 addition & 0 deletions MathConverter/Token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ enum TokenType
LessThanEqual,
GreaterThanEqual,
QuestionMark,
DoubleQuestionMark,
Colon,
String,
Or,
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ A WPF Converter class that does it all



Installation:
-------------

`MathConverter` is available on [NuGet](https://www.nuget.org/packages/MathConverter/)

To install MathConverter, run the following command in the [Package Manager Console](https://docs.microsoft.com/en-us/nuget/tools/package-manager-console):

```
PM> Install-Package MathConverter
```


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

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:
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:
* 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 `%`).
* 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)`.
Expand All @@ -364,7 +374,6 @@ The ternary conditional operator is just one of several operators we can use. In
* Binary operations (`<<`, `>>`, `~`) are not supported.
* The unary operators `++` and `--` are not supported, since they change the values of the inputs.
* Primary operators (`x.y`, `f(x)`, `a[x]`, `new`, `typeof`, `checked`, `unchecked`) are not supported.
* 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.



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

`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`.
`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`.

`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"`.

Expand Down

0 comments on commit af56230

Please sign in to comment.