Skip to content

Commit 5069c7d

Browse files
daxian-dbwadityapatwardhan
authored andcommitted
Support ternary operator in PowerShell language (PowerShell#10367)
1 parent bbd54c3 commit 5069c7d

File tree

18 files changed

+792
-125
lines changed

18 files changed

+792
-125
lines changed

src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6991,6 +6991,13 @@ public object VisitPipeline(PipelineAst pipelineAst)
69916991
return expr != null && (bool)expr.Accept(this);
69926992
}
69936993

6994+
public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst)
6995+
{
6996+
return (bool)ternaryExpressionAst.Condition.Accept(this) &&
6997+
(bool)ternaryExpressionAst.IfTrue.Accept(this) &&
6998+
(bool)ternaryExpressionAst.IfFalse.Accept(this);
6999+
}
7000+
69947001
public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
69957002
{
69967003
return (bool)binaryExpressionAst.Left.Accept(this) && (bool)binaryExpressionAst.Right.Accept(this);

src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@ static ExperimentalFeature()
111111
description: "Recommend potential commands based on fuzzy search on a CommandNotFoundException"),
112112
new ExperimentalFeature(
113113
name: "PSForEachObjectParallel",
114-
description: "New parameter set for ForEach-Object to run script blocks in parallel")
114+
description: "New parameter set for ForEach-Object to run script blocks in parallel"),
115+
new ExperimentalFeature(
116+
name: "PSTernaryOperator",
117+
description: "Support the ternary operator in PowerShell langauge.")
115118
};
116119
EngineExperimentalFeatures = new ReadOnlyCollection<ExperimentalFeature>(engineFeatures);
117120

src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a
258258

259259
public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchStatementAst) { return AstVisitAction.SkipChildren; }
260260

261+
public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) { return AstVisitAction.SkipChildren; }
262+
261263
// Visit one the other variations:
262264
// - Dotting scripts
263265
// - Setting aliases

src/System.Management.Automation/engine/parser/AstVisitor.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Collections.Generic;
55
using System.Diagnostics.CodeAnalysis;
66
using System.Linq;
7-
using System.Reflection;
87
using System.Reflection.Emit;
98

109
namespace System.Management.Automation.Language
@@ -151,6 +150,8 @@ public interface ICustomAstVisitor
151150
/// <summary/>
152151
public interface ICustomAstVisitor2 : ICustomAstVisitor
153152
{
153+
private object DefaultVisit(Ast ast) => null;
154+
154155
/// <summary/>
155156
object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst);
156157

@@ -171,6 +172,9 @@ public interface ICustomAstVisitor2 : ICustomAstVisitor
171172

172173
/// <summary/>
173174
object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst);
175+
176+
/// <summary/>
177+
object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) => DefaultVisit(ternaryExpressionAst);
174178
}
175179

176180
#if DEBUG
@@ -312,6 +316,8 @@ internal AstVisitAction CheckParent(Ast ast)
312316
public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst ast) { return CheckParent(ast); }
313317

314318
public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst ast) { return CheckParent(ast); }
319+
320+
public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ast) => CheckParent(ast);
315321
}
316322

317323
/// <summary>
@@ -544,6 +550,8 @@ protected AstVisitAction CheckScriptBlock(Ast ast)
544550
public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst ast) { return Check(ast); }
545551

546552
public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst ast) { return Check(ast); }
553+
554+
public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ast) { return Check(ast); }
547555
}
548556

549557
/// <summary>
@@ -680,5 +688,7 @@ public abstract class DefaultCustomAstVisitor2 : DefaultCustomAstVisitor, ICusto
680688
public virtual object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return null; }
681689
/// <summary/>
682690
public virtual object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return null; }
691+
/// <summary/>
692+
public virtual object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) { return null; }
683693
}
684694
}

src/System.Management.Automation/engine/parser/CharTraits.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -374,13 +374,26 @@ internal static bool ForceStartNewToken(this char c)
374374
return c.IsWhitespace();
375375
}
376376

377-
// Return true if the character ends the current number token. This allows the tokenizer
378-
// to scan '7z' as a single token, but '7+' as 2 tokens.
379-
internal static bool ForceStartNewTokenAfterNumber(this char c)
377+
/// <summary>
378+
/// Check if the current character forces to end scanning a number token.
379+
/// This allows the tokenizer to scan '7z' as a single token, but '7+' as 2 tokens.
380+
/// </summary>
381+
/// <param name="c">The character to check.</param>
382+
/// <param name="forceEndNumberOnTernaryOperatorChars">
383+
/// In some cases, we want '?' and ':' to end a number token too, so they can be
384+
/// treated as the ternary operator tokens.
385+
/// </param>
386+
/// <returns>Return true if the character ends the current number token.</returns>
387+
internal static bool ForceStartNewTokenAfterNumber(this char c, bool forceEndNumberOnTernaryOperatorChars)
380388
{
381389
if (c < 128)
382390
{
383-
return (s_traits[c] & CharTraits.ForceStartNewTokenAfterNumber) != 0;
391+
if ((s_traits[c] & CharTraits.ForceStartNewTokenAfterNumber) != 0)
392+
{
393+
return true;
394+
}
395+
396+
return forceEndNumberOnTernaryOperatorChars && (c == '?' || c == ':');
384397
}
385398

386399
return c.IsDash();

src/System.Management.Automation/engine/parser/Compiler.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4974,6 +4974,16 @@ public Expression GenerateCallContains(Expression lhs, Expression rhs, bool igno
49744974
rhs.Cast(typeof(object)));
49754975
}
49764976

