Skip to content

Commit

Permalink
Implement Null Coalescing and Null Coalescing assignment operators (P…
Browse files Browse the repository at this point in the history
  • Loading branch information
adityapatwardhan authored and kilasuit committed Oct 31, 2019
1 parent 6ac9ca8 commit d12bd6f
Show file tree
Hide file tree
Showing 7 changed files with 404 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ static ExperimentalFeature()
description: "New formatting for ErrorRecord"),
new ExperimentalFeature(
name: "PSUpdatesNotification",
description: "Print notification message when new releases are available")
description: "Print notification message when new releases are available"),
new ExperimentalFeature(
name: "PSCoalescingOperators",
description: "Support the null coalescing operator and null coalescing assignment operator in PowerShell language")
};
EngineExperimentalFeatures = new ReadOnlyCollection<ExperimentalFeature>(engineFeatures);

Expand Down
41 changes: 40 additions & 1 deletion src/System.Management.Automation/engine/parser/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ internal static class CachedReflectionInfo

internal static readonly MethodInfo LanguagePrimitives_GetInvalidCastMessages =
typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.GetInvalidCastMessages), staticFlags);
internal static readonly MethodInfo LanguagePrimitives_IsNullLike =
typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.IsNullLike), staticPublicFlags);
internal static readonly MethodInfo LanguagePrimitives_ThrowInvalidCastException =
typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.ThrowInvalidCastException), staticFlags);

Expand Down Expand Up @@ -786,6 +788,7 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi
{
IAssignableValue av = left.GetAssignableValue();
ExpressionType et = ExpressionType.Extension;

switch (tokenKind)
{
case TokenKind.Equals: return av.SetValue(this, right);
Expand All @@ -794,15 +797,49 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi
case TokenKind.MultiplyEquals: et = ExpressionType.Multiply; break;
case TokenKind.DivideEquals: et = ExpressionType.Divide; break;
case TokenKind.RemainderEquals: et = ExpressionType.Modulo; break;
case TokenKind.QuestionQuestionEquals when ExperimentalFeature.IsEnabled("PSCoalescingOperators"): et = ExpressionType.Coalesce; break;
}

var exprs = new List<Expression>();
var temps = new List<ParameterExpression>();
var getExpr = av.GetValue(this, exprs, temps);
exprs.Add(av.SetValue(this, DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(et), typeof(object), getExpr, right)));

if(et == ExpressionType.Coalesce)
{
exprs.Add(av.SetValue(this, Coalesce(getExpr, right)));
}
else
{
exprs.Add(av.SetValue(this, DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(et), typeof(object), getExpr, right)));
}

return Expression.Block(temps, exprs);
}

private static Expression Coalesce(Expression left, Expression right)
{
Type leftType = left.Type;

if (leftType.IsValueType)
{
return left;
}
else if(leftType == typeof(DBNull) || leftType == typeof(NullString) || leftType == typeof(AutomationNull))
{
return right;
}
else
{
Expression lhs = left.Cast(typeof(object));
Expression rhs = right.Cast(typeof(object));

return Expression.Condition(
Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, lhs),
rhs,
lhs);
}
}

internal Expression GetLocal(int tupleIndex)
{
Expression result = LocalVariablesParameter;
Expand Down Expand Up @@ -5231,6 +5268,8 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
CachedReflectionInfo.ParserOps_SplitOperator,
_executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)),
ExpressionCache.Constant(false));
case TokenKind.QuestionQuestion when ExperimentalFeature.IsEnabled("PSCoalescingOperators"):
return Coalesce(lhs, rhs);
}

