in AjaxMinDll/JavaScript/jsparser.cs [3746:4111]
private AstNode ParseLeftHandSideExpression(bool isMinus)
{
AstNode ast = null;
List<Context> newContexts = null;
TryItAgain:
// new expression
while (m_currentToken.Is(JSToken.New))
{
if (null == newContexts)
newContexts = new List<Context>(4);
newContexts.Add(m_currentToken.Clone());
GetNextToken();
}
JSToken token = m_currentToken.Token;
switch (token)
{
// primary expression
case JSToken.Identifier:
ast = new Lookup(m_currentToken.Clone())
{
Name = m_scanner.Identifier
};
GetNextToken();
break;
case JSToken.TemplateLiteral:
ast = ParseTemplateLiteral();
break;
case JSToken.ConditionalCommentStart:
// skip past the start to the next token
GetNextToken();
if (m_currentToken.Is(JSToken.ConditionalCompilationVariable))
{
// we have /*@id
ast = new ConstantWrapperPP(m_currentToken.Clone())
{
VarName = m_currentToken.Code,
ForceComments = true
};
GetNextToken();
if (m_currentToken.Is(JSToken.ConditionalCommentEnd))
{
// skip past the closing comment
GetNextToken();
}
else
{
// we ONLY support /*@id@*/ in expressions right now. If there's not
// a closing comment after the ID, then we don't support it.
// throw an error, skip to the end of the comment, then ignore it and start
// looking for the next token.
CCTooComplicated(null);
goto TryItAgain;
}
}
else if (m_currentToken.Is(JSToken.ConditionalCommentEnd))
{
// empty conditional comment! Ignore.
GetNextToken();
goto TryItAgain;
}
else
{
// we DON'T have "/*@IDENT". We only support "/*@IDENT @*/", so since this isn't
// and id, throw the error, skip to the end of the comment, and ignore it
// by looping back and looking for the NEXT token.
m_currentToken.HandleError(JSError.ConditionalCompilationTooComplex);
// skip to end of conditional comment
while (m_currentToken.IsNot(JSToken.EndOfFile) && m_currentToken.IsNot(JSToken.ConditionalCommentEnd))
{
GetNextToken();
}
GetNextToken();
goto TryItAgain;
}
break;
case JSToken.This:
ast = new ThisLiteral(m_currentToken.Clone());
GetNextToken();
break;
case JSToken.StringLiteral:
ast = new ConstantWrapper(m_scanner.StringLiteralValue, PrimitiveType.String, m_currentToken.Clone())
{
MayHaveIssues = m_scanner.LiteralHasIssues
};
GetNextToken();
break;
case JSToken.IntegerLiteral:
case JSToken.NumericLiteral:
{
Context numericContext = m_currentToken.Clone();
double doubleValue;
if (ConvertNumericLiteralToDouble(m_currentToken.Code, (token == JSToken.IntegerLiteral), out doubleValue))
{
// conversion worked fine
// check for some boundary conditions
var mayHaveIssues = m_scanner.LiteralHasIssues;
if (doubleValue == double.MaxValue)
{
ReportError(JSError.NumericMaximum, numericContext);
}
else if (isMinus && -doubleValue == double.MinValue)
{
ReportError(JSError.NumericMinimum, numericContext);
}
// create the constant wrapper from the value
ast = new ConstantWrapper(doubleValue, PrimitiveType.Number, numericContext)
{
MayHaveIssues = mayHaveIssues
};
}
else
{
// if we went overflow or are not a number, then we will use the "Other"
// primitive type so we don't try doing any numeric calcs with it.
if (double.IsInfinity(doubleValue))
{
// overflow
// and if we ARE an overflow, report it
ReportError(JSError.NumericOverflow, numericContext);
}
// regardless, we're going to create a special constant wrapper
// that simply echos the input as-is
ast = new ConstantWrapper(m_currentToken.Code, PrimitiveType.Other, numericContext)
{
MayHaveIssues = true
};
}
GetNextToken();
break;
}
case JSToken.True:
ast = new ConstantWrapper(true, PrimitiveType.Boolean, m_currentToken.Clone());
GetNextToken();
break;
case JSToken.False:
ast = new ConstantWrapper(false, PrimitiveType.Boolean, m_currentToken.Clone());
GetNextToken();
break;
case JSToken.Null:
ast = new ConstantWrapper(null, PrimitiveType.Null, m_currentToken.Clone());
GetNextToken();
break;
case JSToken.ConditionalCompilationVariable:
ast = new ConstantWrapperPP(m_currentToken.Clone())
{
VarName = m_currentToken.Code,
ForceComments = false
};
GetNextToken();
break;
// normally this token is not allowed on the left-hand side of an expression.
// BUT, this might be the start of a regular expression that begins with an equals sign!
// we need to test to see if we can parse a regular expression, and if not, THEN
// we can fail the parse.
case JSToken.DivideAssign:
case JSToken.Divide:
// could it be a regexp?
ast = ScanRegularExpression();
if (ast != null)
{
// yup -- we're done here
break;
}
// nope -- go to the default branch
goto default;
case JSToken.Modulo:
// could it be a replacement token in the format %name%? If so, we
// want to treat that as a constant wrapper.
ast = ScanReplacementToken();
if (ast != null)
{
break;
}
goto default;
// expression
case JSToken.LeftParenthesis:
{
var leftParen = m_currentToken.Clone();
GetNextToken();
if (m_currentToken.Is(JSToken.For))
{
// generator comprehension in ES6 format
ast = ParseComprehension(false, leftParen, null);
}
else
{
if (m_currentToken.Is(JSToken.RightParenthesis))
{
// shortcut the empty parenthetical grouping
// normally not allowed; however this might be the (empty) parameter list
// to an arrow function.
// add the closing paren to the expression context
ast = new GroupingOperator(leftParen);
ast.UpdateWith(m_currentToken);
GetNextToken();
}
else if (m_currentToken.Is(JSToken.RestSpread))
{
// we have (...
// parse an assignment expression, make it the operand of a unary with the rest.
var restContext = m_currentToken.Clone();
GetNextToken();
ast = ParseExpression(true);
if (ast != null)
{
ast = new UnaryOperator(restContext.CombineWith(ast.Context))
{
OperatorContext = restContext,
OperatorToken = JSToken.RestSpread,
Operand = ast
};
}
// now, we want to continue parsing if there is a comma
if (m_currentToken.Is(JSToken.Comma))
{
ast = ParseExpression(ast, false, true, JSToken.None);
}
if (m_currentToken.Is(JSToken.RightParenthesis))
{
ast = new GroupingOperator(leftParen)
{
Operand = ast
};
ast.UpdateWith(m_currentToken);
GetNextToken();
}
else
{
ReportError(JSError.NoRightParenthesis);
}
}
else
{
// parse an expression
var operand = ParseExpression();
if (m_currentToken.Is(JSToken.For))
{
// generator comprehension in Mozille format
ast = ParseComprehension(false, leftParen, operand);
}
else
{
ast = new GroupingOperator(leftParen)
{
Operand = operand
};
ast.UpdateWith(operand.Context);
if (m_currentToken.IsNot(JSToken.RightParenthesis))
{
ReportError(JSError.NoRightParenthesis);
}
else
{
// add the closing paren to the expression context
ast.UpdateWith(m_currentToken);
GetNextToken();
}
}
}
}
}
break;
// array initializer
case JSToken.LeftBracket:
ast = ParseArrayLiteral(false);
break;
// object initializer
case JSToken.LeftCurly:
ast = ParseObjectLiteral(false);
break;
// function expression
case JSToken.Function:
ast = ParseFunction(FunctionType.Expression, m_currentToken.Clone());
break;
// class expression
case JSToken.Class:
ast = ParseClassNode(ClassType.Expression);
break;
case JSToken.AspNetBlock:
ast = new AspNetBlockNode(m_currentToken.Clone())
{
AspNetBlockText = m_currentToken.Code
};
GetNextToken();
break;
case JSToken.Yield:
{
// TODO: not sure if this is the right place to hook for the ES6 YieldExpression semantics!
if (ParsedVersion == ScriptVersion.EcmaScript6 || m_settings.ScriptVersion == ScriptVersion.EcmaScript6)
{
// we already KNOW we're ES6 code, so just parse this as a yield expression.
// in fact, we SHOULD already know, since yield should only be used within a generator
// function, which for ES6 code should have the "*" generator indicator, which when
// parsed should have already set the ES6 flag.
ast = ParseYieldExpression();
}
else
{
// we need to protect against non-ES6 code using "yield" as a variable name versus the
// Mozilla yield syntax. We'll do that further upstream.
ast = new Lookup(m_currentToken.Clone())
{
Name = "yield"
};
GetNextToken();
}
}
break;
default:
var identifier = JSKeyword.CanBeIdentifier(m_currentToken.Token);
if (identifier != null)
{
ast = new Lookup(m_currentToken.Clone())
{
Name = identifier
};
GetNextToken();
}
else
{
ReportError(JSError.ExpressionExpected);
}
break;
}
if (m_currentToken.Is(JSToken.ArrowFunction))
{
ParsedVersion = ScriptVersion.EcmaScript6;
ast = ParseArrowFunction(ast);
}
// can be a CallExpression, that is, followed by '.' or '(' or '['
return ParseMemberExpression(ast, newContexts);
}