4977+
public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst)
4978+
{
4979+
var expr = Expression.Condition(
4980+
Compile(ternaryExpressionAst.Condition).Convert(typeof(bool)),
4981+
Compile(ternaryExpressionAst.IfTrue).Convert(typeof(object)),
4982+
Compile(ternaryExpressionAst.IfFalse).Convert(typeof(object)));
4983+
4984+
return expr;
4985+
}
4986+
49774987
public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
49784988
{
49794989
object constantValue;

src/System.Management.Automation/engine/parser/ConstantValues.cs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace System.Management.Automation.Language
1515
* There is a number of similarities between these two classes, and changes (fixes) in this code
1616
* may need to be reflected in that class and vice versa
1717
*/
18-
internal class IsConstantValueVisitor : ICustomAstVisitor
18+
internal class IsConstantValueVisitor : ICustomAstVisitor2
1919
{
2020
public static bool IsConstant(Ast ast, out object constantValue, bool forAttribute = false, bool forRequires = false)
2121
{
@@ -130,6 +130,20 @@ public static bool IsConstant(Ast ast, out object constantValue, bool forAttribu
130130

131131
public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { return false; }
132132

133+
public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return false; }
134+
135+
public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return false; }
136+
137+
public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return false; }
138+
139+
public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return false; }
140+
141+
public object VisitUsingStatement(UsingStatementAst usingStatement) { return false; }
142+
143+
public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { return false; }
144+
145+
public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { return false; }
146+
133147
public object VisitStatementBlock(StatementBlockAst statementBlockAst)
134148
{
135149
if (statementBlockAst.Traps != null) return false;
@@ -172,6 +186,13 @@ private static bool IsNullDivisor(ExpressionAst operand)
172186
return false;
173187
}
174188

189+
public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst)
190+
{
191+
return (bool)ternaryExpressionAst.Condition.Accept(this) &&
192+
(bool)ternaryExpressionAst.IfTrue.Accept(this) &&
193+
(bool)ternaryExpressionAst.IfFalse.Accept(this);
194+
}
195+
175196
public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
176197
{
177198
return binaryExpressionAst.Operator.HasTrait(TokenFlags.CanConstantFold) &&
@@ -300,7 +321,7 @@ public object VisitParenExpression(ParenExpressionAst parenExpressionAst)
300321
}
301322
}
302323

303-
internal class ConstantValueVisitor : ICustomAstVisitor
324+
internal class ConstantValueVisitor : ICustomAstVisitor2
304325
{
305326
internal bool AttributeArgument { get; set; }
306327
internal bool RequiresArgument { get; set; }
@@ -400,6 +421,21 @@ private static object CompileAndInvoke(Ast ast)
400421

401422
public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { return AutomationNull.Value; }
402423

424+
public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return AutomationNull.Value; }
425+
426+
public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return AutomationNull.Value; }
427+
428+
public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return AutomationNull.Value; }
429+
430+
public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return AutomationNull.Value; }
431+
432+
public object VisitUsingStatement(UsingStatementAst usingStatement) { return AutomationNull.Value; }
433+
434+
public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { return AutomationNull.Value; }
435+
436+
public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { return AutomationNull.Value; }
437+
438+
403439
public object VisitStatementBlock(StatementBlockAst statementBlockAst)
404440
{
405441
CheckIsConstant(statementBlockAst, "Caller to verify ast is constant");
@@ -412,6 +448,16 @@ public object VisitPipeline(PipelineAst pipelineAst)
412448
return pipelineAst.GetPureExpression().Accept(this);
413449
}
414450

451+
public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst)
452+
{
453+
CheckIsConstant(ternaryExpressionAst, "Caller to verify ast is constant");
454+
455+
object condition = ternaryExpressionAst.Condition.Accept(this);
456+
return LanguagePrimitives.IsTrue(condition)
457+
? ternaryExpressionAst.IfTrue.Accept(this)
458+
: ternaryExpressionAst.IfFalse.Accept(this);
459+
}
460+
415461
public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
416462
{
417463
CheckIsConstant(binaryExpressionAst, "Caller to verify ast is constant");

0 commit comments

Comments
 (0)