in src/Bicep.Decompiler/TemplateConverter.cs [226:491]
private bool TryReplaceBannedFunction(FunctionExpression expression, [NotNullWhen(true)] out SyntaxBase? syntax)
{
if (SyntaxHelpers.TryGetBinaryOperatorReplacement(expression.Function) is TokenType binaryTokenType)
{
var binaryOperator = Operators.TokenTypeToBinaryOperator[binaryTokenType];
switch (binaryOperator)
{
// ARM actually allows >= 2 args for and(), or() and coalesce()
case BinaryOperator.LogicalAnd:
case BinaryOperator.LogicalOr:
case BinaryOperator.Coalesce:
if (expression.Parameters.Length < 2)
{
throw new ArgumentException($"Expected a minimum of 2 parameters for function {expression.Function}");
}
break;
default:
if (expression.Parameters.Length != 2)
{
throw new ArgumentException($"Expected 2 parameters for binary function {expression.Function}");
}
break;
}
if (expression.Properties.Any())
{
throw new ArgumentException($"Expected 0 properties for binary function {expression.Function}");
}
var leftParameter = expression.Parameters[0];
var rightParameter = expression.Parameters[1];
// if token is = or != check to see if they are insensitive conditions i.e =~ or !~
if (binaryTokenType is TokenType.Equals || binaryTokenType is TokenType.NotEquals)
{
if (leftParameter is FunctionExpression leftFunctionExpression &&
rightParameter is FunctionExpression rightFunctionExpression &&
leftFunctionExpression.Function == "toLower" &&
rightFunctionExpression.Function == "toLower")
{
leftParameter = leftFunctionExpression.Parameters[0];
rightParameter = rightFunctionExpression.Parameters[0];
binaryTokenType = binaryTokenType == TokenType.Equals ? TokenType.EqualsInsensitive : TokenType.NotEqualsInsensitive;
binaryOperator = Operators.TokenTypeToBinaryOperator[binaryTokenType];
}
}
var binaryOperation = new BinaryOperationSyntax(
ParseLanguageExpression(leftParameter),
SyntaxFactory.CreateToken(binaryTokenType),
ParseLanguageExpression(rightParameter));
foreach (var parameter in expression.Parameters.Skip(2))
{
binaryOperation = new BinaryOperationSyntax(
binaryOperation,
SyntaxFactory.CreateToken(binaryTokenType),
ParseLanguageExpression(parameter));
}
syntax = new ParenthesizedExpressionSyntax(
SyntaxFactory.LeftParenToken,
binaryOperation,
SyntaxFactory.RightParenToken);
return true;
}
if (SyntaxHelpers.TryGetEmptyFunctionKeywordReplacement(expression.Function) is TokenType keywordTokenType)
{
if (expression.Parameters.Any())
{
throw new ArgumentException($"Expected 0 parameters for function {expression.Function}");
}
if (expression.Properties.Any())
{
throw new ArgumentException($"Expected 0 properties for function {expression.Function}");
}
syntax = SyntaxFactory.CreateToken(keywordTokenType);
return true;
}
if (expression.IsNamed("not"))
{
if (expression.Parameters.Length != 1)
{
throw new ArgumentException($"Expected 1 parameters for unary function {expression.Function}");
}
if (expression.Properties.Any())
{
throw new ArgumentException($"Expected 0 properties for unary function {expression.Function}");
}
// Check to see if the inner expression is also a function and if it is equals we can
// simplify the expression from (!(a == b)) to (a != b)
if (expression.Parameters[0] is FunctionExpression functionExpression)
{
if (functionExpression.IsNamed("equals"))
{
return TryReplaceBannedFunction(
new FunctionExpression("notEquals", functionExpression.Parameters, functionExpression.Properties),
out syntax);
}
}
syntax = new ParenthesizedExpressionSyntax(
SyntaxFactory.LeftParenToken,
new UnaryOperationSyntax(
SyntaxFactory.ExclamationToken,
ParseLanguageExpression(expression.Parameters[0])),
SyntaxFactory.RightParenToken);
return true;
}
if (expression.IsNamed("if"))
{
if (expression.Parameters.Length != 3)
{
throw new ArgumentException($"Expected 3 parameters for ternary function {expression.Function}");
}
if (expression.Properties.Any())
{
throw new ArgumentException($"Expected 0 properties for ternary function {expression.Function}");
}
syntax = new ParenthesizedExpressionSyntax(
SyntaxFactory.LeftParenToken,
new TernaryOperationSyntax(
ParseLanguageExpression(expression.Parameters[0]),
[],
SyntaxFactory.QuestionToken,
ParseLanguageExpression(expression.Parameters[1]),
[],
SyntaxFactory.ColonToken,
ParseLanguageExpression(expression.Parameters[2])),
SyntaxFactory.RightParenToken);
return true;
}
if (expression.IsNamed("createArray"))
{
syntax = SyntaxFactory.CreateArray(expression.Parameters.Select(ParseLanguageExpression));
return true;
}
if (expression.IsNamed("createObject"))
{
syntax = SyntaxFactory.CreateObject(expression.Parameters.Select(ParseLanguageExpression).Chunk(2).Select(pair =>
{
if (pair[0] is StringSyntax keyString)
{
var keyLiteral = keyString.TryGetLiteralValue();
if (keyLiteral is not null)
{
return SyntaxFactory.CreateObjectProperty(keyLiteral, pair[1]);
}
else
{
// key is an interpolated string
return new ObjectPropertySyntax(pair[0], SyntaxFactory.ColonToken, pair[1]);
}
}
// key is a non-string expression
// since ObjectPropertySyntax only accepts IdentifierSyntax or StringSyntax, we need
// to wrap it in a string syntax
var keySyntax = SyntaxFactory.CreateString(new[] { string.Empty, string.Empty }, pair[0].AsEnumerable());
return new ObjectPropertySyntax(keySyntax, SyntaxFactory.ColonToken, pair[1]);
}));
return true;
}
if (StringComparer.OrdinalIgnoreCase.Equals(expression.Function, "lambda"))
{
if (expression.Parameters.Length < 1)
{
throw new ArgumentException($"Expected at least 1 argument for function {expression.Function}");
}
var lambdaExpression = expression.Parameters.Last();
var paramNames = new string[expression.Parameters.Length - 1];
for (var i = 0; i < expression.Parameters.Length - 1; i++)
{
if (expression.Parameters[i] is not JTokenExpression jtokenParam ||
jtokenParam.Value.Type is not JTokenType.String)
{
throw new ArgumentException($"Argument {i} for {expression.Function} is not a valid identifier");
}
paramNames[i] = jtokenParam.Value.ToString();
}
syntax = SyntaxFactory.CreateLambdaSyntax(paramNames, ParseLanguageExpression(lambdaExpression));
return true;
}
if (StringComparer.OrdinalIgnoreCase.Equals(expression.Function, "lambdaVariables"))
{
if (expression.Parameters.Length != 1 ||
expression.Parameters[0] is not JTokenExpression jtokenParam ||
jtokenParam.Value.Type is not JTokenType.String)
{
throw new ArgumentException($"Expected exactly 1 parameter of type {JTokenType.String} for function {expression.Function}");
}
syntax = SyntaxFactory.CreateIdentifier(jtokenParam.Value.ToString());
return true;
}
if (expression.IsNamed("tryGet"))
{
if (expression.Parameters.Length < 2)
{
throw new ArgumentException($"Expected at least 2 parameters for function {expression.Function}");
}
syntax = ParsePropertyAccess(ParseLanguageExpression(expression.Parameters[0]), expression.Parameters[1], safeNavigation: true);
for (int i = 2; i < expression.Parameters.Length; i++)
{
syntax = ParsePropertyAccess(syntax, expression.Parameters[i], safeNavigation: false, allowWrappedProperties: true);
}
if (expression.Properties.Length > 0)
{
syntax = new ParenthesizedExpressionSyntax(SyntaxFactory.LeftParenToken, syntax, SyntaxFactory.RightParenToken);
}
return true;
}
if (expression.IsNamed("indexFromEnd"))
{
if (expression.Parameters.Length != 2)
{
throw new ArgumentException($"Expected exactly 2 parameters for function {expression.Function}");
}
syntax = ParsePropertyAccess(ParseLanguageExpression(expression.Parameters[0]), expression.Parameters[1], safeNavigation: false, fromEnd: true);
return true;
}
if (expression.IsNamed("tryIndexFromEnd"))
{
if (expression.Parameters.Length < 2)
{
throw new ArgumentException($"Expected at least 2 parameters for function {expression.Function}");
}
syntax = ParsePropertyAccess(ParseLanguageExpression(expression.Parameters[0]), expression.Parameters[1], safeNavigation: true, fromEnd: true);
for (int i = 2; i < expression.Parameters.Length; i++)
{
syntax = ParsePropertyAccess(syntax, expression.Parameters[i], safeNavigation: false, allowWrappedProperties: true);
}
if (expression.Properties.Length > 0)
{
syntax = new ParenthesizedExpressionSyntax(SyntaxFactory.LeftParenToken, syntax, SyntaxFactory.RightParenToken);
}
return true;
}
syntax = null;
return false;
}