throw new InvalidOperationException("Unknown token in binary operator.");
Expand Down
6 changes: 5 additions & 1 deletion src/System.Management.Automation/engine/parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6555,8 +6555,12 @@ private ExpressionAst BinaryExpressionRule(bool endNumberOnTernaryOpChars = fals
// G bitwise-expression '-bxor' new-lines:opt comparison-expression
// G
// G comparison-expression:
// G nullcoalesce-expression
// G comparison-expression comparison-operator new-lines:opt nullcoalesce-expression
// G
// G nullcoalesce-expression:
// G additive-expression
// G comparison-expression comparison-operator new-lines:opt additive-expression
// G nullcoalesce-expression '??' new-lines:opt additive-expression
// G
// G additive-expression:
// G multiplicative-expression
Expand Down
37 changes: 24 additions & 13 deletions src/System.Management.Automation/engine/parser/token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,12 @@ public enum TokenKind
/// <summary>The ternary operator '?'.</summary>
QuestionMark = 100,

/// <summary>The null conditional assignment operator '??='.</summary>
QuestionQuestionEquals = 101,

/// <summary>The null coalesce operator '??'.</summary>
QuestionQuestion = 102,

#endregion Operators

#region Keywords
Expand Down Expand Up @@ -592,46 +598,51 @@ public enum TokenFlags
/// <summary>
/// The precedence of the logical operators '-and', '-or', and '-xor'.
/// </summary>
BinaryPrecedenceLogical = 1,
BinaryPrecedenceLogical = 0x1,

/// <summary>
/// The precedence of the bitwise operators '-band', '-bor', and '-bxor'
/// </summary>
BinaryPrecedenceBitwise = 2,
BinaryPrecedenceBitwise = 0x2,

/// <summary>
/// The precedence of comparison operators including: '-eq', '-ne', '-ge', '-gt', '-lt', '-le', '-like', '-notlike',
/// '-match', '-notmatch', '-replace', '-contains', '-notcontains', '-in', '-notin', '-split', '-join', '-is', '-isnot', '-as',
/// and all of the case sensitive variants of these operators, if they exists.
/// </summary>
BinaryPrecedenceComparison = 3,
BinaryPrecedenceComparison = 0x5,

/// <summary>
/// The precedence of null coalesce operator '??'.
/// </summary>
BinaryPrecedenceCoalesce = 0x7,

/// <summary>
/// The precedence of the binary operators '+' and '-'.
/// </summary>
BinaryPrecedenceAdd = 4,
BinaryPrecedenceAdd = 0x9,

/// <summary>
/// The precedence of the operators '*', '/', and '%'.
/// </summary>
BinaryPrecedenceMultiply = 5,
BinaryPrecedenceMultiply = 0xa,

/// <summary>
/// The precedence of the '-f' operator.
/// </summary>
BinaryPrecedenceFormat = 6,
BinaryPrecedenceFormat = 0xc,

/// <summary>
/// The precedence of the '..' operator.
/// </summary>
BinaryPrecedenceRange = 7,
BinaryPrecedenceRange = 0xd,

#endregion Precedence Values

/// <summary>
/// A bitmask to get the precedence of binary operators.
/// </summary>
BinaryPrecedenceMask = 0x00000007,
BinaryPrecedenceMask = 0x0000000f,

/// <summary>
/// The token is a keyword.
Expand Down Expand Up @@ -669,7 +680,7 @@ public enum TokenFlags
SpecialOperator = 0x00001000,

/// <summary>
/// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', or '%='
/// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', '%=' or '??='
/// </summary>
AssignmentOperator = 0x00002000,

Expand Down Expand Up @@ -854,8 +865,8 @@ public static class TokenTraits
/* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold,
/* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode,
/* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode,
/* Reserved slot 3 */ TokenFlags.None,
/* Reserved slot 4 */ TokenFlags.None,
/* QuestionQuestionEquals */ TokenFlags.AssignmentOperator,
/* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceCoalesce,
/* Reserved slot 5 */ TokenFlags.None,
/* Reserved slot 6 */ TokenFlags.None,
/* Reserved slot 7 */ TokenFlags.None,
Expand Down Expand Up @@ -1052,8 +1063,8 @@ public static class TokenTraits
/* Shr */ "-shr",
/* Colon */ ":",
/* QuestionMark */ "?",
/* Reserved slot 3 */ string.Empty,
/* Reserved slot 4 */ string.Empty,
/* QuestionQuestionEquals */ "??=",
/* QuestionQuestion */ "??",
/* Reserved slot 5 */ string.Empty,
/* Reserved slot 6 */ string.Empty,
/* Reserved slot 7 */ string.Empty,
Expand Down
19 changes: 19 additions & 0 deletions src/System.Management.Automation/engine/parser/tokenizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4994,6 +4994,25 @@ internal Token NextToken()
return this.NewToken(TokenKind.Colon);

case '?' when InExpressionMode():
if (ExperimentalFeature.IsEnabled("PSCoalescingOperators"))
{
c1 = PeekChar();

if (c1 == '?')
{
SkipChar();
c1 = PeekChar();

if (c1 == '=')
{
SkipChar();
return this.NewToken(TokenKind.QuestionQuestionEquals);
}

return this.NewToken(TokenKind.QuestionQuestion);
}
}

return this.NewToken(TokenKind.QuestionMark);

case '\0':
Expand Down
Loading

0 comments on commit d12bd6f

Please sign in to comment.